So generieren Sie automatisch einen Stacktrace, wenn mein Programm abstürzt


590

Ich arbeite unter Linux mit dem GCC-Compiler. Wenn mein C ++ - Programm abstürzt, möchte ich, dass es automatisch einen Stacktrace generiert.

Mein Programm wird von vielen verschiedenen Benutzern ausgeführt und läuft auch unter Linux, Windows und Macintosh (alle Versionen werden mit kompiliert gcc).

Ich möchte, dass mein Programm beim Absturz einen Stack-Trace generieren kann. Wenn der Benutzer ihn das nächste Mal ausführt, werden sie gefragt, ob es in Ordnung ist, den Stack-Trace an mich zu senden, damit ich das Problem aufspüren kann. Ich kann die Informationen an mich senden, weiß aber nicht, wie ich die Ablaufverfolgungszeichenfolge generieren soll. Irgendwelche Ideen?


4
backtrace und backtrace_symbols_fd sind nicht async-signal-sicher. Sie sollten diese Funktion nicht im Signalhandler
Parag Bafna

10
backtrace_symbols ruft malloc auf und darf daher nicht in einem Signalhandler verwendet werden. Die beiden anderen Funktionen (backtrace und backtrace_symbols_fd) haben dieses Problem nicht und werden häufig in Signalhandlern verwendet.
cmccabe

3
@cmccabe, das falsch ist backtrace_symbols_fd ruft normalerweise nicht malloc auf, kann aber, wenn etwas in seinem catch_error-Block schief geht
Sam Saffron

6
Es "kann" in dem Sinne, dass es keine POSIX-Spezifikation für backtrace_symbols_fd (oder irgendeinen Backtrace) gibt; Es wird jedoch angegeben, dass backtrace_symbols_fd von GNU / Linux niemals malloc aufruft , wie in linux.die.net/man/3/backtrace_symbols_fd angegeben . Daher kann man davon ausgehen, dass unter Linux niemals malloc aufgerufen wird.
Codetaku

Antworten:


509

Wenn Sie unter Linux und Mac OS X gcc oder einen Compiler verwenden, der glibc verwendet, können Sie die Funktionen backtrace () verwenden execinfo.h, um einen Stacktrace zu drucken und ordnungsgemäß zu beenden, wenn ein Segmentierungsfehler auftritt. Die Dokumentation finden Sie im libc-Handbuch .

Hier ist ein Beispielprogramm, das einen SIGSEGVHandler installiert und einen Stacktrace druckt, stderrwenn er fehlerhaft ist. Die baz()Funktion hier verursacht den Segfault, der den Handler auslöst:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:\n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d\n", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

Beim Kompilieren mit erhalten -g -rdynamicSie Symbolinformationen in Ihrer Ausgabe, mit denen glibc eine schöne Stapelverfolgung erstellen kann:

$ gcc -g -rdynamic ./test.c -o test

Wenn Sie dies ausführen, erhalten Sie folgende Ausgabe:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

Dies zeigt das Lademodul, den Versatz und die Funktion, von denen jeder Frame im Stapel stammt. Hier können Sie die Signal - Handler oben auf dem Stapel sehen, und die libc - Funktionen vor , mainzusätzlich zu main, foo, bar, und baz.


53
Es gibt auch /lib/libSegFault.so, das Sie mit LD_PRELOAD verwenden können.
CesarB

6
Es sieht so aus, als ob die ersten beiden Einträge in Ihrer Backtrace-Ausgabe eine Rücksprungadresse im Signalhandler und wahrscheinlich eine sigaction()in libc enthalten. Obwohl Ihr Backtrace korrekt zu sein scheint, habe ich manchmal festgestellt, dass zusätzliche Schritte erforderlich sind, um sicherzustellen, dass der tatsächliche Ort des Fehlers im Backtrace angezeigt wird, da er sigaction()vom Kernel überschrieben werden kann .
Jschmier

9
Was würde passieren, wenn der Absturz von innen kommt? Würden Sie dann nicht eine Sperre halten und dann stecken bleiben, wenn "Backtrace" versucht, Speicher zuzuweisen?
Mattias Nilsson

7
catchsegvist nicht das, was das OP benötigt, aber es ist fantastisch, um Segmentierungsfehler zu erkennen und alle Informationen zu erhalten.
Matt Clarkson

8
Für ARM musste ich auch mit -funwind-Tabellen kompilieren. Ansonsten war meine Stapeltiefe immer 1 (leer).
jfritz42

128

Es ist noch einfacher als "man backtrace", es gibt eine wenig dokumentierte Bibliothek (GNU-spezifisch), die mit glibc als libSegFault.so verteilt wird. Ich glaube, sie wurde von Ulrich Drepper geschrieben, um das Programm catchsegv zu unterstützen (siehe "man catchsegv").

Dies gibt uns 3 Möglichkeiten. Anstatt "program -o hai" auszuführen:

  1. In catchsegv ausführen:

    $ catchsegv program -o hai
  2. Verknüpfung mit libSegFault zur Laufzeit:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Verknüpfung mit libSegFault zur Kompilierungszeit:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai

In allen drei Fällen erhalten Sie klarere Rückverfolgungen mit weniger Optimierung (gcc -O0 oder -O1) und Debugging-Symbolen (gcc -g). Andernfalls erhalten Sie möglicherweise nur einen Stapel Speicheradressen.

Sie können auch mehr Signale für Stapelspuren abfangen, z. B.:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

Die Ausgabe sieht ungefähr so ​​aus (beachten Sie die Rückverfolgung unten):

*** Segmentation fault Register dump:

 EAX: 0000000c   EBX: 00000080   ECX:
00000000   EDX: 0000000c  ESI:
bfdbf080   EDI: 080497e0   EBP:
bfdbee38   ESP: bfdbee20

 EIP: 0805640f   EFLAGS: 00010282

 CS: 0073   DS: 007b   ES: 007b   FS:
0000   GS: 0033   SS: 007b

 Trap: 0000000e   Error: 00000004  
OldMask: 00000000  ESP/signal:
bfdbee20   CR2: 00000024

 FPUCW: ffff037f   FPUSW: ffff0000  
TAG: ffffffff  IPOFF: 00000000  
CSSEL: 0000   DATAOFF: 00000000  
DATASEL: 0000

 ST(0) 0000 0000000000000000   ST(1)
0000 0000000000000000  ST(2) 0000
0000000000000000   ST(3) 0000
0000000000000000  ST(4) 0000
0000000000000000   ST(5) 0000
0000000000000000  ST(6) 0000
0000000000000000   ST(7) 0000
0000000000000000

Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]

Wenn Sie die wichtigsten Details wissen möchten, ist die beste Quelle leider die Quelle: Siehe http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c und das übergeordnete Verzeichnis http://sourceware.org/git/?p=glibc.git;a=tree;f=debug


1
"Möglichkeit 3. Verknüpfung mit libSegFault zur Kompilierungszeit" funktioniert nicht.
HHK

5
@crafter: Was meinst du mit "funktioniert nicht". Was haben Sie versucht, auf welcher Sprache / Compiler / Toolchain / Distribution / Hardware? Hat es nicht kompiliert? Fehler abfangen? Um überhaupt eine Ausgabe zu produzieren? Schwer zu verwendende Ausgabe zu produzieren? Vielen Dank für Details, es wird allen helfen.
Stéphane Gourichon

1
'beste Quelle ist leider die Quelle' ... Hoffentlich wird eines Tages auf der Manpage für catchsegv tatsächlich SEGFAULT_SIGNALS erwähnt. Bis dahin gibt es diese Antwort, auf die man sich beziehen kann.
Greggo

Ich kann nicht glauben, dass ich C seit 5 Jahren programmiere und noch nie davon gehört habe: /
DavidMFrey

6
@ StéphaneGourichon @HansKratz Um eine Verknüpfung mit libSegFault herzustellen, müssen Sie -Wl,--no-as-neededdie Compiler-Flags hinzufügen . Andernfalls ldwird in der Tat keine Verknüpfung hergestellt libSegFault, da erkannt wird, dass die Binärdatei keines ihrer Symbole verwendet.
Phillip

122

Linux

Die Verwendung der Funktionen backtrace () in execinfo.h zum Drucken eines Stacktraces und zum ordnungsgemäßen Beenden, wenn ein Segmentierungsfehler auftritt, wurde bereits vorgeschlagen , werden die Komplikationen, die erforderlich sind, um sicherzustellen, dass die resultierenden Backtrace-Punkte auf den tatsächlichen Speicherort von nicht erwähnt der Fehler (zumindest für einige Architekturen - x86 & ARM).

Die ersten beiden Einträge in der Stapelrahmenkette, wenn Sie in den Signalhandler gelangen, enthalten eine Rücksprungadresse im Signalhandler und einen in sigaction () in libc. Der Stapelrahmen der zuletzt vor dem Signal aufgerufenen Funktion (der der Ort des Fehlers ist) geht verloren.

Code

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 struct ucontext   *uc_link;
 stack_t           uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %p\n", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)\n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

Ausgabe

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

Alle Gefahren beim Aufrufen der Funktionen backtrace () in einem Signalhandler bestehen weiterhin und sollten nicht übersehen werden. Ich finde die hier beschriebene Funktionalität jedoch sehr hilfreich beim Debuggen von Abstürzen.

Es ist wichtig zu beachten, dass das von mir bereitgestellte Beispiel unter Linux für x86 entwickelt / getestet wurde. Ich habe dies auch erfolgreich auf ARM implementiert, indem ich uc_mcontext.arm_pcanstelle vonuc_mcontext.eip .

Hier ist ein Link zu dem Artikel, in dem ich die Details für diese Implementierung erfahren habe: http://www.linuxjournal.com/article/6391


11
Denken Sie auf Systemen, die GNU ld verwenden, daran, mit zu kompilieren -rdynamic, um den Linker anzuweisen, alle Symbole, nicht nur die verwendeten, zur dynamischen Symboltabelle hinzuzufügen. Dies ermöglicht backtrace_symbols()die Konvertierung von Adressen in Funktionsnamen
jschmier

1
Außerdem müssen Sie der GCC-Befehlszeile die Option "-mapcs-frame" hinzufügen, um Stack-Frames auf der ARM-Plattform zu generieren
qehgt

3
Dies mag zu spät sein, aber können wir den addr2lineBefehl irgendwie verwenden, um die genaue Zeile zu erhalten, in der der Absturz aufgetreten ist?
Enthusiastgeek

4
Bei neueren Builds von glibc uc_mcontextenthält kein Feld mit dem Namen eip. Es gibt jetzt ein Array, das indiziert werden muss, uc_mcontext.gregs[REG_EIP]ist das Äquivalent.
mmlb

6
Für ARM hatten meine Backtraces immer die Tiefe 1, bis ich dem Compiler die Option -funwind-tables hinzugefügt habe.
jfritz42

84

Obwohl eine korrekte Antwort gegeben wurde, die beschreibt, wie die GNU libc- backtrace()Funktion 1 verwendet wird, und ich meine eigene Antwort gegeben habe , die beschreibt, wie sichergestellt wird, dass eine Rückverfolgung von einem Signalhandler zum tatsächlichen Ort des Fehlers 2 zeigt , sehe ich nicht jede Erwähnung von entwirrenden C ++ - Symbolen, die vom Backtrace ausgegeben werden.

Wenn Sie Backtraces von einem C ++ - Programm erhalten, kann die Ausgabe durch c++filt1 ausgeführt werden , um die Symbole zu entwirren, oder indem Sie 1 direkt verwenden.abi::__cxa_demangle

  • 1 Linux & OS X Beachten Sie, dass c++filtund __cxa_demangleGCC-spezifisch sind
  • 2 Linux

Das folgende C ++ Linux-Beispiel verwendet denselben Signalhandler wie meine andere Antwort und zeigt, wie c++filtdie Symbole entwirrt werden können.

Code :

class foo
{
public:
    foo() { foo1(); }

private:
    void foo1() { foo2(); }
    void foo2() { foo3(); }
    void foo3() { foo4(); }
    void foo4() { crash(); }
    void crash() { char * p = NULL; *p = 0; }
};

int main(int argc, char ** argv)
{
    // Setup signal handler for SIGSEGV
    ...

    foo * f = new foo();
    return 0;
}

Ausgabe ( ./test):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Demangled Output ( ./test 2>&1 | c++filt):

signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

Das Folgende baut auf dem Signalhandler aus meiner ursprünglichen Antwort auf und kann den Signalhandler im obigen Beispiel ersetzen, um zu demonstrieren, wie abi::__cxa_demangledie Symbole entwirrt werden können. Dieser Signalhandler erzeugt den gleichen entwirrten Ausgang wie im obigen Beispiel.

Code :

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " << caller_address 
              << std::endl << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    array[1] = caller_address;

    char ** messages = backtrace_symbols(array, size);    

    // skip first stack frame (points here)
    for (int i = 1; i < size && messages != NULL; ++i)
    {
        char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;

        // find parantheses and +address offset surrounding mangled name
        for (char *p = messages[i]; *p; ++p)
        {
            if (*p == '(') 
            {
                mangled_name = p; 
            }
            else if (*p == '+') 
            {
                offset_begin = p;
            }
            else if (*p == ')')
            {
                offset_end = p;
                break;
            }
        }

        // if the line could be processed, attempt to demangle the symbol
        if (mangled_name && offset_begin && offset_end && 
            mangled_name < offset_begin)
        {
            *mangled_name++ = '\0';
            *offset_begin++ = '\0';
            *offset_end++ = '\0';

            int status;
            char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);

            // if demangling is successful, output the demangled function name
            if (status == 0)
            {    
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << real_name << "+" << offset_begin << offset_end 
                          << std::endl;

            }
            // otherwise, output the mangled function name
            else
            {
                std::cerr << "[bt]: (" << i << ") " << messages[i] << " : " 
                          << mangled_name << "+" << offset_begin << offset_end 
                          << std::endl;
            }
            free(real_name);
        }
        // otherwise, print the whole line
        else
        {
            std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
        }
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

1
Danke dafür, jschmier. Ich habe ein kleines Bash-Skript erstellt, um die Ausgabe davon in das Dienstprogramm addr2line einzuspeisen. Siehe: stackoverflow.com/a/15801966/1797414
arr_sea

4
Vergessen Sie nicht, <inxude <cxxabi.h>
Bamaco

1
Gute Dokumentation und eine unkomplizierte Header-Datei wurde hier seit 2008 veröffentlicht ... panthema.net/2008/0901-stacktrace-demangled sehr ähnlich zu Ihrem Ansatz :)
Kevinf

abi :: __ cxa_demangle scheint nicht das asynchrone Signal zu sein, daher kann der Signalhandler irgendwo in Malloc einen Deadlock verursachen.
Orcy

Die Verwendung von std::cerr, free()und exit()alle verletzen Einschränkungen gegen Aufruf nicht-Asynchron-Signal-sichere Anrufe auf POSIX - Systemen. Dieser Code wird Deadlock , wenn der Prozess in jedem Anruf wie fehlschlägt free(), malloc() newoder detete.
Andrew Henle

31

Vielleicht lohnt sich ein Blick auf Google Breakpad , einen plattformübergreifenden Crash-Dump-Generator und Tools zur Verarbeitung der Dumps.


Es werden Dinge wie Segmentierungsfehler gemeldet, aber es werden keine Informationen zu nicht behandelten C ++ - Ausnahmen gemeldet.
DBedrenko

21

Sie haben Ihr Betriebssystem nicht angegeben, daher ist dies schwer zu beantworten. Wenn Sie ein auf gnu libc basierendes System verwenden, können Sie möglicherweise die libc-Funktion verwenden backtrace().

GCC verfügt auch über zwei integrierte Funktionen, die Sie unterstützen können, die jedoch möglicherweise vollständig in Ihrer Architektur implementiert sind oder nicht. Diese sind __builtin_frame_addressund __builtin_return_address. Beide wollen eine sofortige Ganzzahlstufe (mit sofort meine ich, dass es keine Variable sein kann). Wenn __builtin_frame_addressfür eine bestimmte Ebene ungleich Null ist, sollte es sicher sein, die Rücksprungadresse derselben Ebene abzurufen.


13

Vielen Dank an enthusiasticgeek, dass Sie mich auf das Dienstprogramm addr2line aufmerksam gemacht haben.

Ich habe ein schnelles und schmutziges Skript geschrieben, um die Ausgabe der hier angegebenen Antwort zu verarbeiten : (! Vielen Dank jschmier) das addr2line Dienstprogramm.

Das Skript akzeptiert ein einziges Argument: Der Name der Datei, die die Ausgabe von jschmiers Dienstprogramm enthält.

Die Ausgabe sollte für jede Ebene der Ablaufverfolgung etwa Folgendes ausgeben:

BACKTRACE:  testExe 0x8A5db6b
FILE:       pathToFile/testExe.C:110
FUNCTION:   testFunction(int) 
   107  
   108           
   109           int* i = 0x0;
  *110           *i = 5;
   111      
   112        }
   113        return i;

Code:

#!/bin/bash

LOGFILE=$1

NUM_SRC_CONTEXT_LINES=3

old_IFS=$IFS  # save the field separator           
IFS=$'\n'     # new field separator, the end of line           

for bt in `cat $LOGFILE | grep '\[bt\]'`; do
   IFS=$old_IFS     # restore default field separator 
   printf '\n'
   EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
   ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
   echo "BACKTRACE:  $EXEC $ADDR"
   A2L=`addr2line -a $ADDR -e $EXEC -pfC`
   #echo "A2L:        $A2L"

   FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
   FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
   echo "FILE:       $FILE_AND_LINE"
   echo "FUNCTION:   $FUNCTION"

   # print offending source code
   SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
   LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
   if ([ -f $SRCFILE ]); then
      cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
   else
      echo "File not found: $SRCFILE"
   fi
   IFS=$'\n'     # new field separator, the end of line           
done

IFS=$old_IFS     # restore default field separator 

12

ulimit -c <value>Legt die maximale Dateigröße für Unix fest. Standardmäßig ist die maximale Dateigrößenbeschränkung 0. Sie können Ihre ulimitWerte mit anzeigen ulimit -a.

Wenn Sie Ihr Programm in gdb ausführen, wird Ihr Programm bei "Segmentierungsverletzungen" angehalten (SIGSEGV im Allgemeinen, wenn Sie auf einen Speicher zugegriffen haben, den Sie nicht zugewiesen haben), oder Sie können Haltepunkte festlegen.

ddd und nemiver sind Frontends für gdb, die dem Anfänger die Arbeit damit erheblich erleichtern.


6
Core-Dumps sind unendlich nützlicher als Stack-Traces, da Sie den Core-Dump in den Debugger laden und den Status des gesamten Programms und seiner Daten zum Zeitpunkt des Absturzes anzeigen können.
Adam Hawes

1
Die von anderen vorgeschlagene Backtrace-Funktion ist wahrscheinlich besser als nichts, aber sie ist sehr einfach - sie gibt nicht einmal Zeilennummern an. Mithilfe von Core-Dumps können Sie dagegen den gesamten Status Ihrer Anwendung zum Zeitpunkt des Absturzes rückwirkend anzeigen (einschließlich einer detaillierten Stapelverfolgung). Es könnte praktische Fragen werden mit dem Versuch , das für das Feld Debuggen zu verwenden, aber es ist auf jeden Fall ein leistungsfähigeres Werkzeug für die Analyse Abstürze und behauptet , während die Entwicklung (zumindest unter Linux).
Nobar

10

Es ist wichtig zu beachten, dass Sie nach dem Generieren einer Kerndatei das GDB-Tool verwenden müssen, um sie anzuzeigen. Damit gdb für Ihre Kerndatei einen Sinn ergibt, müssen Sie gcc anweisen, die Binärdatei mit Debugging-Symbolen zu instrumentieren. Dazu kompilieren Sie mit dem Flag -g:

$ g++ -g prog.cpp -o prog

Dann können Sie entweder "ulimit -c unlimited" festlegen, damit ein Core ausgegeben wird, oder Ihr Programm einfach in gdb ausführen. Ich mag den zweiten Ansatz mehr:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

Ich hoffe das hilft.


4
Sie können auch gdbdirekt von Ihrem Absturzprogramm aus aufrufen . Setup-Handler für SIGSEGV, SEGILL, SIGBUS, SIGFPE, der gdb aufruft. Details: stackoverflow.com/questions/3151779/… Der Vorteil ist, dass Sie eine schöne, mit Anmerkungen versehene Rückverfolgung wie in erhalten bt full. Außerdem können Sie Stapelspuren aller Threads abrufen .
Vi.

Sie können Backtrace auch einfacher als in der Antwort erhalten: gdb -silent ./prog core --eval-command = backtrace --batch -it würde Backtrace anzeigen und Debugger schließen
baziorek

10

Ich habe mich eine Weile mit diesem Problem befasst.

Und tief in der Google Performance Tools README vergraben

http://code.google.com/p/google-perftools/source/browse/trunk/README

spricht über libunwind

http://www.nongnu.org/libunwind/

Würde gerne Meinungen von dieser Bibliothek hören.

Das Problem mit -rdynamic ist, dass es in einigen Fällen die Größe der Binärdatei relativ signifikant erhöhen kann


2
Auf x86 / 64 habe ich nicht gesehen, dass -rdynamic die Binärgröße stark erhöht. Das Hinzufügen von -g führt zu einer viel größeren Erhöhung.
Dan

1
Ich habe festgestellt, dass libunwind keine Funktionalität zum Abrufen der Zeilennummer hat, und ich denke (nicht getestet), dass unw_get_proc_name anstelle des ursprünglichen Namens das Funktionssymbol (das für Überladung und dergleichen verschleiert ist) zurückgibt.
Herbert

1
Das ist richtig. Es wird sehr schwierig, dies richtig zu machen, aber ich hatte ausgezeichnete Erfolge mit gaddr2line. Hier gibt es viele praktische Informationen. Blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

Sie können DeathHandler verwenden - eine kleine C ++ - Klasse, die alles für Sie erledigt und zuverlässig ist.


1
Leider wird es verwendet execlp(), um addr2line-Aufrufe auszuführen ... wäre schön, vollständig im eigenen Programm zu bleiben (was möglich ist, indem der addr2line-Code in irgendeiner Form eingefügt wird)
Beispiel

9

Vergessen Sie das Ändern Ihrer Quellen und machen Sie einige Hacks mit der Funktion backtrace () oder Makros - dies sind nur schlechte Lösungen.

Als richtig funktionierende Lösung würde ich raten:

  1. Kompilieren Sie Ihr Programm mit dem Flag "-g", um Debug-Symbole in Binärdateien einzubetten (keine Sorge, dies hat keine Auswirkungen auf Ihre Leistung).
  2. Führen Sie unter Linux den nächsten Befehl aus: "ulimit -c unlimited" - damit das System große Absturzabbilder erstellen kann.
  3. Wenn Ihr Programm abgestürzt ist, sehen Sie im Arbeitsverzeichnis die Datei "core".
  4. Führen Sie den nächsten Befehl aus, um die Rückverfolgung nach stdout zu drucken: gdb -batch -ex "backtrace" ./Ihr_Programm_exe ./core

Dadurch wird die ordnungsgemäß lesbare Rückverfolgung Ihres Programms auf lesbare Weise gedruckt (mit Quelldateinamen und Zeilennummern). Darüber hinaus gibt Ihnen dieser Ansatz die Freiheit, Ihr System zu automatisieren: Erstellen Sie ein kurzes Skript, das prüft, ob der Prozess einen Core-Dump erstellt hat, und senden Sie dann Backtraces per E-Mail an Entwickler oder melden Sie dies in einem Protokollierungssystem an.


Es gibt die falschen Zeilennummern. Kann es verbessert werden?
HeyJude

7
ulimit -c unlimited

ist eine Systemvariable, mit der nach einem Absturz Ihrer Anwendung ein Core-Dump erstellt werden kann. In diesem Fall eine unbegrenzte Menge. Suchen Sie im selben Verzeichnis nach einer Datei namens core. Stellen Sie sicher, dass Sie Ihren Code mit aktivierten Debugging-Informationen kompiliert haben!

Grüße


5
Der Benutzer fragt nicht nach einem Core-Dump. Er bittet um eine Stapelspur. Siehe delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin

1
Ein Core-Dump enthält den Aufrufstapel zum Zeitpunkt des Absturzes, nicht wahr?
Mo.

3
Sie gehen davon aus, dass er unter Unix arbeitet und Bash verwendet.
Paul Tomblin

2
Wenn Sie tcsh verwenden, müssen Sie tunlimit coredumpsize unlimited
sivabudh

6

Ansehen:

Mann 3 zurückverfolgen

Und:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

Dies sind GNU-Erweiterungen.


2
Auf dieser Seite, die ich vor einiger Zeit
Stéphane

6

Siehe die Stack-Trace-Funktion in ACE (ADAPTIVE Communication Environment). Es wurde bereits geschrieben, um alle wichtigen Plattformen (und mehr) abzudecken. Die Bibliothek ist im BSD-Stil lizenziert, sodass Sie den Code sogar kopieren / einfügen können, wenn Sie ACE nicht verwenden möchten.


Die Verbindung scheint tot zu sein.
tglas

5

Ich kann mit der Linux-Version helfen: Die Funktionen backtrace, backtrace_symbols und backtrace_symbols_fd können verwendet werden. Siehe die entsprechenden Handbuchseiten.


5

Es sieht so aus, als ob in einer der letzten C ++ Boost-Versionen die Bibliothek genau das bietet, was Sie wollen. Wahrscheinlich wäre der Code eine Multiplattform. Es ist boost :: stacktrace , das Sie wie im Boost-Beispiel verwenden können :

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

Unter Linux kompilieren Sie den obigen Code:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

Beispiel-Backtrace aus der Boost-Dokumentation kopiert :

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

4

* nix: Sie können SIGSEGV abfangen (normalerweise wird dieses Signal vor dem Absturz ausgelöst) und die Informationen in einer Datei speichern. (Neben der Kerndatei, mit der Sie beispielsweise mit gdb debuggen können).

win: Überprüfen Sie dies von msdn.

Sie können sich auch den Chrome-Code von Google ansehen, um zu sehen, wie er mit Abstürzen umgeht. Es hat einen schönen Mechanismus zur Behandlung von Ausnahmen.


SEH hilft nicht bei der Erstellung eines Stack-Trace. Diese Lösung könnte zwar Teil einer Lösung sein, ist jedoch schwieriger zu implementieren und bietet weniger Informationen auf Kosten der Offenlegung von mehr Informationen zu Ihrer Anwendung als die eigentliche Lösung: Schreiben Sie einen Mini-Dump. Und richten Sie Windows so ein, dass dies automatisch für Sie erledigt wird.
Unsichtbar

4

Ich habe festgestellt, dass die @ tgamblin-Lösung nicht vollständig ist. Es kann nicht mit Stackoverflow umgehen. Ich denke, weil der Signalhandler standardmäßig mit demselben Stack aufgerufen wird und SIGSEGV zweimal ausgelöst wird. Zum Schutz müssen Sie einen unabhängigen Stapel für den Signalhandler registrieren.

Sie können dies mit dem folgenden Code überprüfen. Standardmäßig schlägt der Handler fehl. Mit dem definierten Makro STACK_OVERFLOW ist alles in Ordnung.

#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>

using namespace std;

//#define STACK_OVERFLOW

#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif

static struct sigaction sigseg_handler;

void handler(int sig) {
  cerr << "sig seg fault handler" << endl;
  const int asize = 10;
  void *array[asize];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, asize);

  // print out all the frames to stderr
  cerr << "stack trace: " << endl;
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  cerr << "resend SIGSEGV to get core dump" << endl;
  signal(sig, SIG_DFL);
  kill(getpid(), sig);
}

void foo() {
  foo();
}

int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
  sigseg_stack.ss_sp = stack_body;
  sigseg_stack.ss_flags = SS_ONSTACK;
  sigseg_stack.ss_size = sizeof(stack_body);
  assert(!sigaltstack(&sigseg_stack, nullptr));
  sigseg_handler.sa_flags = SA_ONSTACK;
#else
  sigseg_handler.sa_flags = SA_RESTART;  
#endif
  sigseg_handler.sa_handler = &handler;
  assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
  cout << "sig action set" << endl;
  foo();
  return 0;
} 

4

Der neue König in der Stadt ist angekommen https://github.com/bombela/backward-cpp

1 Header zum Platzieren in Ihrem Code und 1 Bibliothek zum Installieren.

Persönlich nenne ich es mit dieser Funktion

#include "backward.hpp"
void stacker() {

using namespace backward;
StackTrace st;


st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace

Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}

Beeindruckend! So sollte es endlich gemacht werden! Ich habe gerade durch eigene Lösung zugunsten dieser abgeladen.
tglas

3

Ich würde den Code verwenden, der eine Stapelverfolgung für durchgesickerten Speicher in Visual Leak Detector generiert . Dies funktioniert jedoch nur unter Win32.


Und erfordert, dass Sie Debug-Symbole mit Ihrem Code versenden. Im Allgemeinen nicht wünschenswert. Schreiben Sie einen Mini-Dump und richten Sie Windows so ein, dass dies bei nicht behandelten Ausnahmen automatisch für Sie erledigt wird.
Unsichtbar

3

Ich habe hier viele Antworten gesehen, die einen Signalhandler ausgeführt und dann beendet haben. Das ist der richtige Weg, aber denken Sie an eine sehr wichtige Tatsache: Wenn Sie den Core-Dump für den generierten Fehler erhalten möchten, können Sie nicht aufrufen exit(status). Rufen Sie abort()stattdessen an!


3

Als reine Windows-Lösung können Sie mithilfe der Windows-Fehlerberichterstattung das Äquivalent eines Stack-Trace (mit viel, viel mehr Informationen) abrufen . Mit nur wenigen Registrierungseinträgen kann es so eingerichtet werden, dass Dumps im Benutzermodus erfasst werden :

Ab Windows Server 2008 und Windows Vista mit Service Pack 1 (SP1) kann die Windows-Fehlerberichterstattung (WER) so konfiguriert werden, dass nach einem Absturz einer Anwendung im Benutzermodus vollständige Speicherauszüge im Benutzermodus gesammelt und lokal gespeichert werden. [...]

Diese Funktion ist standardmäßig nicht aktiviert. Für die Aktivierung der Funktion sind Administratorrechte erforderlich. Verwenden Sie zum Aktivieren und Konfigurieren der Funktion die folgenden Registrierungswerte unter dem Schlüssel HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows-Fehlerberichterstattung \ LocalDumps .

Sie können die Registrierungseinträge in Ihrem Installationsprogramm festlegen, das über die erforderlichen Berechtigungen verfügt.

Das Erstellen eines Dumps im Benutzermodus bietet gegenüber dem Generieren eines Stack-Trace auf dem Client die folgenden Vorteile:

  • Es ist bereits im System implementiert. Sie können entweder WER wie oben beschrieben verwenden oder MiniDumpWriteDump selbst aufrufen , wenn Sie eine genauere Kontrolle über die Menge der zu sichernden Informationen benötigen. (Stellen Sie sicher, dass Sie es aus einem anderen Prozess aufrufen.)
  • Weg vollständiger als ein Stack - Trace. Es kann unter anderem lokale Variablen, Funktionsargumente, Stapel für andere Threads, geladene Module usw. enthalten. Die Datenmenge (und folglich die Größe) ist in hohem Maße anpassbar.
  • Debug-Symbole müssen nicht versendet werden. Dies verringert sowohl die Größe Ihrer Bereitstellung drastisch als auch die Rückentwicklung Ihrer Anwendung.
  • Weitgehend unabhängig vom verwendeten Compiler. Für die Verwendung von WER ist nicht einmal Code erforderlich. In beiden Fällen ist es für die Offline-Analyse sehr nützlich , eine Möglichkeit zu haben, eine Symboldatenbank (PDB) abzurufen. Ich glaube, GCC kann entweder PDBs generieren, oder es gibt Tools, um die Symboldatenbank in das PDB-Format zu konvertieren.

Beachten Sie, dass WER nur durch einen Anwendungsabsturz ausgelöst werden kann (dh das System beendet einen Prozess aufgrund einer nicht behandelten Ausnahme). MiniDumpWriteDumpkann jederzeit aufgerufen werden. Dies kann hilfreich sein, wenn Sie den aktuellen Status sichern müssen, um andere Probleme als einen Absturz zu diagnostizieren.

Obligatorische Lektüre, wenn Sie die Anwendbarkeit von Mini-Dumps bewerten möchten:


2

Zusätzlich zu den obigen Antworten erfahren Sie hier, wie Sie Debian Linux OS dazu bringen, einen Core-Dump zu generieren

  1. Erstellen Sie einen "Coredumps" -Ordner im Home-Ordner des Benutzers
  2. Gehen Sie zu /etc/security/limits.conf. Geben Sie unterhalb der Zeile '' "soft core unlimited" und "root soft core unlimited" ein, wenn Sie Core-Dumps für root aktivieren, um unbegrenzten Speicherplatz für Core-Dumps zu ermöglichen.
  3. HINWEIS: "* soft core unlimited" deckt root nicht ab, weshalb root in einer eigenen Zeile angegeben werden muss.
  4. Um diese Werte zu überprüfen, melden Sie sich ab, wieder an und geben Sie "ulimit -a" ein. "Core-Dateigröße" sollte auf unbegrenzt eingestellt sein.
  5. Überprüfen Sie die .bashrc-Dateien (Benutzer und ggf. root), um sicherzustellen, dass dort nicht ulimit festgelegt ist. Andernfalls wird der obige Wert beim Start überschrieben.
  6. Öffnen Sie /etc/sysctl.conf. Geben Sie unten Folgendes ein: "kernel.core_pattern = /home//coredumps/%e_%t.dump". (% e ist der Prozessname und% t ist die Systemzeit)
  7. Beenden Sie und geben Sie "sysctl -p" ein, um die neue Konfiguration zu laden. Überprüfen Sie / proc / sys / kernel / core_pattern und stellen Sie sicher, dass dies mit dem übereinstimmt, was Sie gerade eingegeben haben.
  8. Core Dumping kann getestet werden, indem ein Prozess in der Befehlszeile ("&") ausgeführt und dann mit "kill -11" beendet wird. Wenn das Core-Dumping erfolgreich ist, wird nach der Anzeige des Segmentierungsfehlers "(Core-Dumping)" angezeigt.

2

Wenn du es trotzdem alleine machen willst, wie ich es getan habe, kannst du gegen es verlinken bfdund es vermeidenaddr2line wie ich es hier getan habe:

https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c

Dies erzeugt die Ausgabe:

[E]        crash.linux.c:170  | crit_err_hdlr                  | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E]        crash.linux.c:171  | crit_err_hdlr                  | signal 11 (Segmentation fault), address is (nil)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]

1

Verwenden Sie unter Linux / Unix / MacOSX Kerndateien (Sie können sie mit ulimit oder einem kompatiblen Systemaufruf aktivieren ). Verwenden Sie unter Windows die Microsoft-Fehlerberichterstattung (Sie können Partner werden und Zugriff auf Ihre Anwendungsabsturzdaten erhalten).


0

Ich habe die GNOME-Technologie von "apport" vergessen, weiß aber nicht viel darüber. Es wird verwendet, um Stacktraces und andere Diagnosen für die Verarbeitung zu generieren und kann automatisch Fehler melden. Ein Besuch lohnt sich auf jeden Fall.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.