c++ pila de seguimiento de la excepción no controlada?
exception-handling stack-trace (3)
Esta pregunta se ha hecho antes y ha habido respuestas específicas de Windows, pero no hay una respuesta satisfactoria de gcc. Puedo usar set_terminate()
para establecer una función que se llamará (en lugar de terminate()
) cuando se lance una excepción no controlada. Sé cómo usar la biblioteca de seguimiento para generar un seguimiento de pila desde un punto dado en el programa. Sin embargo, esto no ayudará cuando se llame a mi reemplazo de terminación, ya que en ese momento la pila se ha desenrollado.
Sin embargo, si simplemente permito que el programa abort()
, producirá un volcado de núcleo que contiene la información completa de la pila desde el punto en el que se lanzó la excepción. Entonces, la información está ahí, pero ¿hay una manera programática de obtenerla, por ejemplo, para que se pueda registrar, en lugar de tener que examinar un archivo principal?
Sin embargo, si simplemente permito que el programa aborte (), producirá un volcado de núcleo que contiene la información completa de la pila desde el punto en el que se lanzó la excepción. Entonces, la información está ahí, pero ¿hay una manera programática de obtenerla, por ejemplo, para que se pueda registrar, en lugar de tener que examinar un archivo principal?
Dudo que mi experiencia se ajuste a sus necesidades, pero aquí va de todos modos.
Estaba sobrecargando abort()
: ya sea agregando mi propio archivo de objeto antes de la libc o usando LD_PRELOAD. En mi propia versión de abort()
estaba iniciando el depurador diciéndole que se conectara al proceso (bueno, seguramente conozco mi PID) y volcaba el seguimiento de la pila en un archivo (los comandos se pasaban al depurador a través de la línea de comandos). Después de que el depurador haya terminado, finalice el proceso con, por ejemplo, _exit(100)
.
Eso fue en Linux usando GDB. En Solaris, habitualmente empleo trucos similares, pero debido a la falta de disponibilidad de un depurador sano, uso la herramienta pstack: system("pstack <PID>")
.
Puedes usar 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);
}
}
Hay un buen artículo sobre el tema.
Respuesta editada:
Puedes usar std::set_terminate
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include <execinfo.h>
void
handler()
{
void *trace_elems[20];
int trace_elem_count(backtrace( trace_elems, 20 ));
char **stack_syms(backtrace_symbols( trace_elems, trace_elem_count ));
for ( int i = 0 ; i < trace_elem_count ; ++i )
{
std::cout << stack_syms[i] << "/n";
}
free( stack_syms );
exit(1);
}
int foo()
{
throw std::runtime_error( "hello" );
}
void bar()
{
foo();
}
void baz()
{
bar();
}
int
main()
{
std::set_terminate( handler );
baz();
}
dando esta salida:
samm@macmini ~> ./a.out
./a.out [0x10000d20]
/usr/lib/libstdc++.so.6 [0xf9bb8c8]
/usr/lib/libstdc++.so.6 [0xf9bb90c]
/usr/lib/libstdc++.so.6 [0xf9bbaa0]
./a.out [0x10000c18]
./a.out [0x10000c70]
./a.out [0x10000ca0]
./a.out [0x10000cdc]
/lib/libc.so.6 [0xfe4dd80]
/lib/libc.so.6 [0xfe4dfc0]
samjmill@bgqfen4 ~>
asumiendo que tiene símbolos de depuración en su binario, entonces puede usar addr2line para construir un seguimiento de pila más bonito postmortem
samm@macmini ~> addr2line 0x10000c18
/home/samm/foo.cc:23
samm@macmini ~>
la respuesta original esta abajo
He hecho esto en el pasado usando boost::error_info para inyectar el seguimiento de la pila usando backtrace
de execinfo.h
en una excepción que se produce.
typedef boost::error_info<struct tag_stack_str,std::string> stack_info;
Luego cuando atrapes las excepciones, puedes hacer
} catch ( const std::exception& e ) {
if ( std::string const *stack boost::get_error_info<stack_error_info>(e) ) {
std::cout << stack << std::endl;
}
}