Para una solución solo para Linux, puede usar backtrace(3) que simplemente devuelve una matriz de void *
(de hecho, cada uno de estos apunta a la dirección de retorno del marco de pila correspondiente). Para traducirlos en algo útil, hay backtrace_symbols(3).
Preste atención a la sección de notas en backtrace(3):
Los nombres de los símbolos pueden no estar disponibles sin el uso de opciones de enlace especiales. Para los sistemas que usan el enlace GNU, es necesario usar la opción de enlace dinámico. Tenga en cuenta que los nombres de las funciones "estáticas" no están expuestos y no estarán disponibles en el rastreo inverso.
¿Hay alguna forma de volcar la pila de llamadas en un proceso en ejecución en C o C++ cada vez que se llama a una determinada función?
Puede usar una función de macro en lugar de una declaración de retorno en la función específica.
Por ejemplo, en lugar de usar return,
int foo(...)
{
if (error happened)
return -1;
... do something ...
return 0
}
Puede utilizar una función de macro.
#include "c-callstack.h"
int foo(...)
{
if (error happened)
NL_RETURN(-1);
... do something ...
NL_RETURN(0);
}
Siempre que ocurra un error en una función, verá la pila de llamadas de estilo Java como se muestra a continuación.
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)
El código fuente completo está disponible aquí.
c-callstack en https://github.com/Nanolat
Impulsar seguimiento de pila
Documentado en:https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
Esta es la opción más conveniente que he visto hasta ahora, porque:
-
puede imprimir los números de línea.
Solo hace llamadas a
addr2line
sin embargo, lo que agrega una fea dependencia externa y ralentizará considerablemente su código si está haciendo muchos rastros -
se desarma por defecto
-
Boost es solo encabezado, por lo que probablemente no sea necesario modificar su sistema de compilación
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
}
}
Desafortunadamente, parece ser una adición más reciente y el paquete libboost-stacktrace-dev
no está presente en 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
Tenemos que agregar -ldl
al final o la compilación falla.
Salida:
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
La salida y se explica con más detalle en la sección "glibc backtrace" a continuación, que es análoga.
Note cómo my_func_1(int)
y my_func_1(float)
, que están dañados debido a la sobrecarga de funciones, quedaron muy bien dañados para nosotros.
Tenga en cuenta que el primer int
llamadas está desactivada por una línea (28 en lugar de 27) y la segunda está desactivada por dos líneas (27 en lugar de 29). En los comentarios se sugirió que esto se debe a que se está considerando la siguiente dirección de instrucción, lo que hace que 27 se convierta en 28 y 29 saltan del bucle y se convierten en 27.
Luego observamos que con -O3
, la salida está completamente mutilada:
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
Los backtraces son, en general, irreparablemente mutilados por las optimizaciones. La optimización de llamadas de seguimiento es un ejemplo notable de eso:¿Qué es la optimización de llamadas de seguimiento?
Prueba comparativa ejecutada en -O3
:
time ./boost_stacktrace.out 1000 >/dev/null
Salida:
real 0m43.573s
user 0m30.799s
sys 0m13.665s
Entonces, como era de esperar, vemos que este método es extremadamente lento, probablemente para llamadas externas a addr2line
, y solo será factible si se realiza un número limitado de llamadas.
Cada impresión de rastreo inverso parece tardar cientos de milisegundos, así que tenga en cuenta que si ocurre un rastreo inverso con mucha frecuencia, el rendimiento del programa se verá afectado significativamente.
Probado en Ubuntu 19.10, GCC 9.2.1, boost 1.67.0.
glibc backtrace
Documentado en:https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
principal.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;
}
Compilar:
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
-Wall -Wextra -pedantic-errors main.c
-rdynamic
es la opción clave requerida.
Ejecutar:
./main.out
Salidas:
./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]
Así que inmediatamente vemos que ocurrió una optimización en línea y algunas funciones se perdieron del seguimiento.
Si tratamos de obtener las direcciones:
addr2line -e main.out 0x4008f9 0x4008fe
obtenemos:
/home/ciro/main.c:21
/home/ciro/main.c:36
que está completamente apagado.
Si hacemos lo mismo con -O0
en su lugar, ./main.out
da la traza completa correcta:
./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]
y luego:
addr2line -e main.out 0x400a74 0x400a79
da:
/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35
entonces las líneas están desviadas por solo una, TODO ¿por qué? Pero esto aún podría ser utilizable.
Conclusión:las trazas inversas solo pueden mostrarse perfectamente con -O0
. Con optimizaciones, el backtrace original se modifica fundamentalmente en el código compilado.
No pude encontrar una manera simple de desmantelar automáticamente los símbolos de C++ con esto, sin embargo, aquí hay algunos trucos:
- https://panthema.net/2008/0901-stacktrace-demangled/
- https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Probado en Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace_symbols_fd
Este ayudante es un poco más conveniente que backtrace_symbols
, y produce una salida básicamente idéntica:
/* 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("");
}
Probado en Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc backtrace
con C++ truco de desmantelamiento 1:-export-dynamic
+ dladdr
Adaptado de:https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Este es un "truco" porque requiere cambiar el 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);
}
Compilar y ejecutar:
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
salida:
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]
Probado en Ubuntu 18.04.
glibc backtrace
con C++ demangling hack 2:analizar la salida de rastreo inverso
Mostrado en:https://panthema.net/2008/0901-stacktrace-demangled/
Este es un truco porque requiere análisis.
TODO compilarlo y mostrarlo aquí.
libunwind
TODO ¿tiene esto alguna ventaja sobre glibc backtrace? Salida muy similar, también requiere modificar el comando de compilación, pero no forma parte de glibc, por lo que requiere la instalación de un paquete adicional.
Código adaptado de:https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
principal.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;
}
Compilar y ejecutar:
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
debe estar en la parte superior, o debemos usar -std=gnu99
:
- ¿El tipo `stack_t` ya no está definido en Linux?
- Glibc:error en ucontext.h, pero solo con -std=c11
Ejecutar:
./main.out
Salida:
0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
y:
addr2line -e main.out 0x4007db 0x4007e2
da:
/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)
y:
addr2line -e main.out 0x4009f3 0x4009f8
da:
/home/ciro/main.c:47
/home/ciro/main.c:48
Probado en Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind con nombre C++ desmantelado
Código adaptado de: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);
}
Compilar y ejecutar:
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
Salida:
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)
y luego podemos encontrar las líneas de my_func_2
y my_func_1(int)
con:
addr2line -e unwind.out 0x400c80 0x400cb7
que da:
/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54
TODO:¿por qué las líneas están separadas por una?
Probado en Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1.
Automatización de GDB
También podemos hacer esto con GDB sin volver a compilar usando:¿Cómo realizar una acción específica cuando se alcanza un determinado punto de interrupción en GDB?
Aunque si va a imprimir mucho el backtrace, es probable que sea menos rápido que las otras opciones, pero tal vez podamos alcanzar velocidades nativas con compile code
, pero me da flojera probarlo ahora:¿Cómo llamar a ensamblado en gdb?
principal.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);
}
principal.gdb
start
break my_func_2
commands
silent
backtrace
printf "\n"
continue
end
continue
Compilar y ejecutar:
g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out
Salida:
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 Quería hacer esto con solo -ex
desde la línea de comandos para no tener que crear main.gdb
pero no pude obtener el commands
para trabajar allí.
Probado en Ubuntu 19.04, GDB 8.2.
Núcleo de Linux
¿Cómo imprimir el seguimiento de la pila de subprocesos actual dentro del kernel de Linux?
libdwfl
Esto se mencionó originalmente en:https://stackoverflow.com/a/60713161/895245 y podría ser el mejor método, pero tengo que comparar un poco más, pero vote a favor esa respuesta.
TODO:Traté de minimizar el código en esa respuesta, que estaba funcionando, a una sola función, pero está fallando, avísenme si alguien puede encontrar por qué.
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
}
}
Compilar y ejecutar:
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
También necesitamos libunwind ya que eso hace que los resultados sean más correctos. Si prescindes de él, se ejecuta, pero verás que algunas de las líneas están un poco mal.
Salida:
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
Ejecución de referencia:
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
Salida:
real 0m3.751s
user 0m2.822s
sys 0m0.928s
Entonces vemos que este método es 10 veces más rápido que el seguimiento de pila de Boost y, por lo tanto, podría aplicarse a más casos de uso.
Probado en Ubuntu 22.04 amd64, libdw-dev 0.186, libunwind 1.3.2.
libbacktrace
https://github.com/ianlancetaylor/libbacktrace
Teniendo en cuenta el autor de la biblioteca harcore, vale la pena probar esto, tal vez sea The One. TODO comprobarlo.
Una biblioteca C que se puede vincular a un programa C/C++ para producir trazas inversas simbólicas
A partir de octubre de 2020, libbacktrace admite ejecutables ELF, PE/COFF, Mach-O y XCOFF con información de depuración DWARF. En otras palabras, es compatible con GNU/Linux, *BSD, macOS, Windows y AIX. La biblioteca está escrita para facilitar la adición de compatibilidad con otros archivos de objetos y formatos de depuración.
La biblioteca se basa en la API de desconexión de C++ definida en https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html Esta API es proporcionada por GCC y clang.
Ver también
- ¿Cómo se puede obtener un seguimiento de pila en C?
- ¿Cómo hacer que backtrace()/backtrace_symbols() imprima los nombres de las funciones?
- ¿Existe una forma portátil/que cumpla con los estándares de obtener nombres de archivo y números de línea en un seguimiento de pila?
- ¿La mejor manera de invocar a gdb desde el programa interno para imprimir su stacktrace?
- seguimiento de pila automático en caso de falla:
- en excepción de C++:seguimiento de pila de visualización de C++ en excepción
- genérico:cómo generar automáticamente un seguimiento de pila cuando mi programa falla