GNU/Linux >> Linux Esercitazione >  >> Linux

Come ottenere una traccia dello stack per C++ usando gcc con le informazioni sul numero di riga?

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

Linux
  1. Come ottenere un conteggio di file in una directory utilizzando la riga di comando?

  2. Come creare un ciclo for con un numero variabile di iterazioni?

  3. Come evitare Stack Smashing Attack con GCC

  4. Come tracciare gli script Python usando trace.py

  5. Come riempire un file con FF usando dd?

Come trovare informazioni sulla CPU in Linux utilizzando la riga di comando

Linux – Come ottenere Grindeq (plugin in lattice per Word) per lavorare con Word in Wine?

Come ottenere informazioni sull'hardware su Linux utilizzando il comando dmidecode

Come ottenere le specifiche hardware del tuo sistema utilizzando lshw Hardware Lister

Come utilizzo GDB in Eclipse per il debug C/C++?

Come posso ottenere il numero di fotogrammi in un video sulla riga di comando di Linux?