it-swarm.it

Come generare automaticamente uno stacktrace quando il mio programma si arresta in modo anomalo

Sto lavorando su Linux con il compilatore GCC. Quando il mio programma C++ si blocca mi piacerebbe che generasse automaticamente uno stacktrace.

Il mio programma viene eseguito da molti utenti diversi e funziona anche su Linux, Windows e Macintosh (tutte le versioni sono compilate usando gcc).

Vorrei che il mio programma fosse in grado di generare una traccia dello stack quando si blocca e la prossima volta che l'utente lo esegue, chiederà loro se è giusto inviare la traccia dello stack per poter rintracciare il problema. Posso gestire l'invio delle informazioni a me ma non so come generare la stringa di traccia. Qualche idea?

518
KPexEA

Per Linux e credo che Mac OS X, se si sta utilizzando gcc, o qualsiasi compilatore che usa glibc, è possibile utilizzare le funzioni backtrace () in execinfo.h per stampare uno stacktrace ed uscire con garbo quando si verifica un errore di segmentazione. La documentazione può essere trovata nel manuale di libc .

Ecco un programma di esempio che installa un gestore SIGSEGV e stampa uno stacktrace su stderr quando segfaults. La funzione baz() qui provoca il segfault che attiva il gestore:

#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 ottieni informazioni sui simboli nel tuo output, che glibc può usare per creare uno stacktrace di Nizza:

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

L'esecuzione di questo ti dà questo risultato:

$ ./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]

Questo mostra il modulo di carico, l'offset e la funzione da cui proviene ciascun frame nella pila. Qui puoi vedere il gestore del segnale in cima allo stack e le funzioni libc prima di main oltre a main, foo, bar e baz.

447
Todd Gamblin

Linux

Mentre l'uso delle funzioni backtrace () in execinfo.h per stampare uno stacktrace ed uscire con garbo quando si verifica un errore di segmentazione ha è già stato suggerito , non vedo alcuna menzione delle complessità necessarie per garantire che i punti di backtrace risultanti vadano a la posizione effettiva dell'errore (almeno per alcune architetture - x86 e ARM).

Le prime due voci nella catena del frame dello stack quando si entra nel gestore del segnale contengono un indirizzo di ritorno all'interno del gestore di segnale e uno all'interno di sigaction () in libc. Il frame di stack dell'ultima funzione chiamata prima del segnale (che è la posizione dell'errore) viene perso.

Codice

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

Produzione

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]

Tutti i rischi di chiamare le funzioni backtrace () in un gestore di segnale esistono ancora e non dovrebbero essere trascurati, ma trovo la funzionalità che ho descritto qui abbastanza utile nel debug degli arresti anomali.

È importante notare che l'esempio che ho fornito è sviluppato/testato su Linux per x86. Ho anche implementato questo successo su ARM usando uc_mcontext.arm_pc invece di uc_mcontext.eip

Ecco un link all'articolo in cui ho appreso i dettagli di questa implementazione: http://www.linuxjournal.com/article/6391

115
jschmier

È ancora più semplice di "man backtrace", c'è una libreria poco documentata (specifica per GNU) distribuita con glibc come libSegFault.so, che credo sia stato scritto da Ulrich Drepper per supportare il programma catchsegv (vedi "man catchsegv").

Questo ci dà 3 possibilità. Invece di eseguire "program -o hai":

  1. Esegui all'interno di catchsegv:

    $ catchsegv program -o hai
    
  2. Collegamento con libSegFault in fase di runtime:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Collegamento con libSegFault in fase di compilazione:

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

In tutti e 3 i casi, otterrete backtrace più chiari con meno ottimizzazione (gcc -O0 o -O1) e simboli di debug (gcc -g). Altrimenti potresti finire con una pila di indirizzi di memoria.

Puoi anche catturare più segnali per le tracce dello stack con qualcosa di simile:

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

L'output sarà simile a questo (notare il backtrace in basso):

*** 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]

Se vuoi conoscere i dettagli cruenti, la fonte migliore è sfortunatamente la fonte: vedi http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c e la sua directory principale http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

112
jhclark

Anche se è stata fornita una risposta corretta che descrive come utilizzare la funzione GNU libc backtrace()1 e ho fornito la mia risposta che descrive come garantire che un backtrace di un gestore di segnali punti alla posizione effettiva del guasto2, Non vedo alcuna menzione dei simboli demangling C++ emessi dal backtrace.

Quando si ottengono backtrace da un programma C++, l'output può essere eseguito attraverso c++filt1 per demarcare i simboli o usando abi::__cxa_demangle1 direttamente.

  • 1 Linux e OS X Nota che c++filt e __cxa_demangle sono specifici per GCC
  • 2 Linux

Il seguente esempio C++ Linux usa lo stesso gestore di segnale del mio other answer e dimostra come c++filt può essere usato per demangolare i simboli.

Codice:

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

Output (./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]

Demangled Output (./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]

Quanto segue costruisce sul gestore di segnali dalla mia risposta originale e può sostituire il gestore di segnali nell'esempio precedente per dimostrare come abi::__cxa_demangle può essere usato per demangolare i simboli. Questo gestore di segnale produce la stessa uscita delimitata dell'esempio precedente.

Codice:

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);
}
76
jschmier

Potrebbe essere utile guardare a Google Breakpad , un generatore di dump degli arresti anomali multipiattaforma e strumenti per elaborare i dump.

33
Simon Steele

Non hai specificato il tuo sistema operativo, quindi è difficile rispondere. Se stai usando un sistema basato su gnu libc, potresti essere in grado di usare la funzione backtrace() di libc.

GCC ha anche due builtin che possono aiutarti, ma che possono o non possono essere implementati completamente sulla tua architettura, e quelli sono __builtin_frame_address e __builtin_return_address. Entrambi vogliono un livello intero immediato (per immediato, intendo che non può essere una variabile). Se __builtin_frame_address per un dato livello è diverso da zero, dovrebbe essere sicuro afferrare l'indirizzo di ritorno dello stesso livello.

21
Brian Mitchell

ulimit -c <value> imposta il limite della dimensione del file di base su unix. Per impostazione predefinita, il limite della dimensione del file principale è 0. È possibile visualizzare i valori ulimit con ulimit -a.

inoltre, se esegui il tuo programma da gdb, fermerà il tuo programma su "violazioni di segmentazione" (SIGSEGV, generalmente quando accedi a un pezzo di memoria che non hai allocato) o puoi impostare i breakpoint.

ddd e nemiver sono front-end per gdb che rendono molto più semplice il lavoro con i principianti.

12
Joseph

Ho osservato questo problema per un po '.

E sepolto in profondità nel README di Google Performance Tools

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

parla di libunwind

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

Mi piacerebbe sentire le opinioni di questa biblioteca.

Il problema con -rdynamic è che può aumentare la dimensione del file binario in modo relativamente significativo in alcuni casi

10
Gregory

Grazie a entusiasta per aver attirato la mia attenzione sull'utilità addr2line.

Ho scritto uno script veloce e sporco per elaborare l'output della risposta fornita qui : (Grazie mille a jschmier!) Usando l'utilità addr2line.

Lo script accetta un singolo argomento: il nome del file che contiene l'output dall'utilità di jschmier.

L'output dovrebbe stampare qualcosa come il seguente per ogni livello della traccia:

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;

Codice:

#!/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 
10
arr_sea

Alcune versioni di libc contengono funzioni che gestiscono le tracce dello stack; potresti essere in grado di usarli:

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

Ricordo di aver usato libunwind molto tempo fa per ottenere tracce dello stack, ma potrebbe non essere supportato sulla tua piattaforma.

10
Stephen Deken

È importante notare che una volta generato un file core è necessario utilizzare lo strumento gdb per guardarlo. Per gdb per dare un senso al tuo file core, devi dire a gcc di utilizzare lo strumento binario con i simboli di debug: per farlo, compila con il flag -g:

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

Quindi, è possibile impostare "ulimit -c unlimited" per consentire di eseguire il dump di un core, o semplicemente eseguire il programma in gdb. Mi piace il secondo approccio in più: 

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

Spero che questo possa essere d'aiuto. 

10
Benson
ulimit -c unlimited

è una variabile di sistema, che permetterà di creare un core dump dopo l'arresto anomalo dell'applicazione. In questo caso un importo illimitato. Cerca un file chiamato core nella stessa directory. Assicurati di aver compilato il tuo codice con le informazioni di debug abilitate!

saluti

9
mana

Dimenticati di cambiare le tue fonti e fare alcuni hack con la funzione backtrace () o macrose - queste sono solo soluzioni scadenti.

Come soluzione perfettamente funzionante, consiglierei:

  1. Compila il tuo programma con il flag "-g" per incorporare i simboli di debug in binario (non preoccuparti, questo non influirà sulle tue prestazioni). 
  2. Su linux esegui il comando successivo: "ulimit -c unlimited" - per consentire al sistema di eseguire grandi crash dump.
  3. Quando il programma si è bloccato, nella directory di lavoro verrà visualizzato il file "core".
  4. Esegui il comando successivo per stampare il backtrace su stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Questo stamperà il backtrace leggibile corretto del tuo programma in modo leggibile dall'uomo (con nomi di file e numeri di riga sorgente) .__ Inoltre questo approccio ti darà la libertà di automatizzare il tuo sistema: Avere un breve script che controlla se il processo è stato creato un core dump, quindi inviare i backtrace via e-mail agli sviluppatori o registrarli in qualche sistema di registrazione.

9
loopzilla

Puoi usare DeathHandler - piccola classe C++ che fa tutto per te, affidabile.

8
markhor
8
Roskoto

Guarda a:

uomo 3 backtrace

E:

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

Queste sono estensioni GNU.

7
Stéphane

Vedere la funzione Stack Trace in ACE (ADAPTIVE Communication Environment). È già stato scritto per coprire tutte le principali piattaforme (e non solo). La libreria è dotata di licenza BSD, quindi è possibile copiare/incollare il codice anche se non si desidera utilizzare ACE.

6
Adam Mitz

Posso aiutare con la versione di Linux: la funzione backtrace, backtrace_symbols e backtrace_symbols_fd possono essere usati. Vedi le pagine di manuale corrispondenti.

5
terminus

* nix: puoi intercettare SIGSEGV (normalmente questo segnale viene generato prima di bloccarsi) e mantenere le informazioni in un file. (oltre al file principale che puoi usare per eseguire il debug usando gdb per esempio).

vinci: Controlla questo da msdn.

Puoi anche guardare il codice Chrome di Google per vedere come gestisce gli arresti anomali. Ha un bel meccanismo di gestione delle eccezioni.

4
INS

Ho trovato che la soluzione @tgamblin non è completa . Non può gestire con stackoverflow . Penso che, per impostazione predefinita, il gestore di segnale sia chiamato con lo stesso stack e SIGSEGV sia lanciato due volte. Per proteggere è necessario registrare uno stack indipendente per il gestore del segnale.

Puoi controllare questo con il codice qui sotto. Di default il gestore fallisce. Con la macro definita STACK_OVERFLOW va tutto bene.

#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

Ho visto molte risposte qui eseguire un gestore di segnale e poi uscire . Questa è la strada da percorrere, ma ricorda un fatto molto importante: se vuoi ottenere il core dump per l'errore generato, non puoi chiamare exit(status). Chiama invece abort()!

3
jard18

Il nuovo re in città è arrivato https://github.com/bombela/backward-cpp

1 intestazione da inserire nel codice e 1 libreria da installare.

Personalmente lo chiamo utilizzando questa funzione

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

Vorrei usare il codice che genera una traccia dello stack per la perdita di memoria in Visual Leak Detector . Questo funziona solo su Win32, comunque.

3
Jim Buck

Come soluzione solo per Windows, è possibile ottenere l'equivalente di una traccia di stack (con molte, molte più informazioni) usando Segnalazione errori di Windows . Con solo poche voci di registro, può essere impostato per raccogliere dump in modalità utente :

A partire da Windows Server 2008 e Windows Vista con Service Pack 1 (SP1), è possibile configurare Segnalazione errori Windows (WER) in modo che i dump completi in modalità utente vengano raccolti e archiviati localmente dopo l'arresto anomalo dell'applicazione in modalità utente. [...]

Questa funzione non è abilitata per impostazione predefinita. L'abilitazione della funzione richiede i privilegi di amministratore. Per abilitare e configurare la funzione, utilizzare i seguenti valori di registro sotto la chiave HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps.

È possibile impostare le voci del registro dal proprio programma di installazione, che dispone dei privilegi richiesti.

La creazione di un dump in modalità utente presenta i seguenti vantaggi rispetto alla generazione di una traccia di stack sul client:

  • È già implementato nel sistema. Puoi utilizzare il WER come descritto sopra o chiamare MiniDumpWriteDump yourself, se hai bisogno di un controllo più dettagliato sulla quantità di informazioni da scaricare. (Assicurati di chiamarlo da un processo diverso.)
  • Way più completo di una traccia dello stack. Tra l'altro può contenere variabili locali, argomenti di funzioni, stack per altri thread, moduli caricati e così via. La quantità di dati (e di conseguenza le dimensioni) è altamente personalizzabile.
  • Non è necessario spedire simboli di debug. Ciò riduce drasticamente la dimensione della distribuzione e rende più difficile la decodifica dell'applicazione.
  • In gran parte indipendente dal compilatore che usi. L'utilizzo di WER non richiede nemmeno alcun codice. In ogni caso, avere un modo per ottenere un database di simboli (PDB) è molto utile per l'analisi offline. Credo che GCC può generare PDB o ci sono strumenti per convertire il database dei simboli nel formato PDB.

Prendi nota che il WER può essere attivato solo da un arresto anomalo dell'applicazione (ad esempio il sistema che chiude un processo a causa di un'eccezione non gestita). MiniDumpWriteDump può essere chiamato in qualsiasi momento. Ciò può essere utile se è necessario scaricare lo stato corrente per diagnosticare problemi diversi da un arresto anomalo.

Lettura obbligatoria, se si desidera valutare l'applicabilità delle mini discariche:

2
IInspectable

Oltre alle risposte di cui sopra, ecco come si fa in modo che il SO Linux di Debian generi il core dump 

  1. Creare una cartella "coredumps" nella cartella home dell'utente
  2. Vai a /etc/security/limits.conf. Sotto la riga '', digitare "soft core unlimited" e "root soft core unlimited" se si abilita il core dump per root, per consentire uno spazio illimitato per core dump. 
  3. NOTA: "* soft core unlimited" non copre root, motivo per cui root deve essere specificato nella sua stessa riga.
  4. Per verificare questi valori, disconnettersi, riconnettersi e digitare "ulimit -a". "Dimensione del file core" deve essere impostato su illimitato.
  5. Controlla i file .bashrc (utente e root se possibile) per assicurarti che ulimit non sia impostato lì. In caso contrario, il valore sopra verrà sovrascritto all'avvio.
  6. Apri /etc/sysctl.conf. Immettere quanto segue in basso: "kernel.core_pattern = /home//coredumps/%e_%t.dump". (% e sarà il nome del processo e% t sarà l'ora del sistema)
  7. Esci e digita "sysctl -p" per caricare la nuova configurazione Verifica/proc/sys/kernel/core_pattern e verifica che questo corrisponda a ciò che hai appena digitato.
  8. Il dumping di base può essere testato eseguendo un processo sulla riga di comando ("&"), e quindi uccidendolo con "kill -11". Se il dumping principale ha esito positivo, verrà visualizzato "(core core dumped") dopo l'indicazione di errore di segmentazione.
2

Su Linux/unix/MacOSX usa i file core (puoi abilitarli con ulimit o system call compatibile ). Su Windows usa segnalazione degli errori di Microsoft (puoi diventare un partner e ottenere l'accesso ai dati del crash dell'applicazione).

1
Kasprzol

Se vuoi continuare da solo come ho fatto, puoi collegarti a bfd ed evitare di usare addr2line come ho fatto qui:

https://github.com/gnif/LookingGlass/blob/master/common/src/crash.linux.c

Questo produce l'output:

[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]
0
Geoffrey

Ho dimenticato la tecnologia GNOME di "apport", ma non ne so molto di usarla. Viene utilizzato per generare stacktraces e altri strumenti diagnostici per l'elaborazione e può automaticamente archiviare i bug. Vale sicuramente la pena di fare il check-in.

0
Joseph

Se il programma si arresta in modo anomalo, è il sistema operativo stesso che genera le informazioni di arresto anomalo. Se stai usando un sistema operativo * nix, devi semplicemente non impedirgli di farlo (controlla le opzioni 'coredump' del comando ulimit).

0
nsayer

Sembra che in una delle ultime versioni di boost c ++ apparisse la libreria per fornire esattamente quello che vuoi, probabilmente il codice sarebbe multipiattaforma . È boost :: stacktrace , che puoi usare come come in boost campione :

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

In Linux compili il codice qui sopra:

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

Esempio di backtrace copiato da boost documentation :

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
0
Grzegorz Bazior