691 Stimmen

Wie kann ich automatisch einen Stacktrace erzeugen, wenn mein Programm abstürzt?

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

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

Ich möchte, dass mein Programm in der Lage ist, einen Stack-Trace zu erzeugen, wenn es abstürzt, und dass der Benutzer beim nächsten Mal, wenn er es ausführt, gefragt wird, ob es in Ordnung ist, den Stack-Trace an mich zu senden, damit ich das Problem aufspüren kann. Das Senden der Informationen an mich ist kein Problem, aber ich weiß nicht, wie ich den Trace-String erzeugen kann. Hat jemand eine Idee?

4 Stimmen

Backtrace und backtrace_symbols_fd sind nicht async-signal-safe. Sie sollten diese Funktionen nicht in Signalhandlern verwenden

13 Stimmen

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 üblicherweise in Signalhandlern verwendet.

3 Stimmen

@cmccabe das ist falsch backtrace_symbols_fd ruft normalerweise nicht malloc auf, aber es kann sein, dass etwas in seinem catch_error-Block schief geht

595voto

Todd Gamblin Punkte 56250

Für Linux und ich glaube Mac OS X, wenn Sie gcc oder einen anderen Compiler verwenden, der glibc benutzt, können Sie die backtrace()-Funktionen in execinfo.h um einen Stacktrace auszugeben und bei einem Segmentierungsfehler ordnungsgemäß zu beenden. Die Dokumentation kann gefunden werden im libc-Handbuch .

Hier ist ein Beispielprogramm, das eine SIGSEGV Handler und gibt einen Stacktrace an stderr wenn es zu einem Seggen kommt. Die Website baz() Funktion verursacht hier 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.
}

Kompilieren mit -g -rdynamic liefert Ihnen Symbolinformationen in Ihrer Ausgabe, die die Glibc verwenden kann, um einen schönen Stacktrace zu erstellen:

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

Wenn Sie dies ausführen, erhalten Sie diese 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 Offset und die Funktion, von der jeder Frame im Stack stammt. Hier sehen Sie den Signalhandler oben auf dem Stapel und die libc-Funktionen vor main zusätzlich zu main , foo , bar y baz .

59 Stimmen

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

7 Stimmen

Es sieht so aus, als enthielten die ersten beiden Einträge in Ihrer Backtrace-Ausgabe eine Rücksprungadresse innerhalb des Signalhandlers und wahrscheinlich eine innerhalb von sigaction() in libc. Während Ihr Backtrace korrekt zu sein scheint, habe ich manchmal festgestellt, dass zusätzliche Schritte notwendig sind, um sicherzustellen, dass der tatsächliche Ort des Fehlers im Backtrace erscheint, da er mit sigaction() durch den Kernel.

9 Stimmen

Was würde passieren, wenn der Absturz von innerhalb von malloc kommt? Würden Sie dann nicht eine Sperre halten und dann stecken bleiben, während "backtrace" versucht, Speicher zuzuweisen?

176voto

jhclark Punkte 2363

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

Daraus ergeben sich 3 Möglichkeiten. Anstatt "Programm -o hai" auszuführen:

  1. Innerhalb von catchsegv ausführen:

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

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Link mit libSegFault zur Kompilierzeit:

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

In allen 3 Fällen erhalten Sie klarere Backtraces mit weniger Optimierung (gcc -O0 oder -O1) und Debugsymbolen (gcc -g). Andernfalls kann es sein, dass Sie am Ende nur einen Haufen Speicheradressen haben.

Sie können auch mehr Signale für Stack Traces mit etwas wie fangen:

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

Die Ausgabe sieht in etwa so aus (beachten Sie den Backtrace am unteren Rand):

*** 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 blutigen Details wissen wollen, ist die beste Quelle leider die Quelle: Siehe http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c und sein übergeordnetes Verzeichnis http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

1 Stimmen

"Möglichkeit 3. Link mit libSegFault zur Kompilierzeit" funktioniert nicht.

5 Stimmen

@crafter: Was meinen Sie mit "funktioniert nicht". Was haben Sie ausprobiert, mit welcher Sprache/Compiler/Toolchain/Distribution/Hardware ? Konnte es nicht kompiliert werden? Einen Fehler zu finden? Überhaupt eine Ausgabe zu erzeugen? Eine schwer zu verwendende Ausgabe zu erzeugen? Vielen Dank für Ihre Angaben, sie werden allen helfen.

2 Stimmen

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

130voto

jschmier Punkte 14832

Linux

Während die Verwendung der backtrace()-Funktionen in execinfo.h zum Ausdrucken eines Stacktrace und zum ordnungsgemäßen Beenden, wenn ein Segmentierungsfehler auftritt, die bereits vorgeschlagen worden Ich sehe keine Erwähnung der Feinheiten, die notwendig sind, um sicherzustellen, dass der resultierende Backtrace auf den tatsächlichen Ort des Fehlers zeigt (zumindest für einige Architekturen - x86 & ARM).

Die ersten beiden Einträge in der Stackframe-Kette, wenn Sie in den Signalhandler gelangen, enthalten eine Rücksprungadresse innerhalb des Signalhandlers und eine innerhalb von sigaction() in libc. Der Stack-Frame der letzten Funktion, die vor dem Signal aufgerufen wurde (also der Ort des Fehlers), 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;
 ucontext_t        *uc_link;
 stack_t           uc_stack;
 sigcontext_t      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, die mit dem Aufruf der backtrace()-Funktionen in einem Signalhandler verbunden sind, bestehen weiterhin und sollten nicht übersehen werden, aber ich finde die hier beschriebene Funktionalität sehr hilfreich bei der Fehlersuche in 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_pc anstelle von uc_mcontext.eip .

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

11 Stimmen

Auf Systemen, die GNU ld verwenden, müssen Sie die Kompilierung mit -rdynamic um den Linker anzuweisen, alle Symbole, nicht nur die verwendeten, in die dynamische Symboltabelle aufzunehmen. Dies ermöglicht backtrace_symbols() um Adressen in Funktionsnamen umzuwandeln

0 Stimmen

Die Ausgabe im obigen Beispiel stammt von einem Testprogramm, das mit einer gcc-3.4.5-glibc-2.3.6-Cross-Toolchain kompiliert und auf einer ARMv6-basierten Plattform mit Linux-Kernel 2.6.22 ausgeführt wurde.

0 Stimmen

Die Aktivierung der Backtrace-Unterstützung ist nur sinnvoll, wenn für den Thumb-Modus in ARM kompiliert wird

95voto

jschmier Punkte 14832

Auch wenn ein richtige Antwort zur Verfügung gestellt, das beschreibt, wie man die GNU libc backtrace() Funktion 1 und ich habe meine eigene Antwort das beschreibt, wie man sicherstellt, dass eine Rückverfolgung von einem Signalhandler zum tatsächlichen Ort des Fehlers führt 2 Ich sehe keine Erwähnung von Entflechtung C++-Symbole, die aus dem Backtrace ausgegeben werden.

Wenn man Backtraces von einem C++-Programm erhält, kann man die Ausgabe durch c++filt 1 um die Symbole zu entwirren, oder indem Sie abi::__cxa_demangle 1 direkt.

  • 1 Linux und OS X Beachten Sie, dass <code>c++filt</code> y <code>__cxa_demangle</code> sind GCC-spezifisch
  • 2 Linux

Das folgende C++-Linux-Beispiel verwendet denselben Signalhandler wie mein andere Antwort und demonstriert, wie c++filt kann verwendet werden, um die Symbole zu entwirren.

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]

Entmangelte Ausgabe ( ./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 Originalantwort und kann den Signalhandler im obigen Beispiel ersetzen, um zu zeigen, wie abi::__cxa_demangle kann verwendet werden, um die Symbole zu entwirren. Dieser Signalhandler erzeugt die gleiche entmangelte Ausgabe wie das obige 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 Stimmen

Vielen Dank dafür, jschmier. Ich habe ein kleines Bash-Skript erstellt, um die Ausgabe in das Dienstprogramm addr2line einzuspeisen. See: stackoverflow.com/a/15801966/1797414

6 Stimmen

Vergessen Sie nicht #include <cxxabi.h>

1 Stimmen

Eine gute Dokumentation und eine übersichtliche Header-Datei werden hier seit 2008 veröffentlicht... panthema.net/2008/0901-stacktrace-demangled sehr ähnlich zu Ihrem Ansatz :)

34voto

Simon Steele Punkte 11468

Könnte einen Blick wert sein Google Breakpad einen plattformübergreifenden Crash-Dump-Generator und Tools zur Verarbeitung der Dumps.

0 Stimmen

Es berichtet über Dinge wie Segmentierungsfehler, aber es berichtet keine Informationen über unbehandelte C++-Ausnahmen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X