Mam program, który wyrzuca gdzieś nieprzechwycony wyjątek. Otrzymuję tylko raport o wyrzuceniu wyjątku i brak informacji o tym, gdzie został zgłoszony. Wydaje się nielogiczne, aby program skompilowany zawierał symbole debugowania, aby nie powiadamiać mnie o tym, gdzie w moim kodzie został wygenerowany wyjątek.
Czy jest jakiś sposób, aby stwierdzić, skąd pochodzą moje wyjątki, poza ustawieniem „catch throw” w gdb i wywołaniem śledzenia wstecznego dla każdego rzuconego wyjątku?
Odpowiedzi:
Oto kilka informacji, które mogą być przydatne podczas debugowania problemu
Jeśli wyjątek nie zostanie przechwycony,
std::terminate()
automatycznie wywoływana jest specjalna funkcja biblioteki . Zakończyć jest rzeczywiście wskaźnik do funkcji i wartości domyślnej jest funkcja biblioteka standardowa Cstd::abort()
. Jeśli w przypadku nieprzechwyconego wyjątku † nie nastąpi czyszczenie , to może być pomocne w debugowaniu tego problemu, ponieważ nie są wywoływane żadne destruktory.† Jest zdefiniowane w implementacji, czy stos jest rozwijany przed
std::terminate()
wywołaniem.Wywołanie do
abort()
jest często przydatne przy generowaniu zrzutu pamięci, który można przeanalizować w celu określenia przyczyny wyjątku. Upewnij się, że włączasz zrzuty podstawowe za pośrednictwemulimit -c unlimited
(Linux).Możesz zainstalować własną
terminate()
funkcję za pomocąstd::set_terminate()
. Powinieneś móc ustawić punkt przerwania w swojej funkcji kończącej w gdb. Państwo może być w stanie wygenerować ślad stosu ze swojejterminate()
funkcji, a to backtrace może pomóc w identyfikacji lokalizacji wyjątku.Istnieje krótka dyskusja na temat niezłapanych wyjątków w książce Bruce Eckel Thinking in C ++, 2nd Ed, która również może być pomocna.
Ponieważ
terminate()
połączeńabort()
domyślnie (co spowodujeSIGABRT
sygnał domyślnie), to może być w stanie ustawićSIGABRT
obsługi, a następnie wydrukować ślad stosu z poziomu obsługi sygnału . Ten ślad może pomóc w zidentyfikowaniu lokalizacji wyjątku.Uwaga: mówię może dlatego obsługa C ++ obsługuje non-local błąd poprzez zastosowanie konstrukcji językowych do odrębnego postępowania i raportowanie błędów kodu od zwykłego kodu. Blok catch może znajdować się i często znajduje się w innej funkcji / metodzie niż punkt rzucania. Zwrócono mi na to również uwagę w komentarzach (dzięki Dan ), że jest zdefiniowane w implementacji, czy stos jest rozwijany przed
terminate()
wywołaniem.Aktualizacja: wrzuciłem razem program testowy Linuksa o nazwie, który generuje ślad wstecz w
terminate()
funkcji ustawionej przezset_terminate()
i inny w obsłudze sygnału dlaSIGABRT
. Oba ślady wsteczne poprawnie pokazują lokalizację nieobsługiwanego wyjątku.Aktualizacja 2: Dzięki wpisowi na blogu o łapaniu nieprzechwyconych wyjątków w ramach terminate , nauczyłem się kilku nowych sztuczek; w tym ponowne zgłoszenie nieprzechwyconego wyjątku w ramach procedury obsługi zakończenia. Należy zauważyć, że pusta
throw
instrukcja w niestandardowej procedurze obsługi terminacji działa z GCC i nie jest rozwiązaniem przenośnym.Kod:
#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); }
Wynik:
źródło
main
), a następnie zadzwoniterminate()
. Ale twój przykład pokazuje, że w ogóle się nie odpręża, co jest bardzo fajne.throw(int)
Specyfikacja jest niepotrzebna. 2)uc->uc_mcontext.eip
Prawdopodobnie jest bardzo zależny od platformy (np. Używany...rip
na platformie 64-bitowej). 3) Skompiluj z,-rdynamic
aby uzyskać symbole śledzenia wstecznego. 4) Biegnij,./a.out 2>&1 | c++filt
aby uzyskać ładne symbole śledzenia wstecznego.((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;
pracował dla mojego celu ARMJak powiedziałeś, możemy użyć „catch throw” w gdb i wywołać „backtrace” dla każdego wyrzuconego wyjątku. Chociaż jest to zwykle zbyt żmudne, aby wykonać je ręcznie, gdb umożliwia automatyzację tego procesu. To pozwala zobaczyć ślad wszystkich wyrzuconych wyjątków, w tym ostatniego niezłapanego:
gdb>
set pagination off catch throw commands backtrace continue end run
Bez dalszej ręcznej interwencji generuje to wiele śladów wstecznych, w tym jeden dla ostatniego niezłapanego wyjątku:
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.
Oto świetny post na blogu podsumowujący to: http://741mhz.com/throw-stacktrace [on archive.org]
źródło
Możesz stworzyć makro takie jak:
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
... i poda lokalizację, w której zostanie zgłoszony wyjątek (wprawdzie nie ślad stosu). Konieczne jest wyprowadzenie wyjątków z jakiejś klasy bazowej, która przyjmuje powyższy konstruktor.
źródło
throw new excation(...)
alethrow exception(...)
C ++ to nie Java,Nie przekazałeś informacji o używanym systemie operacyjnym / kompilatorze.
W programie Visual Studio C ++ wyjątki mogą być instrumentowane.
Zobacz „Instrumentacja obsługi wyjątków języka Visual C ++” w witrynie ddj.com
Mój artykuł „Postmortem Debugging” , również na stronie ddj.com, zawiera kod do obsługi wyjątków strukturalnych Win32 (używany przez instrumentację) do logowania itp.
źródło
Możesz oznaczyć główne wąskie miejsca w swoim kodzie,
noexcept
aby zlokalizować wyjątek, a następnie użyć libunwind (po prostu dodaj-lunwind
do parametrów linkera) (przetestowane zclang++ 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 } }
Jest dobry artykuł na ten temat.
źródło
Mam do tego kod w Windows / Visual Studio, daj mi znać, jeśli potrzebujesz konspektu. Nie wiem jednak, jak to zrobić dla kodu dwarf2, szybkie google sugeruje, że w libgcc jest funkcja _Unwind_Backtrace, która prawdopodobnie jest częścią tego, czego potrzebujesz.
źródło
Sprawdź ten wątek, może to pomoże:
Łapanie wszystkich nieobsłużonych wyjątków C ++?
Zrobiłem dobre doświadczenia z tym oprogramowaniem:
http://www.codeproject.com/KB/applications/blackbox.aspx
Może wydrukować ślad stosu do pliku dla każdego nieobsługiwanego wyjątku.
źródło
exception thrown foo.c@54, ..., re-thrown bar.c@54, ....
bez konieczności robienia tego ręcznie.