0%

C++ 捕获异常时的栈信息

由于种种原因,我还是不太推荐在C++使用异常机制。所以也不捕获异常,如果有问题直接让它挂掉。
最近遇到一个问题,我的框架“帮”我捕获了vector抛出的越界异常,没有了core文件,很难定位问题具体出在哪一行。

backtrace 是可以捕获的栈信息的,但是捕获到异常时已经丢失栈信息了。
__cxa_throw 是在抛出异常时被调用的函数,在这个函数中可以捕获栈信息。

示例代码如下:

1
2
3
4
5
6
7
extern "C" {
void __cxa_throw(void *ex, void *info, void (*dest)(void *)) {
last_size = backtrace(last_frames, sizeof last_frames/sizeof(void*));
static void (*const rethrow)(void*,void*,void(*)(void*)) __attribute__ ((noreturn)) = (void (*)(void*,void*,void(*)(void*)))dlsym(RTLD_NEXT, "__cxa_throw");
rethrow(ex,info,dest);
}

这段代码比较有趣,先是重载了__cxa_throw这个函数,然后又通过dlsym找到原函数。

这种做法虽然不是很好,但对于我这种不使用异常的人很合适。

完整的代码:

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
#include <iostream>
#include <dlfcn.h>
#include <execinfo.h>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>
namespace {
void * last_frames[100];
size_t last_size;
std::string exception_name;
std::string demangle(const char *name) {
int status;
std::unique_ptr<char,void(*)(void*)> realname(abi::__cxa_demangle(name, 0, 0, &status), &std::free);
return status ? "failed" : &*realname;
}
}
extern "C" {
void __cxa_throw(void *ex, void *info, void (*dest)(void *)) {
exception_name = demangle(reinterpret_cast<const std::type_info*>(info)->name());
last_size = backtrace(last_frames, sizeof last_frames/sizeof(void*));
static void (*const rethrow)(void*,void*,void(*)(void*)) __attribute__ ((noreturn)) = (void (*)(void*,void*,void(*)(void*)))dlsym(RTLD_NEXT, "__cxa_throw");
rethrow(ex,info,dest);
}
}
void foo() {
throw 0;
}
int main() {
try {
foo();
}
catch (...) {
std::cerr << "Caught a: " << exception_name << std::endl;
// print to stderr
backtrace_symbols_fd(last_frames, last_size, 2);
}
}

编译、执行后会输出:

1
2
3
4
5
6
7
8
9
g++ -std=c++0x -g -rdynamic -ldl test.cpp
./a.out
Caught a: int
./a.out(__cxa_throw+0x82)[0x401e8a]
./a.out(main+0x0)[0x401f18]
./a.out(main+0xc)[0x401f24]
/lib64/libc.so.6(__libc_start_main+0xfd)[0x3b6641ed5d]
./a.out[0x401c69]

然后使用 addr2line 命令可以定位到代码中的位置。

1
2
3
addr2line 0x401f24 -e ./a.out
./test.cpp:38