Comment puis-je trouver où une exception a été lancée en C++?

j'ai un programme qui fait une exception quelque part. Tout ce que j'obtiens, c'est le rapport d'une exception qui a été jetée, et aucune information sur l'endroit où elle a été jetée. Il semble illogique qu'un programme compilé pour contenir des symboles de débogage ne me prévienne pas de l'endroit où dans mon code une exception a été générée.

y a-t-il un moyen de dire où sont mes exceptions à moins de définir 'lancer' dans gdb et d'appeler un backtrace pour chaque exception lancée?

77
demandé sur Alex 2010-03-14 21:05:29

7 réponses

voici quelques informations que peut être d'utilisation dans le débogage de votre problème

si une exception n'est pas activé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 Standard C std::abort() . Si aucun nettoyage ne se produit pour une exception non récupérée , it may est en fait utile pour déboguer ce problème car aucun destructeur n'est appelé.

†il s'agit d'une implémentation-définie que la pile soit déboulée ou non avant que std::terminate() ne soit appelé.


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


vous pouvez installer votre propre fonction terminate() en utilisant std::set_terminate() . Vous devriez être en mesure de définir un point de rupture sur votre fonction terminate dans gdb. Vous peut être en mesure de générer une stack trace de votre terminate() fonction et ce backtrace peut aider à identifier l'emplacement de l'exception.

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


depuis terminate() appelle abort() par défaut (ce qui causera un signal SIGABRT par défaut), vous may être en mesure de définir un SIGABRT handler et ensuite imprimer une pile backtrace à partir de l'intérieur du signal handler . Cette backtrace peut aider à identifier l'emplacement de l'exception.


Remarque: 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 de capture peut être, et est souvent, situé dans une fonction/méthode différente que le point de jeter. Il m'a également été signalé dans les commentaires (merci Dan ) que c'est une implémentation-définie que la pile soit ou non déballée avant que terminate() ne soit appelé.

mise à jour: j'ai assemblé un programme de test Linux appelé qui génère un backtrace dans une fonction terminate() définie via set_terminate() et un autre dans un gestionnaire de signal pour SIGABRT . Les deux bactraces montrent correctement l'emplacement de la exception sans entrave.

Update 2: grâce à un billet de blog sur Catching uncaught exceptions within terminate , j'ai appris quelques nouveaux trucs, y compris le lancement de l'exception uncaught dans le handler de terminate. Il est important de noter que la déclaration vide throw dans le gestionnaire d'extrémité 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 caught unhanded exception. what(): RUNTIME ERROR!
my_terminate backtrace returned 10 frames

[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 (Aborted), address is 0x1239 from 0x42029331
crit_err_hdlr backtrace returned 13 frames

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

65
répondu jschmier 2017-05-23 10:30:43

comme vous le dites, nous pouvons utiliser 'catch throw' dans gdb et appeler 'backtrace' pour chaque exception lancée. Bien que cela soit habituellement trop fastidieux à faire manuellement, gdb permet l'automatisation du processus. Cela permet de voir la trame arrière de toutes les exceptions qui sont lancées, y compris la dernière Non-tirée:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

sans autre intervention manuelle, cela génère beaucoup de backtraces, y compris un pour la dernière exception uncaught:

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 grand billet de blog récapitulant ceci: http://741mhz.com/throw-stacktrace/

36
répondu TimJ 2014-11-02 02:00:50

vous pouvez créer une macro comme:

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

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

16
répondu Erik Hermansen 2010-03-16 17:59:47

vous n'avez pas transmis d'informations sur le type D'OS / compilateur que vous utilisez.

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

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

mon article "débogage Postmortem" , également sur ddj.com inclut le code pour utiliser le traitement D'exception structuré par Win32 (utilisé par l'instrumentation) pour la journalisation, etc.

5
répondu RED SOFT ADAIR 2010-03-15 10:02:10

vous pouvez marquer les principaux endroits serrés dans votre code comme noexcept pour localiser une exception, puis utiliser libunwind (il suffit d'ajouter -lunwind aux paramètres linker) (testé avec clang++ 3.6 ):

demagle.hpp:

#pragma once

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

démangle.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 la question.

4
répondu Orient 2015-07-26 05:42:53

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

1
répondu Ben Voigt 2010-03-14 18:11:26

vérifiez ce fil, peut-être qu'il aide:

Attraper tous les non gérée exceptions C++?

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 à un fichier pour toute exception non manipulée.

1
répondu nabulke 2017-05-23 11:47:32