Beste Möglichkeit, gdb aus dem Programm heraus aufzurufen, um seinen Stacktrace zu drucken?

Lesezeit: 10 Minuten

Verwenden einer Funktion wie dieser:

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>

void print_trace() {
    char pid_buf[30];
    sprintf(pid_buf, "--pid=%d", getpid());
    char name_buf[512];
    name_buf[readlink("/proc/self/exe", name_buf, 511)]=0;
    int child_pid = fork();
    if (!child_pid) {           
        dup2(2,1); // redirect output to stderr
        fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf);
        execlp("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);
    }
}

Ich sehe die Details von print_trace in der Ausgabe.

Was sind andere Möglichkeiten, es zu tun?

  • Gibt es ein Problem damit? Etwas, das es nicht tut?

    – Adam Schiemke

    30. Juni 2010 um 17:37 Uhr

  • @Adam Shiemke Einige Probleme aufgelistet. Möglicherweise kann gdb auf eine geeignetere Weise aufgerufen werden. Vielleicht brauche ich etwas Besonderes, um Multithreading zu unterstützen. Vielleicht gibt es eine Möglichkeit, es portabel zu machen, oder es gibt eine spezielle “libstacktrace.so”. Diskussion.

    – Vi.

    30. Juni 2010 um 18:04 Uhr

  • Sie können die Option -ex mehrmals verwenden.

    – Derek Ledbetter

    30. Juni 2010 um 18:52 Uhr

  • @Derek Ledbetter, OK, Bewerbung.

    – Vi.

    30. Juni 2010 um 19:03 Uhr

  • Als Randnotiz gibt es eine Wrapper-Bibliothek zum Anhängen von GDB: libdebugme.

    – jugr

    25. August 2018 um 12:33 Uhr

Sie haben in meiner anderen Antwort (jetzt gelöscht) erwähnt, dass Sie auch Zeilennummern sehen möchten. Ich bin mir nicht sicher, wie ich das machen soll, wenn ich gdb aus Ihrer Anwendung heraus aufrufe.

Aber ich werde Ihnen ein paar Möglichkeiten zeigen, wie Sie einen einfachen Stacktrace mit Funktionsnamen und ihren jeweiligen Zeilennummern drucken können ohne gdb zu verwenden. Die meisten stammen aus a Sehr schön Artikel aus Linux-Journal:

  • Methode 1:

Die erste Methode besteht darin, es mit Druck- und Protokollnachrichten zu verbreiten, um den Ausführungspfad genau zu bestimmen. In einem komplexen Programm kann diese Option umständlich und langwierig werden, auch wenn sie mit Hilfe einiger GCC-spezifischer Makros etwas vereinfacht werden kann. Betrachten Sie zum Beispiel ein Debug-Makro wie:

 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                          "() [%s:%d] here I am\n", \
                          __FILE__, __LINE__)

Sie können dieses Makro schnell in Ihrem Programm verbreiten, indem Sie es ausschneiden und einfügen. Wenn Sie es nicht mehr benötigen, schalten Sie es einfach aus, indem Sie es auf no-op definieren.

  • Methode #2: (Es sagt nichts über Zeilennummern aus, aber ich mache es mit Methode 4)

Ein schönerer Weg, einen Stack-Backtrace zu erhalten, ist jedoch die Verwendung einiger der spezifischen Unterstützungsfunktionen, die von glibc bereitgestellt werden. Das wichtigste ist backtrace(), das die Stack-Frames vom Aufrufpunkt zum Anfang des Programms navigiert und ein Array von Rückgabeadressen bereitstellt. Sie können dann jede Adresse dem Hauptteil einer bestimmten Funktion in Ihrem Code zuordnen, indem Sie sich die Objektdatei mit dem nm-Befehl ansehen. Oder Sie können es einfacher machen – verwenden Sie backtrace_symbols(). Diese Funktion wandelt eine Liste von Rücksprungadressen, wie sie von backtrace() zurückgegeben wird, in eine Liste von Zeichenfolgen um, die jeweils den Funktionsnamen-Offset innerhalb der Funktion und die Rücksprungadresse enthalten. Die Liste der Strings wird von Ihrem Heap-Speicher zugewiesen (als ob Sie malloc() aufgerufen hätten), also sollten Sie sie freigeben(), sobald Sie damit fertig sind.

Ich ermutige Sie, es zu lesen, da die Seite hat Quellcode Beispiele. Um eine Adresse in einen Funktionsnamen umzuwandeln, müssen Sie Ihre Anwendung mit kompilieren -rdynamisch Möglichkeit.

  • Methode Nr. 3: (Eine bessere Art, Methode 2 zu tun)

Eine noch nützlichere Anwendung für diese Technik besteht darin, einen Stack-Backtrace in einen Signal-Handler zu stecken und diesen alle “schlechten” Signale abfangen zu lassen, die Ihr Programm empfangen kann (SIGSEGV, SIGBUS, SIGILL, SIGFPE und dergleichen). Wenn Ihr Programm leider abstürzt und Sie es nicht mit einem Debugger ausgeführt haben, können Sie auf diese Weise einen Stack-Trace erhalten und wissen, wo der Fehler aufgetreten ist. Diese Technik kann auch verwendet werden, um zu verstehen, wo Ihr Programm eine Schleife durchläuft, falls es nicht mehr reagiert

Eine Implementierung dieser Technik ist verfügbar hier.

  • Methode #4:

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

Im Grunde bin ich einem Tipp gefolgt das nutzt addr2line zu

Adressen in Dateinamen und Zeilennummern umwandeln.

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 davon ??: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 kompiliert werden als: 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

Update 28.04.2012 für neuere Linux-Kernel-Versionen das obige sigaction Signatur ist obsolet. Außerdem habe ich es ein wenig verbessert, indem ich den Namen der ausführbaren Datei aus dieser Antwort entnommen habe. Hier ist ein aktuelle Fassung:

char* exe = 0;

int initialiseExecutableName() 
{
    char link[1024];
    exe = new char[1024];
    snprintf(link,sizeof link,"/proc/%d/exe",getpid());
    if(readlink(link,exe,sizeof link)==-1) {
        fprintf(stderr,"ERRORRRRR\n");
        exit(1);
    }
    printf("Executable name initialised: %s\n",exe);
}

const char* getExecutableName()
{
    if (exe == 0)
        initialiseExecutableName();
    return exe;
}

/* get REG_EIP from ucontext.h */
#define __USE_GNU
#include <ucontext.h>

void bt_sighandler(int sig, siginfo_t *info,
                   void *secret) {

  void *trace[16];
  char **messages = (char **)NULL;
  int i, trace_size = 0;
  ucontext_t *uc = (ucontext_t *)secret;

  /* Do something useful with siginfo_t */
  if (sig == SIGSEGV)
    printf("Got signal %d, faulty address is %p, "
           "from %p\n", sig, info->si_addr, 
           uc->uc_mcontext.gregs[REG_EIP]);
  else
    printf("Got signal %d\n", sig);

  trace_size = backtrace(trace, 16);
  /* overwrite sigaction with caller's address */
  trace[1] = (void *) uc->uc_mcontext.gregs[REG_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] %s\n", 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 filename of the symbol
    system(syscom);

  }
  exit(0);
}

und so initialisieren:

int main() {

  /* Install our signal handler */
  struct sigaction sa;

  sa.sa_sigaction = (void *)bt_sighandler;
  sigemptyset (&sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_SIGINFO;

  sigaction(SIGSEGV, &sa, NULL);
  sigaction(SIGUSR1, &sa, NULL);
  /* ... add any other signal here */

  /* Do something */
  printf("%d\n", func_b());

}

  • “Methode Nr. 1” -> Es gibt meine andere Frage zu SO, wie man sie automatisch “weitergibt”, aber ohne nützliche Antworten.

    – Vi.

    6. Januar 2011 um 15:02 Uhr

  • Methoden #2 – #4 -> Schon ausprobiert – es funktioniert: vi-server.org/vi/simple_sampling_profiler.html Aber der Backtrace/addr2line-Ansatz hat Einschränkungen: 1. oft kann addr2line die Zeile nicht herausfinden (während gdb es kann), 2. gdb kann Threads iterieren: “thread apply all bt”.

    – Vi.

    6. Januar 2011 um 15:03 Uhr


  • @Vi Dieser Typ hat es auf den Punkt gebracht: stackoverflow.com/questions/4636456/stack-trace-for-c-using-gcc/…

    – Karl Philipp

    19. Januar 2011 um 18:01 Uhr


  • @karlphillip: Ich habe einen anderen Weg gefunden, Datei- und Zeilennummern in den Stacktrace einzufügen. Verwenden Sie libbfd (sourceware.org/binutils/docs-2.21/bfd/…) wie in refdbg: refdbg.cvs.sourceforge.net/viewvc/refdbg/refdbg/… Ich selbst habe es noch nicht ausprobiert.

    – rve

    2. November 2011 um 14:19 Uhr


  • Neben der Verwendung -rdynamicüberprüfen Sie auch, ob Ihr Build-System nicht hinzufügt -fvisibility=hidden Möglichkeit! (da es die Wirkung von vollständig verwirft -rdynamic)

    – Dima Litwinow

    13. Mai 2020 um 0:43 Uhr

Wenn Sie Linux verwenden, enthält die Standard-C-Bibliothek eine Funktion namens backtracedas ein Array mit den Rückgabeadressen von Frames füllt, und eine weitere aufgerufene Funktion backtrace_symbolsdie die Adressen übernimmt backtrace und schlagen Sie die entsprechenden Funktionsnamen nach. Diese sind dokumentiert in der Handbuch der GNU C-Bibliothek.

Diese zeigen keine Argumentwerte, Quellzeilen und dergleichen an und gelten nur für den aufrufenden Thread. Sie sollten jedoch viel schneller (und vielleicht weniger flockig) sein, als GDB auf diese Weise auszuführen, also haben sie ihren Platz.

  • Tatsächlich gibt der Ausschnitt, den ich in das Programm einfüge, zuerst Backtrace mit backtrace_symbols aus und startet dann gdb, um vollständig kommentierte Stack-Traces für alle Threads auszugeben. Wenn gdb fehlschlägt, habe ich immer noch die backtraceStacktrace von .

    – Vi.

    4. August 2010 um 16:51 Uhr

nobar hat eine fantastische Antwort gepostet. Zusamenfassend;

Du willst also ein eigenständige Funktion, die einen Stack-Trace druckt mit allen Funktionen, die gdb Stack-Traces haben und das beendet Ihre Anwendung nicht. 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 glaube, das ist es, wonach Sie suchen, @Vi

  • Sehen Sie sich den Beispielcode in der Frage an. Es ist diese Methode. Ich suche nach anderen, weniger schweren Wegen. Das Hauptproblem von addr2line-Quality-Dingen ist, dass es oft keine Zeilennummer anzeigen kann, wo gdb es kann.

    – Vi.

    20. Januar 2011 um 0:05 Uhr


  • @Vi In seiner Antwort heißt es, dass er den Basiscode aus Ihrer Frage in diesem Thread erhalten hat. Wenn Sie jedoch genauer hinsehen, werden Sie feststellen, dass es einige Unterschiede gibt. Hast du es versucht?

    – Karl Philipp

    20. Januar 2011 um 0:43 Uhr

Ist nicht abort() einfacher?

Auf diese Weise kann Ihnen der Kunde, wenn es vor Ort passiert, die Kerndatei senden (ich kenne nicht viele Benutzer, die sich ausreichend damit beschäftigen mein Anwendung, um zu wollen, dass ich sie zum Debuggen zwinge).

1413950cookie-checkBeste Möglichkeit, gdb aus dem Programm heraus aufzurufen, um seinen Stacktrace zu drucken?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy