web-dev-qa-db-fra.com

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

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

Existe-t-il un moyen de savoir d'où viennent mes exceptions, à moins de définir "catch throw" dans gdb et d'appeler une trace pour chaque exception levée?

84
Alex

Voici quelques informations utiles mai pour déboguer votre problème

Si une exception n'est pas interceptée, la fonction de bibliothèque spéciale std::terminate() est automatiquement appelée. Terminate est en fait un pointeur vers une fonction et la valeur par défaut est la fonction de bibliothèque C standard std::abort() . Si aucun nettoyage n'a lieu pour une exception non interceptée, il peut-être peut être utile pour déboguer ce problème car aucun destructeur n'est appelé.
† Il est défini par l'implémentation, que la pile soit déroulée ou non avant l'appel de std::terminate().


Un appel à abort() est souvent utile pour générer un vidage de mémoire qui peut être analysé pour déterminer la cause de l'exception. Assurez-vous que vous activez les vidages mémoire via ulimit -c unlimited (Linux).


Vous pouvez installer votre propre fonction terminate() en utilisant std::set_terminate(). Vous devriez pouvoir définir un point d'arrêt sur votre fonction d'arrêt dans gdb. Vous mai pouvez générer une trace de pile à partir de votre fonction terminate() et cette trace mai aide à identifier l'emplacement de l'exception.

Il y a une brève discussion sur exceptions non capturées dans La pensée de Bruce Eckel en C++, 2e éd. qui peut également être utile.


Puisque terminate() appelle abort() par défaut (ce qui provoquera un signal SIGABRT par défaut), vous mai pourrez définir un SIGABRT handler puis imprime une trace de pile depuis le gestionnaire de signal . Cette trace mai aide à identifier l'emplacement de l'exception.


Remarque: Je dis peut-être car C++ prend en charge la gestion des erreurs non locale grâce à l'utilisation de constructions de langage pour séparer la gestion des erreurs et rapport de code à partir de code ordinaire. Le bloc de capture peut être, et est souvent, situé dans une fonction/méthode différente de celle du point de lancement. Il m'a également été signalé dans les commentaires (merci Dan ) qu'il est défini par l'implémentation, que la pile soit déroulée ou non avant l'appel de terminate().

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

Mise à jour 2: Merci à un article de blog sur Attraper les exceptions non capturées dans terminate , j'ai appris quelques nouvelles astuces; y compris la relance de l'exception non interceptée dans le gestionnaire de terminaison. Il est important de noter que l'instruction throw vide dans le gestionnaire de terminaison personnalisé fonctionne 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 a détecté une exception à mains nues. what (): ERREUR D'EXÉCUTION! 
 my_terminate backtrace a renvoyé 10 images 
 
 [bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52] 
 [bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa] 
 [bt]: (2) /usr/lib/libstdc++-libc6.2-2 .so.3 [0x400468e5] 
 [bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf] 
 [bt ]: (4) ./test(throw_exception__Fv+0x68) [0x8049008] 
 [Bt]: (5) ./test(foo2__Fv+0xb) [0x8049043] 
 [Bt]: (6 ) ./test(foo1__Fv+0xb) [0x8049057] 
 [bt]: (7) ./test(main+0xc1) [0x8049121] 
 [bt]: (8) ./test (__libc_start_main + 0x95) [0x42017589] 
 [bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21] 
 
 signal 6 (abandonné), l'adresse est 0x1239 à partir de 0x42029331 
 crit_err_hdlr backtrace a renvoyé 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.so.3 [0x40045baa] 
 [bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5] 
 [bt]: (6) /usr/lib/libstdc++-libc6.2 -2.so.3 (__ rethrow + 0xaf) [0x40046bdf] 
 [Bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008] 
 [Bt]: (8)./test (foo2__Fv + 0xb) [0x8049043] 
 [bt]: (9) ./test(foo1__Fv+0xb) [0x8049057] 
 [bt]: (10) ./test(main + 0xc1) [0x8049121] 
 [Bt]: (11) ./test(__libc_start_main+0x95) [0x42017589] 
 [Bt]: (12) ./test(__eh_alloc+0x3d) [ 0x8048b21] 
 
69
jschmier

Comme vous le dites, nous pouvons utiliser "catch throw" dans gdb et appeler "backtrace" pour chaque exception levée. Bien que cela soit généralement trop fastidieux à faire manuellement, gdb permet l'automatisation du processus. Cela permet de voir la trace de toutes les exceptions levées, y compris la dernière non détectée:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

Sans autre intervention manuelle, cela génère de nombreuses backtraces, dont une pour la dernière exception non interceptée:

Catchpoint 1 (exception thrown), 0x00a30 in __cxa_throw () from libstdc++.so.6
#0  0x0da30 in __cxa_throw () from /usr/.../libstdc++.so.6
#1  0x021f2 in std::__throw_bad_weak_ptr () at .../shared_ptr_base.h:76
[...]
terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
Program received signal SIGABRT, Aborted.

Voici un excellent article de blog qui conclut ceci: http://741mhz.com/throw-stacktrace [sur archive.org]

45
TimJ

Vous pouvez créer une macro comme:

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

... et cela vous donnera l'emplacement où l'exception est levée (certes pas la trace de la pile). Il est nécessaire que vous dériviez vos exceptions d'une classe de base qui prend le constructeur ci-dessus.

16
Erik Hermansen

Vous pouvez marquer les principaux espaces restreints dans votre code comme noexcept pour localiser une exception, puis utilisez libunwind (ajoutez simplement -lunwind aux paramètres de l'éditeur de liens) (testé avec clang++ 3.6):

demagle.hpp:

#pragma once

char const *
get_demangled_name(char const * const symbol) noexcept;

demangle.cpp:

#include "demangle.hpp"

#include <memory>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free};
#pragma clang diagnostic pop

}

char const *
get_demangled_name(char const * const symbol) noexcept
{
    if (!symbol) {
        return "<null>";
    }
    int status = -4;
    demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status));
    return ((status == 0) ? demangled_name.get() : symbol);
}

backtrace.hpp:

#pragma once
#include <ostream>

void
backtrace(std::ostream & _out) noexcept;

backtrace.cpp:

#include "backtrace.hpp"

#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>

#include <cstdint>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

namespace
{

void
print_reg(std::ostream & _out, unw_Word_t reg) noexcept
{
    constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4;
    _out << "0x" << std::setfill('0') << std::setw(address_width) << reg;
}

char symbol[1024];

}

void
backtrace(std::ostream & _out) noexcept
{
    unw_cursor_t cursor;
    unw_context_t context;
    unw_getcontext(&context);
    unw_init_local(&cursor, &context);
    _out << std::hex << std::uppercase;
    while (0 < unw_step(&cursor)) {
        unw_Word_t ip = 0;
        unw_get_reg(&cursor, UNW_REG_IP, &ip);
        if (ip == 0) {
            break;
        }
        unw_Word_t sp = 0;
        unw_get_reg(&cursor, UNW_REG_SP, &sp);
        print_reg(_out, ip);
        _out << ": (SP:";
        print_reg(_out, sp);
        _out << ") ";
        unw_Word_t offset = 0;
        if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
            _out << "(" << get_demangled_name(symbol) << " + 0x" << offset << ")\n\n";
        } else {
            _out << "-- error: unable to obtain symbol name for this frame\n\n";
        }
    }
    _out << std::flush;
}

backtrace_on_terminate.hpp:

#include "demangle.hpp"
#include "backtrace.hpp"

#include <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>

#include <cstdlib>

#include <cxxabi.h>

namespace
{

[[noreturn]]
void
backtrace_on_terminate() noexcept;

static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{});

#pragma clang diagnostic Push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate};
#pragma clang diagnostic pop

[[noreturn]]
void
backtrace_on_terminate() noexcept
{
    std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any
    backtrace(std::clog);
    if (std::exception_ptr ep = std::current_exception()) {
        try {
            std::rethrow_exception(ep);
        } catch (std::exception const & e) {
            std::clog << "backtrace: unhandled exception std::exception:what(): " << e.what() << std::endl;
        } catch (...) {
            if (std::type_info * et = abi::__cxa_current_exception_type()) {
                std::clog << "backtrace: unhandled exception type: " << get_demangled_name(et->name()) << std::endl;
            } else {
                std::clog << "backtrace: unhandled unknown exception" << std::endl;
            }
        }
    }
    std::_Exit(EXIT_FAILURE); // change to desired return code
}

}

Il y a bon article concernant le problème.

5
Orient

Vous n'avez pas transmis d'informations sur le système d'exploitation/compilateur que vous utilisez.

Dans Visual Studio C++, les exceptions peuvent être instrumentées.

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

Mon article "Postmortem Debugging" , également sur ddj.com inclut du code pour utiliser la gestion des exceptions structurées Win32 (utilisée par l'instrumentation) pour la journalisation, etc.

5
RED SOFT ADAIR

J'ai du code pour le faire dans Windows/Visual Studio, faites-moi savoir si vous voulez un plan. Je ne sais pas comment le faire pour le code nain2 cependant, un rapide google suggère qu'il existe une fonction _Unwind_Backtrace dans libgcc qui fait probablement partie de ce dont vous avez besoin.

1
Ben Voigt

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

Attraper toutes les exceptions C++ non gérées?

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

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

Il peut imprimer une trace de pile dans un fichier pour toute exception non gérée.

1
nabulke