it-swarm-eu.dev

Wie generiert man automatisch einen Stacktrace, wenn mein Programm abstürzt?

Ich arbeite an 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 gcc kompiliert).

Ich möchte, dass mein Programm in der Lage ist, einen Stack-Trace zu generieren, wenn es abstürzt, und wenn der Benutzer es das nächste Mal ausführt. Ich kann das Senden der Informationen an mich abwickeln, aber ich weiß nicht, wie ich den Trace-String generieren soll. Irgendwelche Ideen?

518
KPexEA

Unter Linux und ich glaube Mac OS X, wenn Sie gcc oder einen Compiler verwenden, der glibc verwendet, können Sie die backtrace () - Funktionen in execinfo.h verwenden, um einen Stacktrace zu drucken und bei einem Segmentierungsfehler ordnungsgemäß zu beenden. Dokumentation ist im libc-Handbuch .

Hier ist ein Beispielprogramm, das einen SIGSEGV -Handler installiert und einen Stacktrace auf stderr druckt, wenn er abweicht. 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 -g -rdynamic erhalten Sie Symbolinformationen in Ihrer Ausgabe, mit denen glibc einen Nice-Stacktrace erstellen kann:

$ 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 Stapel stammt. Hier können Sie den Signalhandler oben auf dem Stack und die libc-Funktionen vor main zusätzlich zu main, foo, bar und baz sehen.

447
Todd Gamblin

Linux

Während die Verwendung der backtrace () - Funktionen in execinfo.h zum Ausdrucken eines Stacktraces und zum ordnungsgemäßen Beenden bei einem Segmentierungsfehler verwendet wurde wurde bereits vorgeschlagen , sehe ich keine Erwähnung der Komplikationen, die erforderlich sind, um die resultierenden Backtrace-Punkte sicherzustellen der tatsächliche Ort des Fehlers (zumindest für einige Architekturen - x86 & ARM).

Die ersten beiden Einträge in der Stack-Frame-Kette, wenn Sie in den Signal-Handler gelangen, enthalten eine Rücksprungadresse innerhalb des Signal-Handlers und eine innerhalb von sigaction () in libc. Der Stack-Frame der letzten vor dem Signal aufgerufenen Funktion (das ist 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;
 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 des Aufrufs der backtrace () - Funktionen in einem Signal-Handler sind immer noch vorhanden 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, wobei uc_mcontext.arm_pc anstelle von uc_mcontext.eip verwendet wurde. 

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

115
jschmier

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

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

  1. Innerhalb von catchsegv ausführen:

    $ catchsegv program -o hai
    
  2. Zur Laufzeit mit libSegFault verknüpfen:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Verknüpfung 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 Debugging-Symbolen (gcc -g). Andernfalls erhalten Sie möglicherweise einen Stapel Speicheradressen.

Sie können auch mehr Signale für Stack-Spuren abrufen, beispielsweise mit:

$ 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 blutigen Details erfahren möchten, 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

112
jhclark

Es wurde zwar eine richtige Antwort angegeben, die beschreibt, wie die Funktion GNU libc backtrace() verwendet wird1 und ich habe meine eigene Antwort zur Verfügung gestellt, die beschreibt, wie eine Rückverfolgung von Signalverarbeitungspunkten zum tatsächlichen Fehlerort sichergestellt werden kann2Ich sehe keine Erwähnung von demangling C++ - Symbolen, die vom Backtrace ausgegeben werden.

Beim Abrufen von Backtraces aus einem C++ - Programm kann die Ausgabe über c++filt ausgeführt werden.1 um die Symbole zu entwirren oder mit abi::__cxa_demangle1 direkt.

  • 1 Linux & OS X Beachten Sie, dass c++filt und __cxa_demangle GCC-spezifisch sind
  • 2 Linux

Das folgende C++ Linux-Beispiel verwendet den gleichen Signal-Handler wie meine other answer und zeigt, wie c++filt zum Demangeln der Symbole verwendet werden kann.

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 original answer auf und kann den Signalhandler im obigen Beispiel ersetzen, um zu zeigen, wie abi::__cxa_demangle zum Entmischen der Symbole verwendet werden kann. Dieser Signal-Handler erzeugt die gleiche entschlüsselte Ausgabe 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);
}
76
jschmier

Vielleicht einen Blick auf Google Breakpad , einen plattformübergreifenden Crash-Dump-Generator und Tools zum Verarbeiten der Dumps.

33
Simon Steele

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 backtrace() verwenden.

GCC verfügt außerdem über zwei integrierte Funktionen, die Sie unterstützen können, die jedoch in Ihrer Architektur möglicherweise nicht vollständig implementiert sind. Dies sind __builtin_frame_address und __builtin_return_address. Beide wollen eine sofortige Integer-Ebene (mit sofort, ich meine, es kann keine Variable sein). Wenn __builtin_frame_address für eine bestimmte Ebene nicht Null ist, sollte es sicher sein, die Rücksprungadresse derselben Ebene zu ermitteln.

21
Brian Mitchell

ulimit -c <value> legt die Beschränkung der Kerndateigröße für Unix fest. Standardmäßig ist die maximale Dateigröße 0. Sie können Ihre ulimit-Werte mit ulimit -a anzeigen.

wenn Sie Ihr Programm von gdb aus ausführen, wird Ihr Programm bei "Segmentierungsverletzungen" (SIGSEGV, im Allgemeinen wenn Sie auf einen Speicherplatz zugreifen, den Sie nicht zugewiesen haben) angehalten, oder Sie können Haltepunkte setzen.

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

12
Joseph

Ich habe dieses Problem schon eine Weile betrachtet.

Und tief in der Google Performance Tools README verankert

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

spricht über libunwind

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

Ich würde gerne Meinungen über diese Bibliothek hören.

Das Problem bei -rdynamic ist, dass die Größe der Binärdatei in einigen Fällen relativ stark erhöht werden kann

10
Gregory

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

Ich habe ein schnelles und schmutziges Skript geschrieben, um die Ausgabe der bereitgestellten Antwort hier : (Vielen Dank an Jschmier!) Mithilfe des Dienstprogramms addr2line zu verarbeiten.

Das Skript akzeptiert ein einzelnes Argument: Der Name der Datei, die die Ausgabe des Dienstprogramms von Jschmier enthält.

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

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 
10
arr_sea

Einige Versionen von libc enthalten Funktionen, die sich mit Stack-Spuren beschäftigen. Sie können sie möglicherweise verwenden:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Ich erinnere mich an die Verwendung von libunwind vor langer Zeit, um Stack-Spuren zu erhalten, aber es wird möglicherweise nicht auf Ihrer Plattform unterstützt.

10
Stephen Deken

Es ist wichtig anzumerken, dass Sie nach dem Generieren einer Core-Datei das gdb-Tool verwenden müssen, um die Datei anzuzeigen. Damit gdb Ihre Kerndatei verstehen kann, müssen Sie gcc anweisen, die Binärdatei mit Debugging-Symbolen zu instrumentieren. Dazu kompilieren Sie das Flag -g:

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

Dann können Sie entweder "ulimit -c unlimited" setzen, um einen Core auszugeben, oder einfach Ihr Programm 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. 

10
Benson
ulimit -c unlimited

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

grüße

9
mana

Vergessen Sie nicht, Ihre Quellen zu ändern 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 empfehlen:

  1. Kompilieren Sie Ihr Programm mit dem Flag "-g", um Debug-Symbole in Binärdateien einzubetten (machen Sie sich keine Sorgen, dies beeinträchtigt Ihre Leistung nicht). 
  2. Führen Sie unter Linux folgenden 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 "zurückverfolgen" ./Ihr_Programm_exe ./core

Auf diese Weise können Sie eine gut lesbare Rückverfolgung Ihres Programms in lesbarer Weise (mit Quelldateinamen und Zeilennummern) ausdrucken. Außerdem bietet Ihnen dieser Ansatz die Möglichkeit, Ihr System zu automatisieren einen Core-Dump und senden Sie dann per E-Mail Backtraces an Entwickler oder melden Sie sich bei einem Protokollierungssystem an.

9
loopzilla

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

8
markhor
8
Roskoto

Ansehen:

mann 3 Rückzug

Und:

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

Dies sind die Erweiterungen von GNU.

7
Stéphane

Siehe 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.

6
Adam Mitz

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
terminus

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

win: Check this von msdn.

Sie können sich auch den Chrome-Code von Google ansehen, um zu sehen, wie er Abstürze behandelt. Es hat einen Nice-Ausnahmebehandlungsmechanismus.

4
INS

Ich habe festgestellt, dass die @tgamblin-Lösung nicht vollständig ist. Sie kann nicht mit stackoverflow umgehen. Ich denke, weil der Signalhandler standardmäßig mit demselben Stack aufgerufen wird und SIGSEGV wird zweimal ausgelöst. Zum Schutz müssen Sie einen unabhängigen Stack 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

Ich habe hier viele Antworten gesehen, die einen Signal-Handler 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 exit(status) nicht aufrufen. Rufen Sie stattdessen abort() auf!

3
jard18

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

1 Header in Ihrem Code und 1 Bibliothek zum Installieren.

Ich persönlich rufe es mit dieser Funktion an

#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);
}
3
Roy

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

3
Jim Buck

Als reine Windows-Lösung können Sie mit Windows Error Reporting das Äquivalent einer Stack-Ablaufverfolgung (mit viel, viel mehr Informationen) erhalten. Mit nur wenigen Registrierungseinträgen kann es zu Sammeln von Benutzermodus-Dumps eingerichtet werden:

Mit 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 Dumps im Benutzermodus gesammelt und lokal gespeichert werden. [...]

Diese Funktion ist standardmäßig nicht aktiviert. Zum Aktivieren 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 Error Reporting\LocalDumps.

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

Das Erstellen eines Benutzermodus-Dumps hat gegenüber dem Generieren eines Stack-Trace auf dem Client folgende 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 speichernden Informationen benötigen. (Stellen Sie sicher, dass Sie es aus einem anderen Prozess aufrufen.)
  • Way ist vollständiger als ein Stack-Trace. Es kann unter anderem lokale Variablen, Funktionsargumente, Stacks für andere Threads, geladene Module usw. enthalten. Die Datenmenge (und damit die Größe) ist sehr anpassbar.
  • Debug-Symbole müssen nicht ausgeliefert werden. Dadurch wird sowohl die Größe Ihrer Bereitstellung drastisch reduziert als auch das Reverse-Engineering Ihrer Anwendung schwieriger.
  • Weitgehend unabhängig vom verwendeten Compiler. Die Verwendung von WER erfordert nicht einmal Code. In jedem Fall ist die Möglichkeit, eine Symboldatenbank (PDB) zu erhalten, very, was für die Offline-Analyse nützlich ist. Ich glaube, GCC kann entweder PDBs generieren oder es gibt Werkzeuge, um die Symboldatenbank in das PDB-Format zu konvertieren.

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

Obligatorisches Lesen, wenn Sie die Anwendbarkeit von Mini-Dumps bewerten möchten:

2
IInspectable

Zusätzlich zu den obigen Antworten wird hier beschrieben, wie Sie mit Debian Linux OS Core Dump generieren können 

  1. Erstellen Sie im Stammordner des Benutzers einen Ordner "coredumps"
  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, melden Sie sich erneut 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 gesetzt 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 vergewissern Sie sich, dass diese mit Ihrer Eingabe übereinstimmt.
  8. Core-Dumping kann getestet werden, indem ein Prozess in der Befehlszeile ("&") ausgeführt und anschließend mit "kill -11" abgebrochen wird. Wenn das Core-Dumping erfolgreich ist, wird nach der Anzeige des Segmentierungsfehlers „(Core-Dumping)“ angezeigt.
2

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

1
Kasprzol

Wenn du es trotzdem alleine machen willst, kannst du gegen bfd verlinken und die Verwendung von addr2line vermeiden, 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]
0
Geoffrey

Ich habe die GNOME-Technologie von "apport" vergessen, weiß aber nicht viel über die Verwendung von GNOME. Es wird verwendet, um Stacktraces und andere Diagnosen für die Verarbeitung zu generieren und Fehler automatisch beheben zu können. Es lohnt sich auf jeden Fall.

0
Joseph

Wenn Ihr Programm abstürzt, generiert das Betriebssystem selbst Absturzabbildinformationen. Wenn Sie ein * nix-Betriebssystem verwenden, müssen Sie dies nicht verhindern.

0
nsayer

Es sieht aus wie in einer der letzten C++ - Boost-Version erschienenen Bibliothek, um genau das bereitzustellen, was Sie möchten, wahrscheinlich wäre der Code multiplatform . Es handelt sich um boost :: stacktrace , die Sie wie als Boost verwenden können Probe :

#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
}

In Linux kompilieren Sie den obigen Code:

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

Beispiel-Backtrace aus 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
0
Grzegorz Bazior