Frage Wie finde ich, wo eine Ausnahme in C ++ geworfen wurde?


Ich habe ein Programm, das irgendwo eine nicht abgefangene Ausnahme auslöst. Alles, was ich bekomme, ist ein Bericht über eine ausgelöste Ausnahme und keine Informationen darüber, wo sie ausgelöst wurde. Es scheint unlogisch für ein Programm, das mit Debug-Symbolen kompiliert wurde, mir nicht mitzuteilen, wo in meinem Code eine Ausnahme erzeugt wurde.

Gibt es eine Möglichkeit, zu sagen, wo meine Ausnahmen kommen, wenn ich 'catch throw' in gdb nicht einstelle und für jede einzelne ausgelöste Ausnahme ein backtrace aufruft?


75
2018-03-14 18:05


Ursprung


Antworten:


Hier sind einige Informationen kann nützlich sein, um dein Problem zu debuggen

Wenn eine Ausnahme nicht abgefangen wird, wird die spezielle Bibliotheksfunktion verwendet std::terminate() wird automatisch aufgerufen. Beenden ist eigentlich ein Zeiger auf eine Funktion und der Standardwert ist die Standard-C-Bibliotheksfunktion std::abort(). Wenn für eine nicht abgefangene Ausnahme keine Bereinigungen durchgeführt werdenes kann tatsächlich hilfreich beim Debuggen dieses Problems, da keine Destruktoren aufgerufen werden.
† Es ist implementationsdefiniert, ob der Stapel zuvor abgewickelt wurde oder nicht std::terminate() wird genannt.


Ein Anruf an abort() ist oft nützlich beim Generieren eines Core-Dumps, der analysiert werden kann, um die Ursache der Ausnahme zu ermitteln. Stellen Sie sicher, dass Sie Core-Dumps über aktivieren ulimit -c unlimited (Linux).


Sie können Ihre eigenen installieren terminate() funktionieren mit std::set_terminate(). Sie sollten in der Lage sein, einen Breakpoint für Ihre Terminierungsfunktion in gdb zu setzen. Sie kann in der Lage sein, einen Stack Backtrace von Ihrem zu generieren terminate() Funktion und dieses Backtrace kann Hilfe beim Identifizieren des Standorts der Ausnahme.

Es gibt eine kurze Diskussion über nicht abgefangene Ausnahmen im Bruce Eckels Denken in C ++, 2. Aufl das kann auch hilfreich sein.


Schon seit terminate() Anrufe abort() Standardmäßig (was zu einem SIGABRT Signal standardmäßig), Sie kann in der Lage sein, ein SIGABRT Handler und dann Drucken Sie einen Stack-Backtrace aus dem Signal-Handler. Dieses Backtrace kann Hilfe beim Identifizieren des Standorts der Ausnahme.


Hinweis: ich sage kann weil C ++ nicht-lokale Fehlerbehandlung durch die Verwendung von Sprachkonstrukten unterstützt, um Fehlerbehandlung und Berichtscode von gewöhnlichem Code zu trennen. Der Fangblock kann und ist oft in einer anderen Funktion / Methode als der Wurfpunkt angeordnet. Es wurde auch auf mich in den Kommentaren hingewiesen (danke Dan) dass es implementierungsdefiniert ist, ob der Stapel zuvor abgewickelt wurde oder nicht terminate() wird genannt.

Aktualisieren: Ich habe ein Linux - Testprogramm zusammengerufen, das ein Backtrace in einem terminate() Funktionseinstellung über set_terminate() und ein anderer in einem Signalhandler für SIGABRT. Beide Backtraces zeigen den Speicherort der nicht behandelten Ausnahme korrekt an.

Update 2: Danke an einen Blogbeitrag auf Abfangen von nicht abgefangenen Ausnahmen innerhalb von terminateIch habe ein paar neue Tricks gelernt; einschließlich des erneuten Auswerfens der nicht abgefangenen Ausnahme innerhalb des Beendigungshandlers. Es ist wichtig zu beachten, dass die leeren throw Die Anweisung im benutzerdefinierten Terminierungshandler funktioniert mit GCC und ist keine portable Lösung.

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);
}

Ausgabe:

my_terminate hat eine Ausnahme ohne Handzeichen abgefangen. was (): RUNTIME FEHLER!
my_terminate backtrace hat 10 Frames zurückgegeben

[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(__durchlauf+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 (abgebrochen), Adresse ist 0x1239 von 0x42029331
crit_err_hdlr backtrace hat 13 Frames zurückgegeben

[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(__durchlauf+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]


64
2018-03-15 07:08



Wie Sie sagen, können wir 'catch throw' in gdb verwenden und 'backtrace' für jede einzelne geworfene Ausnahme aufrufen. Während das normalerweise zu mühsam ist, um es manuell zu machen, ermöglicht gdb die Automatisierung des Prozesses. Dies ermöglicht das Anzeigen des Backtrace aller ausgelösten Ausnahmen, einschließlich der letzten nicht abgefangenen Ausnahmen:

gdb>

set pagination off
catch throw
commands
backtrace
continue
end
run

Ohne weiteren manuellen Eingriff erzeugt dies viele Rückverfolgungen, einschließlich einer für die letzte nicht abgefangene Ausnahme:

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.

Hier ist ein großartiger Blog-Beitrag, der dies umschließt: http://741mhz.com/throw-stacktrace/


34
2017-11-02 02:00



Sie können ein Makro erstellen wie:

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

... und es gibt Ihnen den Ort, an dem die Ausnahme ausgelöst wird (zugegebenermaßen nicht der Stack-Trace). Sie müssen Ihre Ausnahmen von einer Basisklasse ableiten, die den obigen Konstruktor verwendet.


16
2018-03-14 19:07



Sie haben keine Informationen über den von Ihnen verwendeten OS / Compiler weitergegeben.

In Visual Studio C ++ können Ausnahmen instrumentiert werden.

Sehen "Visual C ++ Ausnahmebehandlungsinstrumentation" auf ddj.com

Mein Artikel "Postmortem-Debugging", enthält auch auf ddj.com Code, um die strukturierte Win32-Ausnahmebehandlung (die von der Instrumentierung verwendet wird) für die Protokollierung usw. zu verwenden.


5
2018-03-15 10:02



Sie können wichtige, enge Stellen in Ihrem Code als markieren noexcept Um eine Ausnahme zu finden, verwenden Sie libunwind (einfach hinzufügen -lunwind Linker - Parameter) (getestet mit 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
}

}

Es gibt guter Artikel in Bezug auf das Problem.


4
2017-07-26 05:42



Ich habe Code, um dies in Windows / Visual Studio zu tun, lassen Sie mich wissen, wenn Sie eine Gliederung möchten. Ich weiß nicht, wie man es für den dwarf2-Code macht, aber ein kurzer Google-Tipp legt nahe, dass es eine Funktion _Unwind_Backtrace in libgcc gibt, die wahrscheinlich Teil dessen ist, was Sie brauchen.


1
2018-03-14 18:11



Überprüfen Sie diesen Thread, vielleicht hilft es:

Alle nicht behandelten C ++ - Ausnahmen abfangen?

Ich habe mit dieser Software gute Erfahrungen gemacht:

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

Es kann einen Stack-Trace für eine nicht behandelte Ausnahme in eine Datei drucken.


1
2018-03-14 18:11