我有一个程序在某处抛出未捕获的异常。我得到的只是一个抛出异常的报告,并没有关于它被抛出的信息。编译为包含调试符号的程序似乎不合逻辑,不通知我在代码中生成异常的位置。
有没有办法告诉我的异常来自于在gdb中设置'catch throw'并为每个抛出的异常调用回溯?
答案 0 :(得分:69)
如果未捕获异常,则会自动调用特殊库函数std::terminate()
。 Terminate实际上是一个指向函数的指针,默认值是标准C库函数std::abort()
。如果未捕获的异常†没有进行清理,则可能实际上有助于调试此问题,因为没有调用析构函数。
†在调用std::terminate()
之前是否展开堆栈是实现定义的。
调用abort()
通常可用于生成可以分析以确定异常原因的核心转储。确保通过ulimit -c unlimited
(Linux)启用核心转储。
您可以使用 std::set_terminate()
安装自己的terminate()
功能。您应该能够在gdb中的终止函数上设置断点。您可能能够从terminate()
函数生成堆栈回溯,此回溯可能有助于识别异常的位置。
uncaught exceptions中Bruce Eckel's Thinking in C++, 2nd Ed的简短讨论也可能有所帮助。
由于terminate()
默认调用abort()
(默认情况下会导致SIGABRT
信号),您可能能够设置SIGABRT
处理程序然后print a stack backtrace from within the signal handler。此回溯可能有助于识别异常的位置。
注意:我说可能因为C ++通过使用语言结构来支持非本地错误处理,以便将错误处理和报告代码与普通代码分开。捕获块可以并且通常位于与投掷点不同的功能/方法中。在评论中也向我指出了(感谢Dan),在调用terminate()
之前,堆栈是否展开是实现定义的。
更新:我将一个名为Linux的测试程序整合在一起,通过terminate()
在set_terminate()
函数集中生成回溯,在SIGABRT
的信号处理程序中生成另一个回溯}。两个回溯都正确显示了未处理异常的位置。
更新2:感谢Catching uncaught exceptions within 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 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]
答案 1 :(得分:43)
正如你所说,我们可以在gdb中使用'catch throw'并为每个抛出的异常调用'backtrace'。虽然手动操作通常太繁琐,但gdb允许自动化过程。这允许查看抛出的所有异常的回溯,包括最后一个未被捕获的异常:
GDB&GT;
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/
答案 2 :(得分:16)
您可以创建一个宏,如:
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
...它将为您提供抛出异常的位置(当然不是堆栈跟踪)。您需要从一些采用上述构造函数的基类派生您的异常。
答案 3 :(得分:5)
您没有传递有关您使用的操作系统/编译器的信息。
在Visual Studio C ++中可以检测异常。
请参阅 ddj.com上的"Visual C++ Exception-Handling Instrumentation"
我的文章"Postmortem Debugging",也在ddj.com上包含使用Win32结构化异常处理(由仪器使用)进行日志记录等的代码。
答案 4 :(得分:5)
您可以将代码中的主要紧密位置标记为setFocusableWindowState(false);
以查找异常,然后使用 libunwind (只需将noexcept
添加到链接器参数)(使用{{进行测试) 1}}):
demagle.hpp:
-lunwind
demangle.cpp:
clang++ 3.6
backtrace.hpp:
#pragma once
char const *
get_demangled_name(char const * const symbol) noexcept;
backtrace.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_on_terminate.hpp:
#pragma once
#include <ostream>
void
backtrace(std::ostream & _out) noexcept;
关于这个问题有good article。
答案 5 :(得分:1)
我有在Windows / Visual Studio中执行此操作的代码,如果您需要大纲,请告诉我。不知道如何为dwarf2代码执行此操作,快速谷歌建议在libgcc中有一个函数_Unwind_Backtrace可能是你需要的一部分。
答案 6 :(得分:1)
检查这个帖子,也许有帮助:
Catching all unhandled C++ exceptions?
我使用该软件获得了很好的经验:
http://www.codeproject.com/KB/applications/blackbox.aspx
它可以将堆栈跟踪打印到文件中,用于任何未处理的异常。