GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Wie erhalte ich einen Stack-Trace für C++ mit gcc mit Zeilennummerinformationen?

Sie möchten also eine eigenständige Funktion, die einen Stack-Trace druckt mit all den Funktionen, die gdb-Stack-Traces haben und die Ihre Anwendung nicht beenden. Die Antwort ist, den Start von gdb in einem nicht-interaktiven Modus zu automatisieren, um genau die Aufgaben auszuführen, die Sie möchten.

Dies geschieht durch Ausführen von gdb in einem untergeordneten Prozess mit fork() und Skripten, um einen Stack-Trace anzuzeigen, während Ihre Anwendung darauf wartet, dass er abgeschlossen wird. Dies kann ohne die Verwendung eines Core-Dumps und ohne Abbruch der Anwendung durchgeführt werden. Ich habe gelernt, wie das geht, indem ich mir diese Frage angesehen habe:Wie ist es besser, gdb vom Programm aus aufzurufen, um seinen Stacktrace zu drucken?

Das mit dieser Frage gepostete Beispiel hat bei mir nicht genau so funktioniert, wie es geschrieben wurde, also hier ist meine "korrigierte" Version (ich habe sie unter Ubuntu 9.04 ausgeführt).

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

Wie in der referenzierten Frage gezeigt, bietet gdb zusätzliche Optionen, die Sie verwenden könnten. Wenn Sie beispielsweise „bt full“ anstelle von „bt“ verwenden, wird ein noch detaillierterer Bericht erstellt (lokale Variablen sind in der Ausgabe enthalten). Die Manpages für gdb sind ziemlich leicht, aber die vollständige Dokumentation ist hier verfügbar.

Da dies auf gdb basiert, enthält die Ausgabe entstellte Namen , Zeilennummern , Funktionsargumente , und optional sogar lokale Variablen . Außerdem ist gdb Thread-fähig, sodass Sie in der Lage sein sollten, einige Thread-spezifische Metadaten zu extrahieren.

Hier ist ein Beispiel für die Art von Stack-Traces, die ich mit dieser Methode sehe.

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

Hinweis:Ich habe festgestellt, dass dies mit der Verwendung von Valgrind nicht kompatibel ist (wahrscheinlich aufgrund der Verwendung einer virtuellen Maschine durch Valgrind). Es funktioniert auch nicht, wenn Sie das Programm innerhalb einer gdb-Sitzung ausführen (kann keine zweite Instanz von "ptrace" auf einen Prozess anwenden).


Vor nicht allzu langer Zeit habe ich eine ähnliche Frage beantwortet. Sie sollten sich den Quellcode ansehen, der bei Methode #4 verfügbar ist, die auch Zeilennummern und Dateinamen ausgibt.

  • Methode #4:

Eine kleine Verbesserung, die ich an Methode 3 vorgenommen habe, um Zeilennummern zu drucken. Dies könnte kopiert werden, um auch mit Methode #2 zu arbeiten.

Grundsätzlich verwendet es addr2line um Adressen in Dateinamen und Zeilennummern umzuwandeln.

Der folgende Quellcode gibt Zeilennummern für alle lokalen Funktionen aus. Wenn eine Funktion aus einer anderen Bibliothek aufgerufen wird, sehen Sie möglicherweise ein paar ??:0 statt Dateinamen.

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

Dieser Code sollte wie folgt kompiliert werden:gcc sighandler.c -o sighandler -rdynamic

Das Programm gibt aus:

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. Wie erhalte ich eine Anzahl von Dateien in einem Verzeichnis über die Befehlszeile?

  2. Wie erstelle ich eine For-Schleife mit einer veränderbaren Anzahl von Iterationen?

  3. So vermeiden Sie Stack-Smashing-Angriffe mit GCC

  4. So erhalten Sie die gesamte CPU-Auslastung in Linux mit C++

  5. Wie zähle ich die Anzahl der Tabulatoren in jeder Zeile mit Shell-Skript?

So finden Sie CPU-Informationen in Linux über die Befehlszeile

So erhalten Sie Hardwareinformationen unter Linux mit dem Befehl dmidecode

So erhalten Sie die Hardwarespezifikationen Ihres Systems mit dem lshw Hardware Lister

Wie verwende ich GDB in Eclipse für C/C++-Debugging?

Wie überprüfe ich meine gcc C++-Compiler-Version für mein Eclipse?

Wie fülle ich eine Datei mit FF mit dd auf?