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


Tengo un programa 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 programa compilado 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?


75
2018-03-14 18:05


origen


Respuestas:


Aquí hay algo de información que mayo ser de utilidad en la depuración de su problema

Si no se detecta una excepción, la función de biblioteca especial std::terminate() se llama automáticamente. Terminar es en realidad un puntero a una función y el valor predeterminado es la función de biblioteca estándar C. std::abort(). Si no hay limpiezas para una excepción no detectada, eso mayo en realidad ser útil en la depuración de este problema ya que no se llaman destructores.
† Es definido por la implementación si la pila se desenrolla o no antes std::terminate() se llama.


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


Puedes instalar tu propio terminate() función mediante el uso std::set_terminate(). Debería poder establecer un punto de interrupción en su función de terminación en gdb. Tú mayo ser capaz de generar un seguimiento de la pila de su terminate() función y esta backtrace mayo ayuda para identificar la ubicación de la excepción.

Hay una breve discusión sobre excepciones no detectadas en Pensamiento en C ++ de Bruce Eckel, 2nd Ed. eso puede ser útil también.


Ya que terminate() llamadas abort() por defecto (lo que causará un SIGABRT señal por defecto), usted mayo ser capaz de establecer un SIGABRT controlador y luego imprimir una traza inversa desde el manejador de señal. Este backtrace mayo ayuda para identificar la ubicación de la excepción.


Nota: yo digo mayo 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 informe 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 Dan) que está definido por la implementación si la pila se desenrolla o no antes terminate() se llama.

Actualizar: Lancé un programa de prueba llamado Linux que genera una traza inversa en un terminate() función establecida a través de set_terminate() y otro en un controlador 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 en 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 el vacío throw La instrucción 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 <execinfo.h>
#include <signal.h>
#include <string.h>

#include <iostream>
#include <cstdlib>
#include <stdexcept>

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 fotogramas

[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 fotogramas

[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]


64
2018-03-15 07:08



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/


34
2017-11-02 02:00



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 pila). Es necesario derivar sus excepciones de alguna clase base que tome el constructor anterior.


16
2018-03-14 19:07



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

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

Ver "Instrumentación de manejo de excepciones de Visual C ++" en ddj.com

Mi articulo "Depuración post mortem", también en ddj.com incluye código para usar el manejo de excepciones estructurado de Win32 (utilizado por la instrumentación) para el registro, etc.


5
2018-03-15 10:02



Puede marcar los principales lugares estrechos en su código como noexcept para localizar una excepción, luego use libunwind (solo agrega -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 <memory>

#include <cstdlib>

#include <cxxabi.h>

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 "<null>";
    }
    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 <ostream>

void
backtrace(std::ostream & _out) noexcept;

backtrace.cpp:

#include "backtrace.hpp"

#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>

#include <cstdint>

#define UNW_LOCAL_ONLY
#include <libunwind.h>

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 <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>

#include <cstdlib>

#include <cxxabi.h>

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
}

}

Ahi esta buen artículo sobre el problema


4
2017-07-26 05:42



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.


1
2018-03-14 18:11



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 pila a un archivo para cualquier excepción no controlada.


1
2018-03-14 18:11