我有一个程序会在某个地方抛出未捕获的异常。我所得到的只是关于引发异常的报告,而没有关于引发异常的信息。对于编译为包含调试符号而不通知我代码中生成异常的地方的程序,这似乎是不合逻辑的。
有什么办法可以说明我的异常来自哪里,因为没有在gdb中设置“ catch throw”并为每个抛出的异常调用回溯?
我有一个程序会在某个地方抛出未捕获的异常。我所得到的只是关于引发异常的报告,而没有关于引发异常的信息。对于编译为包含调试符号而不通知我代码中生成异常的地方的程序,这似乎是不合逻辑的。
有什么办法可以说明我的异常来自哪里,因为没有在gdb中设置“ catch throw”并为每个抛出的异常调用回溯?
Answers:
如果未捕获到异常,std::terminate()
则会自动调用特殊的库函数。Terminate实际上是指向函数的指针,默认值为Standard C库函数std::abort()
。如果没有清理发生了未捕获的异常†,它可能实际上是因为没有析构函数调用调试这个问题有帮助。
†由实现定义,在std::terminate()
调用之前是否将堆栈退卷。
调用abort()
通常对生成核心转储很有用,可以对其进行分析以确定异常原因。确保通过ulimit -c unlimited
(Linux)启用核心转储。
您可以使用安装自己的terminate()
功能std::set_terminate()
。您应该能够在gdb中的终止函数上设置断点。您可能能够从terminate()
函数中生成堆栈回溯,并且此回溯可能有助于识别异常的位置。
有一个简短的讨论未捕获的异常在布鲁斯Eckel的C ++中的思考,第二版可能有帮助。
由于默认情况下会进行terminate()
调用abort()
(默认情况下会产生SIGABRT
信号),因此您可以设置SIGABRT
处理程序,然后从signal handler中打印堆栈回溯。此回溯可能有助于识别异常的位置。
注意:我说可能是因为通过使用语言结构,以单独的错误处理处理C ++支持非本地的错误,并从普通代码报告代码。捕获块可以并且经常位于与投掷点不同的功能/方法中。在注释中(感谢Dan)也向我指出,terminate()
调用之前是否取消堆栈是实现定义的。
更新:我整理了一个名为Linux的测试程序,该程序在terminate()
通过set_terminate()
和的信号处理程序中的函数集中生成了回溯SIGABRT
。这两个回溯都正确显示了未处理异常的位置。
更新2:多亏了一篇关于在terminate中捕获未捕获的异常的博客文章,我学到了一些新的技巧。包括在终止处理程序中重新抛出未捕获的异常。重要的是要注意,throw
自定义终止处理程序中的空语句可用于GCC,而不是可移植的解决方案。
码:
#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);
}
输出:
my_terminate捕获了未处理的异常。what():运行时错误! my_terminate backtrace返回10帧 [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] 信号6(已终止),地址是0x42029331的0x1239 crit_err_hdlr backtrace返回了13帧 [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]
main
),然后才会调用terminate()
。但是您的示例显示完全不执行放松操作,这很酷。
throw(int)
规格是不必要的。2)uc->uc_mcontext.eip
可能非常依赖平台(例如,...rip
在64位平台上使用)。3)进行编译,-rdynamic
以便获得回溯符号。4)运行./a.out 2>&1 | c++filt
以获得漂亮的回溯符号。
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;
为我的ARM目标工作
如您所说,我们可以在gdb中使用“ catch throw”,并为每个抛出的异常调用“ backtrace”。虽然这通常太麻烦而无法手动完成,但gdb允许该过程自动化。这样可以查看所有引发的异常的回溯,包括最后一个未捕获的异常:
gdb>
set pagination off
catch throw
commands
backtrace
continue
end
run
如果没有进一步的手动干预,则会生成大量回溯,包括最后一个未捕获的异常的回溯:
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 [在archive.org上]
您可以创建如下宏:
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
...并且它将为您提供引发异常的位置(当然不是堆栈跟踪)。您必须从采用上述构造函数的某些基类派生异常。
throw new excation(...)
但throw exception(...)
C ++不是Java,
您没有传递有关使用哪种操作系统/编译器的信息。
在Visual Studio C ++中,可以检测到异常。
请参阅 ddj.com上的“ Visual C ++异常处理规范”
ddj.com上的我的文章“ Postmortem Debugging”也包括使用Win32结构化异常处理(由工具使用)进行日志记录的代码。
您可以将代码中的主要位置标记为noexcept
定位异常,然后使用libunwind(只需添加-lunwind
到链接器参数)(已通过测试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
}
}
关于这个问题有好文章。
我有在Windows / Visual Studio中执行此操作的代码,如果需要轮廓,请告诉我。不过,尽管不知道如何为dwarf2代码做这件事,一个快速的Google建议在libgcc中有一个函数_Unwind_Backtrace可能是您需要的一部分。
检查此线程,也许有帮助:
我在该软件上取得了良好的经验:
http://www.codeproject.com/KB/applications/blackbox.aspx
它可以为任何未处理的异常将堆栈跟踪打印到文件中。
exception thrown foo.c@54, ..., re-thrown bar.c@54, ....
而不必手动执行。