¿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? Lo que tengo en mente es algo como esto:
void foo()
{
print_stack_trace();
// foo's body
return
}
Donde print_stack_trace
funciona de manera similar a caller
en Perl.
O algo como esto:
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
where register_stack_trace_function
coloca algún tipo de punto de interrupción interno que hará que se imprima un seguimiento de pila cada vez que foo
se llame.
¿Existe algo como esto en alguna biblioteca C estándar?
Estoy trabajando en Linux, usando GCC.
Antecedentes
Tengo una ejecución de prueba que se comporta de manera diferente en función de algunos interruptores de línea de comando que no deberían afectar este comportamiento. Mi código tiene un generador de números pseudoaleatorios que supongo que se llama de manera diferente en función de estos interruptores. Quiero poder ejecutar la prueba con cada conjunto de interruptores y ver si el generador de números aleatorios se llama de manera diferente para cada uno.
s/easier/either/
¿cómo diablos pasó eso?s/either/easier
. Lo que tendría que hacer con gdb es escribir un script que se interrumpa en esa función e imprima el seguimiento de la pila, luego continúa. Ahora que lo pienso, tal vez sea hora de que aprenda sobre las secuencias de comandos de gdb.Respuestas:
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 traducir esto en algo útil, hay backtrace_symbols (3) .Preste atención a la sección de notas en backtrace (3) :
fuente
glibc
, desafortunadamente, lasbacktrace_symbols
funciones no proporcionan el nombre de la función, el nombre del archivo fuente y el número de línea.-rdynamic
, también verifique que su sistema de compilación no agregue-fvisibility=hidden
opciones. (ya que descartará por completo el efecto de-rdynamic
)Impulsar stacktrace
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:
realmente puede imprimir los números de línea.
Sin embargo , solo hace llamadas a
addr2line
, lo cual es feo y podría ser lento si está tomando demasiados rastreos.demanda por defecto
El impulso es solo el encabezado, por lo que probablemente no sea necesario modificar su sistema de compilación
boost_stacktrace.cpp
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:Tenemos que agregar
-ldl
al final o la compilación falla.Salida:
La salida y se explica con más detalle en la sección "backtrace glibc" a continuación, que es análoga.
Tenga en cuenta cómo
my_func_1(int)
ymy_func_1(float)
, que están destrozados debido a la sobrecarga de funciones , fueron muy bien demandados para nosotros.Tenga en cuenta que la primera
int
llamada está desactivada en una línea (28 en lugar de 27 y la segunda está desactivada en 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, que hace que 27 se convierta en 28, y 29 salte del círculo y se convierta en 27.Luego observamos que con
-O3
, la salida está completamente mutilada:En general, los retrocesos son mutilados irreparablemente por optimizaciones. La optimización de llamadas finales es un ejemplo notable de eso: ¿Qué es la optimización de llamadas finales?
Evaluación comparativa ejecutada en
-O3
:Salida:
Entonces, como era de esperar, vemos que este método es extremadamente lento, probablemente para llamadas externas
addr2line
y solo será factible si se realiza una cantidad limitada de llamadas.Cada impresión de backtrace parece tardar cientos de milisegundos, así que tenga en cuenta que si ocurre un backtrace 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
C Principal
Compilar:
-rdynamic
es la opción clave requerida.Correr:
Salidas:
Así que inmediatamente vemos que ocurrió una optimización en línea y que algunas funciones se perdieron del seguimiento.
Si intentamos obtener las direcciones:
obtenemos:
que está completamente apagado.
Si hacemos lo mismo con en su
-O0
lugar,./main.out
da el seguimiento completo correcto:y entonces:
da:
entonces las líneas están apagadas por solo una, TODO ¿por qué? Pero esto aún podría ser utilizable.
Conclusión: los backtraces solo pueden mostrarse perfectamente con
-O0
. Con las optimizaciones, el backtrace original se modifica fundamentalmente en el código compilado.No pude encontrar una manera simple de exigir automáticamente los símbolos de C ++ con esto, sin embargo, aquí hay algunos trucos:
Probado en Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace_symbols_fd
Este ayudante es un poco más conveniente
backtrace_symbols
y produce resultados básicamente idénticos:Probado en Ubuntu 16.04, GCC 6.4.0, libc 2.23.
glibc
backtrace
con C ++ exigiendo hack 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
Compila y ejecuta:
salida:
Probado en Ubuntu 18.04.
glibc
backtrace
con C ++ exigiendo hack 2: analizar la salida del backtraceSe muestra en: https://panthema.net/2008/0901-stacktrace-demangled/
Este es un truco porque requiere análisis.
TODO consígalo para compilarlo y mostrarlo aquí.
libunwind
TODO, ¿tiene esto alguna ventaja sobre el backtrace de glibc? Salida muy similar, también requiere modificar el comando de compilación, pero no forma parte de glibc, por lo que requiere una instalación de paquete adicional.
Código adaptado de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
C Principal
Compila y ejecuta:
O
#define _XOPEN_SOURCE 700
debe estar en la parte superior o debemos usar-std=gnu99
:Correr:
Salida:
y:
da:
Con
-O0
:y:
da:
Probado en Ubuntu 16.04, GCC 6.4.0, libunwind 1.1.
libunwind con demanda de nombre C ++
Código adaptado de: https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/
relajarse.cpp
Compila y ejecuta:
Salida:
y luego podemos encontrar las líneas de
my_func_2
ymy_func_1(int)
con:lo que da:
TODO: ¿por qué las líneas están desviadas en uno?
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, esto probablemente será menos rápido que las otras opciones, pero tal vez podamos alcanzar velocidades nativas con
compile code
, pero me da pereza probarlo ahora: ¿Cómo llamar al ensamblaje en gdb?main.cpp
main.gdb
Compila y ejecuta:
Salida:
TODO Quería hacer esto solo
-ex
desde la línea de comando para no tener que crear,main.gdb
pero no pude hacercommands
que funcionara allí.Probado en Ubuntu 19.04, GDB 8.2.
Kernel de Linux
¿Cómo imprimir el seguimiento de la pila de hilos 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 vota esa respuesta.
TODO: Traté de minimizar el código en esa respuesta, que estaba funcionando, a una sola función, pero está fallando, avíseme si alguien puede encontrar por qué.
dwfl.cpp
Compila y ejecuta:
Salida:
Ejecución de referencia:
Salida:
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 ser aplicable a más casos de uso.
Probado en Ubuntu 19.10 amd64, libdw-dev 0.176-1.1.
Ver también
fuente
No existe una forma estandarizada de hacerlo. Para Windows, la funcionalidad se proporciona en la biblioteca DbgHelp
fuente
Puede utilizar 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,
Puede utilizar una función macro.
Siempre que ocurra un error en una función, verá la pila de llamadas de estilo Java como se muestra a continuación.
El código fuente completo está disponible aquí.
c-callstack en https://github.com/Nanolat
fuente
Otra respuesta a un viejo hilo.
Cuando necesito hacer esto, usualmente solo uso
system()
ypstack
Entonces algo como esto:
Esto salidas
Esto debería funcionar en Linux, FreeBSD y Solaris. No creo que macOS tenga pstack o un equivalente simple, pero este hilo parece tener una alternativa .
Si lo está utilizando
C
, necesitará utilizarC
funciones de cadena.He usado 7 para el número máximo de dígitos en el PID, según esta publicación .
fuente
Específico de Linux, TLDR:
backtrace
inglibc
produce trazas de pila precisas solo cuando-lunwind
está vinculado (característica específica de la plataforma no documentada).#include <elfutils/libdwfl.h>
(esta biblioteca está documentada solo en su archivo de encabezado).backtrace_symbols
ybacktrace_symbolsd_fd
son menos informativos.En Linux moderno, puede obtener las direcciones de seguimiento de pila usando la función
backtrace
. La forma indocumentada debacktrace
producir direcciones más precisas en plataformas populares es vincular con-lunwind
(libunwind-dev
en Ubuntu 18.04) (consulte el resultado de ejemplo a continuación).backtrace
usa la función_Unwind_Backtrace
y, por defecto, esta última provienelibgcc_s.so.1
y esa implementación es la más portátil. Cuando-lunwind
está vinculado, proporciona una versión más precisa de,_Unwind_Backtrace
pero esta biblioteca es menos portátil (consulte las arquitecturas compatibles enlibunwind/src
).Desafortunadamente, el complemento
backtrace_symbolsd
y lasbacktrace_symbols_fd
funciones no han podido resolver las direcciones de seguimiento de pila en nombres de funciones con el nombre del archivo de origen y el número de línea durante probablemente una década (consulte el resultado de ejemplo a continuación).Sin embargo, existe otro método para resolver direcciones en símbolos y produce los rastros más útiles con el nombre de la función , el archivo fuente y el número de línea . El método es ay
#include <elfutils/libdwfl.h>
enlazar con-ldw
(libdw-dev
en Ubuntu 18.04).Ejemplo de trabajo en C ++ (
test.cc
):Compilado en Ubuntu 18.04.4 LTS con gcc-8.3:
Salidas:
Cuando no
-lunwind
está vinculado, produce un seguimiento de pila menos preciso:A modo de comparación, la
backtrace_symbols_fd
salida para el mismo stacktrace es menos informativa:En una versión de producción (así como la versión en lenguaje C) que le gusten a hacer de este código extra robusta mediante la sustitución
boost::core::demangle
,std::string
ystd::cout
con sus llamadas subyacentes.También puede anular
__cxa_throw
para capturar el seguimiento de pila cuando se lanza una excepción e imprimirlo cuando se detecta la excepción. En el momento en que ingresa alcatch
bloque, la pila se ha desenrollado, por lo que es demasiado tarde para llamarbacktrace
, y es por eso que se debe capturar la pila en lathrow
que se implementa la función__cxa_throw
. Tenga en cuenta que en un programa de subprocesos múltiples__cxa_throw
se pueden llamar simultáneamente varios subprocesos, por lo que si captura el seguimiento de la pila en una matriz global, debe serthread_local
.fuente
-lunwind
problema se descubrió al hacer esta publicación, anteriormente lo usélibunwind
directamente para obtener el seguimiento de la pila e iba a publicarlo, pero lobacktrace
hace por mí cuando-lunwind
está vinculado.gcc
no expone la API, ¿verdad?Puede implementar la funcionalidad usted mismo:
Use una pila global (cadena) y al comienzo de cada función inserte el nombre de la función y otros valores (por ejemplo, parámetros) en esta pila; al salir de la función, vuelva a abrirla.
Escriba una función que imprima el contenido de la pila cuando se la llame y utilícela en la función en la que desea ver la pila de llamadas.
Esto puede parecer mucho trabajo, pero es bastante útil.
fuente
call_registror MY_SUPERSECRETNAME(__FUNCTION__);
que empuja el argumento en su constructor y aparece en su destructor FUNCTION siempre representa el nombre de la función actual.Por supuesto, la siguiente pregunta es: ¿será suficiente?
La principal desventaja de los stack-traces es que por qué se llama a la función precisa, no tiene nada más, como el valor de sus argumentos, lo cual es muy útil para depurar.
Si tiene acceso a gcc y gdb, sugeriría usarlo
assert
para verificar una condición específica y producir un volcado de memoria si no se cumple. Por supuesto, esto significa que el proceso se detendrá, pero tendrá un informe completo en lugar de un simple seguimiento de pila.Si desea una forma menos molesta, siempre puede usar el registro. Existen instalaciones de tala muy eficientes, como Pantheios, por ejemplo. Lo que una vez más podría darle una imagen mucho más precisa de lo que está sucediendo.
fuente
Puedes usar Poppy para esto. Normalmente se usa para recopilar el seguimiento de la pila durante un bloqueo, pero también puede generarlo para un programa en ejecución.
Ahora, aquí está la parte buena: puede generar los valores de los parámetros reales para cada función en la pila, e incluso las variables locales, contadores de bucle, etc.
fuente
Sé que este hilo es antiguo, pero creo que puede ser útil para otras personas. Si está usando gcc, puede usar sus características de instrumento (opción -finstrument-functions) para registrar cualquier llamada de función (entrada y salida). Eche un vistazo a esto para obtener más información: http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html
Por lo tanto, puede, por ejemplo, insertar y colocar todas las llamadas en una pila, y cuando desee imprimirlas, solo mire lo que tiene en su pila.
Lo he probado, funciona perfectamente y es muy útil.
ACTUALIZACIÓN: también puede encontrar información sobre la opción de compilación -finstrument-functions en el documento de GCC sobre las opciones de instrumentación: https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
fuente
Puede utilizar las bibliotecas de Boost para imprimir la pila de llamadas actual.
Hombre aquí: https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
fuente
cannot locate SymEnumSymbolsExW at C:\Windows\SYSTEM32\dbgeng.dll
en Win10.Puede utilizar el generador de perfiles de GNU. ¡También muestra el gráfico de llamadas! el comando es
gprof
y necesita compilar su código con alguna opción.fuente
No, no lo hay, aunque pueden existir soluciones dependientes de la plataforma.
fuente