Für eine reine Linux-Lösung können Sie backtrace(3) verwenden, das einfach ein Array von void *
zurückgibt (Tatsächlich zeigen alle auf die Rücksendeadresse des entsprechenden Stapelrahmens). Um diese in etwas Nützliches zu übersetzen, gibt es backtrace_symbols(3).
Beachten Sie den Abschnitt mit den Notizen in backtrace(3):
Die Symbolnamen sind möglicherweise ohne die Verwendung spezieller Linker-Optionen nicht verfügbar. Für Systeme, die den GNU-Linker verwenden, ist es notwendig, die Linker-Option -rdynamic zu verwenden. Beachten Sie, dass Namen von "statischen" Funktionen nicht angezeigt werden und im Backtrace nicht verfügbar sind.
Gibt es eine Möglichkeit, den Call-Stack jedes Mal, wenn eine bestimmte Funktion aufgerufen wird, in einem laufenden Prozess in C oder C++ auszugeben?
Sie können eine Makrofunktion anstelle der Rückgabeanweisung in der spezifischen Funktion verwenden.
Anstatt beispielsweise return zu verwenden,
int foo(...)
{
if (error happened)
return -1;
... do something ...
return 0
}
Sie können eine Makrofunktion verwenden.
#include "c-callstack.h"
int foo(...)
{
if (error happened)
NL_RETURN(-1);
... do something ...
NL_RETURN(0);
}
Wann immer ein Fehler in einer Funktion auftritt, sehen Sie den Call-Stack im Java-Stil, wie unten gezeigt.
Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)
Der vollständige Quellcode ist hier verfügbar.
c-callstack unter https://github.com/Nanolat
Stacktrace steigern
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 ruft nur
addr2line
auf Dies fügt jedoch eine hässliche externe Abhängigkeit hinzu und wird Ihren Code erheblich verlangsamen, wenn Sie viele Traces erstellen -
demangles standardmäßig
-
Boost ist nur ein Header, daher müssen Sie Ihr Build-System höchstwahrscheinlich nicht ändern
boost_stacktrace.cpp
#include <iostream>
#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>
void my_func_2(void) {
std::cout << boost::stacktrace::stacktrace() << std::endl;
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main(int argc, char **argv) {
long long unsigned int n;
if (argc > 1) {
n = strtoul(argv[1], NULL, 0);
} else {
n = 1;
}
for (long long unsigned int i = 0; i < n; ++i) {
my_func_1(1); // line 28
my_func_1(2.0); // line 29
}
}
Leider scheint es eine neuere Ergänzung zu sein, und das Paket libboost-stacktrace-dev
ist in Ubuntu 16.04 nicht vorhanden, nur 18.04:
sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
-Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out
Wir müssen -ldl
hinzufügen am Ende, sonst schlägt die Kompilierung fehl.
Ausgabe:
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./boost_stacktrace.out
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./boost_stacktrace.out
Die Ausgabe und wird weiter unten im Abschnitt "glibc backtrace" erklärt, der analog ist.
Beachten Sie, wie my_func_1(int)
und my_func_1(float)
, die aufgrund von Funktionsüberlastung entstellt sind, wurden für uns schön entzerrt.
Beachten Sie, dass die erste int
Anrufe sind um eine Zeile (28 statt 27) und der zweite um zwei Zeilen (27 statt 29) versetzt. In den Kommentaren wurde vorgeschlagen, dass dies daran liegt, dass die folgende Befehlsadresse berücksichtigt wird, wodurch 27 zu 28 wird , und 29 springen aus der Schleife und werden 27.
Das beobachten wir dann bei -O3
, die Ausgabe ist komplett verstümmelt:
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./boost_stacktrace.out
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
1# main at /home/ciro/test/boost_stacktrace.cpp:31
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./boost_stacktrace.out
Backtraces werden im Allgemeinen durch Optimierungen irreparabel verstümmelt. Tail-Call-Optimierung ist ein bemerkenswertes Beispiel dafür:Was ist Tail-Call-Optimierung?
Benchmark-Lauf auf -O3
:
time ./boost_stacktrace.out 1000 >/dev/null
Ausgabe:
real 0m43.573s
user 0m30.799s
sys 0m13.665s
Wie erwartet sehen wir also, dass diese Methode extrem langsam ist, wahrscheinlich für externe Aufrufe von addr2line
, und ist nur möglich, 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 auf 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
main.c
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
char **strings;
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
printf("%s\n", strings[i]);
puts("");
free(strings);
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 33 */
my_func_2(); /* line 34 */
return 0;
}
Kompilieren:
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
-Wall -Wextra -pedantic-errors main.c
-rdynamic
ist die erforderliche Schlüsseloption.
Ausführen:
./main.out
Ausgaben:
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
Wir sehen also sofort, dass eine Inlining-Optimierung stattgefunden hat und einige Funktionen aus dem Trace verloren gegangen sind.
Wenn wir versuchen, die Adressen zu bekommen:
addr2line -e main.out 0x4008f9 0x4008fe
wir erhalten:
/home/ciro/main.c:21
/home/ciro/main.c:36
die komplett ausgeschaltet ist.
Wenn wir dasselbe mit -O0
machen stattdessen ./main.out
gibt die korrekte vollständige Ablaufverfolgung:
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
und dann:
addr2line -e main.out 0x400a74 0x400a79
ergibt:
/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35
Also sind die Zeilen nur um eine aus, TODO warum? Aber das könnte immer noch verwendbar sein.
Fazit:Backtraces können nur mit -O0
perfekt dargestellt werden . Bei Optimierungen wird der ursprüngliche Backtrace im kompilierten Code grundlegend modifiziert.
Ich konnte keinen einfachen Weg finden, C++-Symbole damit automatisch zu entwirren, aber hier sind einige Hacks:
- https://panthema.net/2008/0901-stacktrace-demangled/
- https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Getestet auf Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace_symbols_fd
Dieser Helfer ist etwas bequemer als backtrace_symbols
, und erzeugt im Grunde identische Ausgaben:
/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
backtrace_symbols_fd(array, size, STDOUT_FILENO);
puts("");
}
Getestet auf Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace
mit C++-Demanbling-Hack 1:-export-dynamic
+ dladdr
Angepasst von:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Dies ist ein "Hack", weil es das Ändern des ELF mit -export-dynamic
erfordert .
glibc_ldl.cpp
#include <dlfcn.h> // for dladdr
#include <cxxabi.h> // for __cxa_demangle
#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>
// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
void *callstack[128];
const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
char buf[1024];
int nFrames = backtrace(callstack, nMaxFrames);
char **symbols = backtrace_symbols(callstack, nFrames);
std::ostringstream trace_buf;
for (int i = skip; i < nFrames; i++) {
Dl_info info;
if (dladdr(callstack[i], &info)) {
char *demangled = NULL;
int status;
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
std::snprintf(
buf,
sizeof(buf),
"%-3d %*p %s + %zd\n",
i,
(int)(2 + sizeof(void*) * 2),
callstack[i],
status == 0 ? demangled : info.dli_sname,
(char *)callstack[i] - (char *)info.dli_saddr
);
free(demangled);
} else {
std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
i, (int)(2 + sizeof(void*) * 2), callstack[i]);
}
trace_buf << buf;
std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
trace_buf << buf;
}
free(symbols);
if (nFrames == nMaxFrames)
trace_buf << "[truncated]\n";
return trace_buf.str();
}
void my_func_2(void) {
std::cout << backtrace() << std::endl;
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
Kompilieren und ausführen:
g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
-pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out
Ausgabe:
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3 0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3 0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
Getestet auf Ubuntu 18.04.
glibc backtrace
mit C++ Demanling Hack 2:Backtrace-Ausgabe parsen
Gezeigt unter:https://panthema.net/2008/0901-stacktrace-demangled/
Dies ist ein Hack, weil er Parsing erfordert.
TODO bring es zum Kompilieren und zeige es hier.
libunwind
TODO hat dies einen Vorteil gegenüber glibc backtrace? Sehr ähnliche Ausgabe, erfordert auch eine Änderung des build-Befehls, ist aber 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/
main.c
/* This must be on top. */
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
char sym[256];
unw_context_t context;
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
printf("0x%lx:", pc);
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
printf(" (%s+0x%lx)\n", sym, offset);
} else {
printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
puts("");
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 46 */
my_func_2(); /* line 47 */
return 0;
}
Kompilieren und ausführen:
sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
-Wall -Wextra -pedantic-errors main.c -lunwind
Entweder #define _XOPEN_SOURCE 700
muss oben stehen, oder wir müssen -std=gnu99
verwenden :
- Ist der Typ `stack_t` unter Linux nicht mehr definiert?
- Glibc - Fehler in ucontext.h, aber nur mit -std=c11
Ausführen:
./main.out
Ausgabe:
0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
und:
addr2line -e main.out 0x4007db 0x4007e2
ergibt:
/home/ciro/main.c:34
/home/ciro/main.c:49
Mit -O0
:
0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
und:
addr2line -e main.out 0x4009f3 0x4009f8
ergibt:
/home/ciro/main.c:47
/home/ciro/main.c:48
Getestet auf 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/
entwinden.cpp
#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
void backtrace() {
unw_cursor_t cursor;
unw_context_t context;
// Initialize cursor to current frame for local unwinding.
unw_getcontext(&context);
unw_init_local(&cursor, &context);
// Unwind frames one by one, going up the frame stack.
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
std::printf("0x%lx:", pc);
char sym[256];
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
char* nameptr = sym;
int status;
char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
if (status == 0) {
nameptr = demangled;
}
std::printf(" (%s+0x%lx)\n", nameptr, offset);
std::free(demangled);
} else {
std::printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
}
void my_func_2(void) {
backtrace();
std::cout << std::endl; // line 43
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
} // line 54
int main() {
my_func_1(1);
my_func_1(2.0);
}
Kompilieren und ausführen:
sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
-Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out
Ausgabe:
0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
und dann können wir die Zeilen von my_func_2
finden und my_func_1(int)
mit:
addr2line -e unwind.out 0x400c80 0x400cb7
was ergibt:
/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54
TODO:Warum sind die Zeilen um eins abgeschaltet?
Getestet auf Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
GDB-Automatisierung
Wir können dies auch mit GDB tun, ohne neu zu kompilieren, indem wir Folgendes verwenden:Wie führt man eine bestimmte Aktion aus, wenn ein bestimmter Haltepunkt in GDB erreicht wird?
Wenn Sie den Backtrace häufig drucken, ist dies wahrscheinlich weniger schnell als die anderen Optionen, aber vielleicht können wir mit compile code
native Geschwindigkeiten erreichen , aber ich bin zu faul, es jetzt auszuprobieren:How to call assembly in gdb?
main.cpp
void my_func_2(void) {}
void my_func_1(double f) {
my_func_2();
}
void my_func_1(int i) {
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
main.gdb
start
break my_func_2
commands
silent
backtrace
printf "\n"
continue
end
continue
Kompilieren und ausführen:
g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out
Ausgabe:
Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.
Temporary breakpoint 1, main () at main.cpp:12
12 my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0 my_func_2 () at main.cpp:1
#1 0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2 0x0000555555555162 in main () at main.cpp:12
#0 my_func_2 () at main.cpp:1
#1 0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2 0x000055555555516f in main () at main.cpp:13
[Inferior 1 (process 14193) exited normally]
TODO Ich wollte das nur mit -ex
machen von der Befehlszeile, um main.gdb
nicht erstellen zu müssen aber ich konnte den commands
nicht bekommen dort zu arbeiten.
Getestet in Ubuntu 19.04, GDB 8.2.
Linux-Kernel
Wie drucke ich den Stack-Trace des aktuellen Threads im Linux-Kernel?
libdwfl
Dies wurde ursprünglich erwähnt unter:https://stackoverflow.com/a/60713161/895245 und es könnte die beste Methode sein, aber ich muss ein bisschen mehr Benchmarking durchführen, aber bitte stimmen Sie dieser Antwort zu.
TODO:Ich habe versucht, den Code in dieser Antwort, die funktioniert hat, auf eine einzelne Funktion zu minimieren, aber es ist ein Segfaulting. Lassen Sie mich wissen, wenn jemand den Grund dafür finden kann.
dwfl.cpp
#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid
// https://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
int status = -4;
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};
return (status==0) ? res.get() : name ;
}
std::string debug_info(Dwfl* dwfl, void* ip) {
std::string function;
int line = -1;
char const* file;
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? demangle(name) : "<unknown>";
if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
std::stringstream ss;
ss << ip << ' ' << function;
if (file)
ss << " at " << file << ':' << line;
ss << std::endl;
return ss.str();
}
std::string stacktrace() {
// Initialize Dwfl.
Dwfl* dwfl = nullptr;
{
Dwfl_Callbacks callbacks = {};
char* debuginfo_path = nullptr;
callbacks.find_elf = dwfl_linux_proc_find_elf;
callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
callbacks.debuginfo_path = &debuginfo_path;
dwfl = dwfl_begin(&callbacks);
assert(dwfl);
int r;
r = dwfl_linux_proc_report(dwfl, getpid());
assert(!r);
r = dwfl_report_end(dwfl, nullptr, nullptr);
assert(!r);
static_cast<void>(r);
}
// Loop over stack frames.
std::stringstream ss;
{
void* stack[512];
int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
for (int i = 0; i < stack_size; ++i) {
ss << i << ": ";
// Works.
ss << debug_info(dwfl, stack[i]);
#if 0
// TODO intended to do the same as above, but segfaults,
// so possibly UB In above function that does not blow up by chance?
void *ip = stack[i];
std::string function;
int line = -1;
char const* file;
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? demangle(name) : "<unknown>";
// TODO if I comment out this line it does not blow up anymore.
if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
ss << ip << ' ' << function;
if (file)
ss << " at " << file << ':' << line;
ss << std::endl;
#endif
}
}
dwfl_end(dwfl);
return ss.str();
}
void my_func_2() {
std::cout << stacktrace() << std::endl;
std::cout.flush();
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main(int argc, char **argv) {
long long unsigned int n;
if (argc > 1) {
n = strtoul(argv[1], NULL, 0);
} else {
n = 1;
}
for (long long unsigned int i = 0; i < n; ++i) {
my_func_1(1); // line 122
my_func_1(2.0); // line 123
}
}
Kompilieren und ausführen:
sudo apt install libdw-dev libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw -lunwind
./dwfl.out
Wir brauchen auch libunwind, da dies die Ergebnisse korrekter macht. Wenn Sie darauf verzichten, läuft es, aber Sie werden sehen, dass einige Zeilen etwas falsch sind.
Ausgabe:
0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d76 my_func_1(int) at /home/ciro/test/dwfl.cpp:111
3: 0x402dd1 main at /home/ciro/test/dwfl.cpp:122
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1
0: 0x402b72 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402cda my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d5f my_func_1(double) at /home/ciro/test/dwfl.cpp:106
3: 0x402de2 main at /home/ciro/test/dwfl.cpp:123
4: 0x7ff227ea0d8f __libc_start_call_main at ../sysdeps/nptl/libc_start_call_main.h:58
5: 0x7ff227ea0e3f [email protected]@GLIBC_2.34 at ../csu/libc-start.c:392
6: 0x402534 _start at ../csu/libc-start.c:-1
Benchmark-Lauf:
g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null
Ausgabe:
real 0m3.751s
user 0m2.822s
sys 0m0.928s
Wir sehen also, dass diese Methode 10-mal schneller ist als der Stacktrace von Boost und daher möglicherweise auf mehr Anwendungsfälle anwendbar ist.
Getestet in Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.
libbacktrace
https://github.com/ianlancetaylor/libbacktrace
In Anbetracht des Autors der Hardcore-Bibliothek lohnt es sich, dies auszuprobieren, vielleicht ist es The One. TODO check it out.
Eine C-Bibliothek, die in ein C/C++-Programm eingebunden werden kann, um symbolische Backtraces zu erzeugen
Ab Oktober 2020 unterstützt libbacktrace ausführbare ELF-, PE/COFF-, Mach-O- und XCOFF-Dateien mit DWARF-Debugging-Informationen. Mit anderen Worten, es unterstützt GNU/Linux, *BSD, macOS, Windows und AIX. Die Bibliothek ist so geschrieben, dass es einfach ist, Unterstützung für andere Objektdatei- und Debugging-Formate hinzuzufügen.
Die Bibliothek basiert auf der unter https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html definierten C++-Unwind-API. Diese API wird von GCC und clang bereitgestellt.
Siehe auch
- Wie kann man in C einen Stack-Trace abrufen?
- Wie bringt man backtrace()/backtrace_symbols() dazu, die Funktionsnamen auszugeben?
- Gibt es eine portable/standardkonforme Möglichkeit, Dateinamen und Zeilennummern in einem Stack-Trace zu erhalten?
- Der beste Weg, um gdb innerhalb des Programms aufzurufen, um seinen Stacktrace zu drucken?
- automatischer Stack-Trace bei Fehler:
- bei C++-Ausnahme:C++ zeigt Stack-Trace bei Ausnahme an
- generisch:Wie erstelle ich automatisch einen Stacktrace, wenn mein Programm abstürzt