2012-07-26 82 views
23

捕获的异常如何打印堆栈跟踪我想有堆栈跟踪不是我的异常,也是为我的理解的std::exception在C++代码注入在C++

任何后代,堆栈跟踪是完全丧失当由于堆栈展开(展开)而捕获异常时。

所以我认为抓住它的唯一方法是在构造函数调用std::exception处注入代码保存上下文信息(堆栈跟踪)。我对吗?

如果是的话,请告诉我,代码注入可以怎么做(如果可以)在C++中。因为我需要它只是我的应用程序的调试版本,你的方法是不是完全安全的。可能是我需要使用汇编程序?

我感兴趣的只是在GCC的解决方案。它可以使用C++ 0x功能

+1

[这个答案](http://stackoverflow.com/questions/3355683/c-stack-trace-from-unhandled-exception)可能的帮助。 – jxh 2012-07-26 09:05:52

+0

@ user315052该答案适用于未捕获的异常,不适用于捕获异常。 – boqapt 2012-07-26 09:10:39

+0

确实如此,但是你可以将C字符串数组填充到一个'std :: string'中,并将它作为“what”(或者它的很大一部分)传递给异常的构造函数。 – jxh 2012-07-26 09:15:21

回答

31

既然你提到,你是快乐的东西,是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); 
    } 
} 

我们基本上偷到GCC使用派遣抛出异常的内部实现函数调用。在这一点上,我们采用堆栈跟踪并将其保存在全局变量中。然后,当我们在try/catch中遇到这个异常时,我们可以使用堆栈跟踪来打印/保存或任何你想做的事情。我们使用dlsym()找到__cxa_throw真实版。

我的例子抛出一个int来证明你可以用字面上的任何类型来做到这一点,而不仅仅是你自己的用户定义的异常。

它使用type_info来获取所抛出的类型的名称,然后对其进行解构。

如果你愿意,你可以封装存储栈跟踪的全局变量。

我编译和测试这样的:

g++ -Wall -Wextra test.cc -g -O0 -rdynamic -ldl

这给了运行时:

 
./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] 

请不要以此为好建议的例子,但 - 这是一个例子你可以用一些小小的技巧来做些什么,并在内部进行探索!

+0

非常感谢您!这不是一个不安全的问题,因为我只是为了更快的开发而需要它 - 在我测试和调试时立即看到发生错误的位置,就像现代语言一样。 – boqapt 2012-07-26 18:41:52

+0

@ user484936这里的风险很大,您不会注意到何时发生了ABI变化,最终导致了一个未定义行为痛苦的世界。如果您感兴趣,但我可以扩展它以打印异常的类型,即使在catch(...)块内也是如此。 – Flexo 2012-07-26 18:51:31

+0

是的,我很感兴趣,它会很棒 – boqapt 2012-07-26 18:54:06

4

在Linux上,可以通过在异常构造函数中添加对backtrace()的调用来将堆栈跟踪捕获到异常的成员变量中。不幸的是,它不适用于标准异常,只适用于您定义的异常。

3

几年前,我写了这个:Unchaining chained exceptions in C++

基本上一些宏登录在当一个异常被抛出堆栈展开发生的地方。

框架的更新版本可以在图书馆Imebra(http://imebra.com)中找到。

我会重新实现它的某些部分(比如将堆栈跟踪存储在线程本地存储上)。

0

从柔印解决方案是非常好的,效果很好。它还具有这样的好处,即从回溯地址到程序名称的翻译仅在catch部分中执行,因此如果他们关心跟踪或不跟踪,那么直到接收到异常。

但也有一些情况下,可以优先使用基于libunwind的解决方案,即libunwind可以在某些情况下收集过程名称,其中backtrace函数不能这样做。

在这里,我提出了一种基于柔印的答案的想法,但几个扩展。它使用libunwind在投掷时生成回溯,并直接打印到stderr。它使用libDL来标识共享对象文件名。它使用elfutils的DWARF调试信息来收集源代码文件名和行号。它使用C++ API来消除C++异常。用户可以设置mExceptionStackTrace变量来临时启用/禁用堆栈跟踪。

有关拦截__cxa_throw所有解决方案的重要一点是,他们加入有潜在的开销步行堆栈。对于我的解决方案尤其如此,这会增加访问调试器符号以收集源文件名的大量开销。这可能是可以接受的,即在您希望代码不会抛出的自动测试中,并且您希望有一个功能强大的堆栈跟踪用于(失败的)测试。

// 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