Wie kann man eine Stapelverfolgung in C abrufen?

Antworten:

80

glibc bietet die Funktion backtrace ().

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Sanxiyn
quelle
6
glibc FTW ... schon wieder. (Dies ist ein weiterer Grund, warum ich glibc als den absoluten Goldstandard betrachte, wenn es um C-Programmierung geht (das und den
Trevor Boyd Smith
4
Aber warte, es gibt noch mehr! Die Funktion backtrace () stellt nur ein Array von void * -Zeigern bereit, die die Callstack-Funktionen darstellen. "Das ist nicht sehr nützlich. Arg." Keine Angst! glibc bietet eine Funktion, die alle void * -Adressen (die Callstack-Funktionsadressen) in lesbare Zeichenfolgensymbole konvertiert. char ** backtrace_symbols (void *const *buffer, int size)
Trevor Boyd Smith
1
Vorsichtsmaßnahme: funktioniert nur für C-Funktionen, denke ich. @ Trevor: Es werden nur Syms nach Adresse in der ELF-Tabelle nachgeschlagen.
Conrad Meyer
2
Es gibt auch void backtrace_symbols_fd(void *const *buffer, int size, int fd)welche, die die Ausgabe zum Beispiel direkt an stdout / err senden können.
Woche
2
backtrace_symbols()saugt. Es erfordert den Export aller Symbole und unterstützt keine DWARF-Symbole (Debugging). libbacktrace ist in vielen (den meisten) Fällen eine viel bessere Option.
Erwan Legrand
29

Es gibt backtrace () und backtrace_symbols ():

Von der Manpage:

#include <execinfo.h>
#include <stdio.h>
...
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i < frames; ++i) {
    printf("%s\n", strs[i]);
}
free(strs);
...

Eine Möglichkeit, dies auf eine bequemere / OOP-Weise zu verwenden, besteht darin, das Ergebnis von backtrace_symbols () in einem Ausnahmeklassenkonstruktor zu speichern. Wenn Sie also diese Art von Ausnahme auslösen, haben Sie die Stapelverfolgung. Stellen Sie dann einfach eine Funktion zum Ausdrucken bereit. Beispielsweise:

class MyException : public std::exception {

    char ** strs;
    MyException( const std::string & message ) {
         int i, frames = backtrace(callstack, 128);
         strs = backtrace_symbols(callstack, frames);
    }

    void printStackTrace() {
        for (i = 0; i < frames; ++i) {
            printf("%s\n", strs[i]);
        }
        free(strs);
    }
};

...

try {
   throw MyException("Oops!");
} catch ( MyException e ) {
    e.printStackTrace();
}

Ta da!

Hinweis: Durch Aktivieren von Optimierungsflags kann die resultierende Stapelverfolgung ungenau werden. Idealerweise würde man diese Funktion mit aktivierten Debug-Flags und deaktivierten Optimierungs-Flags verwenden.

Tom
quelle
@shuckc nur zum Konvertieren der Adresse in eine Symbolzeichenfolge, die bei Bedarf extern mit anderen Tools durchgeführt werden kann.
Woodrow Barlow
22

Überprüfen Sie für Windows die StackWalk64 () -API (auch unter 32-Bit-Windows). Unter UNIX sollten Sie die native Methode des Betriebssystems verwenden oder auf glibcs ​​backtrace () zurückgreifen, falls verfügbar.

Beachten Sie jedoch, dass das Erstellen eines Stacktrace in nativem Code selten eine gute Idee ist - nicht, weil dies nicht möglich ist, sondern weil Sie normalerweise versuchen, das Falsche zu erreichen.

Die meisten Leute versuchen, einen Stacktrace beispielsweise in einem außergewöhnlichen Umstand zu erhalten, z. B. wenn eine Ausnahme abgefangen wird, eine Behauptung fehlschlägt oder - am schlimmsten und am falschesten von allen - wenn Sie eine fatale "Ausnahme" oder ein Signal wie ein erhalten Segmentierungsverletzung.

In Anbetracht des letzten Problems müssen Sie bei den meisten APIs explizit Speicher zuweisen oder dies intern tun. Wenn Sie dies in dem fragilen Zustand tun, in dem sich Ihr Programm derzeit befindet, kann dies die Situation akut noch verschlimmern. Beispielsweise gibt der Absturzbericht (oder Coredump) nicht die tatsächliche Ursache des Problems wieder, sondern Ihren fehlgeschlagenen Versuch, das Problem zu beheben.

Ich nehme an, Sie versuchen, diese schwerwiegende Fehlerbehandlung zu erreichen, da die meisten Leute dies zu versuchen scheinen, wenn es darum geht, einen Stacktrace zu erhalten. Wenn ja, würde ich mich auf den Debugger verlassen (während der Entwicklung) und den Prozess in der Produktion entleeren lassen (oder Mini-Dump unter Windows). Zusammen mit einer ordnungsgemäßen Symbolverwaltung sollten Sie keine Probleme haben, die verursachende Anweisung post mortem herauszufinden.

Christian.K
quelle
2
Sie haben Recht damit, dass es fragil ist, die Speicherzuweisung in einem Signal- oder Ausnahmebehandler zu versuchen. Ein möglicher Ausweg besteht darin, beim Programmstart eine feste Menge an "Notfall" -Speicherplatz zuzuweisen oder einen statischen Puffer zu verwenden.
j_random_hacker
Ein anderer Ausweg ist die Erstellung eines Coredump-Dienstes, der unabhängig ausgeführt wird
Kobor42
5

Sie sollten die Abwicklungsbibliothek verwenden .

unw_cursor_t cursor; unw_context_t uc;
unw_word_t ip, sp;
unw_getcontext(&uc);
unw_init_local(&cursor, &uc);
unsigned long a[100];
int ctr = 0;

while (unw_step(&cursor) > 0) {
  unw_get_reg(&cursor, UNW_REG_IP, &ip);
  unw_get_reg(&cursor, UNW_REG_SP, &sp);
  if (ctr >= 10) break;
  a[ctr++] = ip;
}

Ihr Ansatz würde auch gut funktionieren, wenn Sie nicht aus einer gemeinsam genutzten Bibliothek anrufen.

Sie können den addr2lineBefehl unter Linux verwenden, um die Quellfunktion / Zeilennummer des entsprechenden PCs abzurufen.

user262802
quelle
"Quellfunktion / Zeilennummer"? Was ist, wenn die Verknüpfung für eine reduzierte Codegröße optimiert ist? Ich werde jedoch sagen, dass dies ein nützliches Projekt ist. Schade, dass es keine Möglichkeit gibt, an die Register zu kommen. Ich werde das auf jeden Fall untersuchen. Wissen Sie, dass es absolut prozessorunabhängig ist? Funktioniert nur etwas, das einen C-Compiler hat?
Mawg sagt, Monica am
1
Ok, dieser Kommentar hat sich gelohnt, schon allein wegen der Erwähnung des hilfreichen Befehls addr2line!
Oger Psalm33
addr2line schlägt fehl, um Code auf Systemen mit ASLR zu verschieben (dh das meiste, was in den letzten zehn Jahren verwendet wurde).
Erwan Legrand
4

Es gibt keine plattformunabhängige Möglichkeit, dies zu tun.

Das nächste, was Sie tun können, ist, den Code ohne Optimierungen auszuführen. Auf diese Weise können Sie eine Verbindung zum Prozess herstellen (mithilfe des visuellen C ++ - Debuggers oder GDB) und einen verwendbaren Stack-Trace abrufen.

Nils Pipenbrinck
quelle
Das hilft mir nicht, wenn ein Absturz auf einem eingebetteten Computer im Feld passiert. :(
Kevin
@ Kevin: Selbst auf eingebetteten Computern gibt es normalerweise eine Möglichkeit, einen Remote-Debugger-Stub oder zumindest einen Core-Dump zu erhalten. Vielleicht nicht einmal, wenn es auf dem Feld eingesetzt wird ...
kurzlebig am
Wenn Sie mit gcc-glibc auf der Plattform Ihrer Wahl Windows / Linux / Mac ausführen, funktionieren backtrace () und backtrace_symbols () auf allen drei Plattformen. Angesichts dieser Aussage würde ich die Worte "es gibt keinen [tragbaren] Weg, dies zu tun" verwenden.
Trevor Boyd Smith
4

Für Windows CaptureStackBackTrace()ist dies auch eine Option, für die der Benutzer weniger Vorbereitungscode benötigt als für den Benutzer StackWalk64(). (Auch für ein ähnliches Szenario, das ich hatte, CaptureStackBackTrace()funktionierte es besser (zuverlässiger) als StackWalk64().)

Quasidart
quelle
2

Solaris hat den Befehl pstack , der auch in Linux kopiert wurde.

wnoise
quelle
1
Nützlich, aber nicht wirklich C (es ist ein externes Dienstprogramm).
Ephemient
1
Aus der Beschreibung (Abschnitt: Einschränkungen) geht hervor, dass ": pstack derzeit nur unter Linux funktioniert, nur auf einem x86-Computer, auf dem 32-Bit-ELF-Binärdateien ausgeführt werden (64-Bit nicht unterstützt)"
Ciro Costa,
0

In den letzten Jahren habe ich Ian Lance Taylors Libbacktrace verwendet. Es ist viel sauberer als die Funktionen in der GNU C-Bibliothek, bei denen alle Symbole exportiert werden müssen. Es bietet mehr Nutzen für die Erzeugung von Backtraces als libunwind. Und last but not least wird es von ASLR nicht besiegt, ebenso wenig wie Ansätze, die externe Tools erfordern, wie z addr2line.

Libbacktrace war ursprünglich Teil der GCC-Distribution, wird jetzt jedoch vom Autor als eigenständige Bibliothek unter einer BSD-Lizenz zur Verfügung gestellt:

https://github.com/ianlancetaylor/libbacktrace

Zum Zeitpunkt des Schreibens würde ich nichts anderes verwenden, es sei denn, ich muss Backtraces auf einer Plattform generieren, die von libbacktrace nicht unterstützt wird.

Erwan Legrand
quelle
-1

Sie können dies tun, indem Sie den Stapel rückwärts laufen lassen. In der Realität ist es jedoch häufig einfacher, zu Beginn jeder Funktion einen Bezeichner zu einem Aufrufstapel hinzuzufügen und am Ende zu platzieren, und dann einfach den Inhalt zu drucken. Es ist ein bisschen wie eine PITA, aber es funktioniert gut und spart Ihnen am Ende Zeit.

Cody Brocious
quelle
1
Könnten Sie das "Rückwärtsgehen des Stapels" ausführlicher erklären?
Spidey