¿Cómo hacer que backtrace () / backtrace_symbols () imprima los nombres de las funciones?

90

El específico de Linux backtrace()y le backtrace_symbols()permite producir un seguimiento de llamadas del programa. Sin embargo, solo imprime direcciones de funciones, no sus nombres para mi programa. ¿Cómo puedo hacer que impriman también los nombres de las funciones? He intentado compilar el programa con -g, así como -ggdb. El caso de prueba a continuación simplemente imprime esto:

    RETROCESO ------------
    ./a.out () [0x8048616]
    ./a.out () [0x8048623]
    /lib/libc.so.6(__libc_start_main+0xf3) [0x4a937413]
    ./a.out () [0x8048421]
    ----------------------
    

Me gustaría que los primeros 2 elementos también mostraran los nombres de las funciones, fooymain

Código:

#include <execinfo.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) && (errno != EINTR))
                        break;

                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}

void print_backtrace(void)
{
        static const char start[] = "BACKTRACE ------------\n";
        static const char end[] = "----------------------\n";

        void *bt[1024];
        int bt_size;
        char **bt_syms;
        int i;

        bt_size = backtrace(bt, 1024);
        bt_syms = backtrace_symbols(bt, bt_size);
        full_write(STDERR_FILENO, start, strlen(start));
        for (i = 1; i < bt_size; i++) {
                size_t len = strlen(bt_syms[i]);
                full_write(STDERR_FILENO, bt_syms[i], len);
                full_write(STDERR_FILENO, "\n", 1);
        }
        full_write(STDERR_FILENO, end, strlen(end));
    free(bt_syms);
}
void foo()
{
    print_backtrace();
}

int main()
{
    foo();
    return 0;
}
Lyke
fuente
Por cierto, la función backtrace_symbols_fdrealiza la misma operación que backtrace_symbols(), pero las cadenas resultantes se escriben inmediatamente en el descriptor de archivo fd.
nhnghia
He probado varios métodos en detalle en: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

63

Los símbolos se toman de la tabla de símbolos dinámicos; necesita la -rdynamicopción gcc, lo que hace que pase una bandera al enlazador que garantiza que todos los símbolos se coloquen en la tabla.

(Consulte la página de Opciones de enlace del manual de GCC y / o la página de Backtraces del manual de glibc ).

Matthew Slattery
fuente
10
Sin embargo, esto no funciona con símbolos estáticos. libunwindque @Nemo menciona, funciona para funciones estáticas.
Nathan Kidd
En un proyecto de CMake, esto se había configurado erróneamente como ADD_COMPILE_OPTIONS(-rdynamic). En cambio, debe ser ADD_LINK_OPTIONS(-rdynamic)o equivalente para tener el comportamiento deseado.
Stéphane
30

Utilice el comando addr2line para asignar direcciones ejecutables al código fuente nombre de archivo + número de línea. Dé la -fopción de obtener también nombres de funciones.

Alternativamente, pruebe libunwind .

Nemo
fuente
3
La línea addr2 es buena porque incluye el nombre del archivo y el número de línea en la salida, pero falla tan pronto como el cargador realiza las reubicaciones.
Erwan Legrand
... y con ASLR las reubicaciones son más comunes que nunca.
Erwan Legrand
@ErwanLegrand: Antes de ASLR, pensaba que las reubicaciones eran predecibles y addr2linefuncionaban de manera confiable incluso para direcciones en objetos compartidos (?) Pero sí, en las plataformas modernas necesitaría saber la dirección de carga real del objeto reubicable incluso para hacer esta operación en principio .
Nemo
No puedo decir con certeza qué tan bien funcionó antes de ASLR. Hoy en día, solo funcionará para código dentro de ejecutables "regulares" y fallará para código dentro de DSO y PIE (Ejecutables Independientes de Posición). Afortunadamente, libunwind parece funcionar para todos estos.
Erwan Legrand
Me había olvidado de esta discusión. Libbacktrace, que no conocía en ese momento, es una mejor opción. (Vea mi nueva respuesta.)
Erwan Legrand
11

El excelente Libbacktrace de Ian Lance Taylor resuelve este problema. Maneja el desenrollado de la pila y admite tanto los símbolos ELF ordinarios como los símbolos de depuración DWARF.

Libbacktrace no requiere exportar todos los símbolos, lo que sería feo, y ASLR no lo rompe.

Libbacktrace era originalmente parte de la distribución de GCC. Ahora, se puede encontrar una versión independiente en Github:

https://github.com/ianlancetaylor/libbacktrace

Erwan Legrand
fuente
2

la respuesta en la parte superior tiene un error si ret == -1 y errno es EINTER, debería intentarlo de nuevo, pero no contar ret como copiado (no va a crear una cuenta solo para esto, si no le gusta)

static void full_write(int fd, const char *buf, size_t len)
{
        while (len > 0) {
                ssize_t ret = write(fd, buf, len);

                if ((ret == -1) {
                        if (errno != EINTR))
                                break;
                        //else
                        continue;
                }
                buf += (size_t) ret;
                len -= (size_t) ret;
        }
}
Billg
fuente
0

Impulsar el seguimiento

Muy conveniente porque imprime ambos:

  • nombres de funciones de C ++ sin alterar
  • Línea de números

automáticamente para usted.

Resumen de uso:

#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>

std::cout << boost::stacktrace::stacktrace() << std::endl;

He proporcionado un ejemplo mínimo ejecutable para él y muchos otros métodos en: print call stack en C o C ++

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
La pregunta está etiquetada con [c], no con [c ++].
Yakov Galka