如何为 C++ 中捕获的异常和 C++ 中的代码注入打印堆栈跟踪
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/11665829/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How can I print stack trace for caught exceptions in C++ & code injection in C++
提问by boqapt
I want to have stack trace not for my exceptions only but also for any descendants of std::exception
我希望堆栈跟踪不仅适用于我的异常,而且适用于任何后代 std::exception
As I understand, stack trace is completely lost when exception is caught because of stack unwinding (unrolling).
据我了解,由于堆栈展开(展开)而捕获异常时,堆栈跟踪将完全丢失。
So the only way I see to grab it is injection of code saving context info (stack trace) at the place of std::exception
constructor call. Am I right?
所以我看到获取它的唯一方法是在std::exception
构造函数调用的地方注入代码保存上下文信息(堆栈跟踪)。我对吗?
If it is the case, please tell me how code injection can be done (if it can) in C++. Your method may be not completely safe because I need it for Debug version of my app only. May be I need to use assembler?
如果是这种情况,请告诉我如何在 C++ 中完成代码注入(如果可以的话)。您的方法可能并不完全安全,因为我只需要它用于我的应用程序的调试版本。我可能需要使用汇编程序吗?
I'm interested only in solution for GCC. It can use c++0x features
我只对 GCC 的解决方案感兴趣。它可以使用 c++0x 特性
回答by Flexo
Since you mentioned that you're happy with something that is GCC specific I've put together an example of a way you might do this. It's pure evil though, interposing on internals of the C++ support library. I'm not sure I'd want to use this in production code. Anyway:
既然你提到你对 GCC 特定的东西感到满意,我已经整理了一个你可以这样做的方法的例子。不过,它是纯粹的邪恶,介入 C++ 支持库的内部。我不确定我是否想在生产代码中使用它。反正:
#include <iostream>
#include <dlfcn.h>
#include <execinfo.h>
#include <typeinfo>
#include <string>
#include <memory>
#include <cxxabi.h>
#include <cstdlib>
namespace {
void * last_frames[20];
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);
}
}
We basically steal calls to the internal implementation function that GCC uses for dispatching thrown exceptions. At that point we take a stack trace and save it in a global variable. Then when we come across that exception later on in our try/catch we can work with the stacktrace to print/save or whatever it is you want to do. We use dlsym()
to find the real version of __cxa_throw
.
我们基本上窃取了对 GCC 用于调度抛出异常的内部实现函数的调用。在这一点上,我们获取堆栈跟踪并将其保存在全局变量中。然后,当我们稍后在 try/catch 中遇到该异常时,我们可以使用堆栈跟踪来打印/保存或您想做的任何事情。我们dlsym()
用来查找__cxa_throw
.
My example throws an int
to prove that you can do this with literally any type, not just your own user defined exceptions.
我的示例抛出 anint
以证明您可以使用任何类型来执行此操作,而不仅仅是您自己的用户定义的异常。
It uses the type_info
to get the name of the type that was thrown and then demangles it.
它使用type_info
来获取被抛出的类型的名称,然后对其进行破坏。
You could encapsulate the global variables that store the stacktrace a bit better if you wanted to.
如果您愿意,您可以更好地封装存储堆栈跟踪的全局变量。
I compiled and tested this with:
我编译并测试了这个:
g++ -Wall -Wextra test.cc -g -O0 -rdynamic -ldl
Which gave the following when run:
运行时给出了以下内容:
./a.out Caught a: int ./a.out(__cxa_throw+0x74)[0x80499be] ./a.out(main+0x0)[0x8049a61] ./a.out(main+0x10)[0x8049a71] /lib/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0xb75c2ca6] ./a.out[0x80497e1]
Please don't take this as an example of good advice though - it's an example of what you can do with a little bit of trickery and poking around at the internals!
请不要将此作为好的建议的示例 - 这是一个示例,说明您可以通过一些技巧和内部结构来做些什么!
回答by Maxim Egorushkin
On Linux this can be implemented by adding a call to backtrace()
in the exception constructor to capture the stack trace into an exception's member variable. Unfortunately, it won't work for standard exceptions, only for the ones you define.
在 Linux 上,这可以通过backtrace()
在异常构造函数中添加调用来实现,以将堆栈跟踪捕获到异常的成员变量中。不幸的是,它不适用于标准异常,仅适用于您定义的异常。
回答by Paolo Brandoli
Some years ago I wrote this: Unchaining chained exceptions in C++
几年前我写过这个:Unchaining chained exceptions in C++
Basically some macros log the place where the stack unwind happens when an exception is thrown.
基本上,一些宏会记录抛出异常时堆栈展开发生的位置。
An updated version of the framework can be found in the library Imebra (http://imebra.com).
该框架的更新版本可以在库 Imebra ( http://imebra.com) 中找到。
I would reimplement some parts of it (like storing the stack trace on a thread local storage).
我会重新实现它的某些部分(比如将堆栈跟踪存储在线程本地存储上)。
回答by emmenlau
The solution from Flexo is very nice and works well. It also has the benefit that translation from backtrace addresses to procedure names is only performed in the catch
part, so its up to the receiver of an exception if they care about the backtrace or not.
Flexo 的解决方案非常好,而且效果很好。它还具有从回溯地址到过程名称的转换仅在catch
部分执行的好处,因此如果他们关心回溯,则取决于异常的接收者。
However there are also cases where a solution based on libunwind can be prefered, i.e. because libunwind can in some scenarios gather procedure names where the backtrace
functions fail to do so.
然而,在某些情况下,基于 libunwind 的解决方案可能更受欢迎,即因为 libunwind 在某些情况下可以收集backtrace
函数无法这样做的过程名称。
Here I present an idea based on Flexo's answer, but with several extensions. It uses libunwind to generate the backtrace at the time of the throw, and directly prints to stderr. It uses libDL to identify the shared object file name. It uses DWARF debugging information from elfutils to gather the source code file name and line number. It uses the C++ API to demangle C++ exceptions. Users can set the mExceptionStackTrace
variable to temporarily enable/disable the stack traces.
在这里,我提出了一个基于 Flexo 答案的想法,但有几个扩展。它在抛出时使用 libunwind 生成回溯,并直接打印到 stderr。它使用 libDL 来标识共享对象文件名。它使用来自 elfutils 的 DWARF 调试信息来收集源代码文件名和行号。它使用 C++ API 来消除 C++ 异常。用户可以设置mExceptionStackTrace
变量来临时启用/禁用堆栈跟踪。
An important point about all solutions that intercept __cxa_throw
is that they add potentially an overhead for walking the stack. This is especially true for my solution that adds significant overhead for accessing the debugger symbols to gather the source file name. This may be acceptable in i.e. automatic testing where you expect your code not to throw, and you want to have a powerful stack trace for (failed) tests that do throw.
所有拦截解决方案的重要一点__cxa_throw
是它们可能会增加遍历堆栈的开销。对于我的解决方案来说尤其如此,该解决方案为访问调试器符号以收集源文件名增加了大量开销。这在 ie 自动测试中可能是可以接受的,在这种情况下,您希望代码不会抛出,并且您希望为抛出的(失败)测试提供强大的堆栈跟踪。
// Our stack unwinding is a GNU C extension:
#if defined(__GNUC__)
// include elfutils to parse debugger information:
#include <elfutils/libdwfl.h>
// include libunwind to gather the stack trace:
#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;
// We would like to print a stacktrace for every throw (even in
// sub-libraries and independent of the object thrown). This works
// only for gcc and only with a bit of trickery
extern "C" {
void print_exception_info(const std::type_info* aExceptionInfo) {
int vDemangleStatus;
char* vDemangledExceptionName;
if (aExceptionInfo != NULL) {
// Demangle the name of the exception using the GNU C++ ABI:
vDemangledExceptionName = abi::__cxa_demangle(aExceptionInfo->name(), NULL, NULL, &vDemangleStatus);
if (vDemangledExceptionName != NULL) {
fprintf(stderr, "\n");
fprintf(stderr, "Caught exception %s:\n", vDemangledExceptionName);
// Free the memory from __cxa_demangle():
free(vDemangledExceptionName);
} else {
// NOTE: if the demangle fails, we do nothing, so the
// non-demangled name will be printed. Thats ok.
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;
// This is from libDL used for identification of the object file names:
Dl_info dlinfo;
// This is from DWARF for accessing the debugger information:
Dwarf_Addr addr;
char* debuginfo_path = NULL;
Dwfl_Callbacks callbacks = {};
Dwfl_Line* vDWARFObjLine;
// initialize the DWARF handling:
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;
}
// Begin stack unwinding with libunwnd:
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) {
// We ignore the first aFramesToIgnore stack frames:
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;
}
// Resolve the address of the stack frame using libunwind:
unw_get_reg(&vUnwindCursor, UNW_REG_IP, &ip);
unw_get_reg(&vUnwindCursor, UNW_REG_SP, &sp);
// Resolve the name of the procedure using libunwind:
// unw_get_proc_name() returns 0 on success, and returns UNW_ENOMEM
// if the procedure name is too long to fit in the buffer provided and
// a truncated version of the name has been returned:
vUnwindStatus = unw_get_proc_name(&vUnwindCursor, vProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH, &off);
if (vUnwindStatus == 0) {
// Demangle the name of the procedure using the GNU C++ ABI:
vDemangledProcedureName = abi::__cxa_demangle(vProcedureName, NULL, NULL, &vDemangleStatus);
if (vDemangledProcedureName != NULL) {
strncpy(vProcedureName, vDemangledProcedureName, LIBUNWIND_MAX_PROCNAME_LENGTH);
// Free the memory from __cxa_demangle():
free(vDemangledProcedureName);
} else {
// NOTE: if the demangle fails, we do nothing, so the
// non-demangled name will be printed. Thats ok.
}
} else if (vUnwindStatus == UNW_ENOMEM) {
// NOTE: libunwind could resolve the name, but could not store
// it in a buffer of only LIBUNWIND_MAX_PROCNAME_LENGTH characters.
// So we have a truncated procedure name that can not be demangled.
// We ignore the problem and the truncated non-demangled name will
// be printed.
} else {
vProcedureName[0] = '?';
vProcedureName[1] = '?';
vProcedureName[2] = '?';
vProcedureName[3] = 0;
}
// Resolve the object file name using dladdr:
if (dladdr((void *)(pip.start_ip + off), &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname) {
vDynObjectFileName = dlinfo.dli_fname;
} else {
vDynObjectFileName = "???";
}
// Resolve the source file name using DWARF:
if (dwfl != NULL) {
addr = (uintptr_t)(ip - 4);
Dwfl_Module* module = dwfl_addrmodule(dwfl, addr);
// Here we could also ask for the procedure name:
//const char* vProcedureName = dwfl_module_addrname(module, addr);
// Here we could also ask for the object file name:
//vDynObjectFileName = dwfl_module_info(module, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
vDWARFObjLine = dwfl_getsrc(dwfl, addr);
if (vDWARFObjLine != NULL) {
vSourceFileName = dwfl_lineinfo(vDWARFObjLine, &addr, &vSourceFileLineNumber, NULL, NULL, NULL);
//fprintf(stderr, " %s:%d", strrchr(vSourceFileName, '/')+1, vSourceFileLineNumber);
}
}
if (dwfl == NULL || vDWARFObjLine == NULL || vSourceFileName == NULL) {
vSourceFileName = "???";
vSourceFileLineNumber = 0;
}
// Print the stack frame number:
fprintf(stderr, "#%2d:", ++n);
// Print the stack addresses:
fprintf(stderr, " 0x%016" PRIxPTR " sp=0x%016" PRIxPTR, static_cast<uintptr_t>(ip), static_cast<uintptr_t>(sp));
// Print the source file name:
fprintf(stderr, " %s:%d", vSourceFileName, vSourceFileLineNumber);
// Print the dynamic object file name (that is the library name).
// This is typically not interesting if we have the source file name.
//fprintf(stderr, " %s", vDynObjectFileName);
// Print the procedure name:
fprintf(stderr, " %s", vProcedureName);
// Print the procedure offset:
//fprintf(stderr, " + 0x%" PRIxPTR, static_cast<uintptr_t>(off));
// Print a newline to terminate the output:
fprintf(stderr, "\n");
// Stop the stack trace at the main method (there are some
// uninteresting higher level functions on the stack):
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 *)) {
// print the stack trace to stderr:
if (mExceptionStackTrace) {
print_exception_info(info);
libunwind_print_backtrace(1);
}
// call the real __cxa_throw():
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