2012-07-26 15 views
23

Tôi muốn có chồng theo dõi không cho trường hợp ngoại lệ của tôi chỉ mà còn cho bất kỳ hậu duệ của std::exceptionLàm thế nào tôi có thể in chồng dấu vết cho trường hợp ngoại lệ bị bắt trong C++ & tiêm mã trong C++

Theo tôi được biết, stack trace là hoàn toàn bị mất khi ngoại lệ bị bắt vì chồng thư giãn (unrolling).

Vì vậy, cách duy nhất tôi thấy để lấy nó là tiêm mã tiết kiệm thông tin ngữ cảnh (dấu vết ngăn xếp) tại địa chỉ của std::exception gọi hàm tạo. Tôi có đúng không?

Nếu đúng như vậy, hãy cho tôi biết cách thực hiện việc tiêm mã (nếu có thể) trong C++. Phương pháp của bạn có thể không hoàn toàn an toàn vì tôi chỉ cần nó cho phiên bản Gỡ lỗi của ứng dụng của tôi. Có thể tôi cần phải sử dụng assembler?

Tôi chỉ quan tâm đến giải pháp cho GCC. Nó có thể sử dụng các tính năng C++ 0x

+1

[Câu trả lời này] (http://stackoverflow.com/questions/3355683/c-stack-trace-from-unhandled-exception) có thể hữu ích. – jxh

+0

@ user315052 Câu trả lời đó là dành cho các trường hợp ngoại lệ không bị bắt và không hoạt động để bị bắt. – boqapt

+0

Đúng, nhưng bạn có thể xếp chuỗi các chuỗi C vào một 'std :: string' và chuyển nó vào hàm tạo của ngoại lệ của bạn như là' cái gì' (hoặc một phần lớn của nó, dù sao). – jxh

Trả lời

31

Vì bạn đã đề cập rằng bạn hài lòng với điều gì đó đặc biệt là GCC, tôi đã tổng hợp một ví dụ về cách bạn có thể làm điều này. Đó là điều ác thuần túy, xen vào nội dung của thư viện hỗ trợ C++. Tôi không chắc chắn tôi muốn sử dụng điều này trong mã sản xuất. Dù sao:

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

Về cơ bản, chúng tôi lấy cắp cuộc gọi đến chức năng thực hiện nội bộ mà GCC sử dụng để gửi đi các ngoại lệ. Tại thời điểm đó, chúng tôi lấy một dấu vết ngăn xếp và lưu nó trong một biến toàn cầu. Sau đó, khi chúng tôi đi qua ngoại lệ đó sau này trong thử/nắm bắt của chúng tôi, chúng tôi có thể làm việc với stacktrace để in/lưu hoặc bất cứ điều gì nó là bạn muốn làm. Chúng tôi sử dụng dlsym() để tìm phiên bản thực tế của __cxa_throw.

Ví dụ của tôi ném một số int để chứng minh rằng bạn có thể thực hiện điều này với bất kỳ loại nào theo nghĩa đen, không chỉ riêng trường hợp ngoại lệ do người dùng xác định.

Sử dụng tên type_info để lấy tên của loại đã được ném và sau đó phá hủy nó.

Bạn có thể đóng gói các biến toàn cầu lưu trữ stacktrace tốt hơn một chút nếu bạn muốn.

tôi biên soạn và thử nghiệm này với:

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

nào đã đưa ra sau khi chạy:

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

Xin đừng coi đây là một ví dụ về lời khuyên tốt mặc dù - nó là một ví dụ về những gì bạn có thể làm với một chút thủ đoạn và chọc ghẹo xung quanh ở bên trong!

+0

Cảm ơn bạn rất nhiều! Nó không phải là một vấn đề mà nó không an toàn bởi vì tôi cần nó chỉ để phát triển nhanh hơn - nhìn thấy ngay lập tức nơi một lỗi xảy ra khi tôi đang thử nghiệm và gỡ lỗi, như trong các ngôn ngữ hiện đại. – boqapt

+0

@ user484936 Nguy cơ lớn ở đây là bạn không nhận thấy khi có sự thay đổi ABI và kết thúc trong một thế giới của cơn đau hành vi không xác định. Nếu bạn quan tâm mặc dù tôi có thể mở rộng nó để in loại ngoại lệ, ngay cả trong một khối catch (...). – Flexo

+0

Có, tôi quan tâm, nó sẽ tuyệt vời – boqapt

4

Trên Linux, điều này có thể được thực hiện bằng cách thêm lệnh gọi backtrace() trong trình tạo ngoại lệ để ghi lại dấu vết ngăn xếp vào biến thành viên của một ngoại lệ. Thật không may, nó sẽ không hoạt động đối với trường hợp ngoại lệ tiêu chuẩn, chỉ dành cho những trường hợp bạn xác định.

3

Một vài năm trước, tôi đã viết này: Unchaining chained exceptions in C++

Về cơ bản một số macro log nơi chồng Thư giãn sẽ xảy ra khi một ngoại lệ được ném.

Có thể tìm thấy phiên bản cập nhật của khung trong thư viện Imebra (http://imebra.com).

Tôi sẽ thực hiện lại một số phần của nó (như lưu trữ dấu vết ngăn xếp trên bộ nhớ cục bộ luồng).

0

Giải pháp từ Flexo rất tốt và hoạt động tốt. Nó cũng có lợi ích mà dịch từ địa chỉ backtrace đến tên thủ tục chỉ được thực hiện trong phần catch, do đó, nó lên đến người nhận của một ngoại lệ nếu họ quan tâm đến backtrace hay không. Tuy nhiên cũng có những trường hợp giải pháp dựa trên libunwind có thể được ưu tiên, tức là vì libunwind có thể trong một số tình huống thu thập tên thủ tục mà ở đó các hàm backtrace không làm như vậy.

Ở đây tôi trình bày một ý tưởng dựa trên câu trả lời của Flexo, nhưng với một số phần mở rộng. Nó sử dụng libunwind để tạo ra backtrace tại thời điểm ném, và trực tiếp in để stderr. Nó sử dụng libDL để xác định tên tập tin đối tượng được chia sẻ. Nó sử dụng thông tin gỡ lỗi DWARF từ elfutils để thu thập tên tệp mã và số dòng. Nó sử dụng C++ API để loại bỏ các ngoại lệ C++. Người dùng có thể đặt biến số mExceptionStackTrace để tạm thời bật/tắt dấu vết ngăn xếp.

Một điểm quan trọng về tất cả các giải pháp chặn __cxa_throw là chúng thêm khả năng chi phí cho việc đi bộ ngăn xếp. Điều này đặc biệt đúng với giải pháp của tôi, bổ sung thêm chi phí đáng kể để truy cập vào các biểu tượng trình gỡ rối để thu thập tên tệp nguồn. Điều này có thể chấp nhận được trong ví dụ: kiểm tra tự động, nơi bạn mong đợi mã của bạn không được ném và bạn muốn có một dấu vết ngăn xếp mạnh mẽ cho các kiểm tra (không thành công) bị ném.

// 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 
Các vấn đề liên quan