GNU/Linux >> Tutoriales Linux >  >> Linux

imprimir pila de llamadas en C o C++

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

Linux
  1. Comando Preug - SyntaxError:faltan paréntesis en la llamada a 'imprimir' - Solución

  2. ¿Cómo mapear la pila para la llamada al sistema clone () en Linux?

  3. Cómo imprimir la hora actual (con milisegundos) usando C++ / C++11

  4. Llame a una función C desde el código C++

  5. Imprime el valor del puntero de la pila

Formato de estructura timespec

Llamada del sistema Intel x86 frente a x64

¿Imprimir errno como mnemotécnico?

¿Cómo imprimo un mensaje a stderr en Go?

2 impresoras 1 cola

¿Puedo llamar a pushd/popd y evitar que imprima la pila?