675 votes

Comment générer un stacktrace lorsque mon gcc C++ application tombe en panne

Quand mon c++ application se bloque, je voudrais générer une stacktrace.

J'ai déjà demandé cela, mais je suppose que j'ai besoin de clarifier mes besoins.

Mon application est en cours d'exécution par beaucoup d'utilisateurs différents et il fonctionne aussi sur Linux, Windows et Macintosh ( toutes les versions sont compilés à l'aide de gcc ).

Je voudrais que mon programme pour être en mesure de générer une trace de la pile quand il se bloque et la prochaine fois que l'utilisateur de la course, il va leur demander si c'est ok pour envoyer la trace de la pile pour moi afin que je puisse traquer le problème. Je peux gérer l'envoi de l'info pour moi, mais je ne sais pas comment générer la trace de la chaîne. Des idées?

580voto

tgamblin Points 25755

Pour Linux et je crois que Mac OS X, si vous utilisez gcc, ou un compilateur qui utilise la glibc, vous pouvez utiliser le backtrace() les fonctions en execinfo.h imprimer une stacktrace et se terminer normalement, lorsque vous obtenez une erreur de segmentation. La Documentation peut être trouvée dans la libc manuel.

Voici un exemple de programme qui installe un SIGSEGV gestionnaire et imprime une stacktrace d' stderr quand il segmentation. L' baz() fonction provoque l'erreur de segmentation 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.
}

Compilation avec -g -rdynamic vous obtient le symbole de l'info dans votre sortie, qui glibc pouvez utiliser pour faire une belle stacktrace:

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

L'exécution de cette, vous reçoit 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 charge, du décalage et de la fonction que chaque image dans la pile sont venus. Ici vous pouvez voir le gestionnaire de signal sur le dessus de la pile, et les fonctions de la libc avant d' main de plus de main, foo, bar, et baz.

167voto

jhclark Points 763

C'est même plus facile que "l'homme trace", il y a un peu documentés (bibliothèque GNU spécifique) distribué avec la glibc comme libSegFault.alors, qui était, je crois, a été écrit par Ulrich Drepper à l'appui du programme catchsegv (voir "l'homme catchsegv").

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

  1. Exécuter dans un délai de catchsegv:

    $ catchsegv program -o hai
    
  2. Lien avec libSegFault au moment 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 plus claire backtraces avec moins d'optimisation (gcc-O0 ou -O1) et les symboles de débogage (gcc-g). Sinon, vous pourriez finir avec un tas d'adresses de mémoire.

Vous pouvez également prendre 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 doit ressembler à ceci (notez le backtrace en bas):

* Segmentation fault Registre de vidage:

EAX: 0000000c EBX: 00000080 ECX: 00000000 EDX: 0000000c ESI: bfdbf080 EDI: 080497e0 EBP: bfdbee38 ESP: bfdbee20

EIP: 0805640f EFLAGS: 00010282

CS: 0073 DS: 007 b ES: 007 b FS: 0000 GS: 0033 SS: 007 b

Piège: 0000000e Erreur: 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.donc[0xb7f9e100] ??:0(??)[0xb7fa3400] /usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEe4pusherkss)[0x805647a] /home/dbingham/src/la terre du milieu-boue/alpha6/src/engine/joueur.rpc:73(_ZN6Player5inputESs)[0x805377c] /home/dbingham/src/la terre du milieu-boue/alpha6/src/engine/socket.rpc:159(_ZN6Socket4ReadEv)[0x8050698] /home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad] /home/dbingham/src/la terre du milieu-boue/alpha6/src/engine/socket.rpc:300(ZN12ServerSocket4pollEv)[0x8050b44] /home/dbingham/src/la terre du milieu-boue/alpha6/src/engine/main.rpc:34(principale)[0x8049a72] /lib/tls/i686/cmov/libc..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/erreur de segmentation.c et son répertoire parent http://sourceware.org/git/?p=glibc.git;a=arbre;f=debug

127voto

jschmier Points 8088

Linux

Tandis que l'utilisation de la backtrace() fonctions dans execinfo.h pour imprimer une stacktrace et se terminer normalement, lorsque vous obtenez une erreur de segmentation a déjà été suggéré, je ne vois aucune mention de la complexité nécessaire pour que la trace des points à l'emplacement réel de la faute (au moins pour certaines architectures x86 et ARM).

Les deux premières entrées dans le cadre de la pile de la chaîne lorsque vous arrivez dans le gestionnaire de signal contenir une adresse de retour à l'intérieur du gestionnaire de signal et une à l'intérieur de sigaction() de la libc. Le cadre de la pile de la dernière fonction appelée avant que le signal (qui est l'endroit de la faute) 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 dangers de l'appel de la backtrace() fonctions dans un gestionnaire de signal, existent encore, et ne doit pas être négligé, mais je trouve la fonctionnalité que j'ai décrit ici très utile pour le débogage des accidents.

Il est important de noter que l'exemple que j'ai fourni est développé et/ou testé sur Linux pour x86. J'ai également mis en œuvre avec succès sur ce BRAS à l'aide d' 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 mise en œuvre: http://www.linuxjournal.com/article/6391

92voto

jschmier Points 8088

Même si une bonne réponse a été fournie qui décrit l'utilisation de la GNU libc backtrace() de la fonction1 et j'ai fourni ma propre réponse qui décrit comment assurer une trace à partir d'un gestionnaire de signal points à l'emplacement réel de la faute2, je ne vois aucune mention de demangling C++ symboles de sortie à partir de la trace.

Lors de l'obtention de backtraces à partir d'un programme C++, la puissance de sortie peut être exécuté à travers c++filt1 à demangle les symboles ou en utilisant abi::__cxa_demangle1 directement.

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

La suite C++ Linux exemple utilise le même gestionnaire de signal que mon autre réponse, et montre comment, c++filt peut être utilisé pour demangle 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;
}

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

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

Le suivant s'appuie sur le gestionnaire de signal de ma réponse originale à cette question et peut remplacer le gestionnaire de signal dans l'exemple ci-dessus pour démontrer comment l' abi::__cxa_demangle peut être utilisé pour demangle les symboles. Ce gestionnaire de signal produit le même demangled de sortie comme 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);
}

33voto

Simon Steele Points 8344

Pourrait être intéressant de regarder Google Breakpad, un générateur de multi-plateforme crash dump et d’outils pour traiter les décharges.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X