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

Ich habe ein Programm, das irgendwo eine nicht abgefangene Ausnahme austriggers. Alles, was ich bekomme, ist ein Bericht über eine ausgetriggerse Ausnahme und keine Informationen darüber, wo sie ausgetriggers 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 ausgetriggerse Ausnahme ein backtrace aufruft?

Hier sind einige Informationen, die beim Debuggen Ihres Problems hilfreich sein können

Wenn eine Ausnahme nicht gefunden wird, wird automatisch die spezielle Bibliotheksfunktion std::terminate() aufgerufen. Beenden ist eigentlich ein pointers auf eine function und der Standardwert ist die Standard-C-Bibliotheksfunktion std::abort() . Wenn keine Aufräumvorgänge für eine nicht abgefangene Ausnahme † stattfinden , kann dies beim Debuggen dieses Problems hilfreich sein, da keine Destruktoren aufgerufen werden.
† Es ist implementierungsdefiniert, ob der Stapel abgewickelt wird oder nicht, bevor std::terminate() aufgerufen wird.


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


Sie können Ihre eigene function terminate() mit std::set_terminate() installieren. Sie sollten in der Lage sein, einen Breakpoint für Ihre Terminierungsfunktion in gdb zu setzen. Sie können möglicherweise einen Stack-Backtrace von Ihrer function terminate() generieren, und dieser Backtrace kann beim Identifizieren der Position der Ausnahme helfen.

Es gibt eine kurze Diskussion über nicht abgefangene Ausnahmen in Bruce Eckels Thinking in C ++, 2. Ausgabe , die ebenfalls hilfreich sein kann.


Da SIGABRT abort() standardmäßig abort() terminate() aufruft (was standardmäßig ein SIGABRT Signal verursacht), können Sie möglicherweise einen SIGABRT Handler setzen und dann einen Stack-Backtrace aus dem Signal-Handler ausdrucken . Dieses Backtrace kann beim Identifizieren des Standorts der Ausnahme helfen.


Hinweis: Ich sage vielleicht, 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 function / Methode als der Wurfpunkt angeordnet. Es wurde mir auch in den Kommentaren (dan dan ) darauf hingewiesen, dass es implementation-defined ist, ob der Stack abgewickelt wird oder nicht, bevor terminate() aufgerufen wird.

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

Update 2: Dank eines Blogposts über Catching uncaught exceptions in terminate habe ich ein paar neue Tricks gelernt; einschließlich des erneuten Auscastings der nicht abgefangenen Ausnahme innerhalb des Beendigungshandlers. Es ist wichtig zu beachten, dass die leere throw statement innerhalb des benutzerdefinierten Terminierungshandlers mit GCC funktioniert und keine portable Lösung ist.

Code:

 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef __USE_GNU #define __USE_GNU #endif #include  #include  #include  #include  #include  #include  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]

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 processes. Dies ermöglicht das Anzeigen des Backtrace aller ausgetriggersen 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 toller Blog-Beitrag, der das einpackt : http://741mhz.com/throw-stacktrace/

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 ausgetriggers wird (zugegebenermaßen nicht der Stack-Trace). Sie müssen Ihre Ausnahmen von einer Basisklasse ableiten, die den obigen Konstruktor verwendet.

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

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

Siehe “Visual C ++ Exception-Handling Instrumentation” auf ddj.com

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

Sie können die wichtigsten Tight-Stellen in Ihrem Code als noexcept außer um eine Ausnahme zu finden, dann benutzen Sie libunwind (fügen -lunwind einfach -lunwind zu Linker-Parametern hinzu) (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  #include  #include  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 ""; } 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  void backtrace(std::ostream & _out) noexcept; 

backtrace.cpp:

 #include "backtrace.hpp" #include  #include  #include  #include  #include  #define UNW_LOCAL_ONLY #include  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  #include  #include  #include  #include  #include  #include  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 einen guten Artikel zum Thema.

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 function _Unwind_Backtrace in libgcc gibt, die wahrscheinlich Teil dessen ist, was Sie brauchen.

Ü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.