Per una soluzione solo Linux puoi usare backtrace(3) che restituisce semplicemente un array di void *
(infatti ognuno di questi punta all'indirizzo di ritorno dallo stack frame corrispondente). Per tradurli in qualcosa di utile, c'è backtrace_symbols(3).
Presta attenzione alla sezione delle note in backtrace(3):
I nomi dei simboli potrebbero non essere disponibili senza l'uso di speciali opzioni di linker. Per i sistemi che usano il linker GNU, è necessario usare l'opzione-rdynamic linker. Nota che i nomi delle funzioni "statiche" non sono esposti e non saranno disponibili nel backtrace.
Esiste un modo per scaricare lo stack di chiamate in un processo in esecuzione in C o C++ ogni volta che viene chiamata una determinata funzione?
È possibile utilizzare una funzione macro anziché l'istruzione return nella funzione specifica.
Ad esempio, invece di utilizzare return,
int foo(...)
{
if (error happened)
return -1;
... do something ...
return 0
}
Puoi utilizzare una funzione macro.
#include "c-callstack.h"
int foo(...)
{
if (error happened)
NL_RETURN(-1);
... do something ...
NL_RETURN(0);
}
Ogni volta che si verifica un errore in una funzione, vedrai lo stack di chiamate in stile Java come mostrato di seguito.
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)
Il codice sorgente completo è disponibile qui.
c-callstack su https://github.com/Nanolat
Potenzia stacktrace
Documentato su:https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Questa è l'opzione più conveniente che ho visto finora, perché:
-
può effettivamente stampare i numeri di riga.
Effettua solo chiamate a
addr2line
tuttavia, il che aggiunge una brutta dipendenza esterna e rallenterà considerevolmente il tuo codice se stai facendo molte tracce -
demangles per impostazione predefinita
-
Boost è solo l'intestazione, quindi molto probabilmente non è necessario modificare il sistema di compilazione
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
}
}
Sfortunatamente, sembra essere un'aggiunta più recente e il pacchetto libboost-stacktrace-dev
non è presente in Ubuntu 16.04, solo 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
Dobbiamo aggiungere -ldl
alla fine altrimenti la compilazione fallisce.
Uscita:
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
L'output e viene ulteriormente spiegato nella sezione "glibc backtrace" di seguito, che è analoga.
Nota come my_func_1(int)
e my_func_1(float)
, che sono alterati a causa del sovraccarico di funzioni, sono stati ben smantellati per noi.
Nota che il primo int
le chiamate sono disattivate di una riga (28 invece di 27 e la seconda è disattivata di due righe (27 anziché 29). , e 29 salta fuori dal giro e diventa 27.
Lo osserviamo quindi con -O3
, l'output è completamente mutilato:
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
I backtrace sono in generale irrimediabilmente mutilati dalle ottimizzazioni. L'ottimizzazione delle chiamate di coda ne è un esempio notevole:che cos'è l'ottimizzazione delle chiamate di coda?
Benchmark eseguito su -O3
:
time ./boost_stacktrace.out 1000 >/dev/null
Uscita:
real 0m43.573s
user 0m30.799s
sys 0m13.665s
Quindi, come previsto, vediamo che questo metodo è estremamente lento probabilmente per le chiamate esterne a addr2line
, e sarà fattibile solo se viene effettuato un numero limitato di chiamate.
Ogni stampa di backtrace sembra richiedere centinaia di millisecondi, quindi tieni presente che se un backtrace si verifica molto spesso, le prestazioni del programma ne risentiranno in modo significativo.
Testato su Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.
glibc backtrace
Documentato su: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;
}
Compila:
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
-Wall -Wextra -pedantic-errors main.c
-rdynamic
è l'opzione chiave richiesta.
Esegui:
./main.out
Uscite:
./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]
Quindi vediamo immediatamente che si è verificata un'ottimizzazione incorporata e alcune funzioni sono state perse dalla traccia.
Se cerchiamo di ottenere gli indirizzi:
addr2line -e main.out 0x4008f9 0x4008fe
otteniamo:
/home/ciro/main.c:21
/home/ciro/main.c:36
che è completamente spento.
Se facciamo lo stesso con -O0
invece, ./main.out
fornisce la traccia completa corretta:
./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]
e poi:
addr2line -e main.out 0x400a74 0x400a79
dà:
/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35
quindi le linee sono fuori di una sola, TODO perché? Ma potrebbe essere ancora utilizzabile.
Conclusione:i backtrace possono essere mostrati perfettamente solo con -O0
. Con le ottimizzazioni, il backtrace originale viene sostanzialmente modificato nel codice compilato.
Non sono riuscito a trovare un modo semplice per smantellare automaticamente i simboli C++ con questo, tuttavia, ecco alcuni hack:
- https://panthema.net/2008/0901-stacktrace-demangled/
- https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Testato su Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace_symbols_fd
Questo helper è un po' più comodo di backtrace_symbols
, e produce un output sostanzialmente identico:
/* 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("");
}
Testato su Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace
con il demangling in C++ hack 1:-export-dynamic
+ dladdr
Adattato da:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Questo è un "hack" perché richiede di cambiare l'ELF con -export-dynamic
.
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);
}
Compila ed esegui:
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
uscita:
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]
Testato su Ubuntu 18.04.
glibc backtrace
con C++ demangling hack 2:analizza l'output del backtrace
Mostrato su:https://panthema.net/2008/0901-stacktrace-demangled/
Questo è un hack perché richiede l'analisi.
TODO fallo compilare e mostralo qui.
libunwind
TODO ha qualche vantaggio rispetto al backtrace di glibc? Output molto simile, richiede anche la modifica del comando build, ma non fa parte di glibc quindi richiede l'installazione di un pacchetto aggiuntivo.
Codice adattato da: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;
}
Compila ed esegui:
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
O #define _XOPEN_SOURCE 700
deve essere in cima, oppure dobbiamo usare -std=gnu99
:
- Il tipo `stack_t` non è più definito su Linux?
- Glibc - errore in ucontext.h, ma solo con -std=c11
Esegui:
./main.out
Uscita:
0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
e:
addr2line -e main.out 0x4007db 0x4007e2
dà:
/home/ciro/main.c:34
/home/ciro/main.c:49
Con -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)
e:
addr2line -e main.out 0x4009f3 0x4009f8
dà:
/home/ciro/main.c:47
/home/ciro/main.c:48
Testato su Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind con demangling del nome C++
Codice adattato da:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
unwind.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);
}
Compila ed esegui:
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
Uscita:
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)
e poi possiamo trovare le righe di my_func_2
e my_func_1(int)
con:
addr2line -e unwind.out 0x400c80 0x400cb7
che dà:
/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54
DA FARE:perché le battute sono fuori di uno?
Testato su Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Automazione GDB
Possiamo anche farlo con GDB senza ricompilare utilizzando:Come eseguire un'azione specifica quando viene raggiunto un determinato punto di interruzione in GDB?
Sebbene se stamperai molto il backtrace, questo sarà probabilmente meno veloce delle altre opzioni, ma forse possiamo raggiungere velocità native con compile code
, ma sono pigro per provarlo ora: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);
}
principale.gdb
start
break my_func_2
commands
silent
backtrace
printf "\n"
continue
end
continue
Compila ed esegui:
g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out
Uscita:
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]
DA FARE Volevo farlo solo con -ex
dalla riga di comando per non dover creare main.gdb
ma non sono riuscito a ottenere il commands
lavorare lì.
Testato in Ubuntu 19.04, GDB 8.2.
kernel Linux
Come stampare la traccia dello stack del thread corrente all'interno del kernel Linux?
libdwfl
Questo è stato originariamente menzionato su:https://stackoverflow.com/a/60713161/895245 e potrebbe essere il metodo migliore, ma devo fare un benchmark un po' di più, ma per favore vota quella risposta.
TODO:ho provato a minimizzare il codice in quella risposta, che funzionava, a una singola funzione, ma è segfaulting, fammi sapere se qualcuno riesce a trovare il motivo.
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
}
}
Compila ed esegui:
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
Abbiamo anche bisogno di libunwind in quanto ciò rende i risultati più corretti. Se ne fai a meno, funziona, ma vedrai che alcune righe sono un po' sbagliate.
Uscita:
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
Esecuzione del benchmark:
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
Uscita:
real 0m3.751s
user 0m2.822s
sys 0m0.928s
Quindi vediamo che questo metodo è 10 volte più veloce dello stacktrace di Boost e potrebbe quindi essere applicabile a più casi d'uso.
Testato su Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.
libbacktrace
https://github.com/ianlancetaylor/libbacktrace
Considerando l'autore della libreria harcore, vale la pena provarlo, forse è The One. TODO check it out.
Una libreria C che può essere collegata a un programma C/C++ per produrre backtrace simbolici
A partire da ottobre 2020, libbacktrace supporta gli eseguibili ELF, PE/COFF, Mach-O e XCOFF con informazioni di debug DWARF. In altre parole, supporta GNU/Linux, *BSD, macOS, Windows e AIX. La libreria è scritta per semplificare l'aggiunta del supporto per altri file oggetto e formati di debug.
La libreria si basa sull'API C++ unwind definita su https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html Questa API è fornita da GCC e clang.
Vedi anche
- Come si può prendere una traccia dello stack in C?
- Come fare in modo che backtrace()/backtrace_symbols() stampi i nomi delle funzioni?
- Esiste un modo portatile/conforme agli standard per ottenere nomi di file e numeri di riga in una traccia dello stack?
- Il modo migliore per invocare gdb dall'interno del programma per stampare il suo stacktrace?
- traccia automatica dello stack in caso di errore:
- sull'eccezione C++:C++ visualizza la traccia dello stack sull'eccezione
- generico:come generare automaticamente uno stacktrace quando il mio programma va in crash