How do I find where an exception was thrown in C++?
我有一个程序在某个地方抛出一个未捕获的异常。我得到的只是一个异常被抛出的报告,没有关于它被抛出的位置的信息。对于一个编译为包含调试符号的程序来说,不通知我在代码中哪里生成了异常似乎是不合逻辑的。
如果没有在gdb中设置"catch throw"并为每个抛出的异常调用一个backtrace,是否有任何方法可以告诉我的异常来自何处?
以下是一些在调试问题时可能用到的信息
如果未捕获异常,则自动调用特殊库函数
对
您可以使用
简单地讨论了Bruce Eckel在C++中的思想中的例外情况,第二个Ed也有帮助。
由于
注意:我说可以,因为C++通过使用语言构造来支持非本地错误处理,以将错误处理和报告代码与普通代码分开。catch块可以且通常位于与抛出点不同的函数/方法中。在注释(谢谢dan)中还向我指出,在调用
更新:我构建了一个Linux测试程序,它在通过
更新2:多亏了一篇关于在Terminate中捕获未捕获异常的博客文章,我学到了一些新技巧;包括在Terminate处理程序中重新抛出未捕获异常。需要注意的是,自定义终止处理程序中的空
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #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 "; // 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 "; 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) <<") "; exit(EXIT_FAILURE); } foo1(); exit(EXIT_SUCCESS); } |
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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] |
如您所说,我们可以在gdb中使用"catch throw",并为每个抛出的异常调用"backtrace"。虽然这通常过于繁琐,无法手动完成,但gdb允许流程自动化。这允许查看抛出的所有异常的回溯,包括最后一个未捕获的异常:
GDB >
1 2 3 4 5 6 7 | set pagination off catch throw commands backtrace continue end run |
如果不进行进一步的手动干预,这将生成大量的回溯,包括最后一个未捕获异常的回溯:
1 2 3 4 5 6 7 | 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. |
下面是一篇很棒的博客文章,总结了这一点:http://741Mhz.com/throw-stacktrace/
您可以创建如下宏:
1 | #define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) ) |
…它将给出抛出异常的位置(当然不是堆栈跟踪)。您有必要从采用上述构造函数的某个基类派生异常。
您可以将代码中的主要紧密位置标记为
DEMAGLE
1 2 3 4 | #pragma once char const * get_demangled_name(char const * const symbol) noexcept; |
Dimangle CPP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #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); } |
回溯
1 2 3 4 5 | #pragma once #include <ostream> void backtrace(std::ostream & _out) noexcept; |
RACTRAPE.CPP:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | #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 <<") "; } else { _out <<"-- error: unable to obtain symbol name for this frame "; } } _out << std::flush; } |
backtrace_on_terminate.hpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | #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 } } |
关于这个问题有好文章。
您没有传递有关所使用的操作系统/编译器的信息。
在VisualStudioC++中,可以检测异常。
见DDC.NET上的"VisualC++异常处理工具"
我的文章"后期调试",也在ddj.com上,包括使用win32结构化异常处理(由工具使用)进行日志记录等的代码。
检查这个线程,也许它有助于:
捕获所有未处理的C++异常?
我在该软件方面有很好的经验:
http://www.codeproject.com/kb/applications/blackbox.aspx
对于任何未处理的异常,它都可以将堆栈跟踪输出到文件。
我有代码可以在Windows/Visual Studio中执行此操作,如果需要大纲,请通知我。不过,不知道如何处理dwarf2代码,一个快速的google建议在libgcc中有一个函数"unwind"backtrace,这可能是您需要的一部分。