Comment générer automatiquement une trace de pile lorsque mon programme plante

590

Je travaille sur Linux avec le compilateur GCC. Lorsque mon programme C ++ plante, je voudrais qu'il génère automatiquement une trace de pile.

Mon programme est exécuté par de nombreux utilisateurs différents et il fonctionne également sur Linux, Windows et Macintosh (toutes les versions sont compilées à l'aide gcc).

J'aimerais que mon programme puisse générer une trace de pile quand il se bloque et la prochaine fois que l'utilisateur l'exécutera, il leur demandera s'il est correct de m'envoyer la trace de pile afin que je puisse dépister le problème. Je peux gérer l'envoi des informations pour moi mais je ne sais pas comment générer la chaîne de trace. Des idées?

KPexEA
la source
4
backtrace et backtrace_symbols_fd ne sont pas sûrs pour le signal asynchrone. vous ne devez pas utiliser ces fonctions dans le gestionnaire de signaux
Parag Bafna
10
backtrace_symbols appelle malloc et ne doit donc pas être utilisé dans un gestionnaire de signaux. Les deux autres fonctions (backtrace et backtrace_symbols_fd) n'ont pas ce problème et sont couramment utilisées dans les gestionnaires de signaux.
cmccabe
3
@cmccabe qui est incorrect backtrace_symbols_fd n'appelle généralement pas malloc mais peut si quelque chose se passe mal dans son bloc catch_error
Sam Saffron
6
Il "peut" dans le sens où il n'y a pas de spécification POSIX pour backtrace_symbols_fd (ou toute trace); cependant, backtrace_symbols_fd de GNU / Linux est spécifié pour ne jamais appeler malloc, conformément à linux.die.net/man/3/backtrace_symbols_fd . Par conséquent, il est sûr de supposer qu'il n'appellera jamais malloc sous Linux.
codetaku
Comment ça plante?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

509

Pour Linux et je crois que Mac OS X, si vous utilisez gcc ou tout compilateur qui utilise glibc, vous pouvez utiliser les fonctions backtrace () execinfo.hpour imprimer une trace de pile et quitter avec élégance lorsque vous obtenez une erreur de segmentation. La documentation se trouve dans le manuel libc .

Voici un exemple de programme qui installe un SIGSEGVgestionnaire et imprime une trace de pile stderrlorsqu'elle se sépare. La baz()fonction provoque ici le segfault qui déclenche le gestionnaire:

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

La compilation avec -g -rdynamicvous permet d' obtenir des informations sur les symboles dans votre sortie, que la glibc peut utiliser pour créer une belle trace de pile:

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

L'exécution de ceci vous obtient cette sortie:

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

Cela montre le module de chargement, le décalage et la fonction dont chaque trame de la pile est issue. Ici vous pouvez voir le gestionnaire de signal au - dessus de la pile, et les fonctions libc avant mainen plus main, foo, baret baz.

Todd Gamblin
la source
53
Il y a aussi /lib/libSegFault.so que vous pouvez utiliser avec LD_PRELOAD.
CesarB
6
Il semble que les deux premières entrées de votre sortie de trace contiennent une adresse de retour à l'intérieur du gestionnaire de signal et probablement une à l'intérieur sigaction()de libc. Bien que votre backtrace semble être correcte, j'ai parfois constaté que des étapes supplémentaires sont nécessaires pour garantir que l'emplacement réel du défaut apparaît dans la backtrace car il peut être remplacé sigaction()par le noyau.
jschmier
9
Que se passerait-il si le crash venait de l'intérieur de malloc? Souhaitez-vous pas alors tenir un verrou, puis rester coincé pendant que "backtrace" essaie d'allouer de la mémoire?
Mattias Nilsson
7
catchsegvn'est pas ce dont l'OP a besoin mais est génial pour détecter les défauts de segmentation et obtenir toutes les informations.
Matt Clarkson
8
Pour ARM, je devais aussi compiler avec -funwind-tables. Sinon, la profondeur de ma pile était toujours de 1 (vide).
jfritz42
128

C'est encore plus facile que "man backtrace", il y a une bibliothèque peu documentée (spécifique à GNU) distribuée avec glibc comme libSegFault.so, qui a été je crois écrite par Ulrich Drepper pour supporter le programme catchsegv (voir "man catchsegv").

Cela nous donne 3 possibilités. Au lieu d'exécuter "programme -o hai":

  1. Exécutez dans catchsegv:

    $ catchsegv program -o hai
  2. Lien avec libSegFault lors de l'exécution:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
  3. Lien avec libSegFault au moment de la compilation:

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

Dans les 3 cas, vous obtiendrez des traces plus claires avec moins d'optimisation (gcc -O0 ou -O1) et des symboles de débogage (gcc -g). Sinon, vous risquez de vous retrouver avec une pile d'adresses mémoire.

Vous pouvez également capturer plus de signaux pour les traces de pile avec quelque chose comme:

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

La sortie ressemblera à ceci (remarquez la trace en bas):

*** 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 vous voulez connaître les détails sanglants, la meilleure source est malheureusement la source: voir http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c et son répertoire parent http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

jhclark
la source
1
"Possibilité 3. Lien avec libSegFault au moment de la compilation" ne fonctionne pas.
HHK
5
@crafter: Que voulez-vous dire par "ne fonctionne pas". Qu'avez-vous essayé, sur quelle langue / compilateur / chaîne d'outils / distribution / matériel? At-il échoué à compiler? Pour attraper une erreur? Pour produire du tout? Pour produire une sortie difficile à utiliser? Merci pour les détails cela aidera tout le monde.
Stéphane Gourichon
1
'la meilleure source est malheureusement la source' ... Espérons qu'un jour, la page de manuel de catchsegv mentionnera SEGFAULT_SIGNALS. Jusque-là, il y a cette réponse à laquelle se référer.
greggo
Je ne peux pas croire que je programme C depuis 5 ans et que je n'en ai jamais entendu parler: /
DavidMFrey
6
@ StéphaneGourichon @HansKratz Pour créer un lien avec libSegFault, vous devrez ajouter -Wl,--no-as-neededaux drapeaux du compilateur. Sinon, ldil ne sera en effet pas lié libSegFault, car il reconnaît que le binaire n'utilise aucun de ses symboles.
Phillip
122

Linux

Bien que l'utilisation des fonctions backtrace () dans execinfo.h pour imprimer un stacktrace et sortir avec élégance lorsque vous obtenez une erreur de segmentation ait déjà été suggérée , je ne vois aucune mention des subtilités nécessaires pour garantir que la trace résultante pointe vers l'emplacement réel de la faute (au moins pour certaines architectures - x86 & ARM).

Les deux premières entrées de la chaîne de trames de pile lorsque vous entrez dans le gestionnaire de signaux contiennent une adresse de retour à l'intérieur du gestionnaire de signaux et une à l'intérieur de sigaction () dans libc. La trame de pile de la dernière fonction appelée avant le signal (qui est l'emplacement du défaut) est perdue.

Code

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

Production

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]

Tous les risques d'appeler les fonctions backtrace () dans un gestionnaire de signaux existent toujours et ne doivent pas être négligés, mais je trouve les fonctionnalités que j'ai décrites ici très utiles pour déboguer les plantages.

Il est important de noter que l'exemple que j'ai fourni est développé / testé sur Linux pour x86. J'ai également réussi à implémenter cela sur ARM en utilisant uc_mcontext.arm_pcau lieu de uc_mcontext.eip.

Voici un lien vers l'article où j'ai appris les détails de cette implémentation: http://www.linuxjournal.com/article/6391

jschmier
la source
11
Sur les systèmes utilisant GNU ld, n'oubliez pas de compiler avec -rdynamicpour demander à l'éditeur de liens d'ajouter tous les symboles, pas seulement ceux utilisés, à la table des symboles dynamiques. Cela permet backtrace_symbols()de convertir des adresses en noms de fonction
jschmier
1
En outre, vous devez ajouter l'option "-mapcs-frame" à la ligne de commande de GCC pour générer des cadres de pile sur la plate
qehgt
3
Cela peut être trop tard, mais pouvons-nous utiliser la addr2linecommande pour obtenir la ligne exacte où le crash s'est produit?
enthousiastegege
4
Sur les versions plus récentes de glibc uc_mcontextne contient pas de champ nommé eip. Il y a maintenant un tableau qui doit être indexé, uc_mcontext.gregs[REG_EIP]c'est l'équivalent.
mmlb
6
Pour ARM, mes backtraces avaient toujours la profondeur 1 jusqu'à ce que j'ajoute l'option -funwind-tables au compilateur.
jfritz42
84

Même si une réponse correcte a été fournie qui décrit comment utiliser la backtrace()fonction GNU libc 1 et que j'ai fourni ma propre réponse qui décrit comment garantir qu'une trace arrière d'un gestionnaire de signal pointe vers l'emplacement réel de l'erreur 2 , je ne vois pas toute mention de démêlage des symboles C ++ sortis de la trace.

Lors de l'obtention de traces d'un programme C ++, la sortie peut être exécutée via c++filt1 pour démêler les symboles ou en utilisant directement 1 .abi::__cxa_demangle

  • 1 Linux et OS X Notez que c++filtet __cxa_demanglesont spécifiques à GCC
  • 2 Linux

L'exemple C ++ Linux suivant utilise le même gestionnaire de signaux que mon autre réponse et montre comment c++filtpeut être utilisé pour démêler les symboles.

Code :

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

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

Sortie démêlée ( ./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]

Ce qui suit s'appuie sur le gestionnaire de signal de ma réponse d'origine et peut remplacer le gestionnaire de signal dans l'exemple ci-dessus pour montrer comment abi::__cxa_demanglepeut être utilisé pour démêler les symboles. Ce gestionnaire de signal produit la même sortie démêlée que l'exemple ci-dessus.

Code :

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);
}
jschmier
la source
1
Merci pour cela, jschmier. J'ai créé un petit script bash pour alimenter la sortie de ceci dans l'utilitaire addr2line. Voir: stackoverflow.com/a/15801966/1797414
arr_sea
4
N'oubliez pas de #include <cxxabi.h>
Bamaco
1
Bonne documentation, et un fichier d'en-tête simple a été publié ici depuis 2008 ... panthema.net/2008/0901-stacktrace-demangled très similaire à votre approche :)
kevinf
abi :: __ cxa_demangle ne semble pas être async-signal-safe, donc le gestionnaire de signal peut se bloquer quelque part dans malloc.
orcy
L'utilisation de std::cerr, free()et exit()toutes les restrictions violent contre l' appel des appels non-signaux asynchrones de sécurité sur les systèmes POSIX. Ce code impasse si le processus échoue dans un appel tel que free(), malloc() newou detete.
Andrew Henle
31

Cela pourrait valoir la peine de regarder Google Breakpad , un générateur de vidage sur incident multiplateforme et des outils pour traiter les vidages.

Simon Steele
la source
Il signale des choses comme les erreurs de segmentation, mais il ne rapporte aucune information sur les exceptions C ++ non gérées.
DBedrenko
21

Vous n'avez pas spécifié votre système d'exploitation, il est donc difficile de répondre. Si vous utilisez un système basé sur gnu libc, vous pourrez peut-être utiliser la fonction libc backtrace().

GCC dispose également de deux fonctions intégrées qui peuvent vous aider, mais qui peuvent ou non être implémentées entièrement sur votre architecture, et ce sont __builtin_frame_addresset __builtin_return_address. Les deux veulent un niveau entier immédiat (par immédiat, je veux dire que ce ne peut pas être une variable). Si __builtin_frame_addresspour un niveau donné est différent de zéro, il devrait être sûr de saisir l'adresse de retour du même niveau.

Brian Mitchell
la source
13

Merci à enthousiastegeek d'avoir attiré mon attention sur l'utilitaire addr2line.

J'ai écrit un script rapide et sale pour traiter la sortie de la réponse fournie ici : (merci beaucoup à jschmier!) En utilisant l'utilitaire addr2line.

Le script accepte un seul argument: le nom du fichier contenant la sortie de l'utilitaire jschmier.

La sortie doit imprimer quelque chose comme ce qui suit pour chaque niveau de la trace:

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;

Code:

#!/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 
arr_sea
la source
12

ulimit -c <value>définit la taille maximale du fichier sous unix. Par défaut, la taille limite du fichier principal est 0. Vous pouvez voir vos ulimitvaleurs avec ulimit -a.

De plus, si vous exécutez votre programme à partir de gdb, il arrêtera votre programme sur les "violations de segmentation" ( SIGSEGV, généralement lorsque vous accédez à un morceau de mémoire que vous n'avez pas alloué) ou vous pouvez définir des points d'arrêt.

ddd et nemiver sont des frontaux pour gdb, ce qui facilite son utilisation pour le novice.

utilisateur
la source
6
Les vidages de mémoire sont infiniment plus utiles que les traces de pile, car vous pouvez charger le vidage de mémoire dans le débogueur et voir l'état de l'ensemble du programme et de ses données au moment du crash.
Adam Hawes
1
La fonction de trace arrière que d'autres ont suggérée est probablement meilleure que rien, mais elle est très basique - elle ne donne même pas de numéros de ligne. À l'aide des vidages de mémoire, d'autre part, vous permet de visualiser rétroactivement l'état complet de votre application au moment de son crash (y compris une trace de pile détaillée). Il peut y avoir des problèmes pratiques à essayer d'utiliser cela pour le débogage sur le terrain, mais c'est certainement un outil plus puissant pour analyser les plantages et les assertions pendant le développement (au moins sur Linux).
nobar
10

Il est important de noter qu'une fois que vous générez un fichier core, vous devrez utiliser l'outil gdb pour le consulter. Pour que gdb donne un sens à votre fichier core, vous devez dire à gcc d'instrumenter le binaire avec des symboles de débogage: pour ce faire, vous compilez avec le drapeau -g:

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

Ensuite, vous pouvez soit définir "ulimit -c unlimited" pour le laisser vider un noyau, soit simplement exécuter votre programme dans gdb. J'aime plus la deuxième approche:

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

J'espère que ça aide.

Benson
la source
4
Vous pouvez également appeler gdbdirectement depuis votre programme de plantage. Gestionnaire d'installation pour SIGSEGV, SEGILL, SIGBUS, SIGFPE qui appellera gdb. Détails: stackoverflow.com/questions/3151779/… L'avantage est que vous obtenez une belle trace annotée comme dans bt full, vous pouvez également obtenir des traces de pile de tous les threads.
Vi.
Vous pouvez également obtenir une trace plus facile que dans la réponse: gdb -silent ./prog core --eval-command = backtrace --batch -it afficherait la trace et fermera le débogueur
baziorek
10

Cela fait un moment que j'examine ce problème.

Et enfoui profondément dans le README de Google Performance Tools

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

parle de libunwind

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

J'adorerais entendre les opinions de cette bibliothèque.

Le problème avec -rdynamic est qu'il peut augmenter la taille du binaire de manière relativement significative dans certains cas

Gregory
la source
2
Sur x86 / 64, je n'ai pas beaucoup vu -rdynamic augmenter la taille binaire. L'ajout de -g entraîne une augmentation beaucoup plus importante.
Dan
1
J'ai remarqué que libunwind n'a pas de fonctionnalité pour obtenir le numéro de ligne, et je suppose (n'a pas testé) unw_get_proc_name renvoie le symbole de fonction (qui est obscurci pour surcharge et autres) au lieu du nom d'origine.
Herbert
1
C'est correct. Il est très difficile de le faire correctement, mais j'ai eu un excellent succès avec gaddr2line, il y a beaucoup d'informations pratiques ici blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory
9

Vous pouvez utiliser DeathHandler - petite classe C ++ qui fait tout pour vous, fiable.

Markhor
la source
1
malheureusement, il utilise execlp()pour effectuer des appels addr2line ... serait bien de rester entièrement dans son propre programme (ce qui est possible en incluant le code addr2line sous une certaine forme)
exemple
9

Oubliez de changer vos sources et faites quelques hacks avec la fonction backtrace () ou les macroses - ce ne sont que de mauvaises solutions.

En tant que solution fonctionnant correctement, je conseillerais:

  1. Compilez votre programme avec l'indicateur "-g" pour incorporer les symboles de débogage au binaire (ne vous inquiétez pas, cela n'aura pas d'impact sur vos performances).
  2. Sous Linux, exécutez la commande suivante: "ulimit -c unlimited" - pour permettre au système d'effectuer de gros vidages sur incident.
  3. Lorsque votre programme est tombé en panne, dans le répertoire de travail, vous verrez le fichier "core".
  4. Exécutez la commande suivante pour imprimer la trace arrière sur stdout: gdb -batch -ex "backtrace" ./your_program_exe ./core

Cela imprimera une trace lisible appropriée de votre programme de manière lisible par l'homme (avec les noms de fichier source et les numéros de ligne). De plus, cette approche vous donnera la liberté d'automatiser votre système: ayez un court script qui vérifie si le processus a créé un vidage de mémoire, puis envoyez des traces par e-mail aux développeurs, ou connectez-le à un système de journalisation.

loopzilla
la source
Il donne les mauvais numéros de ligne. Peut-il être amélioré?
HeyJude
7
ulimit -c unlimited

est une variable système, qui permettra de créer un core dump après le crash de votre application. Dans ce cas, un montant illimité. Recherchez un fichier appelé core dans le même répertoire. Assurez-vous d'avoir compilé votre code avec les informations de débogage activées!

Cordialement

mana
la source
5
L'utilisateur ne demande pas de vidage de mémoire. Il demande une trace de pile. Voir delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin
1
un vidage de mémoire contiendra la pile d'appels au moment du crash, n'est-ce pas?
Mo
3
Vous supposez qu'il est sous Unix et utilisez Bash.
Paul Tomblin
2
Si vous utilisez tcsh, vous devez le fairelimit coredumpsize unlimited
sivabudh
6

Regarder:

homme 3 backtrace

Et:

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

Ce sont des extensions GNU.

Stéphane
la source
2
Il peut y avoir des exemples supplémentaires pour aider sur cette page que j'ai créée il y a quelque temps: charette.no-ip.com:81/programming/2010-01-25_Backtrace
Stéphane
6

Voir la fonction Stack Trace dans ACE (ADAPTIVE Communication Environment). Il est déjà écrit pour couvrir toutes les principales plateformes (et plus). La bibliothèque est sous licence BSD, vous pouvez donc même copier / coller le code si vous ne souhaitez pas utiliser ACE.

Adam Mitz
la source
Le lien semble être mort.
tglas
5

Je peux vous aider avec la version Linux: les fonctions backtrace, backtrace_symbols et backtrace_symbols_fd peuvent être utilisées. Voir les pages de manuel correspondantes.

terminus
la source
5

Il semble que dans l'une des dernières librairies de la version boost de C ++ est apparue pour fournir exactement ce que vous voulez, probablement le code serait multiplateforme. C'est boost :: stacktrace , que vous pouvez utiliser comme dans l'exemple de boost :

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

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

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

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

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

        // sending the code from st

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

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

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

Sous Linux, vous compilez le code ci-dessus:

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

Exemple de trace copiée à partir de la documentation de 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
baziorek
la source
4

* nix: vous pouvez intercepter SIGSEGV (généralement ce signal est émis avant de planter) et conserver les informations dans un fichier. (en plus du fichier core que vous pouvez utiliser pour déboguer avec gdb par exemple).

win: Vérifiez cela à partir de msdn.

Vous pouvez également consulter le code chrome de Google pour voir comment il gère les plantages. Il a un joli mécanisme de gestion des exceptions.

INS
la source
SEH n'aide pas à produire une trace de pile. Bien qu'elle puisse faire partie d'une solution, cette solution est plus difficile à implémenter et fournit moins d'informations au détriment de la divulgation de plus d'informations sur votre application que la vraie solution: rédigez un mini vidage. Et configurez Windows pour le faire automatiquement pour vous.
IInspectable
4

J'ai trouvé que la solution @tgamblin n'est pas complète. Il ne peut pas gérer avec stackoverflow. Je pense que, par défaut, le gestionnaire de signal est appelé avec la même pile et SIGSEGV est lancé deux fois. Pour vous protéger, vous devez enregistrer une pile indépendante pour le gestionnaire de signaux.

Vous pouvez vérifier cela avec le code ci-dessous. Par défaut, le gestionnaire échoue. Avec la macro définie STACK_OVERFLOW, tout va 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;
} 
Daneel S. Yaitskov
la source
4

Le nouveau roi de la ville est arrivé https://github.com/bombela/backward-cpp

1 en-tête à placer dans votre code et 1 bibliothèque à installer.

Personnellement, je l'appelle en utilisant cette fonction

#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);
}
Roy
la source
Hou la la! Voilà enfin comment cela devrait être fait! Je viens de vider ma propre solution en faveur de celle-ci.
tglas
3

J'utiliserais le code qui génère une trace de pile pour la fuite de mémoire dans Visual Leak Detector . Cela ne fonctionne que sur Win32.

Jim Buck
la source
Et nécessite que vous expédiez des symboles de débogage avec votre code. En général, ce n'est pas souhaitable. Écrivez un mini vidage et configurez Windows pour le faire automatiquement pour vous sur les exceptions non gérées.
IInspectable
3

J'ai vu beaucoup de réponses ici effectuer un gestionnaire de signal puis quitter. C'est la voie à suivre, mais rappelez-vous un fait très important: si vous voulez obtenir le vidage de mémoire pour l'erreur générée, vous ne pouvez pas appeler exit(status). Appelez abort()plutôt!

jard18
la source
3

En tant que solution Windows uniquement, vous pouvez obtenir l'équivalent d'une trace de pile (avec beaucoup, beaucoup plus d'informations) en utilisant le rapport d'erreurs Windows . Avec seulement quelques entrées de registre, il peut être configuré pour collecter les vidages en mode utilisateur :

À partir de Windows Server 2008 et Windows Vista avec Service Pack 1 (SP1), le rapport d'erreurs Windows (WER) peut être configuré de sorte que les vidages en mode utilisateur complets soient collectés et stockés localement après le blocage d'une application en mode utilisateur. [...]

Cette fonctionnalité n'est pas activée par défaut. L'activation de cette fonction nécessite des privilèges d'administrateur. Pour activer et configurer la fonctionnalité, utilisez les valeurs de Registre suivantes sous la clé HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows Error Reporting \ LocalDumps .

Vous pouvez définir les entrées de registre à partir de votre programme d'installation, qui dispose des privilèges requis.

La création d'un vidage en mode utilisateur présente les avantages suivants par rapport à la génération d'une trace de pile sur le client:

  • Il est déjà implémenté dans le système. Vous pouvez soit utiliser WER comme indiqué ci-dessus, soit appeler MiniDumpWriteDump vous-même, si vous avez besoin d'un contrôle plus fin sur la quantité d'informations à vider. (Assurez-vous de l'appeler à partir d'un processus différent.)
  • Bien plus complet qu'une trace de pile. Entre autres, il peut contenir des variables locales, des arguments de fonction, des piles pour d'autres threads, des modules chargés, etc. La quantité de données (et par conséquent la taille) est hautement personnalisable.
  • Pas besoin d'envoyer des symboles de débogage. Cela réduit considérablement la taille de votre déploiement et rend plus difficile la rétro-ingénierie de votre application.
  • Largement indépendant du compilateur que vous utilisez. L'utilisation de WER ne nécessite même aucun code. Quoi qu'il en soit, avoir un moyen d'obtenir une base de données de symboles (PDB) est très utile pour l'analyse hors ligne. Je crois que GCC peut soit générer des PDB, soit il existe des outils pour convertir la base de données de symboles au format PDB.

Notez que le WER ne peut être déclenché que par un crash d'application (c'est-à-dire que le système met fin à un processus en raison d'une exception non gérée). MiniDumpWriteDumppeut être appelé à tout moment. Cela peut être utile si vous avez besoin de vider l'état actuel pour diagnostiquer des problèmes autres qu'un crash.

Lecture obligatoire, si vous souhaitez évaluer l'applicabilité des mini dumps:

Inspectable
la source
2

En plus des réponses ci-dessus, voici comment faire pour que Debian Linux OS génère un vidage de mémoire

  1. Créer un dossier «coredumps» dans le dossier de départ de l'utilisateur
  2. Accédez à /etc/security/limits.conf. Sous la ligne «», tapez «soft core unlimited» et «root soft core unlimited» si vous activez les vidages mémoire pour root, afin de laisser un espace illimité pour les vidages mémoire.
  3. REMARQUE: «* soft core unlimited» ne couvre pas root, c'est pourquoi root doit être spécifié dans sa propre ligne.
  4. Pour vérifier ces valeurs, déconnectez-vous, reconnectez-vous et tapez «ulimit -a». La «taille du fichier principal» doit être définie sur illimitée.
  5. Vérifiez les fichiers .bashrc (utilisateur et root le cas échéant) pour vous assurer que ulimit n'y est pas défini. Sinon, la valeur ci-dessus sera écrasée au démarrage.
  6. Ouvrez /etc/sysctl.conf. Saisissez ce qui suit en bas: «kernel.core_pattern = /home//coredumps/%e_%t.dump». (% e sera le nom du processus et% t sera l'heure système)
  7. Quittez et tapez "sysctl -p" pour charger la nouvelle configuration Vérifiez / proc / sys / kernel / core_pattern et vérifiez que cela correspond à ce que vous venez de taper.
  8. Le vidage du noyau peut être testé en exécutant un processus sur la ligne de commande («&»), puis en le tuant avec «kill -11». Si le vidage du cœur réussit, vous verrez «(vidage du cœur)» après l'indication de défaut de segmentation.
enthousiaste
la source
2

Si vous voulez toujours faire cavalier seul comme je l'ai fait, vous pouvez créer un lien bfdet éviter d'utiliseraddr2line comme je l'ai fait ici:

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

Cela produit la sortie:

[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]
Geoffrey
la source
1

Sous Linux / unix / MacOSX, utilisez les fichiers de base (vous pouvez les activer avec ulimit ou un appel système compatible ). Sous Windows, utilisez le rapport d'erreurs Microsoft (vous pouvez devenir partenaire et accéder aux données de plantage de votre application).

Kasprzol
la source
0

J'ai oublié la technologie GNOME de "répart", mais je ne sais pas grand chose sur son utilisation. Il est utilisé pour générer des traces de pile et d'autres diagnostics pour le traitement et peut automatiquement déposer des bogues. Cela vaut certainement la peine de s'enregistrer.


la source