Gibt es eine Möglichkeit, den Aufrufstapel in einem laufenden Prozess in C oder C ++ bei jedem Aufruf einer bestimmten Funktion zu sichern? Was ich vorhabe, ist ungefähr so:
void foo()
{
print_stack_trace();
// foo's body
return
}
Wo print_stack_trace
funktioniert ähnlich wie caller
in Perl.
Oder sowas:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
Dabei wird register_stack_trace_function
eine Art interner Haltepunkt eingefügt, der bewirkt, dass bei jedem foo
Aufruf eine Stapelverfolgung gedruckt wird.
Gibt es so etwas in einer Standard-C-Bibliothek?
Ich arbeite unter Linux mit GCC.
Hintergrund
Ich habe einen Testlauf, der sich basierend auf einigen Befehlszeilenschaltern, die dieses Verhalten nicht beeinflussen sollten, anders verhält. Mein Code hat einen Pseudozufallszahlengenerator, von dem ich annehme, dass er basierend auf diesen Schaltern anders aufgerufen wird. Ich möchte in der Lage sein, den Test mit jedem Satz von Schaltern durchzuführen und zu sehen, ob der Zufallszahlengenerator für jeden unterschiedlich aufgerufen wird.
s/easier/either/
wie zum Teufel ist das passiert?s/either/easier
. Was ich mit gdb tun müsste, ist ein Skript zu schreiben, das diese Funktion unterbricht, den Stack-Trace druckt und dann fortfährt. Jetzt, wo ich darüber nachdenke, ist es vielleicht Zeit für mich, etwas über GDB-Scripting zu lernen.Antworten:
Für eine Nur-Linux-Lösung können Sie Backtrace (3) verwenden , das einfach ein Array von zurückgibt
void *
(tatsächlich zeigt jeder dieser Punkte auf die Rücksprungadresse des entsprechenden Stapelrahmens). Um diese in etwas Nützliches zu übersetzen, gibt es backtrace_symbols (3) .Beachten Sie den Abschnitt mit den Notizen in Rückverfolgung (3) :
quelle
glibc
leiderbacktrace_symbols
keinen Funktionsnamen, Quellendateinamen und Zeilennummer.-rdynamic
Überprüfen Sie zusätzlich zur Verwendung auch, ob Ihr Build-System keine-fvisibility=hidden
Option hinzufügt ! (da es die Wirkung von vollständig verwerfen wird-rdynamic
)Boost Stacktrace
Dokumentiert unter: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Dies ist die bequemste Option, die ich bisher gesehen habe, weil sie:
kann tatsächlich die Zeilennummern ausdrucken.
Es werden jedoch nur Anrufe
addr2line
getätigt , was hässlich ist und langsam sein kann, wenn Sie zu viele Spuren verfolgen.entwirrt standardmäßig
Boost ist nur ein Header, sodass Sie Ihr Build-System höchstwahrscheinlich nicht ändern müssen
boost_stacktrace.cpp
Leider scheint es sich um eine neuere Erweiterung zu handeln, und das Paket
libboost-stacktrace-dev
ist in Ubuntu 16.04 nicht vorhanden, nur in 18.04:Wir müssen
-ldl
am Ende hinzufügen, sonst schlägt die Kompilierung fehl.Ausgabe:
Die Ausgabe und wird im Abschnitt "glibc backtrace" weiter unten erläutert, der analog ist.
Beachten Sie, wie
my_func_1(int)
undmy_func_1(float)
, die aufgrund von Funktionsüberlastung verstümmelt wurden, für uns schön entwirrt wurden.Beachten Sie, dass der erste
int
Anruf um eine Zeile unterbrochen ist (28 statt 27 und der zweite um zwei Zeilen (27 statt 29). In den Kommentaren wurde vorgeschlagen, dass dies darauf zurückzuführen ist, dass die folgende Anweisungsadresse berücksichtigt wird macht 27 zu 28 und 29 springen von der Schleife und werden zu 27.Wir beobachten dann, dass mit
-O3
die Ausgabe vollständig verstümmelt ist:Backtraces werden im Allgemeinen durch Optimierungen irreparabel verstümmelt. Die Tail-Call-Optimierung ist ein bemerkenswertes Beispiel dafür: Was ist die Tail-Call-Optimierung?
Benchmark-Lauf auf
-O3
:Ausgabe:
Wie erwartet stellen wir fest, dass diese Methode bei externen Anrufen äußerst langsam
addr2line
ist und nur dann durchführbar ist, wenn eine begrenzte Anzahl von Anrufen getätigt wird.Jeder Backtrace-Druck scheint Hunderte von Millisekunden zu dauern. Seien Sie also gewarnt, dass die Programmleistung erheblich beeinträchtigt wird, wenn ein Backtrace sehr häufig auftritt.
Getestet unter Ubuntu 19.10, GCC 9.2.1, Boost 1.67.0.
glibc
backtrace
Dokumentiert unter: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
Haupt c
Kompilieren:
-rdynamic
ist die Schlüsseloption.Lauf:
Ausgänge:
Wir sehen also sofort, dass eine Inlining-Optimierung stattgefunden hat und einige Funktionen aus der Ablaufverfolgung verloren gegangen sind.
Wenn wir versuchen, die Adressen zu bekommen:
wir erhalten:
das ist völlig aus.
Wenn wir
-O0
stattdessen dasselbe tun , erhalten Sie./main.out
die richtige vollständige Ablaufverfolgung:und dann:
gibt:
Also sind die Leitungen nur um eine, TODO warum? Dies könnte jedoch noch verwendbar sein.
Fazit: Backtraces können nur möglicherweise perfekt mit zeigen
-O0
. Mit Optimierungen wird die ursprüngliche Rückverfolgung im kompilierten Code grundlegend geändert.Ich konnte keinen einfachen Weg finden, um C ++ - Symbole damit automatisch zu entwirren. Hier sind einige Hacks:
Getestet unter Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace_symbols_fd
Dieser Helfer ist etwas praktischer als
backtrace_symbols
und erzeugt im Grunde eine identische Ausgabe:Getestet unter Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace
mit C ++ Demangling Hack 1:-export-dynamic
+dladdr
Angepasst von: https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Dies ist ein "Hack", da dafür die ELF geändert werden muss
-export-dynamic
.glibc_ldl.cpp
Kompilieren und ausführen:
Ausgabe:
Getestet unter Ubuntu 18.04.
glibc
backtrace
mit C ++ Demangling Hack 2: Backtrace-Ausgabe analysierenDargestellt unter: https://panthema.net/2008/0901-stacktrace-demangled/
Dies ist ein Hack, da er analysiert werden muss.
TODO bringt es zum Kompilieren und zeigt es hier.
libunwind
TODO hat dies einen Vorteil gegenüber Glibc Backtrace? Eine sehr ähnliche Ausgabe erfordert auch das Ändern des Build-Befehls, ist jedoch nicht Teil von glibc und erfordert daher eine zusätzliche Paketinstallation.
Code angepasst von: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
Haupt c
Kompilieren und ausführen:
Entweder
#define _XOPEN_SOURCE 700
muss oben sein, oder wir müssen verwenden-std=gnu99
:Lauf:
Ausgabe:
und:
gibt:
Mit
-O0
:und:
gibt:
Getestet unter Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind mit C ++ - Namensentflechtung
Code angepasst von: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.cpp
Kompilieren und ausführen:
Ausgabe:
und dann können wir die Zeilen von
my_func_2
undmy_func_1(int)
mit finden:was gibt:
TODO: Warum sind die Zeilen um eins entfernt?
Getestet unter Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
GDB-Automatisierung
Wir können dies auch mit GDB tun, ohne es neu zu kompilieren, indem wir Folgendes verwenden: Wie wird eine bestimmte Aktion ausgeführt, wenn ein bestimmter Haltepunkt in GDB erreicht wird?
Wenn Sie die Rückverfolgung häufig drucken, ist dies wahrscheinlich weniger schnell als die anderen Optionen, aber vielleicht können wir mit native Geschwindigkeiten erreichen
compile code
, aber ich bin faul, es jetzt zu testen: Wie rufe ich Assembly in GDB auf?main.cpp
main.gdb
Kompilieren und ausführen:
Ausgabe:
TODO Ich wollte dies nur
-ex
über die Kommandozeile tun , um nicht erstellen zu müssen,main.gdb
aber ich konnte das nicht bekommencommands
zum Laufen bringen.Getestet in Ubuntu 19.04, GDB 8.2.
Linux Kernel
Wie drucke ich den aktuellen Thread-Stack-Trace im Linux-Kernel?
libdwfl
Dies wurde ursprünglich erwähnt unter: https://stackoverflow.com/a/60713161/895245 und es ist vielleicht die beste Methode, aber ich muss ein bisschen mehr Benchmarking durchführen, aber bitte stimmen Sie dieser Antwort zu.
TODO: Ich habe versucht, den Code in dieser funktionierenden Antwort auf eine einzige Funktion zu minimieren, aber es ist ein Fehler. Lassen Sie mich wissen, ob jemand herausfinden kann, warum.
dwfl.cpp
Kompilieren und ausführen:
Ausgabe:
Benchmark-Lauf:
Ausgabe:
Wir sehen also, dass diese Methode 10x schneller ist als Boosts Stacktrace und daher möglicherweise auf mehr Anwendungsfälle anwendbar ist.
Getestet in Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.
Siehe auch
quelle
Es gibt keinen standardisierten Weg, dies zu tun. Für Windows wird die Funktionalität in der DbgHelp- Bibliothek bereitgestellt
quelle
Sie können in der jeweiligen Funktion eine Makrofunktion anstelle der return-Anweisung verwenden.
Zum Beispiel, anstatt return zu verwenden,
Sie können eine Makrofunktion verwenden.
Wenn in einer Funktion ein Fehler auftritt, wird der Aufrufstapel im Java-Stil angezeigt (siehe unten).
Den vollständigen Quellcode finden Sie hier.
c-callstack unter https://github.com/Nanolat
quelle
Eine andere Antwort auf einen alten Thread.
Wenn ich das tun muss, benutze ich normalerweise nur
system()
undpstack
Also so etwas wie das:
Dies gibt aus
Dies sollte unter Linux, FreeBSD und Solaris funktionieren. Ich denke nicht, dass macOS pstack oder ein einfaches Äquivalent hat, aber dieser Thread scheint eine Alternative zu haben .
Wenn Sie verwenden
C
, müssen SieC
Zeichenfolgenfunktionen verwenden.Ich habe 7 für die maximale Anzahl von Stellen in der PID verwendet, basierend auf diesem Beitrag .
quelle
Linux-spezifisch, TLDR:
backtrace
inglibc
erzeugt nur dann genaue Stacktraces, wenn-lunwind
eine Verknüpfung besteht (undokumentierte plattformspezifische Funktion).#include <elfutils/libdwfl.h>
(diese Bibliothek dokumentierte nur in der Header - Datei).backtrace_symbols
undbacktrace_symbolsd_fd
sind am wenigsten informativ.Unter modernem Linux können Sie die Stacktrace-Adressen mithilfe der Funktion abrufen
backtrace
. Die undokumentierte Möglichkeit,backtrace
auf gängigen Plattformen genauere Adressen zu erstellen, besteht in der Verknüpfung mit-lunwind
(libunwind-dev
unter Ubuntu 18.04) (siehe Beispielausgabe unten).backtrace
verwendet die Funktion_Unwind_Backtrace
und standardmäßig stammt diese vonlibgcc_s.so.1
und diese Implementierung ist am portabelsten. Wenn-lunwind
es verknüpft ist, bietet es eine genauere Version von,_Unwind_Backtrace
aber diese Bibliothek ist weniger portabel (siehe unterstützte Architekturen inlibunwind/src
).Leider konnten der Begleiter
backtrace_symbolsd
und diebacktrace_symbols_fd
Funktionen die Stacktrace-Adressen seit wahrscheinlich einem Jahrzehnt nicht mehr in Funktionsnamen mit Quelldateinamen und Zeilennummer auflösen (siehe Beispielausgabe unten).Es gibt jedoch eine andere Methode, um Adressen in Symbole aufzulösen, und sie erzeugt die nützlichsten Spuren mit Funktionsname , Quelldatei und Zeilennummer . Die Methode ist zu
#include <elfutils/libdwfl.h>
und verknüpfen mit-ldw
(libdw-dev
unter Ubuntu 18.04).Arbeits C ++ Beispiel (
test.cc
):Kompiliert unter Ubuntu 18.04.4 LTS mit gcc-8.3:
Ausgänge:
Wenn no
-lunwind
verknüpft ist, wird eine weniger genaue Stapelverfolgung erstellt:Zum Vergleich ist die
backtrace_symbols_fd
Ausgabe für denselben Stacktrace am wenigsten informativ:In einer Produktionsversion (sowie C Sprachversion) können Sie diesen Code besonders robust zu machen , durch den Austausch
boost::core::demangle
,std::string
undstd::cout
mit ihren zugrunde liegenden Anrufen.Sie können auch überschreiben
__cxa_throw
, um die Stapelverfolgung zu erfassen, wenn eine Ausnahme ausgelöst wird, und sie zu drucken, wenn die Ausnahme abgefangen wird. Zum Zeitpunkt des Eintritts in dencatch
Block wurde der Stapel abgewickelt, sodass es zu spät ist, ihn aufzurufenbacktrace
. Aus diesem Grund muss der Stapel erfasst werden, aufthrow
dem die Funktion implementiert ist__cxa_throw
. Beachten Sie, dass in einem Multithread-Programm__cxa_throw
mehrere Threads gleichzeitig aufrufen können, sodass dies der Fall sein muss, wenn der Stacktrace in einem globalen Array erfasst wirdthread_local
.quelle
-lunwind
Problem wurde beim Erstellen dieses Beitrags entdeckt. Ich habe es zuvorlibunwind
direkt verwendet, um den Stacktrace zu erhalten, und wollte es veröffentlichen,backtrace
mache es aber für mich, wenn-lunwind
es verlinkt ist.gcc
macht die API nicht verfügbar, stimmt das?Sie können die Funktionalität selbst implementieren:
Verwenden Sie einen globalen (String-) Stapel und drücken Sie zu Beginn jeder Funktion den Funktionsnamen und solche anderen Werte (z. B. Parameter) auf diesen Stapel. beim Beenden der Funktion erneut einfügen.
Schreiben Sie eine Funktion, die den Stapelinhalt beim Aufruf ausdruckt, und verwenden Sie diese in der Funktion, in der Sie den Aufrufstapel anzeigen möchten.
Das mag nach viel Arbeit klingen, ist aber sehr nützlich.
quelle
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
die das Argument in ihrem Konstruktor drückt und in seinem Destruktor erscheint. FUNCTION repräsentiert immer den Namen der aktuellen Funktion.Die nächste Frage ist natürlich: Wird das ausreichen?
Der Hauptnachteil von Stack-Traces besteht darin, dass Sie, warum Sie die genaue Funktion haben, die aufgerufen wird, nichts anderes haben, wie den Wert der Argumente, was für das Debuggen sehr nützlich ist.
Wenn Sie Zugriff auf gcc und gdb haben, würde ich empfehlen, zu verwenden
assert
, um nach einer bestimmten Bedingung zu suchen und einen Speicherauszug zu erstellen, wenn dieser nicht erfüllt ist. Dies bedeutet natürlich, dass der Prozess gestoppt wird, aber Sie haben einen vollständigen Bericht anstelle einer bloßen Stapelverfolgung.Wenn Sie einen weniger aufdringlichen Weg wünschen, können Sie jederzeit die Protokollierung verwenden. Es gibt sehr effiziente Protokollierungsmöglichkeiten, wie zum Beispiel Pantheios . Dies könnte Ihnen wieder ein viel genaueres Bild davon geben, was vor sich geht.
quelle
Sie können Poppy dafür verwenden. Es wird normalerweise verwendet, um den Stack-Trace während eines Absturzes zu erfassen, kann ihn aber auch für ein laufendes Programm ausgeben.
Hier ist der gute Teil: Es kann die tatsächlichen Parameterwerte für jede Funktion auf dem Stapel und sogar lokale Variablen, Schleifenzähler usw. ausgeben.
quelle
Ich weiß, dass dieser Thread alt ist, aber ich denke, dass er für andere Leute nützlich sein kann. Wenn Sie gcc verwenden, können Sie die Gerätefunktionen (Option -finstrument-functions) verwenden, um jeden Funktionsaufruf (Ein- und Ausstieg) zu protokollieren. Weitere Informationen finden Sie hier: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
So können Sie beispielsweise jeden Aufruf in einen Stapel schieben und einfügen, und wenn Sie ihn drucken möchten, sehen Sie sich nur an, was sich in Ihrem Stapel befindet.
Ich habe es getestet, es funktioniert perfekt und ist sehr praktisch
UPDATE: Informationen zur Kompilierungsoption -finstrument-functions finden Sie im GCC-Dokument zu den Instrumentierungsoptionen: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
quelle
Mit den Boost-Bibliotheken können Sie den aktuellen Callstack drucken.
Mann hier: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
quelle
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
unter Win10 erhalten.Sie können den GNU-Profiler verwenden. Es zeigt auch das Anrufdiagramm! Der Befehl lautet
gprof
und Sie müssen Ihren Code mit einer Option kompilieren.quelle
Nein, gibt es nicht, obwohl plattformabhängige Lösungen existieren könnten.
quelle