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?
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.
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.
#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);
}
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
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:
Innerhalb von catchsegv ausführen:
$ catchsegv program -o hai
Zur Laufzeit mit libSegFault verknüpfen:
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
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
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_demangle
1 direkt.
c++filt
und __cxa_demangle
GCC-spezifisch sindDas 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);
}
Vielleicht einen Blick auf Google Breakpad , einen plattformübergreifenden Crash-Dump-Generator und Tools zum Verarbeiten der Dumps.
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.
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.
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
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
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.
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.
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
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:
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.
Sie können DeathHandler - kleine C++ - Klasse verwenden, die alles zuverlässig für Sie erledigt.
win: Wie wäre es mit StackWalk64 http://msdn.Microsoft.com/en-us/library/ms680650.aspx
Ansehen:
mann 3 Rückzug
Und:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
Dies sind die Erweiterungen von GNU.
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.
Ich kann mit der Linux-Version helfen: Die Funktionen backtrace, backtrace_symbols und backtrace_symbols_fd können verwendet werden. Siehe die entsprechenden Handbuchseiten.
* 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.
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;
}
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!
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);
}
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.
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:
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:
Zusätzlich zu den obigen Antworten wird hier beschrieben, wie Sie mit Debian Linux OS Core Dump generieren können
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).
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]
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.
Wenn Ihr Programm abstürzt, generiert das Betriebssystem selbst Absturzabbildinformationen. Wenn Sie ein * nix-Betriebssystem verwenden, müssen Sie dies nicht verhindern.
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