GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo obtener un seguimiento de pila para C ++ usando gcc con información de número de línea?

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

Linux
  1. ¿Cómo obtener un recuento de archivos en un directorio usando la línea de comando?

  2. ¿Cómo crear un bucle for con un número variable de iteraciones?

  3. Cómo evitar los ataques de Stack Smashing con GCC

  4. Cómo rastrear scripts de Python usando trace.py

  5. ¿Cómo obtener la cantidad de CPU en Linux usando C?

Cómo encontrar información de la CPU en Linux usando la línea de comandos

Linux:¿cómo hacer que Grindeq (complemento de látex para Word) funcione con Word en Wine?

Cómo obtener información de hardware en Linux usando el comando dmidecode

Cómo obtener especificaciones de hardware de su sistema usando lshw Hardware Lister

Cómo obtener el uso total de la CPU en Linux usando C++

¿Cómo uso GDB en Eclipse para la depuración de C/C++?