Flexo的解决方案非常好,效果也很好。它有一个好处,即从回溯地址到过程名称的转换仅在catch部分执行,因此是否关心回溯是异常接收者的责任。
然而,在某些情况下,基于libunwind的解决方案可能更可取,例如因为在某些情况下libunwind可以收集过程名,而backtrace函数却无法这样做。
在这里,我提出了一种基于Flexo答案的想法,但进行了多个扩展。它使用libunwind在抛出时生成回溯,并直接打印到stderr。它使用libDL来识别共享对象文件名。它使用elfutils的DWARF调试信息收集源代码文件名和行号。它使用C++ API解构C++异常。用户可以设置mExceptionStackTrace变量以临时启用/禁用堆栈跟踪。
关于拦截__cxa_throw的所有解决方案的一个重要点是,它们增加了遍历堆栈的潜在开销。我的解决方案特别适用于访问调试器符号以收集源文件名而添加了显着的开销。这在自动测试中可能是可以接受的,因为您希望代码不会引发异常,并且您希望对引发异常的(失败的)测试有一个强大的堆栈跟踪。
#if defined(__GNUC__)
#include <elfutils/libdwfl.h>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <dlfcn.h>
#include <cxxabi.h>
#include <typeinfo>
#include <stdio.h>
#include <stdlib.h>
#define LIBUNWIND_MAX_PROCNAME_LENGTH 4096
static bool mExceptionStackTrace = false;
extern "C" {
void print_exception_info(const std::type_info* aExceptionInfo) {
int vDemangleStatus;
char* vDemangledExceptionName;
if (aExceptionInfo != NULL) {
vDemangledExceptionName = abi::__cxa_demangle(aExceptionInfo->name(), NULL, NULL, &vDemangleStatus);
if (vDemangledExceptionName != NULL) {
fprintf(stderr, "\n");
fprintf(stderr, "Caught exception %s:\n", vDemangledExceptionName);
free(vDemangledExceptionName);
} else {
fprintf(stderr, "\n");
fprintf(stderr, "Caught exception %s:\n", aExceptionInfo->name());
}
} else {
fprintf(stderr, "\n");
fprintf(stderr, "Caught exception:\n");
}
}
void libunwind_print_backtrace(const int aFramesToIgnore) {
unw_cursor_t vUnwindCursor;
unw_context_t vUnwindContext;
unw_word_t ip, sp, off;
unw_proc_info_t pip;
int vUnwindStatus, vDemangleStatus, i, n = 0;
char vProcedureName[LIBUNWIND_MAX_PROCNAME_LENGTH];
char* vDemangledProcedureName;
const char* vDynObjectFileName;
const char* vSourceFileName;
int vSourceFileLineNumber;
Dl_info dlinfo;
Dwarf_Addr addr;
char* debuginfo_path = NULL;
Dwfl_Callbacks callbacks = {};
Dwfl_Line* vDWARFObjLine;
callbacks.find_elf = dwfl_linux_proc_find_elf;
callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
callbacks.debuginfo_path = &debuginfo_path;
Dwfl* dwfl = dwfl_begin(&callbacks);
if (dwfl == NULL) {
fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
}
if ((dwfl != NULL) && (dwfl_linux_proc_report(dwfl, getpid()) != 0)) {
fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
dwfl = NULL;
}
if ((dwfl != NULL) && (dwfl_report_end(dwfl, NULL, NULL) != 0)) {
fprintf(stderr, "libunwind_print_backtrace(): Error initializing DWARF.\n");
dwfl = NULL;
}
vUnwindStatus = unw_getcontext(&vUnwindContext);
if (vUnwindStatus) {
fprintf(stderr, "libunwind_print_backtrace(): Error in unw_getcontext: %d\n", vUnwindStatus);
return;
}
vUnwindStatus = unw_init_local(&vUnwindCursor, &vUnwindContext);
if (vUnwindStatus) {
fprintf(stderr, "libunwind_print_backtrace(): Error in unw_init_local: %d\n", vUnwindStatus);
return;
}
vUnwindStatus = unw_step(&vUnwindCursor);
for (i = 0; ((i < aFramesToIgnore) && (vUnwindStatus > 0)); ++i) {
vUnwindStatus = unw_step(&vUnwindCursor);
}
while (vUnwindStatus > 0) {
pip.unwind_info = NULL;
vUnwindStatus = unw_get_proc_info(&vUnwindCursor, &pip);
if (vUnwindStatus) {
fprintf(stderr, "libunwind_print_backtrace(): Error in unw_get_proc_info: %d\n", vUnwindStatus);
break;
}
unw_get_reg(&vUnwindCursor, UNW_REG_IP, &ip);
unw_get_reg(&vUnwindCursor, UNW_REG_SP, &sp);
vUnwindStatus = unw_get_proc_name(&vUnwindCursor, vProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH, &off);
if (vUnwindStatus == 0) {
vDemangledProcedureName = abi::__cxa_demangle(vProcedureName, NULL, NULL, &vDemangleStatus);
if (vDemangledProcedureName != NULL) {
strncpy(vProcedureName, vDemangledProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH);
free(vDemangledProcedureName);
} else {
}
} else if (vUnwindStatus == UNW_ENOMEM) {
} else {
vProcedureName[0] = '?';
vProcedureName[1] = '?';
vProcedureName[2] = '?';
vProcedureName[3] = 0;
}
if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname) {
vDynObjectFileName = dlinfo.dli_fname;
} else {
vDynObjectFileName = "???";
}
if (dwfl != NULL) {
addr = (uintptr_t)(ip - 4);
Dwfl_Module* module = dwfl_addrmodule(dwfl, addr);
vDWARFObjLine = dwfl_getsrc(dwfl, addr);
if (vDWARFObjLine != NULL) {
vSourceFileName = dwfl_lineinfo(vDWARFObjLine, &addr, &vSourceFileLineNumber, NULL, NULL, NULL);
}
}
if (dwfl == NULL || vDWARFObjLine == NULL || vSourceFileName == NULL) {
vSourceFileName = "???";
vSourceFileLineNumber = 0;
}
fprintf(stderr, "#%2d:", ++n);
fprintf(stderr, " 0x%016" PRIxPTR " sp=0x%016" PRIxPTR, static_cast<uintptr_t>(ip), static_cast<uintptr_t>(sp));
fprintf(stderr, " %s:%d", vSourceFileName, vSourceFileLineNumber);
fprintf(stderr, " %s", vProcedureName);
fprintf(stderr, "\n");
if (strcmp(vProcedureName, "main") == 0) {
break;
}
vUnwindStatus = unw_step(&vUnwindCursor);
if (vUnwindStatus < 0) {
fprintf(stderr, "libunwind_print_backtrace(): Error in unw_step: %d\n", vUnwindStatus);
}
}
}
void __cxa_throw(void *thrown_exception, std::type_info *info, void (*dest)(void *)) {
if (mExceptionStackTrace) {
print_exception_info(info);
libunwind_print_backtrace(1);
}
static void (*const rethrow)(void*,void*,void(*)(void*)) __attribute__ ((noreturn)) = (void (*)(void*,void*,void(*)(void*)))dlsym(RTLD_NEXT, "__cxa_throw");
rethrow(thrown_exception,info,dest);
}
}
#endif
std::string
中,并将其作为异常构造函数中的what
参数(或者仅作为what
的一部分)传递进去。 - jxhthrow_with_nested
将回溯附加到捕获的异常上,但遗憾的是,我的编译器缺乏对C++11的支持。 - jxh