GNU/Linux >> Znalost Linux >  >> Linux

Jak získat trasování zásobníku pro C++ pomocí gcc s informacemi o čísle řádku?

Chcete tedy samostatnou funkci, která tiskne trasování zásobníku se všemi funkcemi, které mají trasování zásobníku gdb a které neukončí vaši aplikaci. Odpovědí je automatizovat spouštění gdb v neinteraktivním režimu, aby bylo možné provádět pouze úkoly, které chcete.

To se provádí spuštěním gdb v podřízeném procesu pomocí fork() a jeho skriptováním, aby zobrazilo trasování zásobníku, zatímco vaše aplikace čeká na dokončení. To lze provést bez použití core-dump a bez přerušení aplikace. Naučil jsem se, jak to udělat, když jsem se podíval na tuto otázku:Jak je lepší vyvolat gdb z programu pro tisk jeho stacktrace?

Příklad zveřejněný s touto otázkou pro mě nefungoval přesně tak, jak byl napsán, takže zde je moje „opravená“ verze (spustil jsem to na 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);
    }
}

Jak je uvedeno v odkazované otázce, gdb poskytuje další možnosti, které byste mohli použít. Například použití "bt full" místo "bt" vytvoří ještě podrobnější zprávu (místní proměnné jsou zahrnuty do výstupu). Manuálové stránky pro gdb jsou trochu lehké, ale kompletní dokumentace je k dispozici zde.

Protože je to založeno na gdb, výstup obsahuje deanglovaná jména , čísla-řádků , argumenty funkcí a volitelně i místní proměnné . Gdb také podporuje vlákna, takže byste měli být schopni extrahovat některá metadata specifická pro vlákna.

Zde je příklad druhu trasování zásobníku, který vidím u této metody.

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

Poznámka:Zjistil jsem, že to není kompatibilní s použitím valgrindu (pravděpodobně kvůli tomu, že Valgrind používá virtuální stroj). Také to nefunguje, když program spouštíte v rámci relace gdb (nelze na proces použít druhou instanci „ptrace“).


Není to tak dávno, co jsem odpovídal na podobnou otázku. Měli byste se podívat na zdrojový kód dostupný v metodě #4, která také tiskne čísla řádků a názvy souborů.

  • Metoda č. 4:

Malé vylepšení, které jsem provedl v metodě #3 pro tisk čísel řádků. Toto lze také zkopírovat, aby fungovalo s metodou #2.

V zásadě používá addr2line pro převod adres na názvy souborů a čísla řádků.

Níže uvedený zdrojový kód tiskne čísla řádků pro všechny místní funkce. Pokud je volána funkce z jiné knihovny, můžete vidět pár ??:0 místo názvů souborů.

#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());
}

Tento kód by měl být zkompilován jako:gcc sighandler.c -o sighandler -rdynamic

Výstup programu:

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. Jak získat sériové číslo pevného disku pomocí Pythonu

  2. Jak získat počet CPU v Linuxu pomocí C?

  3. Jak napsat víceřádkový řetězec pomocí Bash s proměnnými?

  1. Jak vytisknout aktuální čas (v milisekundách) pomocí C++ / C++11

  2. Jak zkontroluji verzi kompilátoru gcc C++ pro můj Eclipse?

  3. Bash - Jak tisknout víceřádkové řetězce (s '\n') pomocí printf

  1. Jak získat počet souborů v adresáři pomocí příkazového řádku?

  2. Jak vytvořit smyčku For s měnitelným počtem iterací?

  3. Jak získám počet snímků ve videu na příkazovém řádku linuxu?