У меня есть программа, которая где-то выдает неперехваченное исключение. Все, что я получаю, это отчет о возникшем исключении и никакой информации о том, где оно было создано. Кажется нелогичным, если программа, скомпилированная с использованием символов отладки, не уведомляет меня о том, где в моем коде было сгенерировано исключение.
Есть ли способ узнать, откуда берутся мои исключения, если не установить 'catch throw' в gdb и вызвать обратную трассировку для каждого отдельного выброшенного исключения?
Ответы:
Вот некоторая информация, которая может быть полезна при отладке вашей проблемы.
Если исключение не перехвачено,
std::terminate()
автоматически вызывается специальная библиотечная функция . Прекратить это указатель на функцию и значение по умолчанию функции стандартной библиотеки Cstd::abort()
. Если для неперехваченного исключения не происходит очистки † , это может быть полезно при отладке этой проблемы, поскольку деструкторы не вызываются.† Это определяется реализацией, разворачивается ли стек перед вызовом
std::terminate()
.Обращение к
abort()
часто бывает полезным при создании дампа памяти, который можно проанализировать для определения причины исключения. Убедитесь, что вы включили дампы ядра черезulimit -c unlimited
(Linux).Вы можете установить свою собственную
terminate()
функцию, используяstd::set_terminate()
. У вас должна быть возможность установить точку останова для функции завершения в gdb. Вы можете сгенерировать обратную трассировку стека из своейterminate()
функции, и эта обратная трассировка может помочь в определении местоположения исключения.Краткое обсуждение неперехваченных исключений в книге Брюса Эккеля «Мышление на C ++», 2-е изд. , Также может быть полезно.
Поскольку
terminate()
вызовыabort()
по умолчанию (что по умолчанию вызоветSIGABRT
сигнал), вы можете установитьSIGABRT
обработчик, а затем распечатать трассировку стека из обработчика сигнала . Эта обратная трассировка может помочь в определении местоположения исключения.Примечание. Я говорю « может», потому что C ++ поддерживает нелокальную обработку ошибок за счет использования языковых конструкций для отделения кода обработки ошибок и создания отчетов от обычного кода. Блок catch может быть и часто находится в другой функции / методе, нежели точка выброса. Мне также было указано в комментариях (спасибо, Дэн ), что это зависит от реализации, разворачивается ли стек перед вызовом
terminate()
.Обновление: я собрал тестовую программу Linux под названием, которая генерирует обратную трассировку в
terminate()
функции, установленной через,set_terminate()
и другую в обработчике сигналов дляSIGABRT
. Обе трассировки правильно показывают местоположение необработанного исключения.Обновление 2: благодаря сообщению в блоге « Перехват неперехваченных исключений в terminate» я узнал несколько новых приемов; включая повторную генерацию неперехваченного исключения в обработчике завершения. Важно отметить, что пустой
throw
оператор в настраиваемом обработчике завершения работает с GCC и не является переносимым решением.Код:
#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); }
Выход:
источник
main
), а затем вызовет егоterminate()
. Но ваш пример показывает, что разматывания вообще не происходит, что очень круто.throw(int)
не нужна. 2)uc->uc_mcontext.eip
Вероятно, очень зависит от платформы (например, использование...rip
на 64-битной платформе). 3) Скомпилируйте с,-rdynamic
чтобы получить символы обратной трассировки. 4) Запустите,./a.out 2>&1 | c++filt
чтобы получить красивые символы обратной трассировки.((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;
работал на моей цели ARMКак вы говорите, мы можем использовать «catch throw» в gdb и вызывать «backtrace» для каждого отдельного сгенерированного исключения. Хотя это обычно слишком утомительно, чтобы делать это вручную, gdb позволяет автоматизировать процесс. Это позволяет увидеть трассировку всех сгенерированных исключений, включая последнее неперехваченное:
gdb>
set pagination off catch throw commands backtrace continue end run
Без дальнейшего ручного вмешательства это генерирует множество обратных трассировок, в том числе одну для последнего неперехваченного исключения:
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.
Вот отличный пост в блоге, завершающий это: http://741mhz.com/throw-stacktrace [на archive.org]
источник
Вы можете создать такой макрос:
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
... и он предоставит вам место, где выбрасывается исключение (по общему признанию, не трассировка стека). Вам необходимо получить исключения из некоторого базового класса, который принимает вышеуказанный конструктор.
источник
throw new excation(...)
ноthrow exception(...)
C ++ - это не Java,Вы можете пометить основные узкие места в своем коде,
noexcept
чтобы найти исключение, а затем использовать 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); // change to desired return code } }
По этому поводу есть хорошая статья .
источник
Вы не передали информацию о том, какую ОС / компилятор вы используете.
В Visual Studio C ++ исключения могут быть инструментированы.
См. «Инструментарий обработки исключений Visual C ++» на ddj.com.
Моя статья «Посмертная отладка» , также на ddj.com, включает код для использования структурированной обработки исключений Win32 (используемой инструментарием) для ведения журнала и т. Д.
источник
У меня есть код для этого в Windows / Visual Studio, дайте мне знать, если вам нужен план. Не знаю, как это сделать для кода dwarf2, быстрый гугл подсказывает, что в libgcc есть функция _Unwind_Backtrace, которая, вероятно, является частью того, что вам нужно.
источник
Проверьте эту ветку, возможно, это поможет:
Поймать все необработанные исключения C ++?
Я получил хороший опыт работы с этим программным обеспечением:
http://www.codeproject.com/KB/applications/blackbox.aspx
Он может распечатать трассировку стека в файл для любого необработанного исключения.
источник
exception thrown foo.c@54, ..., re-thrown bar.c@54, ....
не выполнялась вручную.