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


590

Sto lavorando su Linux con il compilatore GCC. Quando il mio programma C ++ si arresta in modo anomalo, vorrei che generasse automaticamente uno stacktrace.

Il mio programma è gestito 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 arresta in modo anomalo e la prossima volta che l'utente lo esegue, chiederà loro se è giusto inviarmi la traccia dello stack in modo che io possa rintracciare il problema. Sono in grado di gestire l'invio delle informazioni a me, ma non so come generare la stringa di traccia. Qualche idea?


4
backtrace e backtrace_symbols_fd non sono sicuri per il segnale asincrono. non dovresti usare queste funzioni nel gestore del segnale
Parag Bafna,

10
backtrace_symbols chiama malloc e quindi non deve essere utilizzato in un gestore di segnale. Le altre due funzioni (backtrace e backtrace_symbols_fd) non presentano questo problema e sono comunemente utilizzate nei gestori di segnali.
cmccabe,

3
@cmccabe che è errato backtrace_symbols_fd di solito non chiama malloc ma può se qualcosa va storto nel suo blocco catch_error
Sam Saffron

6
"Può" nel senso che non esiste alcuna specifica POSIX per backtrace_symbols_fd (o qualsiasi backtrace); tuttavia, backtrace_symbols_fd di GNU / Linux è specificato per non chiamare mai malloc, come da linux.die.net/man/3/backtrace_symbols_fd . Pertanto, è sicuro supporre che non chiamerà mai malloc su Linux.
codetaku,

Risposte:


509

Per Linux e credo che Mac OS X, se si utilizza gcc o qualsiasi compilatore che utilizza glibc, è possibile utilizzare le funzioni backtrace () execinfo.hper stampare una traccia stack e uscire con grazia quando si verifica un errore di segmentazione. La documentazione è disponibile nel manuale di libc .

Ecco un programma di esempio che installa un SIGSEGVgestore e stampa uno stack stack stderrquando segfault. La baz()funzione qui causa 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 -rdynamicottieni informazioni sui simboli nel tuo output, che glibc può usare per creare una bella pila di stack:

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

L'esecuzione di questo ti dà questo output:

$ ./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 proveniva ogni frame nello stack. Qui puoi vedere il gestore del segnale in cima allo stack e le funzioni libc primamain in aggiunta a main, foo, bar, e baz.


53
C'è anche /lib/libSegFault.so che puoi usare con LD_PRELOAD.
CesarB,

6
Sembra che le prime due voci nell'output backtrace contengano un indirizzo di ritorno all'interno del gestore del segnale e probabilmente uno all'interno sigaction()di libc. Mentre la tua backtrace sembra essere corretta, a volte ho scoperto che sono necessari ulteriori passaggi per garantire che l'effettiva posizione dell'errore appaia nella backtrace poiché può essere sovrascritta sigaction()dal kernel.
Jschmier,

9
Cosa accadrebbe se lo schianto proviene da dentro Malloc? Non tieni premuto un lucchetto e rimani bloccato mentre "backtrace" tenta di allocare memoria?
Mattias Nilsson,

7
catchsegvnon è ciò di cui l'OP ha bisogno, ma è eccezionale per rilevare i guasti della segmentazione e ottenere tutte le informazioni.
Matt Clarkson,

8
Per ARM, ho dovuto anche compilare con -funwind-tables. Altrimenti la mia profondità dello stack era sempre 1 (vuota).
jfritz42,

128

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

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

  1. Esegui in 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, otterrai 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 del tipo:

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

L'output avrà un aspetto simile al seguente (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 è purtroppo 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


1
"Possibilità 3. Collegamento con libSegFault al momento della compilazione" non funziona.
HHK,

5
@crafter: cosa intendi con "non funziona". Cosa hai provato, su quale lingua / compilatore / toolchain / distribuzione / hardware? Non è riuscito a compilare? Per catturare l'errore? Per produrre output a tutti? Per produrre output difficili da usare? Grazie per i dettagli aiuterà tutti.
Stéphane Gourichon,

1
"la migliore fonte è purtroppo la fonte" ... Speriamo, un giorno, che la pagina man di catchsegv menzioni effettivamente SEGFAULT_SIGNALS. Fino ad allora, c'è questa risposta a cui fare riferimento.
Greggo,

Non posso credere di aver programmato C per 5 anni e non ne ho mai sentito parlare: /
DavidMFrey

6
@ StéphaneGourichon @HansKratz Per collegarti a libSegFault dovrai aggiungere -Wl,--no-as-neededi flag del compilatore. Altrimenti, ldin realtà non si collegherà libSegFault, perché riconosce che il binario non usa nessuno dei suoi simboli.
Phillip,

122

Linux

Mentre l'uso delle funzioni backtrace () in execinfo.h per stampare uno stacktrace ed uscire con grazia quando si ottiene un errore di segmentazione è già stato suggerito , non vedo alcuna menzione delle complessità necessarie per garantire che il backtrace risultante punti alla posizione effettiva di l'errore (almeno per alcune architetture - x86 e ARM).

Le prime due voci nella catena di frame dello stack quando si entra nel gestore del segnale contengono un indirizzo di ritorno all'interno del gestore del segnale e uno all'interno della sigazione () in libc. Lo stack frame 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 segnali esistono ancora e non dovrebbero essere trascurati, ma trovo che la funzionalità che ho descritto qui sia molto utile nel crash di debug.

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

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


11
Sui sistemi che usano GNU ld, ricordati di compilare -rdynamicper indicare al linker di aggiungere tutti i simboli, non solo quelli usati, alla tabella dei simboli dinamica. Ciò consente backtrace_symbols()di convertire gli indirizzi in nomi di funzioni
jschmier

1
Inoltre, è necessario aggiungere l'opzione "-mapcs-frame" alla riga di comando di GCC per generare stack frame sulla piattaforma ARM
qehgt

3
Potrebbe essere troppo tardi, ma possiamo usare il addr2linecomando in qualche modo per ottenere la riga esatta in cui si è verificato l'incidente?
enthusiasticgeek,

4
Su build più recenti di glibc uc_mcontextnon contiene un campo denominato eip. Ora c'è un array che deve essere indicizzato, uc_mcontext.gregs[REG_EIP]è l'equivalente.
mmlb,

6
Per ARM, i miei backtrace avevano sempre la profondità 1 fino a quando non ho aggiunto l'opzione -funwind-tables al compilatore.
jfritz42,

84

Anche se è stata fornita una risposta corretta che descrive come utilizzare la backtrace()funzione GNU libc 1 e ho fornito la mia risposta che descrive come garantire che un backtrace da un gestore di segnali punti alla posizione effettiva dell'errore 2 , non vedo qualsiasi menzione di demangling dell'output di simboli C ++ dal backtrace.

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

  • 1 Linux e OS X Si noti che c++filte __cxa_demanglesono specifici di GCC
  • 2 Linux

Il seguente esempio di C ++ Linux usa lo stesso gestore di segnali della mia altra risposta e dimostra come c++filtpuò essere usato per districare 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;
}

Uscita ( ./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 si basa sul gestore del segnale dalla mia risposta originale e può sostituire il gestore del segnale nell'esempio precedente per dimostrare come abi::__cxa_demanglepuò essere utilizzato per districare i simboli. Questo gestore di segnale produce lo stesso output confuso 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);
}

1
Grazie per questo, Jschmier. Ho creato un piccolo script bash per alimentare l'output di questo nell'utility addr2line. Vedi:
stackoverflow.com/a/15801966/1797414

4
Non dimenticare di #include <cxxabi.h>
Bamaco,

1
Una buona documentazione, e un file di intestazione semplice è stato inviato qui dal 2008 ... panthema.net/2008/0901-stacktrace-demangled molto simile al vostro approccio :)
kevinf

abi :: __ cxa_demangle sembra non essere il segnale asincrono sicuro, quindi il gestore del segnale può bloccarsi da qualche parte in malloc.
orcy,

L'uso di std::cerr, free()e exit()tutti violano le restrizioni nei confronti calling non async-signal-safe su sistemi POSIX. Questo codice stallo se il processo non riesce a qualsiasi chiamata come ad esempio free(), malloc() newo detete.
Andrew Henle,

31

Potrebbe valere la pena consultare Google Breakpad , un generatore di dump di crash multipiattaforma e strumenti per elaborare i dump.


Riferisce su cose come errori di segmentazione, ma non riporta informazioni su eccezioni C ++ non gestite.
DBedrenko,

21

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 libcbacktrace() .

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


13

Grazie a entusiasta geek 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'utility addr2line.

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

L'output dovrebbe stampare qualcosa di simile al 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 

12

ulimit -c <value>imposta il limite della dimensione del file core su unix. Per impostazione predefinita, il limite della dimensione del file principale è 0. Puoi vedere i tuoi ulimitvalori con ulimit -a.

inoltre, se esegui il tuo programma all'interno di gdb, questo interromperà il programma su "violazioni della segmentazione" (SIGSEGV , generalmente quando accedi a un pezzo di memoria che non hai allocato) o puoi impostare punti di interruzione.

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


6
I core dump sono infinitamente più utili delle tracce dello stack perché puoi caricare il core dump nel debugger e vedere lo stato dell'intero programma e i suoi dati nel punto del crash.
Adam Hawes,

1
La funzione di backtrace suggerita da altri è probabilmente migliore di niente, ma è molto semplice: non fornisce nemmeno numeri di riga. Utilizzando i core dump, d'altra parte, ti consente di visualizzare in modo retroattivo l'intero stato dell'applicazione nel momento in cui si è bloccato (inclusa una traccia dettagliata dello stack). Ci potrebbero essere problemi pratici con il tentativo di utilizzare questo campo per il debugging, ma è sicuramente uno strumento più potente per crash analisi e afferma durante lo sviluppo (almeno su Linux).
nobar,

10

È importante notare che una volta generato un file core dovrai usare lo strumento gdb per vederlo. Affinché gdb abbia un senso per il tuo file core, devi dire a gcc di strumentare il binario con simboli di debug: per fare ciò, devi compilare con il flag -g:

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

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

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


4
Puoi anche chiamare gdbdirettamente dal tuo programma in crash. Gestore di installazione per SIGSEGV, SEGILL, SIGBUS, SIGFPE che chiamerà gdb. Dettagli: stackoverflow.com/questions/3151779/… Il vantaggio è che si ottiene una bt fulltraccia backtrace bella e annotata come in , inoltre è possibile ottenere tracce di stack di tutti i thread.
Vi.

Puoi anche ottenere backtrace più facilmente che nella risposta: gdb -silent ./prog core --eval-command = backtrace --batch-mostrerebbe backtrace e chiuderà il debugger
baziorek

10

Sto osservando questo problema da un po '.

E sepolto 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 conoscere le opinioni di questa biblioteca.

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


2
Su x86 / 64, non ho visto aumentare molto le dimensioni binarie della dinamica. Aggiungendo -g si ottiene un aumento molto maggiore.
Dan

1
Ho notato che libunwind non ha funzionalità per ottenere il numero di riga, e suppongo (non ha testato) unw_get_proc_name restituisce il simbolo della funzione (che è offuscato per sovraccarico e simili) invece del nome originale.
Herbert,

1
È corretto. È molto difficile farlo correttamente, ma ho avuto un eccellente successo con gaddr2line. Qui ci sono molte informazioni pratiche qui blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

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


1
sfortunatamente viene utilizzato execlp()per eseguire chiamate addr2line ... sarebbe bello rimanere completamente nel proprio programma (che è possibile includendo il codice addr2line in qualche forma)
esempio

9

Dimentica di cambiare le tue fonti e fai alcuni hack con la funzione backtrace () o le macro - queste sono solo soluzioni scadenti.

Come soluzione correttamente funzionante, consiglierei:

  1. Compila il tuo programma con il flag "-g" per incorporare i simboli di debug in binario (non preoccuparti, ciò non influirà sulle tue prestazioni).
  2. Su Linux eseguire 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 vedrai il file "core".
  4. Esegui il comando successivo per stampare backtrace su stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Questo stamperà la corretta backtrace leggibile del tuo programma in modo leggibile (con nomi di file sorgente e numeri di riga). Inoltre, questo approccio ti darà la libertà di automatizzare il tuo sistema: disponi di un breve script che controlla se il processo ha creato un dump principale, quindi invia backtrace via e-mail agli sviluppatori o accedi a un sistema di registrazione.


Fornisce i numeri di riga errati. Può essere migliorato?
HeyJude,

7
ulimit -c unlimited

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

Saluti


5
L'utente non richiede un dump principale. Sta chiedendo una traccia dello stack. Vedi delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin,

1
un dump core conterrà lo stack di chiamate al momento del crash, non è vero?
Mo.

3
Stai assumendo che sia su Unix e usi Bash.
Paul Tomblin,

2
Se stai usando tcsh, devi farlolimit coredumpsize unlimited
sivabudh,


6

Vedere la funzione Stack Trace in ACE (ADAPTIVE Communication Environment). È già stato scritto per coprire tutte le principali piattaforme (e altro). La libreria ha una licenza in stile BSD, quindi puoi anche copiare / incollare il codice se non vuoi usare ACE.


Il collegamento sembra essere morto.
novembre

5

Posso aiutarti con la versione Linux: è possibile utilizzare la funzione backtrace, backtrace_symbols e backtrace_symbols_fd. Vedi le pagine di manuale corrispondenti.


5

Sembra che in una delle ultime versioni di c ++ boost apparso per fornire esattamente quello che vuoi, probabilmente il codice sarebbe multipiattaforma. È boost :: stacktrace , che puoi usare come nell'esempio boost :

#include <filesystem>
#include <sstream>
#include <fstream>
#include <signal.h>     // ::signal, ::raise
#include <boost/stacktrace.hpp>

const char* backtraceFileName = "./backtraceFile.dump";

void signalHandler(int)
{
    ::signal(SIGSEGV, SIG_DFL);
    ::signal(SIGABRT, SIG_DFL);
    boost::stacktrace::safe_dump_to(backtraceFileName);
    ::raise(SIGABRT);
}

void sendReport()
{
    if (std::filesystem::exists(backtraceFileName))
    {
        std::ifstream file(backtraceFileName);

        auto st = boost::stacktrace::stacktrace::from_dump(file);
        std::ostringstream backtraceStream;
        backtraceStream << st << std::endl;

        // sending the code from st

        file.close();
        std::filesystem::remove(backtraceFileName);
    }
}

int main()
{
    ::signal(SIGSEGV, signalHandler);
    ::signal(SIGABRT, signalHandler);

    sendReport();
    // ... rest of code
}

In Linux Compilare il codice sopra:

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

Esempio di backtrace copiato dalla documentazione boost :

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: puoi intercettare SIGSEGV (di solito questo segnale viene generato prima dell'arresto anomalo) e conservare le informazioni in un file. (oltre al file core che puoi usare per il debug usando gdb per esempio).

win: 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.


SEH non aiuta a produrre una traccia dello stack. Sebbene possa far parte di una soluzione, quella soluzione è più difficile da implementare e fornisce meno informazioni a spese della divulgazione di più informazioni sull'applicazione rispetto alla soluzione reale : scrivere un mini dump. E imposta Windows per farlo automaticamente per te.
prevedibile il

4

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

Puoi verificarlo con il codice qui sotto. Per impostazione predefinita, il gestore non riesce. 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

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

Wow! Ecco finalmente come dovrebbe essere fatto! Ho appena scaricato la propria soluzione a favore di questa.
novembre

3

Vorrei utilizzare il codice che genera una traccia dello stack per la memoria trapelata in Visual Leak Detector . Questo funziona solo su Win32, però.


E richiede la spedizione dei simboli di debug con il tuo codice. In generale non è desiderabile. Scrivi un mini dump e configura Windows per farlo automaticamente per te su eccezioni non gestite.
prevedibile il

3

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


3

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

A partire da Windows Server 2008 e Windows Vista con Service Pack 1 (SP1), è possibile configurare Windows Error Reporting (WER) in modo che i dump completi della modalità utente vengano raccolti e archiviati localmente dopo il crash di un'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 nella chiave HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumps .

È possibile impostare le voci di 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 dello stack sul client:

  • È già implementato nel sistema. Puoi utilizzare WER come indicato sopra o chiamare tu stesso MiniDumpWriteDump , se hai bisogno di un controllo più approfondito sulla quantità di informazioni da scaricare. (Assicurati di chiamarlo da un processo diverso.)
  • Modo più completo di una traccia dello stack. Tra gli altri, 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 le dimensioni della distribuzione e rende più difficile la retroingegnerizzazione dell'applicazione.
  • In gran parte indipendente dal compilatore che usi. L'uso di WER non richiede nemmeno alcun codice. In entrambi i casi, avere un modo per ottenere un database di simboli (PDB) è molto utile per l'analisi offline. Credo che GCC sia in grado di generare PDB, oppure esistono strumenti per convertire il database dei simboli nel formato PDB.

Si noti che WER può essere attivato solo da un arresto anomalo dell'applicazione (ovvero il sistema che termina un processo a causa di un'eccezione non gestita). MiniDumpWriteDumppuò 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

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

  1. Creare una cartella "coredumps" nella cartella principale dell'utente
  2. Vai a /etc/security/limits.conf. Sotto la riga '', digitare "soft core unlimited" e "root soft core unlimited" se si abilitano i core dump per root, per consentire uno spazio illimitato per i core dump.
  3. NOTA: "* soft core unlimited" non copre root, motivo per cui root deve essere specificato nella propria riga.
  4. Per controllare questi valori, disconnettersi, riconnettersi e digitare "ulimit -a". "Dimensione file core" deve essere impostato su illimitato.
  5. Controllare i file .bashrc (utente e root, se applicabile) per assicurarsi 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 fondo: "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 Controlla / proc / sys / kernel / core_pattern e verifica che corrisponda a ciò che hai appena digitato.
  8. Il dumping core può essere testato eseguendo un processo dalla riga di comando ("&"), e quindi uccidendolo con "kill -11". Se il dumping core ha esito positivo, dopo l'indicazione dell'errore di segmentazione verrà visualizzato "(core dumped)".

2

Se vuoi ancora andare da solo come ho fatto puoi collegarti bfded evitare di usare addr2linecome 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]

1

Su Linux / unix / MacOSX utilizzare i file core (è possibile abilitarli con ulimit o chiamata di sistema compatibile ). Su Windows utilizza la segnalazione errori Microsoft (puoi diventare un partner e ottenere l'accesso ai dati di crash dell'applicazione).


0

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.