Question

Je travaille sur Linux avec le compilateur GCC. Lorsque mon programme C ++ se bloque, j'aimerais qu'il génère automatiquement un stacktrace.

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

Je voudrais que mon programme puisse générer une trace de pile lorsqu'il se bloque et que l'utilisateur le lance à nouveau, il leur demandera s'il est correct de m'envoyer la trace de pile afin que je puisse localiser le problème. . Je peux gérer l'envoi des informations mais je ne sais pas comment générer la chaîne de trace. Des idées?

Était-ce utile?

La solution

Pour Linux et Mac OS X, si vous utilisez gcc, ou tout compilateur utilisant glibc, vous pouvez utiliser les fonctions backtrace () de execinfo.h pour imprimer un stacktrace et se terminer normalement lorsque vous effectuez une segmentation. faute. La documentation se trouve dans le manuel de libc .

Voici un exemple de programme qui installe un gestionnaire SIGSEGV et imprime un chemin de pile sur stderr lorsqu’il commute par défaut. La baz() fonction ici provoque 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 -rdynamic vous permet d’obtenir des informations sur les symboles dans votre sortie, que la glibc peut utiliser pour créer une belle pile:

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

L'exécution de ceci vous donne 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]

Ceci affiche le module de chargement, le décalage et la fonction dont provient chaque image de la pile. Ici, vous pouvez voir le gestionnaire de signal en haut de la pile et les fonctions libc avant main en plus de foo, bar, baz et <=>.

Autres conseils

Linux

Bien que l'utilisation de la fonction backtrace () dans execinfo.h pour imprimer une trace de pile et quitter normalement lorsque vous obtenez une erreur de segmentation a déjà été suggéré , je ne vois aucune mention des subtilités nécessaires pour garantir que les points de trace résultants indiquent l'emplacement réel du défaut (du moins pour certaines architectures - x86 & amp; ARM).

Lorsque vous entrez dans le gestionnaire de signaux, les deux premières entrées de la chaîne d'images de la pile contiennent une adresse de retour dans le gestionnaire de signaux et une autre dans sigaction () dans libc. La trame de la dernière fonction appelée avant le signal (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);
}

Sortie

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 je viens de décrire ici très utiles pour le débogage des 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_pc au 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

C’est encore plus simple que & "; backtrace man &"; il existe une bibliothèque peu documentée (spécifique à GNU) distribuée avec la glibc sous le nom libSegFault.so, qui a été, je crois, écrite par Ulrich Drepper programme catchsegv (voir & "; homme catchsegv &";).

Cela nous donne 3 possibilités. Au lieu d'exécuter & Quot; programme -o hai & Quot;

  1. Exécuter dans catchsegv:

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

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  3. Lien avec libSegFault lors 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 en 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 (notez 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; un = arbre; f = déboguer

Même si un La bonne réponse a été fournie. Elle décrit comment utiliser la fonction GNU libc backtrace() et 1 . J'ai fourni ma propre réponse , qui explique comment assurer la traçabilité d'un gestionnaire de signaux sur l'emplacement réel de le défaut 2 , je ne vois aucune mention de démêler le C ++ sortie des symboles de la trace.

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

  • 1 Linux & amp; OS X Notez que __cxa_demangle et ./test sont spécifiques à GCC
  • 2 Linux

L'exemple C ++ Linux suivant utilise le même gestionnaire de signal que mon autre réponse et montre comment ./test 2>&1 | c++filt peut ê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;
}

Résultat (<=>):

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]

Résultats démêlés (<=>):

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 construit sur le gestionnaire de signaux à partir de mon réponse originale et peut remplacer le gestionnaire de signaux dans l'exemple ci-dessus pour montrer comment <=> peut être utilisé pour démêler les symboles. Ce gestionnaire de signaux produit la même sortie 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);
}

Il vaut peut-être la peine de consulter Google Breakpad , un générateur de vidage de mémoire multiplate-forme et des outils permettant de: traiter les décharges.

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

GCC dispose également de deux éléments intégrés qui peuvent vous aider, mais qui peuvent ou non être implémentés intégralement dans votre architecture, à savoir __builtin_frame_address et __builtin_return_address. Les deux qui veulent un niveau entier immédiat (par immédiat, je veux dire que ça ne peut pas être une variable). Si <=> pour un niveau donné est différent de zéro, il convient de récupérer l'adresse de retour du même niveau.

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

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

ddd et nemiver sont des interfaces pour gdb qui facilitent grandement son travail pour les novices.

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

J'ai écrit un script rapide et compliqué 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 devrait afficher le résultat suivant pour chaque niveau de 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 

Il est important de noter qu’une fois que vous avez généré un fichier core, vous devez utiliser l’outil gdb pour l’examiner. Pour que gdb donne un sens à votre fichier core, vous devez dire à gcc d’instrumenter le binaire avec les 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"! "; le laisser vider un noyau, ou 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 cela aide.

Je regarde ce problème depuis un moment.

Et enfoui dans le fichier 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'aimerais 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

Certaines versions de libc contiennent des fonctions traitant des traces de pile; vous pourrez peut-être les utiliser:

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

Je me souviens d’avoir utilisé libunwind il y a longtemps pour obtenir des traces de pile, mais cela peut ne pas être pris en charge. sur votre plate-forme.

Oubliez les modifications de vos sources et faites quelques piratages avec la fonction backtrace () ou les macroses - ce ne sont que des solutions médiocres.

Comme solution efficace, je vous conseillerais:

  1. Compilez votre programme avec " -g " indicateur d’incorporation des symboles de débogage en binaire (ne vous inquiétez pas, cela n’affectera pas vos performances).
  2. Sous Linux, exécutez la commande suivante: " ulimit -c unlimited " - permettre au système de faire de gros dumps.
  3. Lorsque votre programme a échoué, vous verrez dans le répertoire de travail le fichier " core ".
  4. Exécutez la commande suivante pour imprimer la trace de retour sur la sortie standard: gdb -batch -ex & "backtrace &"; ./votre_programme_exe ./core

Ceci imprimera une trace lisible 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 core dump, puis envoyez les traces par courrier électronique aux développeurs, ou connectez-le à un système de journalisation.

ulimit -c unlimited

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

salutations

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

Regardez:

man 3 backtrace

Et:

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

Ce sont des extensions GNU.

Voir la fonction de trace de pile dans ACE (environnement de communication ADAPTIVE). Il est déjà écrit pour couvrir toutes les plateformes majeures (et plus). La bibliothèque est sous licence BSD. Vous pouvez même copier / coller le code si vous ne voulez pas utiliser ACE.

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

* nix: vous pouvez intercepter SIGSEGV (ce signal est généralement émis avant le crash) et conserver les informations dans un fichier . (à part le fichier core que vous pouvez utiliser pour déboguer en utilisant gdb par exemple).

gagner: Vérifiez ceci à partir de msdn.

Vous pouvez également consulter le code chrome de Google pour savoir comment il gère les plantages. Son mécanisme de gestion des exceptions est agréable.

J'ai constaté que la solution @tgamblin n'est pas complète. Il ne peut pas gérer avec stackoverflow. Je pense que parce que le gestionnaire de signal par défaut 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;
} 

J'utiliserais le code qui génère une trace de pile pour la mémoire perdue dans Détecteur de fuite visuel . Cela ne fonctionne que sur Win32, cependant.

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

Le nouveau roi en 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);
}

Outre les réponses ci-dessus, expliquez comment vous souhaitez que le système d’exploitation Debian Linux génère un vidage mémoire

  1. Créez une & # 8220; coredumps & # 8221; dossier dans le dossier de base de l'utilisateur
  2. Accédez à /etc/security/limits.conf. Au-dessous de la ligne, tapez & # 8220; noyau dur illimité & # 8221 ;, et & # 8220; noyau mou racine illimité & # 8221; si vous activez les vidages mémoire pour root, laissez un espace illimité pour les vidages mémoire.
  3. REMARQUE: & # 8220; * noyau souple illimité & # 8221; ne couvre pas la racine, raison pour laquelle la racine doit être spécifiée dans sa propre ligne.
  4. Pour vérifier ces valeurs, déconnectez-vous, reconnectez-vous et tapez & # 8220; ulimit -a & # 8221 ;. & # 8220; Taille du fichier principal & # 8221; devrait être mis à illimité.
  5. Vérifiez les fichiers .bashrc (utilisateur et root, le cas échéant) pour vous assurer que ulimit n’est pas défini à cet emplacement. Sinon, la valeur ci-dessus sera écrasée au démarrage.
  6. Ouvrez /etc/sysctl.conf. Entrez les éléments suivants en bas: & # 8220; 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 & # 8220; sysctl -p & # 8221; charger la nouvelle configuration Vérifiez / proc / sys / kernel / core_pattern et vérifiez que cela correspond à ce que vous venez de taper.
  8. La sauvegarde du noyau peut être testée en exécutant un processus sur la ligne de commande (& # 8220; & et;! & # 8221;), puis en le tuant avec & # 8220; kill -11 & # 8221 ;. Si le transfert du noyau réussit, vous verrez & # 8220; (noyau déchargé) & # 8221; après l'indication d'erreur de segmentation.

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

  

À partir de Windows Server 2008 et de Windows Vista avec Service Pack 1 (SP1), le rapport d'erreurs Windows (WER) peut être configuré pour que les vidages complets en mode utilisateur 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 la fonctionnalité 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 en rapportant des erreurs \ 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 cliché 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 utiliser WER comme indiqué ci-dessus ou appeler MiniDumpWriteDump vous-même, si vous avez besoin d’un contrôle plus fin de la quantité d’informations à vider. (Assurez-vous de l'appeler depuis un processus différent.)
  • Un élément 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'expédier les symboles de débogage. Cela réduit considérablement la taille de votre déploiement et rend plus difficile le reverse engineering de votre application.
  • Largement indépendant du compilateur que vous utilisez. L'utilisation de WER ne nécessite même pas de code. De toute façon, avoir un moyen d’obtenir une base de données de symboles (PDB) est très utile pour l’analyse hors ligne. Je pense que GCC peut générer des PDB ou qu'il existe des outils pour convertir la base de données de symboles au format PDB.

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

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

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

J'ai oublié la technologie GNOME de & "fournir &", mais je ne sais pas trop comment l'utiliser. Il est utilisé pour générer des stacktraces et d’autres diagnostics à des fins de traitement et peut automatiquement classer les bogues. Cela vaut certainement la peine de vérifier.

Il semble que dans l'une des dernières versions de c ++ boost, une bibliothèque est apparue pour fournir exactement ce que vous voulez, probablement le code serait multiplateforme. C'est boost :: stacktrace , que vous pouvez utilisez comme dans l'échantillon 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 recopiée à partir de la documentation de renforcement :

0# bar(int) at /path/to/source/file.cpp:70
1# bar(int) at /path/to/source/file.cpp:70
2# bar(int) at /path/to/source/file.cpp:70
3# bar(int) at /path/to/source/file.cpp:70
4# main at /path/to/main.cpp:93
5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
6# _start

Si vous voulez toujours y aller seul comme vous l'avez fait, vous pouvez créer un lien contre bfd et éviter d'utiliser addr2line comme je l'ai fait ici:

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

Ceci 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]
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top