Saya memiliki program yang menampilkan pengecualian yang tidak tertangkap di suatu tempat. Yang saya dapatkan hanyalah laporan tentang pengecualian yang dilemparkan, dan tidak ada informasi ke mana itu dilemparkan. Tampaknya tidak masuk akal untuk program yang dikompilasi berisi simbol debug untuk tidak memberi tahu saya di mana pengecualian kode saya dibuat.
Apakah ada cara untuk mengetahui di mana pengecualian saya berasal dari kekurangan pengaturan 'catch throw' di gdb dan memanggil backtrace untuk setiap pengecualian yang dilempar?
Jawaban:
Berikut beberapa info yang mungkin berguna untuk men-debug masalah Anda
Jika pengecualian tidak tertangkap, fungsi perpustakaan khusus
std::terminate()
dipanggil secara otomatis. Hentikan sebenarnya adalah penunjuk ke fungsi dan nilai defaultnya adalah fungsi pustaka C Standarstd::abort()
. Jika tidak ada pembersihan terjadi karena eksepsi tidak tertangkap † , itu mungkin benar-benar membantu dalam debugging masalah ini karena tidak ada destructors disebut.† Ini adalah implementasi-ditentukan apakah tumpukan dibatalkan sebelum
std::terminate()
dipanggil.Panggilan ke
abort()
sering kali berguna dalam menghasilkan dump inti yang dapat dianalisis untuk menentukan penyebab pengecualian. Pastikan Anda mengaktifkan core dump melaluiulimit -c unlimited
(Linux).Anda dapat menginstal
terminate()
fungsi Anda sendiri dengan menggunakanstd::set_terminate()
. Anda harus bisa mengatur breakpoint pada fungsi terminate Anda di gdb. Anda mungkin dapat membuat pelacakan mundur tumpukan dariterminate()
fungsi Anda dan pelacakan mundur ini dapat membantu dalam mengidentifikasi lokasi pengecualian.Ada diskusi singkat tentang pengecualian yang tidak tertangkap dalam Bruce Eckel's Thinking in C ++, 2nd Ed yang mungkin bisa membantu juga.
Karena
terminate()
panggilanabort()
secara default (yang akan menyebabkanSIGABRT
sinyal secara default), Anda mungkin dapat mengaturSIGABRT
penangan dan kemudian mencetak pelacakan mundur tumpukan dari dalam penangan sinyal . Pelacakan balik ini dapat membantu dalam mengidentifikasi lokasi pengecualian.Catatan: Saya katakan mungkin karena C ++ mendukung penanganan kesalahan non-lokal melalui penggunaan konstruksi bahasa untuk memisahkan penanganan kesalahan dan kode pelaporan dari kode biasa. Blok tangkapan dapat, dan sering kali, terletak di fungsi / metode yang berbeda dari titik lemparan. Itu juga telah ditunjukkan kepada saya di komentar (terima kasih Dan ) bahwa itu adalah implementasi-ditentukan apakah tumpukan dibatalkan sebelum
terminate()
dipanggil.Pembaruan: Saya mengumpulkan program uji Linux yang disebut yang menghasilkan pelacakan balik dalam
terminate()
fungsi yang ditetapkan melaluiset_terminate()
dan lainnya dalam penangan sinyal untukSIGABRT
. Kedua jejak belakang dengan benar menunjukkan lokasi pengecualian tidak tertangani.Pembaruan 2: Berkat posting blog tentang Menangkap pengecualian yang tidak tertangkap dalam penghentian , saya belajar beberapa trik baru; termasuk membuang kembali pengecualian yang tidak tertangkap dalam penangan terminate. Penting untuk diperhatikan bahwa
throw
pernyataan kosong dalam penangan penghentian khusus berfungsi dengan GCC dan bukan solusi portabel.Kode:
#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); }
Keluaran:
sumber
main
) dan kemudian akan dipanggilterminate()
. Tapi contoh Anda menunjukkan bahwa tidak ada relaksasi sama sekali, yang sangat keren.throw(int)
Spesifikasi tidak diperlukan. 2)uc->uc_mcontext.eip
Mungkin sangat bergantung pada platform (misalnya, digunakan...rip
pada platform 64-bit). 3) Kompilasi dengan-rdynamic
sehingga Anda mendapatkan simbol backtrace. 4) Jalankan./a.out 2>&1 | c++filt
untuk mendapatkan simbol lacak balik yang cantik.((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;
bekerja untuk sasaran ARM sayaSeperti yang Anda katakan, kita bisa menggunakan 'catch throw' di gdb dan memanggil 'backtrace' untuk setiap pengecualian yang dilempar. Meskipun biasanya terlalu membosankan untuk dilakukan secara manual, gdb memungkinkan otomatisasi proses. Itu memungkinkan melihat lacak balik dari semua pengecualian yang muncul, termasuk yang terakhir yang tidak tertangkap:
gdb>
set pagination off catch throw commands backtrace continue end run
Tanpa intervensi manual lebih lanjut, ini menghasilkan banyak jejak balik, termasuk satu untuk pengecualian terakhir yang tidak tertangkap:
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.
Berikut entri blog bagus yang merangkumnya : http://741mhz.com/throw-stacktrace [di archive.org]
sumber
Anda dapat membuat makro seperti:
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )
... dan ini akan memberi Anda lokasi tempat pengecualian dilemparkan (memang bukan pelacakan tumpukan). Anda perlu mendapatkan pengecualian dari beberapa kelas dasar yang menggunakan konstruktor di atas.
sumber
throw new excation(...)
tetapithrow exception(...)
C ++ bukan Java,Anda tidak menyampaikan informasi tentang OS / Compiler yang Anda gunakan.
Dalam Visual Studio C ++ pengecualian dapat diinstrumentasi.
Lihat "Instrumentasi Penanganan Pengecualian C ++ Visual" di ddj.com
Artikel saya "Postmortem Debugging" , juga di ddj.com menyertakan kode untuk menggunakan penanganan pengecualian terstruktur Win32 (digunakan oleh instrumentasi) untuk logging dll.
sumber
Anda dapat menandai tempat sempit utama dalam kode Anda
noexcept
untuk menemukan pengecualian, lalu gunakan libunwind (cukup tambahkan-lunwind
ke parameter linker) (diuji denganclang++ 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 } }
Ada artikel bagus tentang masalah ini.
sumber
Saya punya kode untuk melakukan ini di Windows / Visual Studio, beri tahu saya jika Anda menginginkan garis besar. Tidak tahu bagaimana melakukannya untuk kode dwarf2, Google cepat menyarankan bahwa ada fungsi _Unwind_Backtrace di libgcc yang mungkin merupakan bagian dari apa yang Anda butuhkan.
sumber
Periksa utas ini, mungkin ini membantu:
Menangkap semua pengecualian C ++ yang tidak tertangani?
Saya mendapatkan pengalaman yang baik dengan perangkat lunak itu:
http://www.codeproject.com/KB/applications/blackbox.aspx
Itu dapat mencetak jejak tumpukan ke file untuk pengecualian yang tidak tertangani.
sumber
exception thrown foo.c@54, ..., re-thrown bar.c@54, ....
tanpa harus melakukannya secara manual.