Pertanyaan Bagaimana saya menemukan di mana pengecualian dilemparkan dalam C ++?


Saya memiliki program yang melempar pengecualian yang tidak tertangkap di suatu tempat. Semua yang saya dapatkan adalah laporan tentang pengecualian yang dilempar, dan tidak ada informasi mengenai tempat itu dilemparkan. Tampaknya tidak logis untuk sebuah program yang dikompilasi untuk memuat simbol-simbol debug untuk tidak memberi tahu saya di mana di dalam kode saya sebuah pengecualian dihasilkan.

Apakah ada cara untuk mengetahui di mana pengecualian saya berasal dari pendek pengaturan 'menangkap lemparan' di gdb dan memanggil backtrace untuk setiap pengecualian tunggal?


75
2018-03-14 18:05


asal


Jawaban:


Berikut beberapa info itu mungkin berguna dalam debugging masalah Anda

Jika pengecualian tidak tertangkap, fungsi perpustakaan khusus std::terminate() secara otomatis dipanggil. Hentikan sebenarnya pointer ke fungsi dan nilai default adalah fungsi pustaka C Standar std::abort(). Jika tidak ada pembersihan terjadi untuk pengecualian yang tidak tertangkap, saya t mungkin sebenarnya sangat membantu dalam debugging masalah ini karena tidak ada destruktor yang dipanggil.
† Ini adalah implementasi yang ditentukan apakah tumpukan tidak dihapus sebelumnya std::terminate() disebut.


Panggilan ke abort() sering berguna dalam menghasilkan inti dump yang dapat dianalisis untuk menentukan penyebab pengecualian. Pastikan Anda mengaktifkan core dumps melalui ulimit -c unlimited (Linux).


Anda dapat menginstal sendiri terminate() berfungsi dengan menggunakan std::set_terminate(). Anda harus dapat mengatur breakpoint pada fungsi penghentian Anda di gdb. Kamu mungkin dapat menghasilkan tumpukan backtrace dari Anda terminate() fungsi dan backtrace ini mungkin membantu mengidentifikasi lokasi pengecualian.

Ada diskusi singkat tentang pengecualian yang tidak tertangkap di Pemikiran Bruce Eckel di C ++, 2nd Ed yang mungkin membantu juga.


Sejak terminate() panggilan abort() secara default (yang akan menyebabkan SIGABRT sinyal secara default), Anda mungkin dapat mengatur SIGABRT pawang dan kemudian cetak backtrace tumpukan dari dalam pengatur sinyal. Backtrace ini mungkin membantu 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, terletak dalam fungsi / metode yang berbeda dari titik lempar. Ini juga telah ditunjukkan kepada saya di komentar (terima kasih Dan) bahwa itu adalah implementasi-ditentukan atau tidak tumpukan dibatalkan sebelum terminate() disebut.

Memperbarui: Saya melemparkan program pengujian Linux yang disebut yang menghasilkan backtrace dalam terminate() fungsi diatur melalui set_terminate() dan satu lagi di handler sinyal untuk SIGABRT. Kedua backtraces benar menunjukkan lokasi pengecualian yang tidak tertangani.

Perbarui 2: Berkat posting blog di Menangkap pengecualian yang tidak tertangkap dalam waktu berakhirSaya belajar beberapa trik baru; termasuk melempar kembali pengecualian yang tidak tertangkap dalam pengendali berakhir. Penting untuk dicatat bahwa yang kosong throw pernyataan dalam handler mengakhiri kustom bekerja 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:

my_terminate tertangkap tanpa pengecualian. what (): RUNTIME ERROR!
backtrace my_terminate menghasilkan 10 frame

[bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52]
[bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (3) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (4) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (5) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (6) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (7) ./test(main+0xc1) [0x8049121]
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21]

signal 6 (Aborted), alamat 0x1239 dari 0x42029331
crit_err_hdlr backtrace mengembalikan 13 frame

[bt]: (1) ./test(kill+0x11) [0x42029331]
[bt]: (2) ./test(abort+0x16e) [0x4202a8c2]
[bt]: (3) ./test [0x8048f9f]
[bt]: (4) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa]
[bt]: (5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5]
[bt]: (6) /usr/lib/libstdc++-libc6.2-2.so.3(__rethrow+0xaf) [0x40046bdf]
[bt]: (7) ./test(throw_exception__Fv+0x68) [0x8049008]
[bt]: (8) ./test(foo2__Fv+0xb) [0x8049043]
[bt]: (9) ./test(foo1__Fv+0xb) [0x8049057]
[bt]: (10) ./test(main+0xc1) [0x8049121]
[bt]: (11) ./test(__libc_start_main+0x95) [0x42017589]
[bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21]


64
2018-03-15 07:08



Seperti yang Anda katakan, kita dapat menggunakan 'menangkap lemparan' di gdb dan memanggil 'backtrace' untuk setiap pengecualian dilemparkan tunggal. Meskipun biasanya terlalu membosankan untuk dilakukan secara manual, gdb memungkinkan otomatisasi proses. Itu memungkinkan melihat backtrace dari semua pengecualian yang dilemparkan, 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 backtraces, 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 adalah entri blog yang bagus yang membungkusnya: http://741mhz.com/throw-stacktrace/


34
2017-11-02 02:00



Anda dapat membuat makro seperti:

#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, __LINE__, (message) )

... dan itu akan memberi Anda lokasi di mana pengecualian dilempar (memang bukan tumpukan jejak). Anda perlu mengambil pengecualian dari beberapa kelas dasar yang menggunakan konstruktor di atas.


16
2018-03-14 19:07



Anda tidak memberikan informasi tentang apa OS / Compiler yang Anda gunakan.

Dalam Visual Studio C ++ Pengecualian dapat diinstrumentasi.

Lihat "Visual C ++ Exception-Handling Instrumentasi" di ddj.com

Artikel saya "Debugging Postmortem", juga pada ddj.com termasuk kode untuk menggunakan penanganan exception terstruktur Win32 (digunakan oleh instrumentasi) untuk logging dll.


5
2018-03-15 10:02



Anda dapat menandai tempat-tempat ketat utama dalam kode Anda sebagai noexcept untuk menemukan pengecualian, lalu gunakan libunwind (tambahkan saja -lunwind ke parameter penghubung) (diuji dengan 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
}

}

Ada artikel bagus tentang masalah ini.


4
2017-07-26 05:42



Saya punya kode untuk melakukan ini di Windows / Visual Studio, beri tahu saya jika Anda ingin membuat outline. Tidak tahu bagaimana melakukannya untuk kode dwarf2 sekalipun, google cepat menunjukkan bahwa ada fungsi _Unwind_Backtrace di libgcc yang mungkin merupakan bagian dari apa yang Anda butuhkan.


1
2018-03-14 18:11



Periksa utas ini, mungkin ini membantu:

Menangkap semua pengecualian C ++ yang tidak tertangani?

Saya membuat pengalaman yang baik dengan perangkat lunak itu:

http://www.codeproject.com/KB/applications/blackbox.aspx

Ini dapat mencetak jejak stack ke file untuk pengecualian yang tidak tertangani.


1
2018-03-14 18:11