Blog
Thursday, 19. November 2020

PostgreSQL Crash-Dump-Dateien analysieren

Denise
IT-Consultant

Einleitung

Wir werden uns in diesem Beitrag damit beschäftigen, wie die Generierung von Crash-Dump-Dateien (auch als Core-Dump bezeichnet) aktiviert werden kann und uns einige gängige GDB-Befehle anschauen, die Entwicklern bei der Behebung von Crash-Problemen in PostgreSQL und anderen Anwendungen helfen können. Eine ordnungsgemäße Analyse des Problems erfordert normalerweise Zeit und ein gewisses Maß an Wissen über den Quellcode der Anwendung, jedoch ist es manchmal besser, die größere Umgebung zu betrachten, als den Absturzpunkt.

Was ist eine Crash-Dump-Datei?

Eine Crash-Dump-Datei ist eine Datei, die aus dem aufgezeichneten Status des Arbeitsspeichers einer Anwendung besteht wenn diese abstürzt. Dieser Zustand wird durch Speicheradressen und CPU-Register dargestellt. Normalerweise ist es äußerst schwierig, nur mit Speicheradressen und CPU-Registern zu debuggen, da diese keine Informationen über die Anwendungslogik enthalten. Überprüft man den folgenden Core-Dump-Inhalt, der die Rückverfolgung der Speicheradressen bis zum Absturz zeigt, kann man sehen, dass er nicht sehr nützlich ist:

#1  0x00687a3d in ?? ()
#2  0x00d37f06 in ?? ()
#3  0x00bf0ba4 in ?? ()
#4  0x00d3333b in ?? ()
#5  0x00d3f682 in ?? ()
#6  0x00d3407b in ?? ()
#7  0x00d3f2f7 in ?? ()

Wenn wir also eine Crash-Dump-Datei sehen, die so aussieht, bedeutet dies, dass die Anwendung nicht mit Debugging-Symbolen erstellt wurde, wodurch diese Crash-Dump-Datei für uns unbrauchbar wird. In diesem Fall müssen wir die Debug-Version der Anwendung installieren oder die Anwendung mit aktiviertem Debugging neu erstellen.

So erstellen Sie eine nützliche Crash-Dump-Datei

Vor dem Erstellen der Crash-Dump-Datei müssen wir sicherstellen, dass die Anwendung mit Debugging-Symbolen erstellt wird. Dies kann durch Ausführen des ./configure-Skripts erfolgen:

./configure enable-debug

Dies fügt das -g -Argument zu CFLAGS in src/Makefile.global hinzu – wobei die Optimierungsstufe auf 2 gesetzt ist – wir bevorzugen es in diesem Beispiel allerdings die Optimierungsstufe auf 0 ( -O0) zu setzen. Dadurch ist die Navigation sinnvoller, wenn wir mit GDB durch den Stack navigieren und wir können die meisten Variablenwerte im Speicher ausgeben, anstatt optimized out -Fehler in GDB zu erhalten.

CFLAGS = -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement 
-Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 
-Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard 
-Wno-format-truncation -g -O0

Jetzt können wir die Crash-Dump-Generierung aktivieren. Dies kann mit dem user-limit-Befehl erfolgen.

ulimit -c unlimited

Zur Deaktivierung:

ulimit -c 0

Stellen Sie sicher, dass genügend Speicherplatz vorhanden ist, da die Crash-Dump-Datei normalerweise sehr groß ist (sie zeichnet alle Speicherzustände vom Start bis zum Absturz auf). Stellen Sie sicher, dass ulimit in der Shell eingerichtet ist, bevor Sie PostgreSQL starten. Wenn PostgreSQL abstürzt, wird eine Core-Dump-Datei mit dem Namen core in $PGDATA erstellt.

Analysieren der Dump-Datei mit GDB

GDB (GNU Debugger) ist ein portabler Debugger, der auf vielen Unix-ähnlichen Systemen ausgeführt wird und mit vielen Programmiersprachen arbeiten kann.
Wir fügen als Nächstes absichtlich eine Zeile im PostgreSQL-Quellcode hinzu, die beim Ausführen eines CREATE TABLE-Befehls zu einem segmentation fault Absturz führt.
Angenommen PostgreSQL ist bereits abgestürzt und hat eine Core-Dump-Datei core an dieser Stelle erstellt: ~/postgres/postgresdb/core, dann würden wir zuerst das file-Dienstprogramm wie folgt verwenden, um mehr über die Core-Datei an sich zu erfahren, z.B. durch Informationen wie die Kernel-Informationen und das Programm.

denise:~$ file /home/denise/postgres/postgresdb/core
postgresdb/core: ELF 64-bit LSB core file x86-64, version 1 (SYSV), SVR4-style, from 'postgres: dr dr [local] CREATE TABLE', real uid: 1000, effective uid: 1000, real gid: 1000, effective gid: 1000, execfn: '/home/denise/postgres/bin/postgres', platform: 'x86_64'
denise:~$

Das file-Dienstprogramm teilt uns mit, dass die Core-Datei von dieser Anwendung: /home/denise/postgres/bin/postgres generiert wird. Daher führen wir als nächstes folgenden Befehl aus:

gdb /home/denise/postgres/bin/postgres -c  /home/denise/postgres/postgresdb/core

Unmittelbar nach dem Ausführen des gdb-Befehls wird der Ort des Absturzes ausgegeben: heapam.c:1840 gibt uns an, dass das Programm, das gerade ausgeführt wurde in Zeile 1840 unseren vorher gebauten Fehler ausführt und an dieser Stelle abstürzt.

GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from /home/denise/postgres/bin/postgres...done.
[New LWP 27417]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `postgres: dr dr [local] CREATE TABLE                                 '.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778, cid=0, options=options@entry=0,
    bistate=bistate@entry=0x0) at heapam.c:1840
1840            ereport(LOG,(errmsg("heap tuple len = %d", heaptup->t_len)));
(gdb)

Nützliche GDB-Befehle

Mit gdb ist es sehr einfach den Ort eines Absturzes zu identifizieren. Leider ist der Ort des Absturzes in 95% der Fälle nicht die eigentliche Ursache des Problems. Aus diesem Grund ist es manchmal besser, wie bereits erwähnt, die größere Umgebung zu betrachten und nicht den Absturzpunkt. Der Absturz wird wahrscheinlich durch einen Fehler in der Anwendungslogik verursacht, der sich an einer anderen Stelle in der Anwendung befindet. Selbst wenn Sie den Absturz beheben, besteht der Fehler in der Anwendungslogik immer noch, und höchstwahrscheinlich stürzt die Anwendung später an einem anderen Ort ab oder führt zu unbefriedigenden Ergebnissen. Daher lohnt es sich, einige der leistungsstarken gdb-Befehle zu verstehen, die uns helfen könnten die Stack-calls besser zu verstehen, um die eigentliche Grundursache zu identifizieren.

5.1 Der bt-Befehl
Der bt-Befehl zeigt eine Reihe von Stack-Calls vom Start der Anwendung bis zum Absturz. Wenn das vollständige Debugging aktiviert ist, können Sie die Funktionsargumente und -werte sehen, die an die einzelnen Funktionsaufrufe übergeben werden, sowie die Quelldatei und die Zeilennummern, in denen sie aufgerufen wurden. Auf diese Weise kann der Entwickler mögliche Fehler in der Anwendungslogik in der früheren Verarbeitung nachvollziehen.

(gdb) bt
#0  heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778, cid=0, options=options@entry=0,
    bistate=bistate@entry=0x0) at heapam.c:1840
#1  0x000055ba81ccde3e in simple_heap_insert (relation=relation@entry=0x7f872f532228, tup=tup@entry=0x55ba8290f778)
    at heapam.c:2356
#2  0x000055ba81d7826d in CatalogTupleInsert (heapRel=0x7f872f532228, tup=0x55ba8290f778) at indexing.c:228
#3  0x000055ba81d946ea in TypeCreate (newTypeOid=newTypeOid@entry=0, typeName=typeName@entry=0x7ffcf56ef820 "test",
    typeNamespace=typeNamespace@entry=2200, relationOid=relationOid@entry=16392, relationKind=relationKind@entry=114 'r',
    ownerId=ownerId@entry=16385, internalSize=-1, typeType=99 'c', typeCategory=67 'C', typePreferred=false,
    typDelim=44 ',', inputProcedure=2290, outputProcedure=2291, receiveProcedure=2402, sendProcedure=2403,
    typmodinProcedure=0, typmodoutProcedure=0, analyzeProcedure=0, elementType=0, isImplicitArray=false, arrayType=16393,
    baseType=0, defaultTypeValue=0x0, defaultTypeBin=0x0, passedByValue=false, alignment=100 'd', storage=120 'x',
    typeMod=-1, typNDims=0, typeNotNull=false, typeCollation=0) at pg_type.c:484
#4  0x000055ba81d710bc in AddNewRelationType (new_array_type=16393, new_row_type=<optimized out>, ownerid=<optimized out>,
    new_rel_kind=<optimized out>, new_rel_oid=<optimized out>, typeNamespace=2200, typeName=0x7ffcf56ef820 "test")
    at heap.c:1033
#5  heap_create_with_catalog (relname=relname@entry=0x7ffcf56ef820 "test", relnamespace=relnamespace@entry=2200,
    reltablespace=reltablespace@entry=0, relid=16392, relid@entry=0, reltypeid=reltypeid@entry=0,
    reloftypeid=reloftypeid@entry=0, ownerid=16385, accessmtd=2, tupdesc=0x55ba8287c620, cooked_constraints=0x0,
    relkind=114 'r', relpersistence=112 'p', shared_relation=false, mapped_relation=false, oncommit=ONCOMMIT_NOOP,
    reloptions=0, use_user_acl=true, allow_system_table_mods=false, is_internal=false, relrewrite=0, typaddress=0x0)
    at heap.c:1294
#6  0x000055ba81e3782a in DefineRelation (stmt=stmt@entry=0x55ba82876658, relkind=relkind@entry=114 'r', ownerId=16385,
    ownerId@entry=0, typaddress=typaddress@entry=0x0,
    queryString=queryString@entry=0x55ba82855648 "create table test (a int, b char(10)) using heap;") at tablecmds.c:885
#7  0x000055ba81fd5b2f in ProcessUtilitySlow (pstate=pstate@entry=0x55ba82876548, pstmt=pstmt@entry=0x55ba828565a0,
    queryString=queryString@entry=0x55ba82855648 "create table test (a int, b char(10)) using heap;",
    context=context@entry=PROCESS_UTILITY_TOPLEVEL, params=params@entry=0x0, queryEnv=queryEnv@entry=0x0, qc=0x7ffcf56efe50,
    dest=0x55ba82856860) at utility.c:1161
#8  0x000055ba81fd4120 in standard_ProcessUtility (pstmt=0x55ba828565a0,
    queryString=0x55ba82855648 "create table test (a int, b char(10)) using heap;", context=PROCESS_UTILITY_TOPLEVEL,
    params=0x0, queryEnv=0x0, dest=0x55ba82856860, qc=0x7ffcf56efe50) at utility.c:1069
#9  0x000055ba81fd1962 in PortalRunUtility (portal=0x55ba828b7dd8, pstmt=0x55ba828565a0, isTopLevel=<optimized out>,
    setHoldSnapshot=<optimized out>, dest=<optimized out>, qc=0x7ffcf56efe50) at pquery.c:1157
#10 0x000055ba81fd23e3 in PortalRunMulti (portal=portal@entry=0x55ba828b7dd8, isTopLevel=isTopLevel@entry=true,
    setHoldSnapshot=setHoldSnapshot@entry=false, dest=dest@entry=0x55ba82856860, altdest=altdest@entry=0x55ba82856860,
    qc=qc@entry=0x7ffcf56efe50) at pquery.c:1310
#11 0x000055ba81fd2f51 in PortalRun (portal=portal@entry=0x55ba828b7dd8, count=count@entry=9223372036854775807,
    isTopLevel=isTopLevel@entry=true, run_once=run_once@entry=true, dest=dest@entry=0x55ba82856860,
    altdest=altdest@entry=0x55ba82856860, qc=0x7ffcf56efe50) at pquery.c:779
#12 0x000055ba81fce967 in exec_simple_query (query_string=0x55ba82855648 "create table test (a int, b char(10)) using heap;")
    at postgres.c:1239
#13 0x000055ba81fd0d7e in PostgresMain (argc=<optimized out>, argv=argv@entry=0x55ba8287fdb0, dbname=<optimized out>,
    username=<optimized out>) at postgres.c:4315
#14 0x000055ba81f4f52a in BackendRun (port=0x55ba82877110, port=0x55ba82877110) at postmaster.c:4536
#15 BackendStartup (port=0x55ba82877110) at postmaster.c:4220
#16 ServerLoop () at postmaster.c:1739
#17 0x000055ba81f5063f in PostmasterMain (argc=3, argv=0x55ba8284fee0) at postmaster.c:1412
#18 0x000055ba81c91c04 in main (argc=3, argv=0x55ba8284fee0) at main.c:210
(gdb)

5.2 Der f-Befehl
Mit dem f-Befehl gefolgt von einer Stack-Nummer kann gdb zu einem bestimmten Aufrufstapel springen, der im bt-Befehl aufgeführt ist und Sie können andere Variablen in diesem bestimmten Stapel wie folgt ausgeben:

(gdb) f 3
#3  0x000055ba81d946ea in TypeCreate (newTypeOid=newTypeOid@entry=0, typeName=typeName@entry=0x7ffcf56ef820 "test",
    typeNamespace=typeNamespace@entry=2200, relationOid=relationOid@entry=16392, relationKind=relationKind@entry=114 'r',
    ownerId=ownerId@entry=16385, internalSize=-1, typeType=99 'c', typeCategory=67 'C', typePreferred=false,
    typDelim=44 ',', inputProcedure=2290, outputProcedure=2291, receiveProcedure=2402, sendProcedure=2403,
    typmodinProcedure=0, typmodoutProcedure=0, analyzeProcedure=0, elementType=0, isImplicitArray=false, arrayType=16393,
    baseType=0, defaultTypeValue=0x0, defaultTypeBin=0x0, passedByValue=false, alignment=100 'd', storage=120 'x',
    typeMod=-1, typNDims=0, typeNotNull=false, typeCollation=0) at pg_type.c:484
484                     CatalogTupleInsert(pg_type_desc, tup);
(gdb)

Dieser Befehl zwingt gdb dazu, zu Stapel Nummer 3 zu springen, der sich unter pg_type.c: 484 befindet. Hier können Sie alle anderen Variablen in diesem Frame untersuchen.

5.3 Der p-Befehl
Der p-Befehl gibt Speicheradressen und die zugehörigen Werte aus.

(gdb) p tup
$1 = (HeapTuple) 0x55ba8290f778
(gdb) p pg_type_desc
$2 = (Relation) 0x7f872f532228

(gdb)  p * tup
$3 = {t_len = 176, t_self = {ip_blkid = {bi_hi = 65535, bi_lo = 65535}, ip_posid = 0}, t_tableOid = 0,
  t_data = 0x55ba8290f790}

(gdb) p * pg_type_desc
$4 = {rd_node = {spcNode = 1663, dbNode = 16384, relNode = 1247}, rd_smgr = 0x55ba828e2a38, rd_refcnt = 2, rd_backend = -1,
  rd_islocaltemp = false, rd_isnailed = true, rd_isvalid = true, rd_indexvalid = true, rd_statvalid = false,
  rd_createSubid = 0, rd_newRelfilenodeSubid = 0, rd_firstRelfilenodeSubid = 0, rd_droppedSubid = 0,
  rd_rel = 0x7f872f532438, rd_att = 0x7f872f532548, rd_id = 1247, rd_lockInfo = {lockRelId = {relId = 1247, dbId = 16384}},
  rd_rules = 0x0, rd_rulescxt = 0x0, trigdesc = 0x0, rd_rsdesc = 0x0, rd_fkeylist = 0x0, rd_fkeyvalid = false,
  rd_partkey = 0x0, rd_partkeycxt = 0x0, rd_partdesc = 0x0, rd_pdcxt = 0x0, rd_partcheck = 0x0, rd_partcheckvalid = false,
  rd_partcheckcxt = 0x0, rd_indexlist = 0x7f872f477d00, rd_pkindex = 0, rd_replidindex = 0, rd_statlist = 0x0,
  rd_indexattr = 0x0, rd_keyattr = 0x0, rd_pkattr = 0x0, rd_idattr = 0x0, rd_pubactions = 0x0, rd_options = 0x0,
  rd_amhandler = 0, rd_tableam = 0x55ba82562c20 <heapam_methods>, rd_index = 0x0, rd_indextuple = 0x0, rd_indexcxt = 0x0,
  rd_indam = 0x0, rd_opfamily = 0x0, rd_opcintype = 0x0, rd_support = 0x0, rd_supportinfo = 0x0, rd_indoption = 0x0,
  rd_indexprs = 0x0, rd_indpred = 0x0, rd_exclops = 0x0, rd_exclprocs = 0x0, rd_exclstrats = 0x0, rd_indcollation = 0x0,
  rd_opcoptions = 0x0, rd_amcache = 0x0, rd_fdwroutine = 0x0, rd_toastoid = 0, pgstat_info = 0x55ba828d5cb0}
(gdb)

Mit Hilfe des Asterisks kann der p-Befehl verwendet werden, um sowohl den Wert auszugeben auf den der Pointer zeigt, als auch die Speicheradresse dieses Wertes.

5.4 Der x-Befehl
Der x-Befehl wird verwendet, um den Inhalt eines Speicherblocks mit der angegebenen Größe und dem angegebenen Format zu untersuchen. In folgenden Beispiel wollen wir die t_data-Werte innerhalb einer HeapTuple-Struktur untersuchen. Dazu geben wir zuerst den *tup Pointer aus, um zu erfahren, dass die t_data-Größe 176 ist und untersuchen anschließend mit dem x-Befehl die ersten 176 Bytes auf die gezeigt wird.

(gdb)  p *tup
$6 = {t_len = 176, t_self = {ip_blkid = {bi_hi = 65535, bi_lo = 65535}, ip_posid = 0}, t_tableOid = 0,
  t_data = 0x55ba8290f790}

(gdb)  p tup->t_data
$7 = (HeapTupleHeader) 0x55ba8290f790
(gdb) x/176bx  tup->t_data
0x55ba8290f790: 0xc0    0x02    0x00    0x00    0xff    0xff    0xff    0xff
0x55ba8290f798: 0x47    0x00    0x00    0x00    0xff    0xff    0xff    0xff
0x55ba8290f7a0: 0x00    0x00    0x1f    0x00    0x01    0x00    0x20    0xff
0x55ba8290f7a8: 0xff    0xff    0x0f    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7b0: 0x0a    0x40    0x00    0x00    0x74    0x65    0x73    0x74
0x55ba8290f7b8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7c0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7c8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7d0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7d8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7e0: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7e8: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f7f0: 0x00    0x00    0x00    0x00    0x98    0x08    0x00    0x00
0x55ba8290f7f8: 0x01    0x40    0x00    0x00    0xff    0xff    0x00    0x63
0x55ba8290f800: 0x43    0x00    0x01    0x2c    0x08    0x40    0x00    0x00
0x55ba8290f808: 0x00    0x00    0x00    0x00    0x09    0x40    0x00    0x00
0x55ba8290f810: 0xf2    0x08    0x00    0x00    0xf3    0x08    0x00    0x00
0x55ba8290f818: 0x62    0x09    0x00    0x00    0x63    0x09    0x00    0x00
0x55ba8290f820: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x55ba8290f828: 0x00    0x00    0x00    0x00    0x64    0x78    0x00    0x00
0x55ba8290f830: 0x00    0x00    0x00    0x00    0xff    0xff    0xff    0xff
0x55ba8290f838: 0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00
(gdb)

Wir haben uns nun in unserem Beitrag angeschaut, wie eine Crash-Dump-Datei mit ausreichend Debug-Symbolen erstellt wird und Sie haben einige der häufigsten gdb-Befehle kennengelernt. Dies sollte Ihnen nun bei der Behebung eines Crash-Problems in PostgreSQL helfen.

Interesse geweckt?

Unsere Expert:innen stehen Ihnen bei allen Fragen rund um Ihre IT Infrastruktur zur Seite.

Kontaktieren Sie uns gerne über das Kontaktformular und vereinbaren ein unverbindliches Beratungsgespräch mit unseren Berater:innen zur Bedarfsevaluierung. Gemeinsam optimieren wir Ihre Umgebung und steigern Ihre Performance!
Wir freuen uns auf Ihre Kontaktaufnahme!

Taunusstraße 72
55118 Mainz
info@madafa.de
+49 6131 3331612
Bürozeiten
Montag bis Donnerstag:
9:00 - 17:00 Uhr MEZ

Freitags:
9:30 - 14:00 Uhr MEZ
Wir sind Ihre SQL Expert:innen!
Noch Fragen? - Wir haben immer die passende Antwort für Sie!