2012-07-26 16 views
23

chcę mieć stos nie śledzić dla moich wyjątkami tylko, ale także dla wszystkich potomków std::exceptionJak mogę wydrukować ślad stosu dla złowionych wyjątków w C++ i wstrzyknięcie kodu w C++

jak rozumiem, ślad stosu jest całkowicie zagubiony gdy wyjątek zostanie przechwycony z powodu rozwijania stosu (rozwijania).

Tak więc jedynym sposobem, jaki widzę, aby go pobrać, jest wstrzyknięcie informacji o kontekście zapisywania kodu (śledzenie stosu) w miejscu std::exception wywołania konstruktora. Czy mam rację?

Jeśli tak jest, proszę powiedz mi, jak można wykonać wstrzyknięcie kodu (jeśli to możliwe) w C++. Twoja metoda może nie być całkowicie bezpieczna, ponieważ potrzebuję jej tylko do debugowania mojej aplikacji. Czy mogę potrzebować asemblera?

Jestem zainteresowany tylko rozwiązaniem dla GCC. Może używać funkcji C++ 0x:

+1

[Ta odpowiedź] (http://stackoverflow.com/questions/3355683/c-stack-trace-from-unhandled-exception) może pomóc. – jxh

+0

@ user315052 Ta odpowiedź dotyczy niezatłoczonych wyjątków i nie działa dla złapanych. – boqapt

+0

To prawda, ale możesz umieścić tablicę ciągów C w 'std :: string' i przekazać to do konstruktora swojego wyjątku jako' what' (lub dużą jego część, tak czy inaczej). – jxh

Odpowiedz

31

Odkąd wspomniałeś, że jesteś zadowolony z czegoś, co jest specyficzne dla GCC, stworzyłem przykład sposobu, w jaki możesz to zrobić. Jest to jednak czyste zło, polegające na wstawianiu elementów biblioteki wsparcia C++. Nie jestem pewien, czy chciałbym użyć tego w kodzie produkcyjnym. W każdym razie:

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

Zasadniczo kradniemy wywołania funkcji wewnętrznego wdrożenia, której GCC używa do wysyłania zgłaszanych wyjątków. W tym momencie pobieramy ślad stosu i zapisujemy go w zmiennej globalnej. Następnie, gdy natkniemy się na ten wyjątek w późniejszym czasie w naszym try/catch, możemy pracować z stacktrace, aby wydrukować/zapisać lub cokolwiek innego chcemy zrobić. Używamy dlsym(), aby znaleźć prawdziwą wersję __cxa_throw.

Mój przykład rzuca int, aby udowodnić, że można to zrobić dosłownie każdego typu, a nie tylko własnych zdefiniowanych przez użytkownika wyjątków.

Używa nazwy type_info, aby uzyskać nazwę typu, który został zgłoszony, a następnie rozplątuje go.

Można hermetyzować zmienne globalne przechowujące ślad stosu nieco lepiej, jeśli chcesz.

skompilowany i przetestowane z:

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

który dał następujące po uruchomieniu:

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

Proszę nie brać tego jako przykład dobrych rad, choć - jest przykładem co możesz zrobić z odrobiną oszustwa i szturchaniem wewnętrznych elementów!

+0

Dziękuję bardzo! Nie jest to problemem, ponieważ jest to niebezpieczne, ponieważ potrzebuję go tylko do szybszego rozwoju - natychmiast widzę, gdzie wystąpił błąd podczas testowania i debugowania, jak w nowoczesnych językach. – boqapt

+0

@ user484936 Duże ryzyko polega na tym, że nie zauważysz zmiany ABI i skończysz w świecie nieokreślonego bólu zachowania. Jeśli jesteś zainteresowany, mogę go rozwinąć, aby wydrukować typ wyjątku, nawet w bloku catch (...). – Flexo

+0

Tak, jestem zainteresowany, będzie świetnie – boqapt

4

W systemie Linux można to zrealizować, dodając wywołanie do backtrace() w konstruktorze wyjątków w celu przechwycenia śledzenia stosu w zmiennej składowej wyjątku. Niestety, nie będzie działać dla standardowych wyjątków, tylko dla tych, które zdefiniujesz.

3

Kilka lat temu pisałem tak: Unchaining chained exceptions in C++

Zasadniczo kilka makr zalogować miejsce, w którym odpoczynek stos dzieje, gdy jest wyjątek.

Zaktualizowaną wersję frameworka można znaleźć w bibliotece Imebra (http://imebra.com).

Chciałbym reimplementować niektóre jego części (jak przechowywanie śladu stosu w lokalnym magazynie wątku).

0

Rozwiązanie Flexo jest bardzo ładne i działa dobrze. Ma również tę zaletę, że tłumaczenie z adresu śledzenia wstecznego na nazwy procedur jest wykonywane tylko w części catch, a więc aż do odbiorcy wyjątku, jeśli zależy mu na śledzeniu wstecznym lub nie.

Jednak są również przypadki, w których rozwiązanie oparte na libunwind może być preferowane, tj. Ponieważ libunwind może w niektórych sytuacjach zbierać nazwy procedur, w których funkcje nie są w stanie wykonać funkcji backtrace.

Poniżej przedstawiam pomysł oparty na odpowiedzi Flexo, ale z kilkoma rozszerzeniami. Używa libunwind do generowania śladu wstecznego w momencie rzutu i bezpośrednio drukuje na stderr. Używa biblioteki libDL do identyfikacji nazwy pliku obiektu współużytkowanego. Używa informacji o debugowaniu DWARF z elfutils, aby zebrać nazwę pliku kodu źródłowego i numer wiersza. Używa API C++ do demaskowania wyjątków C++. Użytkownicy mogą ustawić zmienną mExceptionStackTrace, aby tymczasowo włączyć/wyłączyć śledzenie stosu.

Ważną kwestią dotyczącą wszystkich rozwiązań przechwytujących __cxa_throw jest to, że potencjalnie zwiększają koszty związane z chodzeniem po stosie. Jest to szczególnie ważne w przypadku mojego rozwiązania, które dodaje znaczny narzut do uzyskiwania dostępu do symboli debuggera w celu zebrania nazwy pliku źródłowego. Może to być dopuszczalne np. W przypadku automatycznego testowania, w którym oczekuje się, że kod nie zostanie wygenerowany, i chcesz mieć potężny ślad stosu dla (nieudanych) testów, które rzucają.

// 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 
Powiązane problemy