108 votes

Comment trouver où une exception a été levée en C ++?

J'ai un programme qui jette une exception non capturée quelque part. Tout ce que je reçois est le rapport d'une exception levée et aucune information sur l'endroit où elle a été lancée. Il semble illogique qu'un programme compilé contenant des symboles de débogage ne me prévienne pas de l'endroit où une exception a été générée dans mon code.

Existe-t-il un moyen de savoir d'où viennent mes exceptions si je ne mets pas "catch throw" dans gdb et appelle un backtrace pour chaque exception levée?

81voto

jschmier Points 8088

Voici quelques infos qui peuvent être utiles dans le débogage de votre problème

Si une exception est interceptée, la bibliothèque spéciale de la fonction std::terminate() est automatiquement appelé. Mettre fin est en fait un pointeur à une fonction et la valeur par défaut est le Standard de la fonction de la bibliothèque C std::abort(). Si aucun des nettoyages de se produire pour l'une exception non interceptée, il peut effectivement être utile pour le débogage de ce problème car aucun les destructeurs sont appelés.
†Il est définis par l'implémentation de savoir si ou non la pile est déroulée avant d' std::terminate() est appelé.


Un appel à l' abort() est souvent utile dans la génération d'un core dump qui peuvent être analysés afin de déterminer la cause de l'exception. Assurez-vous d'activer core dumps via ulimit -c unlimited (Linux).


Vous pouvez installer votre propre terminate() fonction à l'aide de std::set_terminate(). Vous devriez être en mesure de définir un point d'arrêt sur votre mettre fin à la fonction de gdb. Vous pouvez être en mesure de générer une trace de la pile de votre terminate() fonction et cette trace peut aider à déterminer l'emplacement de l'exception.

Il y a une brève discussion sur les exceptions non traitées dans Bruce Eckel de Penser en C++, 2e Ed qui peut être utile.


Depuis terminate() des appels abort() par défaut (ce qui va provoquer un SIGABRT du signal par défaut), vous pourriez être en mesure de définir un SIGABRT gestionnaire, puis imprimer une trace de la pile à partir de dans le gestionnaire de signal. Cette trace peut aider à déterminer l'emplacement de l'exception.


Note: je dis peut parce que le C++ prend en charge non-locale de gestion des erreurs par le biais de l'utilisation de la langue des constructions de séparer la gestion d'erreur et de reporting du code de l'action ordinaire de code. Le bloc catch peut être, et est souvent, situé dans une autre fonction/méthode que le point de jeter. Il a également été souligné à moi dans les commentaires (merci Dan) qu'elle est mise en œuvre-définir si ou non la pile est déroulée avant d' terminate() est appelé.

Mise à jour: j'ai jeté un Linux programme de test appelé qui génère une trace dans un terminate() de la fonction définie par set_terminate() et un autre dans un gestionnaire de signal pour SIGABRT. Les deux backtraces afficher correctement l'emplacement de l'exception non gérée.

Mise à jour 2: Grâce à un billet de blog sur Attraper les exceptions à l'intérieur de résilier, j'ai appris quelques nouveaux trucs, y compris le re-lancement de l'exception non interceptée dans le résilier gestionnaire. Il est important de noter que le vide d' throw énoncé dans la coutume de résilier le gestionnaire travaille avec GCC et n'est pas une solution portable.

Code:

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <stdexcept>

void my_terminate(void);

namespace {
    // invoke set_terminate as part of global constant initialization
    static const bool SET_TERMINATE = std::set_terminate(my_terminate);
}

// 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) {
    sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

    // Get the address at the time the signal was raised from the EIP (x86)
    void * caller_address = (void *) uc->uc_mcontext.eip;

    std::cerr << "signal " << sig_num 
              << " (" << strsignal(sig_num) << "), address is " 
              << info->si_addr << " from " 
              << caller_address << std::endl;

    void * array[50];
    int size = backtrace(array, 50);

    std::cerr << __FUNCTION__ << " backtrace returned " 
              << size << " frames\n\n";

    // overwrite sigaction with caller's address
    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) {
        std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    exit(EXIT_FAILURE);
}

void my_terminate() {
    static bool tried_throw = false;

    try {
        // try once to re-throw currently active exception
        if (!tried_throw++) throw;
    }
    catch (const std::exception &e) {
        std::cerr << __FUNCTION__ << " caught unhandled exception. what(): "
                  << e.what() << std::endl;
    }
    catch (...) {
        std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." 
                  << std::endl;
    }

    void * array[50];
    int size = backtrace(array, 50);    

    std::cerr << __FUNCTION__ << " backtrace returned " 
              << size << " frames\n\n";

    char ** messages = backtrace_symbols(array, size);

    for (int i = 0; i < size && messages != NULL; ++i) {
        std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl;
    }
    std::cerr << std::endl;

    free(messages);

    abort();
}

int throw_exception() {
    // throw an unhandled runtime error
    throw std::runtime_error("RUNTIME ERROR!");
    return 0;
}

int foo2() {
    throw_exception();
    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(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) {
        std::cerr << "error setting handler for signal " << SIGABRT 
                  << " (" << strsignal(SIGABRT) << ")\n";
        exit(EXIT_FAILURE);
    }

    foo1();

    exit(EXIT_SUCCESS);
}

Sortie:

my_terminate pris unhanded exception. quel (le): RUNTIME ERROR!
my_terminate backtrace retourné à 10 images

[bt]: (0) ./test(mon_résilier__Fv+0x1a) [0x8048e52]
[bt]: (1) /usr/lib/libstdc++-libc6.2-2..3 [0x40045baa]
[bt]: (2) /usr/lib/libstdc++-libc6.2-2..3 [0x400468e5]
[bt]: (3) /usr/lib/libstdc++-libc6.2-2..3(__rethrow+0xaf) [0x40046bdf]
[bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (5) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (6) ./test(toto1__Fv+0xb) [0x8049057]
[bt]: (7) ./test(principal+0xc1) [0x8049121]
[bt]: (8) ./test(__libc_start_+0x95) [0x42017589]
[bt]: (9) ./test(__hein_alloc+0x3d) [0x8048b21]

signal 6 (Avorté), l'adresse est 0x1239 de 0x42029331
crit_err_hdlr backtrace retournée 13 images

[bt]: (1) ./test(kill+0x11) [0x42029331]
[bt]: (2) ./test(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./test [0x8048f9f]
[bt]: (4) /usr/lib/libstdc++-libc6.2-2..3 [0x40045baa]
[bt]: (5) /usr/lib/libstdc++-libc6.2-2..3 [0x400468e5]
[bt]: (6) /usr/lib/libstdc++-libc6.2-2..3(__rethrow+0xaf) [0x40046bdf]
[bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (8) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./test(toto1__Fv+0xb) [0x8049057]
[bt]: (10) ./test(principal+0xc1) [0x8049121]
[bt]: (11) ./test(__libc_start_+0x95) [0x42017589]
[bt]: (12) ./test(__hein_alloc+0x3d) [0x8048b21]

19voto

Erik Hermansen Points 566

Vous pouvez créer une macro comme:

 #define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
 

... et cela vous indiquera l'emplacement où l'exception est levée (certes, pas la trace de la pile). Vous devez dériver vos exceptions d'une classe de base prenant le constructeur ci-dessus.

3voto

RED SOFT ADAIR Points 5762

Vous n'avez pas transmettre d'informations à propos de l'OS / Compilateur que vous utilisez.

Dans Visual Studio C++ Exceptions peuvent être instrumentés.

Voir "Visual C++ de gestion des exceptions de l'Instrumentation" sur ddj.com

Mon article "Post-mortem de Débogage", également sur ddj.com inclut le code à utiliser Win32 gestion structurée des exceptions (utilisé par l'instrumentation) pour l'enregistrement, etc.

1voto

nabulke Points 3874

Vérifiez ce fil, peut-être que cela aide:

http://stackoverflow.com/questions/276102/catching-all-unhandled-c-exceptions

J'ai fait de bonnes expériences avec ce logiciel:

http://www.codeproject.com/KB/applications/blackbox.aspx

Il peut imprimer une trace de la pile d'un fichier pour une exception non gérée.

1voto

Ben Voigt Points 151460

J'ai le code pour le faire dans Windows / Visual Studio, laissez-moi savoir si vous voulez un aperçu. Cependant, je ne sais pas comment le faire pour le code nain2, un rapide Google suggère qu’il existe une fonction _Unwind_Backtrace dans libgcc qui fait probablement partie de vos besoins.

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