Estoy trabajando en Linux con el compilador GCC. Cuando mi programa de C++ falla, me gustaría que generara automáticamente un seguimiento de pila.
Mi programa está siendo ejecutado por muchos usuarios diferentes y también se ejecuta en Linux, Windows y Macintosh (todas las versiones se compilan usando gcc
).
Me gustaría que mi programa pueda generar un seguimiento de pila cuando se bloquee y la próxima vez que el usuario lo ejecute, les preguntará si está bien que me envíen el seguimiento de pila para que pueda localizar el problema. Puedo manejar el envío de la información, pero no sé cómo generar la cadena de seguimiento. ¿Algunas ideas?
Para Linux y creo que Mac OS X, si está usando gcc, o cualquier compilador que use glibc, puede usar las funciones backtrace () en execinfo.h
para imprimir un stacktrace y salir con gracia cuando se produce un fallo de segmentación. La documentación se puede encontrar en el manual de libc .
Aquí hay un programa de ejemplo que instala un controlador SIGSEGV
e imprime un seguimiento de pila en stderr
cuando se ejecuta de forma predeterminada. La función baz()
aquí hace que el segfault que activa el manejador:
#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void handler(int sig) {
void *array[10];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, 10);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
exit(1);
}
void baz() {
int *foo = (int*)-1; // make a bad pointer
printf("%d\n", *foo); // causes segfault
}
void bar() { baz(); }
void foo() { bar(); }
int main(int argc, char **argv) {
signal(SIGSEGV, handler); // install our handler
foo(); // this will call foo, bar, and baz. baz segfaults.
}
Al compilar con -g -rdynamic
se obtiene la información del símbolo en su salida, que glibc puede usar para hacer un buen seguimiento de pila:
$ gcc -g -rdynamic ./test.c -o test
Ejecutando esto obtienes esta salida:
$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]
Esto muestra el módulo de carga, el desplazamiento y la función de la que procedía cada fotograma de la pila. Aquí puede ver el manejador de señales en la parte superior de la pila, y las funciones libc antes de main
además de main
, foo
, bar
, y baz
.
Si bien el uso de backtrace () funciona en execinfo.h para imprimir un stacktrace y salir correctamente cuando se produce un fallo de segmentación ya se ha sugerido , no veo ninguna mención de las complejidades necesarias para garantizar el backtrace resultante apunta a la ubicación real de la falla (al menos para algunas arquitecturas - x86 y ARM).
Las dos primeras entradas en la cadena de cuadros de la pila cuando ingresas al manejador de señales contienen una dirección de retorno dentro del manejador de señales y una dentro de sigaction () en libc. El marco de la pila de la última función llamada antes de la señal (que es la ubicación de la falla) se pierde.
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif
#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>
/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
unsigned long uc_flags;
struct ucontext *uc_link;
stack_t uc_stack;
struct sigcontext uc_mcontext;
sigset_t uc_sigmask;
} sig_ucontext_t;
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
void * array[50];
void * caller_address;
char ** messages;
int size, i;
sig_ucontext_t * uc;
uc = (sig_ucontext_t *)ucontext;
/* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#Elif defined(__x86_64__) // gcc specific
caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other Arch.
#endif
fprintf(stderr, "signal %d (%s), address is %p from %p\n",
sig_num, strsignal(sig_num), info->si_addr,
(void *)caller_address);
size = backtrace(array, 50);
/* overwrite sigaction with caller's address */
array[1] = caller_address;
messages = backtrace_symbols(array, size);
/* skip first stack frame (points here) */
for (i = 1; i < size && messages != NULL; ++i)
{
fprintf(stderr, "[bt]: (%d) %s\n", i, messages[i]);
}
free(messages);
exit(EXIT_FAILURE);
}
int crash()
{
char * p = NULL;
*p = 0;
return 0;
}
int foo4()
{
crash();
return 0;
}
int foo3()
{
foo4();
return 0;
}
int foo2()
{
foo3();
return 0;
}
int foo1()
{
foo2();
return 0;
}
int main(int argc, char ** argv)
{
struct sigaction sigact;
sigact.sa_sigaction = crit_err_hdlr;
sigact.sa_flags = SA_RESTART | SA_SIGINFO;
if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
{
fprintf(stderr, "error setting signal handler for %d (%s)\n",
SIGSEGV, strsignal(SIGSEGV));
exit(EXIT_FAILURE);
}
foo1();
exit(EXIT_SUCCESS);
}
signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]
Todos los peligros de llamar a las funciones backtrace () en un manejador de señales aún existen y no deben pasarse por alto, pero me parece que la funcionalidad que describí aquí es muy útil para eliminar errores en la depuración.
Es importante tener en cuenta que el ejemplo que proporcioné está desarrollado/probado en Linux para x86. También lo he implementado con éxito en ARM usando uc_mcontext.arm_pc
en lugar de uc_mcontext.eip
.
Aquí hay un enlace al artículo donde aprendí los detalles de esta implementación: http://www.linuxjournal.com/article/6391
Es incluso más fácil que "man backtrace", hay una biblioteca poco documentada (específica de GNU) distribuida con glibc como libSegFault.so, que creo que fue escrita por Ulrich Drepper para apoyar el programa catchsegv (consulte "man catchsegv").
Esto nos da 3 posibilidades. En lugar de ejecutar "programa -o hai":
Ejecutar dentro de catchsegv:
$ catchsegv program -o hai
Enlace con libSegFault en tiempo de ejecución:
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
Enlace con libSegFault en tiempo de compilación:
$ gcc -g1 -lSegFault -o program program.cc
$ program -o hai
En los 3 casos, obtendrá backtraces más claros con menos optimización (gcc -O0 o -O1) y símbolos de depuración (gcc -g). De lo contrario, puede terminar con un montón de direcciones de memoria.
También puedes capturar más señales para trazas de pila con algo como:
$ export SEGFAULT_SIGNALS="all" # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt" # SIGBUS and SIGABRT
La salida tendrá un aspecto similar a este (observe el retroceso en la parte inferior):
*** Segmentation fault Register dump:
EAX: 0000000c EBX: 00000080 ECX:
00000000 EDX: 0000000c ESI:
bfdbf080 EDI: 080497e0 EBP:
bfdbee38 ESP: bfdbee20
EIP: 0805640f EFLAGS: 00010282
CS: 0073 DS: 007b ES: 007b FS:
0000 GS: 0033 SS: 007b
Trap: 0000000e Error: 00000004
OldMask: 00000000 ESP/signal:
bfdbee20 CR2: 00000024
FPUCW: ffff037f FPUSW: ffff0000
TAG: ffffffff IPOFF: 00000000
CSSEL: 0000 DATAOFF: 00000000
DATASEL: 0000
ST(0) 0000 0000000000000000 ST(1)
0000 0000000000000000 ST(2) 0000
0000000000000000 ST(3) 0000
0000000000000000 ST(4) 0000
0000000000000000 ST(5) 0000
0000000000000000 ST(6) 0000
0000000000000000 ST(7) 0000
0000000000000000
Backtrace:
/lib/libSegFault.so[0xb7f9e100]
??:0(??)[0xb7fa3400]
/usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
/home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
/build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]
Si desea conocer los detalles sangrientos, desafortunadamente la mejor fuente es la fuente: Consulte http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c y su directorio principal http://sourceware.org/git/?p=glibc.git;a=tree;f=debug
Aunque se ha proporcionado una respuesta correcta que describe cómo usar la función GNU libc backtrace()
1 y proporcioné mi propia respuesta que describe cómo garantizar que el retroceso de un controlador de señales apunta a la ubicación real de la falla2, No veo ninguna mención de desmangling C++ salida de símbolos desde el backtrace.
Al obtener backtraces de un programa de C++, la salida se puede ejecutar a través de c++filt
1 para desenredar los símbolos o utilizando abi::__cxa_demangle
1 directamente.
c++filt
y __cxa_demangle
son específicos de GCCEl siguiente ejemplo de C++ Linux usa el mismo controlador de señal que mi otra respuesta y demuestra cómo c++filt
puede usarse para desentrañar los símbolos.
Código :
class foo
{
public:
foo() { foo1(); }
private:
void foo1() { foo2(); }
void foo2() { foo3(); }
void foo3() { foo4(); }
void foo4() { crash(); }
void crash() { char * p = NULL; *p = 0; }
};
int main(int argc, char ** argv)
{
// Setup signal handler for SIGSEGV
...
foo * f = new foo();
return 0;
}
Salida (./test
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
[bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
[bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
[bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
[bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
[bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
Salida demangled (./test 2>&1 | c++filt
):
signal 11 (Segmentation fault), address is (nil) from 0x8048e07
[bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
[bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
[bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
[bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
[bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
[bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
[bt]: (7) ./test(main+0xe0) [0x8048d18]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]
Lo siguiente se basa en el controlador de señales de mi respuesta original y puede reemplazar el controlador de señales en el ejemplo anterior para demostrar cómo abi::__cxa_demangle
se puede usar para desentrañar los símbolos. Este manejador de señales produce la misma salida descompuesta que el ejemplo anterior.
Código :
void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;
void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific
std::cerr << "signal " << sig_num
<< " (" << strsignal(sig_num) << "), address is "
<< info->si_addr << " from " << caller_address
<< std::endl << std::endl;
void * array[50];
int size = backtrace(array, 50);
array[1] = caller_address;
char ** messages = backtrace_symbols(array, size);
// skip first stack frame (points here)
for (int i = 1; i < size && messages != NULL; ++i)
{
char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char *p = messages[i]; *p; ++p)
{
if (*p == '(')
{
mangled_name = p;
}
else if (*p == '+')
{
offset_begin = p;
}
else if (*p == ')')
{
offset_end = p;
break;
}
}
// if the line could be processed, attempt to demangle the symbol
if (mangled_name && offset_begin && offset_end &&
mangled_name < offset_begin)
{
*mangled_name++ = '\0';
*offset_begin++ = '\0';
*offset_end++ = '\0';
int status;
char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0)
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< real_name << "+" << offset_begin << offset_end
<< std::endl;
}
// otherwise, output the mangled function name
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << " : "
<< mangled_name << "+" << offset_begin << offset_end
<< std::endl;
}
free(real_name);
}
// otherwise, print the whole line
else
{
std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
}
}
std::cerr << std::endl;
free(messages);
exit(EXIT_FAILURE);
}
Podría valer la pena ver Google Breakpad , un generador de volcado de plataforma multiplataforma y herramientas para procesar los volcados.
No ha especificado su sistema operativo, por lo que es difícil de responder. Si está utilizando un sistema basado en gnu libc, es posible que pueda utilizar la función libc backtrace()
.
GCC también tiene dos componentes integrados que pueden ayudarlo, pero que pueden o no implementarse completamente en su arquitectura, y son __builtin_frame_address
y __builtin_return_address
. Ambos quieren un nivel entero inmediato (por inmediato, quiero decir que no puede ser una variable). Si __builtin_frame_address
para un nivel dado no es cero, debería ser seguro obtener la dirección de retorno del mismo nivel.
ulimit -c <value>
establece el límite de tamaño del archivo central en Unix. De forma predeterminada, el límite de tamaño del archivo principal es 0. Puede ver sus valores de ulimit
con ulimit -a
.
también, si ejecuta su programa desde gdb, detendrá su programa en "infracciones de segmentación" (SIGSEGV
, generalmente cuando accedió a un fragmento de memoria que no había asignado) o puede establecer puntos de interrupción.
ddd y nemiver son front-ends para gdb, lo que hace que trabajar con él sea mucho más fácil para el principiante.
He estado mirando este problema por un tiempo.
Y enterrado profundamente en las herramientas de rendimiento de Google README
http://code.google.com/p/google-perftools/source/browse/trunk/README
habla de libunwind
http://www.nongnu.org/libunwind/
Me encantaría escuchar las opiniones de esta biblioteca.
El problema con -rdynamic es que puede aumentar el tamaño del binario de manera relativamente significativa en algunos casos
Gracias a enthusiasticgeek por llamar mi atención a la utilidad addr2line.
He escrito un script rápido y sucio para procesar el resultado de la respuesta proporcionada aquí : (¡muchas gracias a jschmier!) Usando la utilidad addr2line.
El script acepta un solo argumento: el nombre del archivo que contiene el resultado de la utilidad de jschmier.
La salida debe imprimir algo como lo siguiente para cada nivel de la traza:
BACKTRACE: testExe 0x8A5db6b
FILE: pathToFile/testExe.C:110
FUNCTION: testFunction(int)
107
108
109 int* i = 0x0;
*110 *i = 5;
111
112 }
113 return i;
Código:
#!/bin/bash
LOGFILE=$1
NUM_SRC_CONTEXT_LINES=3
old_IFS=$IFS # save the field separator
IFS=$'\n' # new field separator, the end of line
for bt in `cat $LOGFILE | grep '\[bt\]'`; do
IFS=$old_IFS # restore default field separator
printf '\n'
EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`
ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
echo "BACKTRACE: $EXEC $ADDR"
A2L=`addr2line -a $ADDR -e $EXEC -pfC`
#echo "A2L: $A2L"
FUNCTION=`echo $A2L | sed 's/\<at\>.*//' | cut -d' ' -f2-99`
FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
echo "FILE: $FILE_AND_LINE"
echo "FUNCTION: $FUNCTION"
# print offending source code
SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
if ([ -f $SRCFILE ]); then
cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES "^ *$LINENUM\>" | sed "s/ $LINENUM/*$LINENUM/"
else
echo "File not found: $SRCFILE"
fi
IFS=$'\n' # new field separator, the end of line
done
IFS=$old_IFS # restore default field separator
Algunas versiones de libc contienen funciones que tratan con trazas de pila; Usted podría ser capaz de usarlos:
http://www.gnu.org/software/libc/manual/html_node/Backtraces.html
Recuerdo haber usado libunwind hace mucho tiempo para obtener trazas de pila, pero puede que no sea compatible con su plataforma.
Es importante tener en cuenta que una vez que genere un archivo central, necesitará usar la herramienta gdb para verlo. Para que gdb le dé sentido a su archivo principal, debe decirle a gcc que instale el binario con símbolos de depuración: para hacer esto, compile con el indicador -g:
$ g++ -g prog.cpp -o prog
Luego, puedes configurar "ulimit -c unlimited" para que descargue un núcleo, o simplemente ejecutar tu programa dentro de gdb. Me gusta más el segundo enfoque:
$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...
Espero que esto ayude.
ulimit -c unlimited
es una variable del sistema, que permitirá crear un volcado de memoria después de que la aplicación se bloquee. En este caso una cantidad ilimitada. Busque un archivo llamado núcleo en el mismo directorio. ¡Asegúrese de compilar su código con las informaciones de depuración habilitadas!
saludos
Olvídese de cambiar sus fuentes y haga algunos trucos con la función backtrace () o macroses, estas son solo soluciones pobres.
Como una solución que funciona correctamente, aconsejaría:
Esto imprimirá el seguimiento legible adecuado de su programa de forma legible para el ser humano (con nombres de archivos de origen y números de línea). Además, este enfoque le dará libertad para automatizar su sistema: tenga una secuencia de comandos corta que compruebe si el proceso creó un volcado de núcleo y luego envíe los registros por correo electrónico a los desarrolladores, o inicie sesión en algún sistema de registro.
Puede usar DeathHandler - pequeña clase de C++ que hace todo por usted, confiable.
ganar: ¿Qué hay de StackWalk64 http://msdn.Microsoft.com/en-us/library/ms680650.aspx
Mirar:
hombre 3 retroceso
Y:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
Estas son GNU extensiones.
Consulte la función Stack Trace en AS (Entorno de comunicación ADAPTABLE). Ya está escrito para cubrir todas las plataformas principales (y más). La biblioteca tiene una licencia de estilo BSD, por lo que incluso puede copiar/pegar el código si no desea utilizar ACE.
Puedo ayudar con la versión de Linux: se pueden usar la función backtrace, backtrace_symbols y backtrace_symbols_fd. Consulte las páginas de manual correspondientes.
* nix: puede interceptar SIGSEGV (normalmente, esta señal se genera antes de fallar) y mantener la información en un archivo. (además del archivo principal que puede usar para depurar usando gdb por ejemplo).
ganar: Marque este desde msdn.
También puede consultar el código chrome de google para ver cómo maneja los bloqueos. Tiene un mecanismo de manejo de excepciones agradable.
Encontré que la solución @tgamblin no está completa. No se puede manejar con stackoverflow. Creo que porque, por defecto, el manejador de señales se llama con la misma pila y SIGSEGV se lanza dos veces. Para protegerte necesitas registrar una pila independiente para el manejador de señales.
Puedes verificar esto con el código de abajo. Por defecto el manejador falla. Con la macro definida STACK_OVERFLOW está bien.
#include <iostream>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#include <cassert>
using namespace std;
//#define STACK_OVERFLOW
#ifdef STACK_OVERFLOW
static char stack_body[64*1024];
static stack_t sigseg_stack;
#endif
static struct sigaction sigseg_handler;
void handler(int sig) {
cerr << "sig seg fault handler" << endl;
const int asize = 10;
void *array[asize];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, asize);
// print out all the frames to stderr
cerr << "stack trace: " << endl;
backtrace_symbols_fd(array, size, STDERR_FILENO);
cerr << "resend SIGSEGV to get core dump" << endl;
signal(sig, SIG_DFL);
kill(getpid(), sig);
}
void foo() {
foo();
}
int main(int argc, char **argv) {
#ifdef STACK_OVERFLOW
sigseg_stack.ss_sp = stack_body;
sigseg_stack.ss_flags = SS_ONSTACK;
sigseg_stack.ss_size = sizeof(stack_body);
assert(!sigaltstack(&sigseg_stack, nullptr));
sigseg_handler.sa_flags = SA_ONSTACK;
#else
sigseg_handler.sa_flags = SA_RESTART;
#endif
sigseg_handler.sa_handler = &handler;
assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
cout << "sig action set" << endl;
foo();
return 0;
}
He visto muchas respuestas aquí realizando un manejador de señal y luego saliendo. Ese es el camino a seguir, pero recuerde un hecho muy importante: si desea obtener el volcado central para el error generado, no puede llamar a exit(status)
. ¡Llama a abort()
en su lugar!
Ha llegado el nuevo rey de la ciudad https://github.com/bombela/backward-cpp
1 encabezado para colocar en tu código y 1 biblioteca para instalar.
Personalmente lo llamo usando esta función.
#include "backward.hpp"
void stacker() {
using namespace backward;
StackTrace st;
st.load_here(99); //Limit the number of trace depth to 99
st.skip_n_firsts(3);//This will skip some backward internal function from the trace
Printer p;
p.snippet = true;
p.object = true;
p.color = true;
p.address = true;
p.print(st, stderr);
}
Usaré el código que genera un seguimiento de pila para la memoria filtrada en Detector de fugas visual . Sin embargo, esto solo funciona en Win32.
Como solución solo para Windows, puede obtener el equivalente a un seguimiento de pila (con mucha, mucha más información) utilizando Informe de errores de Windows . Con solo unas pocas entradas de registro, se puede configurar para recopilar volcados en modo usuario :
A partir de Windows Server 2008 y Windows Vista con Service Pack 1 (SP1), el Informe de errores de Windows (WER) se puede configurar para que los volcados de modo de usuario completos se recopilen y almacenen localmente después de que una aplicación de modo de usuario falla. [...]
Esta característica no está habilitada por defecto. Habilitar la característica requiere privilegios de administrador. Para habilitar y configurar la función, use los siguientes valores de registro debajo de HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error de informe\LocalDumps key.
Puede configurar las entradas de registro desde su instalador, que tiene los privilegios necesarios.
La creación de un volcado en modo usuario tiene las siguientes ventajas sobre la generación de un seguimiento de pila en el cliente:
Tenga en cuenta que WER solo puede ser activado por un bloqueo de la aplicación (es decir, que el sistema finalice un proceso debido a una excepción no controlada). MiniDumpWriteDump
se puede llamar en cualquier momento. Esto puede ser útil si necesita volcar el estado actual para diagnosticar problemas que no sean una falla.
Lectura obligatoria, si desea evaluar la aplicabilidad de mini dumps:
Además de las respuestas anteriores, aquí se explica cómo hacer que el sistema operativo Debian Linux genere un volcado de memoria.
En Linux/Unix/MacOSX, use los archivos principales (puede habilitarlos con ulimit o llamada al sistema compatible ). En Windows, utilice los informes de errores de Microsoft (puede convertirse en socio y obtener acceso a los datos de bloqueo de su aplicación).
Si todavía quieres hacerlo solo como lo hice, puedes vincularlo con bfd
y evitar usar addr2line
como lo he hecho aquí:
https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c
Esto produce la salida:
[E] crash.linux.c:170 | crit_err_hdlr | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
[E] crash.linux.c:171 | crit_err_hdlr | signal 11 (Segmentation fault), address is (nil)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
[E] crash.linux.c:194 | crit_err_hdlr | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
[E] crash.linux.c:199 | crit_err_hdlr | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]
Olvidé la tecnología de GNOME de "apport", pero no sé mucho sobre su uso. Se utiliza para generar stacktraces y otros diagnósticos para el procesamiento y puede archivar errores automáticamente. Sin duda vale la pena registrarse.
Si su programa falla, es el propio sistema operativo el que genera la información del volcado. Si está utilizando un sistema operativo * nix, simplemente no debe evitar que lo haga (consulte las opciones de 'coredump' del comando ulimit).
Parece que en una de las últimas bibliotecas de c ++ boost apareció la biblioteca para proporcionar exactamente lo que desea, probablemente el código sea multiplataforma. Es boost :: stacktrace , que puede usar como como en muestra de boost :
#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h> // ::signal, ::raise
#include <boost/stacktrace.hpp>
const char* backtraceFileName = "./backtraceFile.dump";
void signalHandler(int)
{
::signal(SIGSEGV, SIG_DFL);
::signal(SIGABRT, SIG_DFL);
boost::stacktrace::safe_dump_to(backtraceFileName);
::raise(SIGABRT);
}
void sendReport()
{
if (std::filesystem::exists(backtraceFileName))
{
std::ifstream file(backtraceFileName);
auto st = boost::stacktrace::stacktrace::from_dump(file);
std::ostringstream backtraceStream;
backtraceStream << st << std::endl;
// sending the code from st
file.close();
std::filesystem::remove(backtraceFileName);
}
}
int main()
{
::signal(SIGSEGV, signalHandler);
::signal(SIGABRT, signalHandler);
sendReport();
// ... rest of code
}
En Linux compila el código anterior:
g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace
Ejemplo de rastreo hacia atrás copiado de impulsar documentación :
0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start