2012-07-26 12 views
23

Quiero tener Seguimiento de la pila no para mis excepciones, sino también por los descendientes de std::exceptiontraza cómo se puede imprimir la pila de excepciones atrapados en C++ y la inyección de código en C++

como yo lo entiendo, seguimiento de la pila está completamente perdido cuando se atrapa una excepción debido al desenrollamiento de la pila (desenrollado).

Así que la única forma en que veo que agarro es la inyección de información de contexto de ahorro de código (seguimiento de la pila) en el lugar de la llamada al constructor std::exception. ¿Estoy en lo cierto?

Si es el caso, dígame cómo se puede hacer la inyección de código (si puede) en C++. Es posible que su método no sea completamente seguro porque solo lo necesito para la versión de depuración de mi aplicación. ¿Puede ser que necesite usar el ensamblador?

Estoy interesado solo en la solución para GCC. Puede usar las funciones de C++ 0x

+1

[Esta respuesta] (http://stackoverflow.com/questions/3355683/c-stack-trace-from-unhandled-exception) podría ayudar. – jxh

+0

@ user315052 Esa respuesta es para excepciones no detectadas y no funciona para atrapado. – boqapt

+0

Es cierto, pero puede rellenar la matriz de cadenas C en un 'std :: string', y pasar eso al constructor de su excepción como' what' (o una gran parte de ella, de todos modos). – jxh

Respuesta

31

Como mencionaste que estás contento con algo que es específico de GCC, he creado un ejemplo de cómo podrías hacerlo. Aunque es pura maldad, interponiéndose en el interior de la biblioteca de soporte de C++. No estoy seguro de querer usar esto en el código de producción. De todos modos:

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

Básicamente robamos las llamadas a la función de implementación interna que GCC usa para despachar excepciones arrojadas. En ese punto tomamos un seguimiento de pila y lo guardamos en una variable global. Luego, cuando nos encontremos con esa excepción más adelante en nuestro try/catch, podemos trabajar con stacktrace para imprimir/guardar o lo que sea que quieras hacer. Utilizamos dlsym() para encontrar la versión real de __cxa_throw.

Mi ejemplo arroja un int para demostrar que puede hacer esto con literalmente cualquier tipo, no solo sus propias excepciones definidas por el usuario.

Utiliza el type_info para obtener el nombre del tipo que se arrojó y luego lo demanda.

Puede encapsular las variables globales que almacenan la stacktrace un poco mejor si lo desea.

I compilado y probado esto con:

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

que dio los siguientes cuando se ejecuta:

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

Por favor, no tome esto como un ejemplo de un buen consejo, aunque - es un ejemplo de ¡Qué puedes hacer con un poco de engaño y hurgando en el interior!

+0

¡Muchas gracias! No es un problema que no sea seguro porque lo necesito solo para un desarrollo más rápido: ver inmediatamente dónde ocurrió un error cuando estoy probando y depurando, como en los idiomas modernos. – boqapt

+0

@ user484936 El gran riesgo aquí es que no se da cuenta cuando hay un cambio de ABI y termina en un mundo de dolor de comportamiento indefinido. Si está interesado, puedo expandirlo para imprimir el tipo de excepción, incluso dentro de un bloque catch (...). – Flexo

+0

Sí, estoy interesado, será genial – boqapt

4

En Linux esto puede implementarse agregando una llamada a backtrace() en el constructor de excepción para capturar el rastreo de la pila en la variable miembro de una excepción. Desafortunadamente, no funcionará para las excepciones estándar, solo para las que usted defina. Hace

3

algunos años escribí esto: Unchaining chained exceptions in C++

Básicamente algunas macros registrar el lugar donde el desenredo de pila ocurre cuando se produce una excepción.

Puede encontrar una versión actualizada del marco en la biblioteca Imebra (http://imebra.com).

Reimplementaría algunas partes (como almacenar el seguimiento de pila en un almacenamiento local de subprocesos).

0

La solución de Flexo es muy agradable y funciona bien. También tiene la ventaja de que la traducción de direcciones de rastreo a nombres de procedimiento solo se realiza en la parte catch, por lo que depende del receptor de una excepción si se preocupan por la traza inversa o no.

Sin embargo, también hay casos en los que se puede preferir una solución basada en libunwind, es decir, porque libunwind puede recopilar nombres de procedimientos donde las funciones backtrace no lo hacen.

Aquí presento una idea basada en la respuesta de Flexo, pero con varias extensiones. Utiliza libunwind para generar la traza inversa en el momento del lanzamiento e imprime directamente en stderr. Utiliza libDL para identificar el nombre del archivo del objeto compartido. Utiliza la información de depuración de DWARF de elfutils para recopilar el nombre del archivo de código fuente y el número de línea. Utiliza la API de C++ para exigir excepciones de C++. Los usuarios pueden establecer la variable mExceptionStackTrace para habilitar/deshabilitar temporalmente los rastreos de la pila.

Un punto importante acerca de todas las soluciones que interceptan __cxa_throw es que agregan potencialmente una sobrecarga para recorrer la pila. Esto es especialmente cierto para mi solución que agrega una sobrecarga significativa para acceder a los símbolos del depurador para recopilar el nombre del archivo de origen. Esto puede ser aceptable en, por ejemplo, las pruebas automáticas en las que espera que su código no se lance, y desea tener un seguimiento de pila potente para las pruebas (fallidas) que arrojan.

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