Question Comment générer automatiquement une pile quand mon programme gcc C ++ se bloque


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 et fonctionne également sous Linux, Windows et Macintosh (toutes les versions sont compilées à l'aide de gcc).

Je voudrais que mon programme soit capable de générer une trace de pile en cas de panne et que la prochaine fois que l'utilisateur l'exécute, il lui demandera s'il peut envoyer la trace de la pile pour que je puisse identifier le problème. Je peux gérer l'envoi de l'information à moi mais je ne sais pas comment générer la chaîne de trace. Des idées?


482
2017-09-16 20:58


origine


Réponses:


Pour Linux et je crois que Mac OS X, si vous utilisez gcc, ou n'importe quel compilateur qui utilise glibc, vous pouvez utiliser les fonctions backtrace () dans execinfo.h pour imprimer une pile et quitter normalement lorsque vous obtenez une erreur de segmentation. La documentation peut être trouvée dans le manuel libc.

Voici un exemple de programme qui installe un SIGSEGV gestionnaire et imprime une pile à stderr quand il segfaults. le baz() La fonction provoque ici la 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.
}

Compiler avec -g -rdynamic obtient des informations sur les symboles dans votre sortie, que glibc peut utiliser pour créer une belle 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 provient chaque image de la pile. Ici vous pouvez voir le gestionnaire de signal au-dessus de la pile, et les fonctions de libc avant main en plus de main, foo, bar, et baz.


418
2017-09-16 21:30



Linux

Alors que l'utilisation de la fonction backtrace () fonctionne dans execinfo.h pour imprimer une pile et quitter gracieusement lorsque vous obtenez une erreur de segmentation déjà été suggéré, Je ne vois aucune mention des subtilités nécessaires pour s'assurer que le backtrace qui en résulte pointe vers l'emplacement réel de la faille (au moins pour certaines architectures - x86 et ARM).

Les deux premières entrées de la chaîne de cadres de pile lorsque vous entrez dans le gestionnaire de signal contiennent une adresse de retour dans le gestionnaire de signal et une dans sigaction () dans libc. Le frame de pile de la dernière fonction appelée avant que le signal (qui est l'emplacement de la panne) est perdu.

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 signal existent toujours et ne doivent pas être négligés, mais je trouve que les fonctionnalités que j'ai décrites ici sont 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é sous Linux pour x86. J'ai également mis en œuvre avec succès 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


107
2017-12-18 00:05



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, ce qui a été écrit par Ulrich Drepper pour supporter le programme catchsegv (voir "man catchsegv").

Cela nous donne 3 possibilités. Au lieu de lancer "programme -o hai":

  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 au moment de la compilation:

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

Dans les 3 cas, vous obtiendrez des backtraces plus claires avec une optimisation moindre (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 (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;a=tree;f=debug 


101
2017-07-06 15:51



Même si un bonne réponse a été fourni qui décrit comment utiliser la libc GNU backtrace() fonction1 et j'ai fourni ma propre réponse qui décrit comment faire en sorte qu'une trace d'un gestionnaire de signal pointe vers l'emplacement réel de la panne2, Je ne vois aucune mention de démêler Symboles C ++ sortis du backtrace.

Lors de l'obtention de backtraces à partir d'un programme C ++, le résultat peut être exécuté via c++filt1 pour démêler les symboles ou en utilisant abi::__cxa_demangle1 directement.

  • 1  Linux et OS X Notez que c++filt et __cxa_demangle sont spécifiques au GCC
  • 2  Linux

L'exemple C ++ Linux suivant utilise le même gestionnaire de signal que mon autre réponse et démontre comment 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;
}

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

73
2018-03-26 19:52



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


33
2017-09-17 11:31



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 entièrement implémentées dans votre architecture. __builtin_frame_address et __builtin_return_address. Les deux qui veulent un niveau entier immédiat (par immédiat, je veux dire qu'il ne peut pas être une variable). Si __builtin_frame_address pour un niveau donné est non-nul, il devrait être sûr d'attraper l'adresse de retour du même niveau.


20
2017-09-16 20:52



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

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

ddd et nemiver sont des frontaux pour gdb, ce qui facilite le travail avec le novice.


12
2017-09-16 21:23



Certaines versions de libc contiennent des fonctions qui traitent des traces de pile; vous pourriez être capable de 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.


10
2017-09-16 20:51