Wie lässt man backtrace () / backtrace_symbols () die Funktionsnamen drucken?

90

Das Linux-spezifische backtrace()und backtrace_symbols()ermöglicht es Ihnen, eine Aufrufverfolgung des Programms zu erstellen. Es werden jedoch nur Funktionsadressen gedruckt, nicht deren Namen für mein Programm. Wie kann ich sie dazu bringen, auch die Funktionsnamen zu drucken? Ich habe versucht , mit dem Programm zusammenzustellen -gsowie -ggdb. Der folgende Testfall druckt nur Folgendes:

    HINTERGRUND ------------
    ./a.out () [0x8048616]
    ./a.out () [0x8048623]
    /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
    ./a.out () [0x8048421]
    ----------------------
    

Ich würde die ersten 2 Artikel wollen auch die Funktionsnamen zeigen, fooundmain

Code:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                size_t len = strlen(bt_syms[i]);
                full_write(STDERR_FILENO, bt_syms[i], len);
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}
void foo()
{
    print_backtrace();
}

int main()
{
    foo();
    return 0;
}
Lyke
quelle
mögliches Duplikat von Wie man detailliertere Rückverfolgung erhält
Nemo
Übrigens backtrace_symbols_fdführt die Funktion dieselbe Operation aus wie backtrace_symbols(), aber die resultierenden Zeichenfolgen werden sofort in den Dateideskriptor geschrieben fd.
Nhnghia
Ich habe verschiedene Methoden im Detail getestet unter: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 25 冠状 病 六四 事件 25

Antworten:

63

Die Symbole stammen aus der dynamischen Symboltabelle. Sie benötigen die -rdynamicOption to gcc, wodurch ein Flag an den Linker übergeben wird, der sicherstellt, dass alle Symbole in der Tabelle platziert werden.

(Siehe die Seite Link Options des GCC-Handbuchs und / oder die Backtraces- Seite des glibc-Handbuchs .)

Matthew Slattery
quelle
10
Dies funktioniert jedoch nicht für statische Symbole. libunwinddass @Nemo erwähnt, funktioniert für statische Funktionen.
Nathan Kidd
In einem CMake-Projekt wurde dies fälschlicherweise als festgelegt ADD_COMPILE_OPTIONS(-rdynamic). Stattdessen muss es ADD_LINK_OPTIONS(-rdynamic)gleichwertig sein, um das gewünschte Verhalten zu erzielen.
Stéphane
30

Verwenden Sie den Befehl addr2line , um ausführbare Adressen dem Quellcode-Dateinamen + der Zeilennummer zuzuordnen. Geben Sie die -fOption an, auch Funktionsnamen abzurufen.

Alternativ können Sie auch libunwind ausprobieren .

Nemo
quelle
3
Die Zeile addr2 ist nett, da sie den Dateinamen und die Zeilennummer in der Ausgabe enthält. Sie schlägt jedoch fehl, sobald der Loader Verschiebungen vornimmt.
Erwan Legrand
... und mit ASLR sind Umzüge häufiger als je zuvor.
Erwan Legrand
@ErwanLegrand: Vor ASLR dachte ich, dass die Verschiebungen vorhersehbar sind und addr2lineauch für Adressen in gemeinsam genutzten Objekten zuverlässig funktionieren (?). Aber ja, auf modernen Plattformen müssten Sie die tatsächliche Ladeadresse des verschiebbaren Objekts kennen, um diesen Vorgang im Prinzip ausführen zu können .
Nemo
Ich kann nicht sicher sagen, wie gut es vor ASLR funktioniert hat. Heutzutage gilt dies nur für Code in "normalen" ausführbaren Dateien und schlägt für Code in DSOs und PIEs (Position Independent Executables) fehl. Glücklicherweise scheint libunwind für all diese zu funktionieren.
Erwan Legrand
Ich hatte diese Diskussion vergessen. Libbacktrace, von dem ich damals noch nichts wusste, ist eine bessere Option. (Siehe meine neue Antwort.)
Erwan Legrand
11

Die exzellente Libbacktrace von Ian Lance Taylor löst dieses Problem. Es behandelt das Abwickeln von Stapeln und unterstützt sowohl normale ELF-Symbole als auch DWARF-Debugging-Symbole.

Libbacktrace erfordert nicht das Exportieren aller Symbole, was hässlich wäre, und ASLR bricht es nicht.

Libbacktrace war ursprünglich Teil der GCC-Distribution. Jetzt kann eine eigenständige Version auf Github gefunden werden:

https://github.com/ianlancetaylor/libbacktrace

Erwan Legrand
quelle
2

Die Antwort oben hat einen Fehler, wenn ret == -1 und errno EINTER ist. Sie sollten es erneut versuchen, aber ret nicht als kopiert zählen (Sie werden kein Konto nur dafür erstellen, wenn Sie es nicht schwer mögen).

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) {
                        if (errno != EINTR))
                                break;
                        //else
                        continue;
                }
                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}
Billg
quelle
0

Boost Backtrace

Sehr praktisch, da beide gedruckt werden:

  • entwirrte C ++ - Funktionsnamen
  • Linien Nummern

automatisch für Sie.

Nutzungsübersicht:

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

std::cout << boost::stacktrace::stacktrace() << std::endl;

Ich habe ein minimales ausführbares Beispiel dafür und viele andere Methoden bereitgestellt: Druckaufrufstapel in C oder C ++

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
Die Frage ist mit [c] und nicht mit [c ++] gekennzeichnet.
Yakov Galka