una - ¿Cómo puedo imprimir el seguimiento de la pila para las excepciones detectadas en C++ y la inyección de código en C++?
try catch c++ reference (4)
Quiero tener un seguimiento de la pila no solo para mis excepciones sino también para cualquier descendiente de std::exception
Según tengo entendido, el seguimiento de la pila se pierde completamente cuando se detecta una excepción debido al desenrollado (desenrollado) de la pila.
Así que la única manera que veo para capturarlo es mediante la inyección de información de contexto de guardado de código (seguimiento de pila) en el lugar de la llamada del constructor std::exception
. Estoy en lo cierto
Si es así, dígame cómo se puede hacer la inyección de código (si es posible) en C ++. Es posible que su método no sea completamente seguro porque lo necesito solo para la versión de depuración de mi aplicación. Puede ser que necesito usar ensamblador?
Sólo me interesa la solución para GCC. Puede utilizar las características de c ++ 0x.
Como mencionó que está contento con algo que es específico de GCC, he reunido un ejemplo de una forma en que podría hacerlo. Sin embargo, es pura maldad, interponiéndose en los aspectos internos de la biblioteca de soporte de C ++. No estoy seguro de que quisiera usar esto en el código de producción. De todas formas:
#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 llamadas a la función de implementación interna que GCC usa para enviar excepciones lanzadas. En ese momento tomamos un seguimiento de pila y lo guardamos en una variable global. Luego, cuando encontremos esa excepción más adelante en nuestro try / catch, podemos trabajar con el stacktrace para imprimir / guardar o lo que sea que desee hacer. Usamos dlsym()
para encontrar la versión real de __cxa_throw
.
Mi ejemplo arroja un int
para demostrar que puede hacer esto literalmente con cualquier tipo, no solo con sus propias excepciones definidas por el usuario.
Utiliza el type_info
para obtener el nombre del tipo que fue lanzado y luego lo desmarca.
Podría encapsular las variables globales que almacenan el stacktrace un poco mejor si quisiera.
Compilé y probé esto con:
g++ -Wall -Wextra test.cc -g -O0 -rdynamic -ldl
Que dio lo siguiente 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]
Sin embargo, no tome esto como un ejemplo de un buen consejo, ¡es un ejemplo de lo que puede hacer con un poco de engaño y hurgando en el interior!
En Linux, esto se puede implementar agregando una llamada a backtrace()
en el constructor de excepciones para capturar el seguimiento de la pila en una variable miembro de una excepción. Desafortunadamente, no funcionará para las excepciones estándar, solo para las que usted define.
Hace algunos años escribí esto: Desencadenar excepciones encadenadas en C ++
Básicamente, algunas macros registran el lugar donde se desenrolla la pila cuando se lanza una excepción.
Se puede encontrar una versión actualizada del marco en la biblioteca Imebra ( http://imebra.com ).
Reimplementaría algunas partes de él (como almacenar el seguimiento de la pila en un almacenamiento local de subprocesos).
La solución de Flexo es muy agradable y funciona bien. También tiene la ventaja de que la traducción de direcciones de retroceso a nombres de procedimientos solo se realiza en la parte de catch
, por lo que depende del destinatario de una excepción si les importa el retroceso o no.
Sin embargo, también hay casos en los que se puede preferir una solución basada en libunwind, es decir, porque en algunos casos, libunwind puede recopilar nombres de procedimientos en los que las funciones de backtrace
no pueden hacerlo.
Aquí presento una idea basada en la respuesta de Flexo, pero con varias extensiones. Utiliza libunwind para generar el retroceso en el momento del lanzamiento, y se imprime directamente en stderr. Utiliza libDL para identificar el nombre de archivo de objeto compartido. Utiliza información de depuración 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 demangle las excepciones de C ++. Los usuarios pueden configurar la variable mExceptionStackTrace
para habilitar / deshabilitar temporalmente los mExceptionStackTrace
la pila.
Un punto importante sobre todas las soluciones que interceptan __cxa_throw
es que agregan potencialmente una sobrecarga para caminar sobre 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 las pruebas automáticas, es decir, donde espera que su código no se lance, y desea tener un poderoso rastreo de pila para las pruebas (fallidas) que sí se producen.
// 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