1. ホーム
  2. c++

[解決済み] C++で例外が発生した場所を見つけるにはどうしたらいいですか?

2022-12-19 17:18:40

質問

どこかでキャッチできない例外をスローするプログラムを持っています。例外がスローされたというレポートだけが表示され、どこでスローされたかについての情報はありません。デバッグ シンボルを含むようにコンパイルされたプログラムが、私のコードのどこで例外が発生したかを私に通知しないのは、非論理的だと思われます。

gdb で 'catch throw' を設定し、スローされたすべての例外のバックトレースを呼び出す以外に、例外がどこから来るのかを知る方法はないのでしょうか?

どのように解決するのですか?

ここで、いくつかの情報は があります。 あなたの問題のデバッグに役立つかもしれない情報です。

例外がキャッチされない場合、特殊なライブラリ関数である std::terminate() が自動的に呼び出されます。 Terminateは実際には関数へのポインターで、デフォルト値はStandard Cライブラリ関数の std::abort() . 捕捉されない例外に対してクリーンアップが行われない場合 の場合、それは かもしれない は、デストラクタが呼び出されないので、この問題のデバッグに実際に役立つかもしれません。

の前にスタックが巻き戻されるかどうかは実装で定義されています。 std::terminate() が呼ばれる前にスタックが巻き戻されるかどうかは実装で決まる。


への呼び出し abort() の呼び出しは、例外の原因を特定するために分析できるコアダンプを生成するのに役立つことがよくあります。 コアダンプを有効にするには ulimit -c unlimited を使用してコアダンプを有効にします (Linux)。


自分でインストールできる terminate() 関数をインストールすることができます。 std::set_terminate() . gdbでterminate関数にブレークポイントを設定することができるはずです。 あなたは かもしれません からスタックバックトレースを生成することができます。 terminate() 関数からスタックバックトレースを生成し、このバックトレース は例外の発生場所を特定するのに役立ちます。

に関する簡単な議論があります。 キャッチできない例外 ブルース・エッケルの「C++で考える」第2版 も参考になるかもしれません。


から terminate() コール abort() をデフォルトで呼び出します(これによって SIGABRT シグナルが発生します)、あなたは かもしれません を設定することができます。 SIGABRT ハンドラを設定し シグナルハンドラからスタックバックトレースを表示する。 . このバックトレースは は、例外の発生場所を特定するのに役立ちます。


注意してください。 私は かもしれない というのは、C++はエラー処理と報告コードを通常のコードから分離するための言語構造を使用することで、非ローカルなエラー処理をサポートしているからです。 キャッチブロックは、投げるポイントとは異なる関数/メソッドに配置することができ、またしばしば配置されます。 また、コメントで指摘されたこともあります(Thanks Dan の前にスタックが巻き戻されるかどうかは実装で決まっているとのことです。 terminate() が呼び出される前にスタックが巻き戻されるかどうかは、実装によって決まるということです。

更新しました。 バックトレースを生成するLinuxのテストプログラムというのを投げました。 terminate() 関数セットで set_terminate() のシグナルハンドラで、もうひとつは SIGABRT . 両方のバックトレースは、処理されない例外の場所を正しく示しています。

更新 2: のブログ記事に感謝します。 terminateでキャッチできない例外をキャッチする のブログ記事のおかげで、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);
}

出力します。

my_terminate は手つかずの例外をキャッチしました。RUNTIME ERROR!
my_terminateのバックトレースは10フレームを返しました。

[bt]: (0) ./test(my_terminate__Fv+0x1a) [0x8048e52].
[bt]: (1) /usr/lib/libstdc++-libc6.2-2.so.3 [0x40045baa] [0x40045baa
[bt]: (2) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5] [0x400468e5] [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] (7) ./test(main+0xc1) [0x8049122
[bt]: (8) ./test(__libc_start_main+0x95) [0x42017589] [0x42017589]を実行します。
[bt]: (9) ./test(__eh_alloc+0x3d) [0x8048b21] です。

シグナル 6 (中止)、アドレスは 0x42029331 から 0x1239 です。
crit_err_hdlrバックトレースは、13フレームを返しました。

[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] [0x40045baa] [0x40045baa
[bt]です。(5) /usr/lib/libstdc++-libc6.2-2.so.3 [0x400468e5] [0x400468e5] [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] (10) ./test(main+0xc1) [0x8049122
[bt]: (11) ./test(__libc_start_main+0x95) [0x42017589] [0x42017589]を実行します。
[bt]: (12) ./test(__eh_alloc+0x3d) [0x8048b21] のようになります。