Pregunta

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

Mi programa lo ejecutan 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 pudiera generar un seguimiento de la pila cuando falla y la próxima vez que el usuario lo ejecute, le preguntará si está bien enviarme el seguimiento de la pila para que pueda rastrear el problema.Puedo encargarme de enviarme la información, pero no sé cómo generar la cadena de seguimiento.¿Algunas ideas?

¿Fue útil?

Solución

Para Linux y creo que Mac OS X, si estás usando gcc, o cualquier compilador que use glibc, puedes usar las funciones backtrace() en execinfo.h para imprimir un seguimiento de pila y salir correctamente cuando se produzca un error de segmentación.Se puede encontrar documentación en el manual de la libc.

A continuación se muestra un programa de ejemplo que instala un SIGSEGV controlador e imprime un seguimiento de pila en stderr cuando se produce una segmentación.El baz() La función aquí provoca el error de segmentación 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.
}

Compilando con -g -rdynamic obtiene información de símbolos en su salida, que glibc puede usar para hacer un buen seguimiento de pila:

$ gcc -g -rdynamic ./test.c -o test

Al ejecutar esto, obtendrá este resultado:

$ ./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 donde proviene cada cuadro de la pila.Aquí puede ver el controlador de señales en la parte superior de la pila y las funciones libc antes. main además de main, foo, bar, y baz.

Otros consejos

linux

Si bien el uso de las funciones backtrace() en execinfo.h para imprimir un seguimiento de pila y salir correctamente cuando se produce un error de segmentación tiene ya ha sido sugerido, No veo ninguna mención de las complejidades necesarias para garantizar que el rastreo resultante apunte a la ubicación real de la falla (al menos para algunas arquitecturas: x86 y ARM).

Las dos primeras entradas en la cadena del marco de la pila cuando ingresa al controlador de señales contienen una dirección de retorno dentro del controlador de señales y una dentro de sigaction() en libc.Se pierde el marco de pila de la última función llamada antes de 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);
}

Producción

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 controlador de señales todavía existen y no deben pasarse por alto, pero la funcionalidad que describí aquí me parece bastante útil para depurar fallas.

Es importante tener en cuenta que el ejemplo que proporcioné está desarrollado/probado en Linux para x86.También implementé esto 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 soportar el programa catchsegv (ver "man catchsegv").

Esto nos da 3 posibilidades.En lugar de ejecutar "programa -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á rastreos más claros con menos optimización (gcc -O0 o -O1) y símbolos de depuración (gcc -g).De lo contrario, podría terminar con un montón de direcciones de memoria.

También puedes captar más señales para seguimientos de pila con algo como:

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

El resultado se verá así (observe el seguimiento 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:Ver 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

Aunque un respuesta correcta Se ha proporcionado una descripción de cómo utilizar la libc de GNU. backtrace() función1 y yo proporcioné mi propia respuesta que describe cómo garantizar que un rastreo desde un controlador de señales apunte a la ubicación real de la falla2, no veo ninguna mención de desmantelando Símbolos C++ generados desde el rastreo inverso.

Al obtener trazas de un programa C++, la salida se puede ejecutar a través de c++filt1 para exigir los símbolos o mediante el uso abi::__cxa_demangle1 directamente.

  • 1 Linux y OS XTenga en cuenta que c++filt y __cxa_demangle son específicos del CCG
  • 2 linux

El siguiente ejemplo de C++ Linux utiliza el mismo controlador de señal que mi otra respuesta y demuestra cómo c++filt se puede utilizar para exigir 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;
}

Producción (./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 demandada (./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 utilizar para exigir los símbolos.Este manejador de señales produce la misma salida solicitada 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);
}

Puede que valga la pena mirarlo Google Breakpad, un generador de volcados de emergencia multiplataforma y herramientas para procesar los volcados.

No especificaste tu sistema operativo, por lo que es difícil 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 ayudarle, pero que pueden o no implementarse completamente en su arquitectura, y esos 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 es distinto de 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 principal en Unix.De forma predeterminada, el límite de tamaño del archivo principal es 0.Puedes ver tu ulimit valores con ulimit -a.

Además, si ejecuta su programa desde gdb, lo detendrá debido a "violaciones de segmentación" (SIGSEGV, generalmente cuando accedió a una parte de la memoria que no había asignado) o puede establecer puntos de interrupción.

ddd y nemiver son interfaces para gdb que hacen que trabajar con él sea mucho más fácil para los principiantes.

Gracias a entusiastageek por llamar mi atención sobre 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 único argumento:El nombre del archivo que contiene el resultado de la utilidad jschmier.

El resultado debería imprimir algo como lo siguiente para cada nivel del seguimiento:

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 

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

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

Luego, puede configurar "ulimit -c unlimited" para permitirle volcar 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.

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 de libunwind

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

Me encantaría escuchar opiniones sobre esta biblioteca.

El problema con -rdynamic es que puede aumentar el tamaño del binario de manera relativamente significativa en algunos casos.

Algunas versiones de libc contienen funciones que se ocupan de los seguimientos de la pila;es posible que puedas utilizarlos:

http://www.gnu.org/software/libc/manual/html_node/Backtraces.html

Recuerdo haber usado libunviento hace mucho tiempo para obtener seguimientos de la pila, pero es posible que no sea compatible con su plataforma.

Olvídese de cambiar sus fuentes y haga algunos trucos con la función backtrace() o macros; estas son simplemente malas soluciones.

Como solución que funcione correctamente, recomendaría:

  1. Compile su programa con el indicador "-g" para incrustar símbolos de depuración en binario (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 falló, en el directorio de trabajo verá el archivo "core".
  4. Ejecute el siguiente comando para imprimir el seguimiento en la salida estándar:gdb -batch -ex "trace inverso" ./your_program_exe ./core

Esto imprimirá un seguimiento legible adecuado de su programa en forma legible por humanos (con nombres de archivos fuente y números de línea).Además, este enfoque le dará libertad para automatizar su sistema:tenga un script breve que verifique si el proceso creó un volcado de núcleo y luego envíe seguimientos por correo electrónico a los desarrolladores, o regístrelo en algún sistema de registro.

ulimit -c unlimited

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

saludos

Puedes usar Manejador de la muerte - pequeña clase de C++ que hace todo por usted, confiable.

Mira a:

hombre 3 retroceder

Y:

#include <exeinfo.h>
int backtrace(void **buffer, int size);

Estas son extensiones de GNU.

Vea 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 licencia de estilo BSD, por lo que incluso puede copiar y pegar el código si no desea utilizar ACE.

Puedo ayudar con la versión de Linux:Se pueden utilizar las funciones backtrace, backtrace_symbols y backtrace_symbols_fd.Consulte las páginas del manual correspondientes.

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

ganar:Controlar este desde msdn.

También puedes consultar el código de Chrome de Google para ver cómo maneja los fallos.Tiene un buen mecanismo de manejo de excepciones.

Descubrí que la solución @tgamblin no está completa.No puede manejarse con stackoverflow.Creo que porque el controlador de señal de forma predeterminada se llama con la misma pila y SigseGV se arroja dos veces.Para protegerlo, necesita registrar una pila independiente para el manejador de señales.

Puede verificar esto con el código a continuación.De forma predeterminada, el controlador falla.Con la macro STACK_OVERFLOW definida 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;
} 

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

He visto muchas respuestas aquí realizando un controlador de señales y luego saliendo.Ese es el camino a seguir, pero recuerda un hecho muy importante:Si desea obtener el volcado del núcleo del error generado, no puede llamar exit(status).Llamar abort() ¡en cambio!

El nuevo rey de la ciudad ha llegadohttps://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);
}

Además de las respuestas anteriores, aquí le mostramos cómo hacer que el sistema operativo Debian Linux genere un volcado de 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, por lo que la raíz debe especificarse en su propia línea.
  4. Para verificar estos valores, cierre sesión, vuelva a iniciarla y escriba "ulimit -a".El “tamaño del archivo principal” debe configurarse como 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á al 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 el nuevo cheque de configuración/proc/sys/kernel/core_pattern y verifique que esto coincida con lo que acaba de escribir.
  8. El volcado de 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 se realiza correctamente, verá "(núcleo volcado)" después de la indicación de error de segmentación.

Como solución exclusiva de Windows, puede obtener el equivalente a un seguimiento de pila (con mucha, mucha más información) usando Informe de errores de Windows.Con sólo 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 de forma predeterminada.Habilitar la función requiere privilegios de administrador.Para habilitar y configurar la función, utilice los siguientes valores de registro en el HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps llave.

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 utilizar WER como se describe anteriormente o llamar MiniDumpWriteDump usted mismo, si necesita un control más detallado sobre la cantidad de información que desea volcar.(Asegúrese de llamarlo desde un proceso diferente).
  • Forma más completo que un seguimiento de pila.Entre otros, puede contener variables locales, argumentos de funciones, 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 reduce drásticamente el tamaño de su implementación y dificulta la aplicación de ingeniería inversa a su aplicación.
  • En gran medida independiente del compilador que utilice.Usar WER ni siquiera requiere ningún código.De cualquier manera, tener una manera de obtener una base de datos de símbolos (PDB) es muy útil para 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 el WER solo puede activarse si una aplicación falla (es decir,el sistema finaliza un proceso debido a una excepción no controlada). MiniDumpWriteDump Se puede llamar en cualquier momento.Esto puede resultar útil si necesita deshacerse del estado actual para diagnosticar problemas distintos de un bloqueo.

Lectura obligatoria, si se quiere evaluar la aplicabilidad de los minivolcados:

En Linux/unix/MacOSX use archivos principales (puede habilitarlos con ulimit o llamada al sistema compatible).En Windows, utilice el informe de errores de Microsoft (puede convertirse en socio y obtener acceso a los datos de fallos de su aplicación).

Me olvidé de la tecnología "apport" de GNOME, pero no sé mucho sobre su uso.Se utiliza para generar seguimientos de pila y otros diagnósticos para su procesamiento y puede presentar errores automáticamente.Sin duda vale la pena visitarlo.

Parece que en una de las últimas versiones de C++ Boost apareció una biblioteca para proporcionar exactamente lo que desea, probablemente el código sería multiplataforma.Es impulso::rastreo de pila, que puedes usar como como en la muestra de refuerzo:

#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 compilas el código anterior:

g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

Ejemplo de rastreo copiado de impulsar la 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

Si aún quieres hacerlo solo como lo hice yo, puedes vincularlo. bfd y evitar el uso 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]
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top