菜鸟笔记
提升您的技术认知

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

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

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

示例代码如下:

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找到原函数。
这种做法虽然不是很好,但对于我这种不使用异常的人很合适。

完整的代码:

#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);
  }
}

编译、执行后会输出:

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 命令可以定位到代码中的位置。

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