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
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
7 Stimmen
Es "kann" in dem Sinne, dass es keine POSIX-Spezifikation für backtrace_symbols_fd (oder irgendeinen Backtrace) gibt; allerdings ist GNU/Linux's backtrace_symbols_fd so spezifiziert, dass es niemals malloc aufruft, wie in linux.die.net/man/3/backtrace_symbols_fd . Daher kann man davon ausgehen, dass es unter Linux niemals malloc aufrufen wird.
0 Stimmen
Wie kommt es zum Absturz?
0 Stimmen
Ich bin mir nicht sicher, ob unbehandelte Ausnahmen als "Programmabsturz" gelten, aber die Methode zum Ausdrucken eines Stacktrace, wenn Ausnahmen ausgelöst werden, beschreibt in dieser Antwort könnte auch für Sie interessant sein.
1 Stimmen
Gibt es im Jahr 2021 eine bessere Lösung für dieses Problem? Ich möchte nur einen Stack-Trace wie in Java oder Python ausgeben.