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

5 minutos de lectura

avatar de usuario
como

El específico de Linux backtrace() y backtrace_symbols() le 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 también -ggdb. El caso de prueba a continuación solo imprime esto:


    BACKTRACE ------------
    ./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 muestren los nombres de las funciones, foo y main

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;
}

  • posible duplicado de Cómo obtener un seguimiento más detallado

    – Nemo

    4 de agosto de 2011 a las 0:51

  • por cierto, función backtrace_symbols_fd realiza la misma operación que backtrace_symbols()pero las cadenas resultantes se escriben inmediatamente en el descriptor de archivo fd.

    – nhghia

    16 de julio de 2018 a las 10:01

  • He probado varios métodos en detalle en: stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c/…

    – Ciro Santilli Путлер Капут 六四事

    25 de enero de 2019 a las 12:20

  • Dado que el segundo argumento de backtrace() especifica el número máximo de direcciones que se pueden almacenar en el búfer especificado por el primero (man 3 backtrace), un valor de 1024 es innecesariamente generoso aquí. Un valor de 20 debería funcionar bien, e imprimir una advertencia por posible truncamiento cuando bt_size == 20 es una buena práctica.

    – Roberto

    27 de julio de 2021 a las 13:13

avatar de usuario
Mateo Slattery

Los símbolos se toman de la tabla de símbolos dinámicos; necesitas el -rdynamic opción a gcclo que hace que pase una bandera al enlazador que asegura que todos los símbolos se colocan en la tabla.

(Ver el Opciones de enlace pagina de la manual de CCGy/o el rastros pagina de la manual de glibc.)

  • Sin embargo, esto no funciona para símbolos estáticos. libunwind que menciona @Nemo, funciona para funciones estáticas.

    -Nathan Kidd

    18 de marzo de 2014 a las 0:22

  • En un proyecto de CMake, esto se había establecido por error como ADD_COMPILE_OPTIONS(-rdynamic). En cambio, debe ser ADD_LINK_OPTIONS(-rdynamic) o equivalente para tener el comportamiento deseado.

    – Stéphane

    17 de julio de 2020 a las 19:54

  • Mejor aún para aquellos que usan cmake: SET (CMAKE_ENABLE_EXPORTS TRUE). Esto establece el indicador del enlazador -rdynamic.

    – Stéphane

    17/01/2021 a las 18:55

Utilizar el comando addr2line para asignar direcciones ejecutables al código fuente nombre de archivo + número de línea. Dar el -f opción para obtener nombres de funciones también.

Alternativamente, intente libunwind.

  • 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

    5 de junio de 2014 a las 14:14

  • … y con ASLR las reubicaciones son más comunes que nunca.

    – Erwan Legrand

    5 de junio de 2014 a las 14:18

  • @ErwanLegrand: Antes de ASLR, pensaba que las reubicaciones eran predecibles y addr2line funcionó 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 realizar esta operación en principio.

    – Nemo

    5 de junio de 2014 a las 14:24

  • 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

    5 de junio de 2014 a las 14:34

  • Me había olvidado de esta discusión. Libbacktrace, que no conocía en ese momento, es una mejor opción. (Ver mi nueva respuesta.)

    – Erwan Legrand

    27 mayo 2015 a las 10:15

avatar de usuario
Erwan Legrand

El excelente Libbacktrace de Ian Lance Taylor resuelve este problema. Maneja el desenredado de la pila y admite símbolos ELF ordinarios y 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 fue originalmente parte de la distribución de GCC. Ahora, se puede encontrar una versión independiente en Github:

https://github.com/ianlancetaylor/libbacktrace

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

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;
        }
}

Impulsar el seguimiento

Muy conveniente porque imprime ambos:

  • nombres de funciones de C++ no alterados
  • 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 ejecutable mínimo para él y muchos otros métodos en: imprimir pila de llamadas en C o C++

  • la pregunta esta etiquetada [c]no [c++].

    – Yakov Galka

    5 de diciembre de 2019 a las 0:55

  • la pregunta esta etiquetada [c]no [c++].

    – Yakov Galka

    5 de diciembre de 2019 a las 0:55

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad