Quindi vuoi una funzione autonoma che stampi una traccia dello stack con tutte le funzionalità che hanno le tracce dello stack gdb e che non terminano la tua applicazione. La risposta è automatizzare l'avvio di gdb in una modalità non interattiva per eseguire solo le attività desiderate.
Questo viene fatto eseguendo gdb in un processo figlio, usando fork(), e programmandolo per visualizzare una traccia dello stack mentre l'applicazione attende il suo completamento. Questa operazione può essere eseguita senza l'uso di un core dump e senza interrompere l'applicazione. Ho imparato a farlo guardando questa domanda:Com'è meglio invocare gdb dal programma per stampare il suo stacktrace?
L'esempio pubblicato con quella domanda non ha funzionato per me esattamente come scritto, quindi ecco la mia versione "corretta" (l'ho eseguita su Ubuntu 9.04).
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/prctl.h>
void print_trace() {
char pid_buf[30];
sprintf(pid_buf, "%d", getpid());
char name_buf[512];
name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
int child_pid = fork();
if (!child_pid) {
dup2(2,1); // redirect output to stderr - edit: unnecessary?
execl("/usr/bin/gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL);
abort(); /* If gdb failed to start */
} else {
waitpid(child_pid,NULL,0);
}
}
Come mostrato nella domanda di riferimento, gdb fornisce opzioni aggiuntive che potresti utilizzare. Ad esempio, l'utilizzo di "bt full" invece di "bt" produce un rapporto ancora più dettagliato (le variabili locali sono incluse nell'output). Le manpage per gdb sono un po' leggere, ma la documentazione completa è disponibile qui.
Poiché si basa su gdb, l'output include nomi smussati , numeri-riga , argomenti della funzione e facoltativamente anche variabili locali . Inoltre, gdb è thread-aware, quindi dovresti essere in grado di estrarre alcuni metadati specifici del thread.
Ecco un esempio del tipo di tracce dello stack che vedo con questo metodo.
0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
[Current thread is 0 (process 15573)]
#0 0x00007f97e1fc2925 in waitpid () from /lib/libc.so.6
#1 0x0000000000400bd5 in print_trace () at ./demo3b.cpp:496
2 0x0000000000400c09 in recursive (i=2) at ./demo3b.cpp:636
3 0x0000000000400c1a in recursive (i=1) at ./demo3b.cpp:646
4 0x0000000000400c1a in recursive (i=0) at ./demo3b.cpp:646
5 0x0000000000400c46 in main (argc=1, argv=0x7fffe3b2b5b8) at ./demo3b.cpp:70
Nota:l'ho trovato incompatibile con l'uso di valgrind (probabilmente a causa dell'uso di una macchina virtuale da parte di Valgrind). Inoltre, non funziona quando esegui il programma all'interno di una sessione gdb (non puoi applicare una seconda istanza di "ptrace" a un processo).
Non molto tempo fa ho risposto a una domanda simile. Dovresti dare un'occhiata al codice sorgente disponibile nel metodo n. 4, che stampa anche i numeri di riga e i nomi dei file.
- Metodo n. 4:
Un piccolo miglioramento che ho apportato al metodo n. 3 per stampare i numeri di riga. Questo potrebbe essere copiato per funzionare anche sul metodo n. 2.
Fondamentalmente, utilizza addr2line per convertire gli indirizzi in nomi di file e numeri di riga.
Il codice sorgente riportato di seguito stampa i numeri di riga per tutte le funzioni locali. Se viene chiamata una funzione da un'altra libreria, potresti vedere un paio di ??:0
invece dei nomi dei file.
#include <stdio.h>
#include <signal.h>
#include <stdio.h>
#include <signal.h>
#include <execinfo.h>
void bt_sighandler(int sig, struct sigcontext ctx) {
void *trace[16];
char **messages = (char **)NULL;
int i, trace_size = 0;
if (sig == SIGSEGV)
printf("Got signal %d, faulty address is %p, "
"from %p\n", sig, ctx.cr2, ctx.eip);
else
printf("Got signal %d\n", sig);
trace_size = backtrace(trace, 16);
/* overwrite sigaction with caller's address */
trace[1] = (void *)ctx.eip;
messages = backtrace_symbols(trace, trace_size);
/* skip first stack frame (points here) */
printf("[bt] Execution path:\n");
for (i=1; i<trace_size; ++i)
{
printf("[bt] #%d %s\n", i, messages[i]);
/* find first occurence of '(' or ' ' in message[i] and assume
* everything before that is the file name. (Don't go beyond 0 though
* (string terminator)*/
size_t p = 0;
while(messages[i][p] != '(' && messages[i][p] != ' '
&& messages[i][p] != 0)
++p;
char syscom[256];
sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]);
//last parameter is the file name of the symbol
system(syscom);
}
exit(0);
}
int func_a(int a, char b) {
char *p = (char *)0xdeadbeef;
a = a + b;
*p = 10; /* CRASH here!! */
return 2*a;
}
int func_b() {
int res, a = 5;
res = 5 + func_a(a, 't');
return res;
}
int main() {
/* Install our signal handler */
struct sigaction sa;
sa.sa_handler = (void *)bt_sighandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGSEGV, &sa, NULL);
sigaction(SIGUSR1, &sa, NULL);
/* ... add any other signal here */
/* Do something */
printf("%d\n", func_b());
}
Questo codice dovrebbe essere compilato come:gcc sighandler.c -o sighandler -rdynamic
Il programma emette:
Got signal 11, faulty address is 0xdeadbeef, from 0x8048975
[bt] Execution path:
[bt] #1 ./sighandler(func_a+0x1d) [0x8048975]
/home/karl/workspace/stacktrace/sighandler.c:44
[bt] #2 ./sighandler(func_b+0x20) [0x804899f]
/home/karl/workspace/stacktrace/sighandler.c:54
[bt] #3 ./sighandler(main+0x6c) [0x8048a16]
/home/karl/workspace/stacktrace/sighandler.c:74
[bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6]
??:0
[bt] #5 ./sighandler() [0x8048781]
??:0