С++ трассировка стека из необработанного исключения?
Этот вопрос задан раньше, и были ответы на конкретные окна, но не удовлетворительный ответ gcc. Я могу использовать set_terminate()
для установки функции, которая будет вызываться (вместо terminate()
), когда генерируется необработанное исключение. Я знаю, как использовать библиотеку backtrace для создания трассировки стека из заданной точки в программе. Тем не менее, это не поможет, когда вызывается моя замена окончания, так как в этот момент стопка была размотана.
Тем не менее, если я просто разрешаю программе abort()
, она создаст ядро-дамп, который содержит полную информацию о стеке от точки, в которой было выбрано исключение. Таким образом, информация есть, но есть ли программный способ ее получить, например, чтобы ее можно было регистрировать, вместо того, чтобы анализировать основной файл?
Ответы
Ответ 1
Отредактированный ответ:
Вы можете использовать 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();
}
используя этот вывод:
[email protected] ~> ./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]
[email protected] ~>
Предполагая, что у вас есть отладочные символы в двоичном формате, вы можете использовать addr2line для построения более красивого трассировки трассировки стека
[email protected] ~> addr2line 0x10000c18
/home/samm/foo.cc:23
[email protected] ~>
оригинальный ответ ниже
Я делал это в прошлом, используя boost:: error_info, чтобы добавить трассировку стека, используя backtrace
from execinfo.h
в исключение, которое выбрано.
typedef boost::error_info<struct tag_stack_str,std::string> stack_info;
Затем, когда вы ловите исключения, вы можете сделать
} catch ( const std::exception& e ) {
if ( std::string const *stack boost::get_error_info<stack_error_info>(e) ) {
std::cout << stack << std::endl;
}
}
Ответ 2
Тем не менее, если я просто разрешаю программе abort(), она создаст ядро-дамп, который содержит полную информацию о стеке с точки, в которой было выбрано исключение. Таким образом, информация есть, но есть ли программный способ ее получить, например, чтобы ее можно было регистрировать, вместо того, чтобы анализировать основной файл?
Я сомневаюсь, что мой опыт будет соответствовать вашим потребностям, но здесь все равно.
Я перегружал abort()
: либо добавляя свой собственный объектный файл перед libc, либо используя LD_PRELOAD. В моей собственной версии abort()
я запускал отладчик, который сообщал ему, что он присоединяется к процессу (ну, я точно знаю свой PID) и удаляет трассировку стека в файл (команды передавались в отладчик через командную строку). После завершения отладки завершите процесс, например. _exit(100)
.
Это было на Linux с использованием GDB. В Solaris я обычно использую подобный трюк, но из-за отсутствия работоспособного отладчика я использую инструмент pstack: system("pstack <PID>")
.
Ответ 3
Вы можете использовать libunwind (просто добавьте -lunwind
в параметры компоновщика) (проверено с помощью 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);
}
}
хорошая статья, касающаяся этой проблемы.