Cómo generar automáticamente un stacktrace cuando mi programa falla


590

Estoy trabajando en Linux con el compilador GCC. Cuando mi programa C ++ falla, me gustaría que genere automáticamente un stacktrace.

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 la pila cuando falla y la próxima vez que el usuario lo ejecute, les preguntaré si está bien enviarme el seguimiento de la 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?


44
backtrace y backtrace_symbols_fd no son async-signal-safe. no debe usar estas funciones en el controlador de señal
Parag Bafna

10
backtrace_symbols llama a malloc, por lo que no debe usarse en un controlador de señal. Las otras dos funciones (backtrace y backtrace_symbols_fd) no tienen este problema y se usan comúnmente en manejadores de señales.
cmccabe

3
@cmccabe que es incorrecto backtrace_symbols_fd generalmente no llama a malloc, pero puede hacerlo si algo sale mal en su bloque catch_error
Sam Saffron

66
"Puede" en el sentido de que no hay una especificación POSIX para backtrace_symbols_fd (o cualquier retroceso); sin embargo, se especifica backtrace_symbols_fd de GNU / Linux para nunca llamar a malloc, según linux.die.net/man/3/backtrace_symbols_fd . Por lo tanto, es seguro asumir que nunca llamará a malloc en Linux.
codetaku

Respuestas:


509

Para Linux y creo que Mac OS X, si está usando gcc, o cualquier compilador que use glibc, puede usar las funciones backtrace () execinfo.hpara imprimir un stacktrace y salir con gracia cuando se produce un error de segmentación. La documentación se puede encontrar en el manual de libc .

Aquí hay un programa de ejemplo que instala un SIGSEGVcontrolador e imprime un seguimiento de la pila stderrcuando se configura por defecto. La baz()función aquí provoca el segfault que activa el controlador:

#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.
}

Compilar con -g -rdynamicobtiene 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

Ejecutar esto te da 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 provino cada cuadro en la pila. Aquí se puede ver el manejador de la señal en la parte superior de la pila, y las funciones de libc antes main, además de main, foo, bar, y baz.


53
También hay /lib/libSegFault.so que puedes usar con LD_PRELOAD.
CesarB

66
Parece que las dos primeras entradas en su salida de traza inversa contienen una dirección de retorno dentro del controlador de señal y probablemente una dentro sigaction()de libc. Si bien su traza inversa parece ser correcta, a veces he descubierto que son necesarios pasos adicionales para garantizar que la ubicación real de la falla aparezca en la traza trasera, ya que sigaction()el núcleo puede sobrescribirla .
jschmier

99
¿Qué pasaría si el choque viene desde adentro de Malloc? ¿No sostendrías un candado y luego te quedarías atascado mientras "traza inversa" intenta asignar memoria?
Mattias Nilsson

77
catchsegvno es lo que necesita el OP, pero es increíble para detectar fallas de segmentación y obtener toda la información.
Matt Clarkson el

8
Para ARM, tuve que compilar también con -funwind-tables. De lo contrario, la profundidad de mi pila siempre fue 1 (vacía).
jfritz42

128

Es incluso más fácil que "traza inversa", hay una biblioteca poco documentada (específica de GNU) distribuida con glibc como libSegFault.so, que creo fue escrita por Ulrich Drepper para soportar el programa catchsegv (ver "man catchsegv").

Esto nos da 3 posibilidades. En lugar de ejecutar "program -o hai":

  1. Ejecutar dentro de catchsegv:

    $ catchsegv program -o hai
  2. Enlace con libSegFault en tiempo de ejecución:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Enlace con libSegFault en tiempo de compilación:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai

En los 3 casos, obtendrá retrocesos más claros con menos optimización (gcc -O0 u -O1) y símbolos de depuración (gcc -g). De lo contrario, puede terminar con un montón de direcciones de memoria.

También puede capturar más señales para los seguimientos de pila con algo como:

$ export SEGFAULT_SIGNALS="all"       # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT

La salida se verá más o menos así (observe la traza 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 padre http://sourceware.org/git/?p=glibc.git;a=tree;f=debug


1
"Posibilidad 3. Enlace con libSegFault en tiempo de compilación" no funciona.
HHK

55
@crafter: ¿Qué quieres decir con "no funciona"? ¿Qué has probado, en qué idioma / compilador / cadena de herramientas / distribución / hardware? ¿No pudo compilar? Para atrapar el error? Para producir la producción en absoluto? ¿Para producir resultados difíciles de usar? Gracias por los detalles, ayudará a todos.
Stéphane Gourichon

1
'desafortunadamente, la mejor fuente es la fuente' ... Con suerte, algún día, la página de manual de catchsegv mencionará SEGFAULT_SIGNALS. Hasta entonces, hay esta respuesta para referirse.
greggo

No puedo creer que he estado programando C durante 5 años y nunca he oído hablar de esto: /
DavidMFrey

66
@ StéphaneGourichon @HansKratz Para vincular con libSegFault, tendrá que agregar -Wl,--no-as-neededa los indicadores del compilador. De lo contrario, ldserá de hecho no se enlacen contra libSegFault, porque reconoce que el binario no utiliza ninguno de sus símbolos.
Phillip

122

Linux

Aunque ya se ha sugerido el uso de las funciones backtrace () en execinfo.h para imprimir un stacktrace y salir con gracia cuando se produce un error de segmentación , no veo ninguna mención de las complejidades necesarias para garantizar que los puntos de retroceso resultantes 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 pila cuando ingresa al controlador de señal contienen una dirección de retorno dentro del controlador de señal y una dentro de sigaction () en libc. El marco de la pila de la última función llamada antes de que se pierda la señal (que es la ubicación de la falla).

Código

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

Salida

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 riesgos de llamar a las funciones backtrace () en un controlador de señal todavía existen y no deben pasarse por alto, pero encuentro que la funcionalidad que describí aquí es bastante útil para la depuración de fallas.

Es importante tener en cuenta que el ejemplo que proporcioné está desarrollado / probado en Linux para x86. También he implementado con éxito esto en ARM usando en uc_mcontext.arm_pclugar 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


11
En los sistemas que usan GNU ld, recuerde compilar -rdynamicpara indicar al vinculador que agregue todos los símbolos, no solo los usados, a la tabla de símbolos dinámicos. Esto permite backtrace_symbols()convertir direcciones a nombres de funciones
jschmier

1
Además, debe agregar la opción "-mapcs-frame" a la línea de comando de GCC para generar marcos de pila en la plataforma ARM
qehgt

3
Esto puede ser demasiado tarde, pero ¿podemos usar el addr2linecomando de alguna manera para obtener la línea exacta donde ocurrió el bloqueo?
enthusiasticgeek

44
En versiones más recientes de glibc uc_mcontextno contiene un campo llamado eip. Ahora hay una matriz que necesita ser indexada, uc_mcontext.gregs[REG_EIP]es el equivalente.
mmlb

66
Para ARM, mis trazas inversas siempre tuvieron profundidad 1 hasta que agregué la opción -funwind-tables al compilador.
jfritz42

84

A pesar de que se ha proporcionado una respuesta correcta que describe cómo usar la backtrace()función 1 de GNU libc y proporcioné mi propia respuesta que describe cómo garantizar que una traza inversa de un controlador de señal apunte a la ubicación real de la falla 2 , no veo Cualquier mención de la demanda de salida de símbolos C ++ de la traza inversa .

Al obtener rastreos de un programa C ++, la salida se puede ejecutar a través de c++filt1 para solicitar los símbolos o usando 1 directamente.abi::__cxa_demangle

  • 1 Linux y OS X Tenga en cuenta que c++filty __cxa_demangleson específicos de GCC
  • 2 Linux

El siguiente ejemplo de C ++ Linux usa el mismo controlador de señal que mi otra respuesta y demuestra cómo c++filtse puede usar para solicitar 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 Desmangled ( ./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ñal de mi respuesta original y puede reemplazar el controlador de señal en el ejemplo anterior para demostrar cómo abi::__cxa_demanglese puede utilizar para solicitar los símbolos. Este controlador de señal produce la misma salida demandada 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);
}

1
Gracias por esto, jschmier. Creé un pequeño script bash para alimentar el resultado de esto en la utilidad addr2line. Ver: stackoverflow.com/a/15801966/1797414
arr_sea

44
No olvides #incluir <cxxabi.h>
Bamaco

1
Buena documentación, y un archivo de encabezado directo se ha publicado aquí desde 2008 ... panthema.net/2008/0901-stacktrace-demangled muy similar a su enfoque :)
kevinf

abi :: __ cxa_demangle parece no ser async-signal-safe, por lo que el manejador de señales puede llegar a un punto muerto en algún lugar de malloc.
orcy

El uso de std::cerr, free()y exit()todos violan las restricciones contra la llamada de llamadas no asíncronas seguras en sistemas POSIX. Este código será un punto muerto si el proceso falla en cualquier llamada como free(), malloc() newo detete.
Andrew Henle

31

Podría valer la pena mirar Google Breakpad , un generador de volcado de plataforma multiplataforma y herramientas para procesar los volcados.


Informa sobre cosas como fallas de segmentación, pero no informa ninguna información sobre excepciones de C ++ no manejadas.
DBedrenko

21

No especificó su sistema operativo, por lo que es difícil de responder. Si está utilizando un sistema basado en gnu libc, es posible que pueda usar 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 esos son __builtin_frame_addressy __builtin_return_address. Ambos quieren un nivel entero inmediato (por inmediato, quiero decir que no puede ser una variable). Si __builtin_frame_addresspara un nivel dado no es cero, debería ser seguro tomar la dirección de retorno del mismo nivel.


13

Gracias a enthusiasticgeek por llamar mi atención sobre la utilidad addr2line.

He escrito un script rápido y sucio para procesar la salida de la respuesta provista aquí : (¡muchas gracias a jschmier!) Usando la utilidad addr2line.

El script acepta un único argumento: el nombre del archivo que contiene la salida de la utilidad jschmier.

La salida debería 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 

12

ulimit -c <value>establece el límite de tamaño del archivo central en Unix. De forma predeterminada, el límite de tamaño de archivo principal es 0. Puede ver sus ulimitvalores con ulimit -a.

Además, si ejecuta su programa desde gdb, detendrá su programa en "violaciones de segmentación" ( SIGSEGVgeneralmente cuando accedió a un fragmento de memoria que no había asignado) o puede establecer puntos de interrupción.

ddd y nemiver son front-end para gdb que hacen que trabajar con él sea mucho más fácil para el principiante.


66
Los volcados de núcleo son infinitamente más útiles que los seguimientos de pila porque puede cargar el volcado de núcleo en el depurador y ver el estado de todo el programa y sus datos en el punto del bloqueo.
Adam Hawes

1
La facilidad de rastreo que otros han sugerido es probablemente mejor que nada, pero es muy básica: ni siquiera da números de línea. Por otro lado, el uso de volcados de núcleo le permite ver retroactivamente todo el estado de su aplicación en el momento en que se bloqueó (incluyendo un seguimiento detallado de la pila). No podría haber problemas prácticos con el intento de utilizar este campo para la depuración, pero es sin duda una herramienta más potente para el análisis de accidentes y afirma durante el desarrollo (al menos en Linux).
nobar

10

Es importante tener en cuenta que una vez que genere un archivo central, deberá usar la herramienta gdb para verlo. Para que gdb tenga sentido de su archivo principal, debe decirle a gcc que instrumente el binario con símbolos de depuración: para hacer esto, compila con el indicador -g:

$ g++ -g prog.cpp -o prog

Luego, puede configurar "ulimit -c unlimited" para que descargue un núcleo, o simplemente ejecutar su 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.


44
También puede llamar gdbdirectamente desde su programa de bloqueo. Controlador de configuración para SIGSEGV, SEGILL, SIGBUS, SIGFPE que llamará a gdb. Detalles: stackoverflow.com/questions/3151779/… La ventaja es que bt fullobtienes un seguimiento hermoso y anotado como en , también puedes obtener rastros de la pila de todos los hilos.
Vi.

También puede obtener una traza inversa más fácil que en la respuesta: gdb -silent ./prog core --eval-command = backtrace --batch -it mostrará la traza inversa y cerrará el depurador
baziorek

10

He estado mirando este problema por un tiempo.

Y enterrado profundamente en el archivo README de Google Performance Tools

http://code.google.com/p/google-perftools/source/browse/trunk/README

habla sobre libunwind

http://www.nongnu.org/libunwind/

Me encantaría conocer 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


2
En x86 / 64, no he visto mucho el aumento dinámico del tamaño binario. Agregar -g hace un aumento mucho mayor.
Dan

1
Noté que libunwind no tiene funcionalidad para obtener el número de línea, y supongo que (no lo probé) unw_get_proc_name devuelve el símbolo de función (que está ofuscado por sobrecarga y tal) en lugar del nombre original.
Herbert

1
Eso es correcto. Hacer esto es muy complicado, pero he tenido un éxito excelente con gaddr2line. Aquí hay mucha información práctica blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

Puede usar DeathHandler , una pequeña clase de C ++ que hace todo por usted, confiable.


1
desafortunadamente, se usa execlp()para realizar llamadas a addr2line ... sería bueno permanecer completamente en el propio programa (que es posible al incluir el código addr2line de alguna forma)
ejemplo

9

Olvídate de cambiar tus fuentes y haz algunos hacks con la función backtrace () o macroses; estas son solo soluciones deficientes.

Como una solución que funciona correctamente, recomendaría:

  1. Compile su programa con el indicador "-g" para incrustar símbolos de depuración en binarios (no se preocupe, esto no afectará su rendimiento).
  2. En Linux, ejecute el siguiente comando: "ulimit -c unlimited" - para permitir que el sistema realice grandes volcados de memoria.
  3. Cuando su programa se bloqueó, en el directorio de trabajo verá el archivo "core".
  4. Ejecute el siguiente comando para imprimir el rastreo hacia atrás en stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Esto imprimirá la trazabilidad legible adecuada de su programa en forma legible por humanos (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 verifique si el proceso creó un volcado de núcleo, y luego envíe retrocesos por correo electrónico a los desarrolladores, o inicie sesión en algún sistema de registro.


Da los números de línea equivocados. ¿Se puede mejorar?
HeyJude

7
ulimit -c unlimited

es una variable del sistema, que permitirá crear un volcado de núcleo después de que su aplicación se bloquee. En este caso una cantidad ilimitada. Busque un archivo llamado core en el mismo directorio. ¡Asegúrese de compilar su código con la información de depuración habilitada!

Saludos


55
El usuario no solicita un volcado del núcleo. Él está pidiendo un rastro de pila. Ver delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin

1
un volcado de núcleo contendrá la pila de llamadas en el momento del bloqueo, ¿no?
Mo.

3
Estás asumiendo que está en Unix y usando Bash.
Paul Tomblin

2
Si está utilizando tcsh, debe hacerlolimit coredumpsize unlimited
sivabudh


6

Vea la instalación de Stack Trace en ACE (ADAPTIVE Communication Environment). Ya está escrito para cubrir todas las plataformas principales (y más). La biblioteca tiene licencia de estilo BSD, por lo que incluso puede copiar / pegar el código si no desea usar ACE.


El enlace parece estar muerto.
tglas

5

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 del manual correspondientes.


5

Parece que en una de las últimas versiones de c ++ boost apareció la biblioteca para proporcionar exactamente lo que desea, probablemente el código sería multiplataforma. Es boost :: stacktrace , que puede usar como en la muestra de impulso :

#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 traza inversa copiada de la documentación de impulso :

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

4

* nix: puedes interceptar SIGSEGV (normalmente esta señal se genera antes de fallar) y mantener la información en un archivo. (además del archivo central que puede usar para depurar usando gdb, por ejemplo).

win: Comprueba esto desde msdn.

También puede mirar el código de Google Chrome para ver cómo maneja los bloqueos. Tiene un buen mecanismo de manejo de excepciones.


SEH no ayuda a producir un seguimiento de pila. Si bien podría ser parte de una solución, esa solución es más difícil de implementar y proporciona menos información a expensas de revelar más información sobre su aplicación que la solución real : escriba un mini volcado. Y configure Windows para hacer esto automáticamente por usted.
Inspeccionable

4

Encontré que la solución @tgamblin no está completa. No se puede manejar con stackoverflow. Creo que, por defecto, el controlador de señal se llama con la misma pila y SIGSEGV se lanza dos veces. Para protegerlo, necesita registrar una pila independiente para el manejador de señal.

Puede verificar esto con el código a continuación. Por defecto, el controlador 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;
} 

4

El nuevo rey en la ciudad ha llegado https://github.com/bombela/backward-cpp

1 encabezado para colocar en su 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);
}

¡Guauu! ¡Así es como debe hacerse! Acabo de abandonar mi propia solución a favor de esta.
tglas

3

Usaría el código que genera un seguimiento de pila para la memoria perdida en el Detector de fugas visuales . Sin embargo, esto solo funciona en Win32.


Y requiere que envíe símbolos de depuración con su código. En general no es deseable. Escriba un mini volcado y configure Windows para que lo haga automáticamente en excepciones no controladas.
Inspeccionable

3

He visto muchas respuestas aquí realizando un controlador de señal y luego saliendo. Ese es el camino a seguir, pero recuerde un hecho muy importante: si desea obtener el volcado del núcleo para el error generado, no puede llamar exit(status). Llame en su abort()lugar!


3

Como solución exclusiva de Windows, puede obtener el equivalente de un seguimiento de la pila (con mucha, mucha más información) mediante el Informe de errores de Windows . Con solo unas pocas entradas de registro, se puede configurar para recopilar volcados en modo de 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 completos en modo de usuario se recopilen y almacenen localmente después de que una aplicación en modo de usuario falle. [...]

Esta característica no está habilitada por defecto. Habilitar la función requiere privilegios de administrador. Para habilitar y configurar la función, use los siguientes valores de registro en la clave HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumps .

Puede configurar las entradas del registro desde su instalador, que tiene los privilegios necesarios.

La creación de un volcado en modo de usuario tiene las siguientes ventajas sobre la generación de un seguimiento de pila en el cliente:

  • Ya está implementado en el sistema. Puede usar WER como se describe anteriormente, o llamar a MiniDumpWriteDump usted mismo, si necesita un control más preciso sobre la cantidad de información a volcar. (Asegúrese de llamarlo desde un proceso diferente).
  • Mucho más completo que un rastro de pila. Entre otros, puede contener variables locales, argumentos de función, pilas para otros subprocesos, módulos cargados, etc. La cantidad de datos (y, en consecuencia, el tamaño) es altamente personalizable.
  • No es necesario enviar símbolos de depuración. Esto disminuye drásticamente el tamaño de su implementación y hace que sea más difícil realizar ingeniería inversa en su aplicación.
  • En gran medida independiente del compilador que utiliza. Usar WER ni siquiera requiere ningún código. De cualquier manera, tener una forma de obtener una base de datos de símbolos (PDB) es muy útil para el análisis fuera de línea. Creo que GCC puede generar PDB o existen herramientas para convertir la base de datos de símbolos al formato PDB.

Tenga en cuenta que WER solo puede activarse por un bloqueo de la aplicación (es decir, el sistema finaliza un proceso debido a una excepción no controlada). MiniDumpWriteDumpSe puede llamar en cualquier momento. Esto puede ser útil si necesita volcar el estado actual para diagnosticar problemas distintos de un bloqueo.

Lectura obligatoria, si desea evaluar la aplicabilidad de los mini volcados:


2

Además de las respuestas anteriores, aquí le mostramos cómo hacer que el sistema operativo Linux de Debian genere un volcado del núcleo

  1. Cree una carpeta "coredumps" en la carpeta de inicio del usuario
  2. Vaya a /etc/security/limits.conf. Debajo de la línea '', escriba “soft core unlimited” y “root soft core unlimited” si habilita los volcados de núcleo para root, para permitir espacio ilimitado para los volcados de núcleo.
  3. NOTA: "* soft core unlimited" no cubre la raíz, razón por la cual la raíz debe especificarse en su propia línea.
  4. Para verificar estos valores, cierre sesión, vuelva a iniciar sesión y escriba "ulimit -a". El "tamaño del archivo principal" debe establecerse en ilimitado.
  5. Verifique los archivos .bashrc (usuario y root si corresponde) para asegurarse de que ulimit no esté configurado allí. De lo contrario, el valor anterior se sobrescribirá en el inicio.
  6. Abra /etc/sysctl.conf. Ingrese lo siguiente en la parte inferior: "kernel.core_pattern = /home//coredumps/%e_%t.dump". (% e será el nombre del proceso y% t será la hora del sistema)
  7. Salga y escriba "sysctl -p" para cargar la nueva configuración. Compruebe / proc / sys / kernel / core_pattern y verifique que esto coincida con lo que acaba de escribir.
  8. El volcado del núcleo se puede probar ejecutando un proceso en la línea de comando ("&") y luego eliminándolo con "kill -11". Si el volcado del núcleo es exitoso, verá "(volcado del núcleo)" después de la indicación de falla de segmentación.

2

Si todavía quiere hacerlo solo como lo hice, puede vincular bfdy evitar usar addr2linecomo 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]

1

En Linux / unix / MacOSX, use archivos principales (puede habilitarlos con ulimit o una llamada de 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).


0

Olvidé la tecnología de GNOME de "apport", pero no sé mucho sobre cómo usarla. Se utiliza para generar seguimientos de pila y otros diagnósticos para el procesamiento y puede archivar errores automáticamente. Ciertamente vale la pena registrarse.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.