Entonces desea una función independiente que imprima un seguimiento de la pila con todas las características que tienen los seguimientos de pila de gdb y eso no termina su aplicación. La respuesta es automatizar el lanzamiento de gdb en un modo no interactivo para realizar solo las tareas que desea.
Esto se hace ejecutando gdb en un proceso secundario, usando fork() y escribiendo un script para mostrar un seguimiento de la pila mientras su aplicación espera que se complete. Esto se puede realizar sin el uso de un volcado de núcleo y sin cancelar la aplicación. Aprendí cómo hacer esto mirando esta pregunta:¿Cómo es mejor invocar gdb desde el programa para imprimir su stacktrace?
El ejemplo publicado con esa pregunta no me funcionó exactamente como está escrito, así que aquí está mi versión "arreglada" (la ejecuté en 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);
}
}
Como se muestra en la pregunta a la que se hace referencia, gdb proporciona opciones adicionales que podría usar. Por ejemplo, el uso de "bt full" en lugar de "bt" produce un informe aún más detallado (las variables locales se incluyen en la salida). Las páginas de manual de gdb son un poco ligeras, pero la documentación completa está disponible aquí.
Dado que esto se basa en gdb, la salida incluye nombres desenredados , números de línea , argumentos de función y, opcionalmente, incluso variables locales . Además, gdb reconoce subprocesos, por lo que debería poder extraer algunos metadatos específicos de subprocesos.
Este es un ejemplo del tipo de rastros de pila que veo con este método.
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:Descubrí que esto es incompatible con el uso de valgrind (probablemente debido al uso de una máquina virtual por parte de Valgrind). Tampoco funciona cuando está ejecutando el programa dentro de una sesión de gdb (no se puede aplicar una segunda instancia de "ptrace" a un proceso).
No hace mucho tiempo respondí una pregunta similar. Debería echar un vistazo al código fuente disponible en el método #4, que también imprime números de línea y nombres de archivos.
- Método #4:
Una pequeña mejora que hice en el método #3 para imprimir números de línea. Esto también podría copiarse para trabajar en el método #2.
Básicamente, usa addr2line para convertir direcciones en nombres de archivo y números de línea.
El siguiente código fuente imprime números de línea para todas las funciones locales. Si se llama a una función de otra biblioteca, es posible que vea un par de ??:0
en lugar de nombres de archivos.
#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());
}
Este código debe compilarse como:gcc sighandler.c -o sighandler -rdynamic
El programa genera:
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