¿Cómo puedo encontrar dónde se lanzó una excepción en C ++?

Tengo un progtwig que arroja una excepción no detectada en alguna parte. Todo lo que obtengo es un informe de una excepción lanzada, y no hay información sobre dónde fue lanzada. Parece ilógico que un progtwig comstackdo para contener símbolos de depuración no me notifique dónde se generó una excepción en mi código.

¿Hay alguna forma de saber de dónde vienen mis excepciones si no se establece ‘atrapar lanzamiento’ en gdb y se llama a una traza inversa para cada excepción lanzada?

Aquí hay alguna información que puede ser útil para depurar su problema

Si no se detecta una excepción, se llama automáticamente a la función de biblioteca especial std::terminate() . Terminar es en realidad un puntero a una función y el valor predeterminado es la función de la biblioteca Estándar C std::abort() . Si no se realiza una limpieza para una excepción no detectada , en realidad puede ser útil para depurar este problema ya que no se invocan destructores.
† Está definido por la implementación si la stack se desenrolla o no antes de llamar a std::terminate() .


Una llamada a abort() suele ser útil para generar un volcado de núcleo que se puede analizar para determinar la causa de la excepción. Asegúrese de habilitar los volcados del núcleo a través de ulimit -c unlimited (Linux).


Puede instalar su propia función terminate() usando std::set_terminate() . Debería poder establecer un punto de interrupción en su función de terminación en gdb. Es posible que pueda generar una traza inversa desde su función terminate() y esta traza inversa puede ayudar a identificar la ubicación de la excepción.

Hay una breve discusión sobre las excepciones no detectadas en Thinking in C ++, 2nd ed de Bruce Eckel, que también puede ser útil.


Como las llamadas a terminate() abort() por defecto (lo que provocará una señal SIGABRT por defecto), es posible que pueda configurar un controlador SIGABRT y luego imprimir una traza inversa desde el manejador de señal . Esta traza inversa puede ayudar a identificar la ubicación de la excepción.


Nota: Digo may porque C ++ admite el manejo de errores no locales mediante el uso de construcciones de lenguaje para separar el manejo de errores y el código de informes del código ordinario. El bloque catch se puede ubicar, y a menudo se encuentra, en una función / método diferente al punto de lanzamiento. También se me ha señalado en los comentarios (gracias a Dan ) que está definido por la implementación si la stack se desenrolla o no antes de llamar a terminate() .

Actualización: Lancé un progtwig de prueba llamado Linux que genera una set_terminate() en una función terminate() establecida a través de set_terminate() y otra en un manejador de señal para SIGABRT . Ambos trazos retrospectivos muestran correctamente la ubicación de la excepción no controlada.

Actualización 2: gracias a una publicación de blog sobre Captura de excepciones no detectadas dentro de terminate , aprendí algunos trucos nuevos; incluyendo el relanzamiento de la excepción no detectada dentro del manejador de finalización. Es importante tener en cuenta que la instrucción throw vacía dentro del controlador de finalización personalizado funciona con GCC y no es una solución portátil.

Código:

 #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifndef __USE_GNU #define __USE_GNU #endif #include  #include  #include  #include  #include  #include  void my_terminate(void); namespace { // invoke set_terminate as part of global constant initialization static const bool SET_TERMINATE = std::set_terminate(my_terminate); } // This structure mirrors the one found in /usr/include/asm/ucontext.h typedef struct _sig_ucontext { unsigned long uc_flags; struct ucontext *uc_link; stack_t uc_stack; struct sigcontext uc_mcontext; sigset_t uc_sigmask; } sig_ucontext_t; void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext) { sig_ucontext_t * uc = (sig_ucontext_t *)ucontext; // Get the address at the time the signal was raised from the EIP (x86) void * caller_address = (void *) uc->uc_mcontext.eip; std::cerr < < "signal " << sig_num << " (" << strsignal(sig_num) << "), address is " << info->si_addr < < " from " << caller_address << std::endl; void * array[50]; int size = backtrace(array, 50); std::cerr << __FUNCTION__ << " backtrace returned " << size << " frames\n\n"; // overwrite sigaction with caller's address array[1] = caller_address; char ** messages = backtrace_symbols(array, size); // skip first stack frame (points here) for (int i = 1; i < size && messages != NULL; ++i) { std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl; } std::cerr << std::endl; free(messages); exit(EXIT_FAILURE); } void my_terminate() { static bool tried_throw = false; try { // try once to re-throw currently active exception if (!tried_throw++) throw; } catch (const std::exception &e) { std::cerr << __FUNCTION__ << " caught unhandled exception. what(): " << e.what() << std::endl; } catch (...) { std::cerr << __FUNCTION__ << " caught unknown/unhandled exception." << std::endl; } void * array[50]; int size = backtrace(array, 50); std::cerr << __FUNCTION__ << " backtrace returned " << size << " frames\n\n"; char ** messages = backtrace_symbols(array, size); for (int i = 0; i < size && messages != NULL; ++i) { std::cerr << "[bt]: (" << i << ") " << messages[i] << std::endl; } std::cerr << std::endl; free(messages); abort(); } int throw_exception() { // throw an unhandled runtime error throw std::runtime_error("RUNTIME ERROR!"); return 0; } int foo2() { throw_exception(); return 0; } int foo1() { foo2(); return 0; } int main(int argc, char ** argv) { struct sigaction sigact; sigact.sa_sigaction = crit_err_hdlr; sigact.sa_flags = SA_RESTART | SA_SIGINFO; if (sigaction(SIGABRT, &sigact, (struct sigaction *)NULL) != 0) { std::cerr << "error setting handler for signal " << SIGABRT << " (" << strsignal(SIGABRT) << ")\n"; exit(EXIT_FAILURE); } foo1(); exit(EXIT_SUCCESS); } 

Salida:

 my_terminate capturó la excepción sin manos.  qué (): ¡ERROR DE TIEMPO REAL!
 my_terminate backtrace devolvió 10 fotogtwigs

 [bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52]
 [bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
 [bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
 [bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
 [bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
 [bt]: (5) ./test (foo2__Fv+0xb) [0x8049043]
 [bt]: (6) ./test (foo1__Fv+0xb) [0x8049057]
 [bt]: (7) ./test(main+0xc1) [0x8049121]
 [bt]: (8) ./test (__libc_start_main+0x95) [0x42017589]
 [bt]: (9) ./test (__eh_alloc+0x3d) [0x8048b21]

 señal 6 (abortada), la dirección es 0x1239 desde 0x42029331
 crit_err_hdlr backtrace devolvió 13 fotogtwigs

 [bt]: (1) ./test (killer +0x11) [0x42029331]
 [bt]: (2) ./test (aborto +0x16e) [0x4202a8c2]
 [bt]: (3) ./test [0x8048f9f]
 [bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
 [bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
 [bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
 [bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
 [bt]: (8) ./test (foo2__Fv+0xb) [0x8049043]
 [bt]: (9) ./test (foo1__Fv+0xb) [0x8049057]
 [bt]: (10) ./test(main+0xc1) [0x8049121]
 [bt]: (11) ./test (__libc_start_main+0x95) [0x42017589]
 [bt]: (12) ./test (__eh_alloc+0x3d) [0x8048b21]

Como dices, podemos usar ‘catch throw’ en gdb y llamar ‘backtrace’ para cada excepción lanzada. Si bien suele ser demasiado tedioso hacerlo manualmente, gdb permite la automatización del proceso. Eso permite ver la traza inversa de todas las excepciones que se lanzan, incluido el último no capturado:

gdb>

 set pagination off catch throw commands backtrace continue end run 

Sin más intervención manual, esto genera muchas trazas inversas, incluida una para la última excepción no detectada:

 Catchpoint 1 (exception thrown), 0x00a30 in __cxa_throw () from libstdc++.so.6 #0 0x0da30 in __cxa_throw () from /usr/.../libstdc++.so.6 #1 0x021f2 in std::__throw_bad_weak_ptr () at .../shared_ptr_base.h:76 [...] terminate called after throwing an instance of 'std::bad_weak_ptr' what(): bad_weak_ptr Program received signal SIGABRT, Aborted. 

Aquí hay una gran publicación de blog que resume esto: http://741mhz.com/throw-stacktrace/

Puede crear una macro como:

 #define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) ) 

… y le dará la ubicación donde se lanza la excepción (no es cierto que el seguimiento de la stack). Es necesario derivar sus excepciones de alguna clase base que tome el constructor anterior.

No pasó información sobre qué sistema operativo / comstackdor usa.

En Visual Studio C ++, las excepciones se pueden instrumentar.

Consulte “Instrumentación de control de excepciones de Visual C ++” en ddj.com

Mi artículo “Depuración postmortem” , también en ddj.com incluye código para usar el manejo de excepciones estructuradas de Win32 (utilizado por la instrumentación) para el registro, etc.

Puede marcar los principales lugares estrechos en su código como noexcept para localizar una excepción, luego use libunwind (simplemente agregue -lunwind a los parámetros del enlazador) (probado con clang++ 3.6 ):

demagle.hpp:

 #pragma once char const * get_demangled_name(char const * const symbol) noexcept; 

demangle.cpp:

 #include "demangle.hpp" #include  #include  #include  namespace { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #pragma clang diagnostic ignored "-Wexit-time-destructors" std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free}; #pragma clang diagnostic pop } char const * get_demangled_name(char const * const symbol) noexcept { if (!symbol) { return ""; } int status = -4; demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status)); return ((status == 0) ? demangled_name.get() : symbol); } 

backtrace.hpp:

 #pragma once #include  void backtrace(std::ostream & _out) noexcept; 

backtrace.cpp:

 #include "backtrace.hpp" #include  #include  #include  #include  #include  #define UNW_LOCAL_ONLY #include  namespace { void print_reg(std::ostream & _out, unw_word_t reg) noexcept { constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4; _out < < "0x" << std::setfill('0') << std::setw(address_width) << reg; } char symbol[1024]; } void backtrace(std::ostream & _out) noexcept { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); _out << std::hex << std::uppercase; while (0 < unw_step(&cursor)) { unw_word_t ip = 0; unw_get_reg(&cursor, UNW_REG_IP, &ip); if (ip == 0) { break; } unw_word_t sp = 0; unw_get_reg(&cursor, UNW_REG_SP, &sp); print_reg(_out, ip); _out << ": (SP:"; print_reg(_out, sp); _out << ") "; unw_word_t offset = 0; if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) { _out << "(" << get_demangled_name(symbol) << " + 0x" << offset << ")\n\n"; } else { _out << "-- error: unable to obtain symbol name for this frame\n\n"; } } _out << std::flush; } 

backtrace_on_terminate.hpp:

 #include "demangle.hpp" #include "backtrace.hpp" #include  #include  #include  #include  #include  #include  #include  namespace { [[noreturn]] void backtrace_on_terminate() noexcept; static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{}); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wglobal-constructors" #pragma clang diagnostic ignored "-Wexit-time-destructors" std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate}; #pragma clang diagnostic pop [[noreturn]] void backtrace_on_terminate() noexcept { std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any backtrace(std::clog); if (std::exception_ptr ep = std::current_exception()) { try { std::rethrow_exception(ep); } catch (std::exception const & e) { std::clog < < "backtrace: unhandled exception std::exception:what(): " << e.what() << std::endl; } catch (...) { if (std::type_info * et = abi::__cxa_current_exception_type()) { std::clog << "backtrace: unhandled exception type: " << get_demangled_name(et->name()) < < std::endl; } else { std::clog << "backtrace: unhandled unknown exception" << std::endl; } } } std::_Exit(EXIT_FAILURE); // change to desired return code } } 

Hay un buen artículo sobre el tema.

Tengo un código para hacer esto en Windows / Visual Studio, avíseme si quiere un esquema. Sin embargo, no sé cómo hacerlo para el código dwarf2, un rápido Google sugiere que hay una función _Unwind_Backtrace en libgcc que probablemente sea parte de lo que necesita.

Verifique este hilo, quizás ayude:

¿Captura todas las excepciones de C ++ no controladas?

Hice buenas experiencias con ese software:

http://www.codeproject.com/KB/applications/blackbox.aspx

Puede imprimir un seguimiento de stack a un archivo para cualquier excepción no controlada.