C ++で例外がスローされた場所を見つけるにはどうすればよいですか?


92

どこかでキャッチされない例外をスローするプログラムがあります。私が受け取るのは、例外がスローされたというレポートであり、例外がスローされた場所に関する情報はありません。デバッグシンボルを含むようにコンパイルされたプログラムが、コードのどこで例外が生成されたかを通知しないのは、非論理的です。

gdbで 'catch throw'を設定し、スローされたすべての例外に対してバックトレースを呼び出す以外に、私の例外がどこにあるのかを知る方法はありますか?



例外をキャッチして、内部メッセージを確認します。例外が標準の例外(std :: runtime_error)の1つから派生するのは良い習慣であるため、catch(std :: exception const&e)でそれをキャッチできるはずです
Martin York

1
そしてstd :: exception / Std :: runtime_errorは「パス」と例外の原因を見つける問題を解決しますか?
VolkerK 2010年

1
あなたの質問はgdbを述べているので、私はあなたの解決策がすでにSOにあると思います: stackoverflow.com/questions/77005/… ここで説明した解決策を使用しましたが、完全に機能します。
ニューロ2015年

2
タグでOSを指定することを検討する必要があります。gdbについて言及しているので、WindowsではなくLinuxソリューションを探していると思います。
jschmier 2010年

回答:


72

問題のデバッグに役立つ可能性のある情報を以下に示します

例外がキャッチされない場合、特別なライブラリ関数std::terminate()が自動的に呼び出されます。Terminateは実際には関数へのポインタであり、デフォルト値は標準Cライブラリ関数std::abort()です。キャッチされなかった例外に対してクリーンアップが行われない場合、デストラクタが呼び出されないため、この問題のデバッグに実際に役立つ場合があります。
†スタックstd::terminate()が呼び出される前にスタックが巻き戻されるかどうかは、実装によって定義されます。


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


terminate()を使用して、独自の関数をインストールできますstd::set_terminate()。gdbの終了関数にブレークポイントを設定できるはずです。あなたは可能あなたからスタックバックトレースを生成することができるterminate()機能と、このバックトレースがあり、例外の場所を識別するのに役立ちます。

上の簡単なディスカッションがありますキャッチされない例外ではC ++、第2版でブルースEckel氏の思考にも役立つかもしれません。


デフォルトでterminate()は呼び出しが行わabort()れるため(デフォルトではSIGABRTシグナルが発生します)、ハンドラーを設定して、シグナルハンドラー内からスタックバックトレースを出力できる場合があります。このバックトレース、例外の場所を特定するのに役立ちます。SIGABRT


注: C ++は、エラー処理とレポートコードを通常のコードから分離するために、言語構造を使用して非ローカルエラー処理をサポートしているため、そうかもしれません。catchブロックは、スローするポイントとは別の関数/メソッドに配置できます。スタックが呼び出される前にスタックが巻き戻されるかどうかは、実装によって定義されることがコメントで指摘されています(Danに感謝します)terminate()

更新:terminate()経由の関数セットset_terminate()とのシグナルハンドラーに別のバックトレースを生成するLinuxテストプログラムを一緒に投げましたSIGABRT。両方のバックトレースは、未処理の例外の場所を正しく示しています。

更新2:「終了内キャッチされない例外キャッチする」に関するブログ投稿のおかげで、私はいくつかの新しいトリックを学びました。終了ハンドラ内でキャッチされなかった例外の再スローを含みます。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が未処理の例外をキャッチしました。what():実行時エラー!
my_terminateバックトレースが10フレームを返しました

[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]

信号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]
[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]


1
とても興味深い。私はいつもそれがトップレベル(に着くまで、未処理の例外がスタックをアンワインドすることを疑わmain)と、その後、それは呼ぶだろうterminate()。しかし、あなたの例は、巻き戻しがまったく行われていないことを示しています。これはとてもクールです。
Dan

6
1)throw(int)スペックは不要です。2)これuc->uc_mcontext.eipはおそらくプラットフォームに非常に依存しています(たとえば、...rip64ビットプラットフォームでの使用)。3)でコンパイルし-rdynamicて、バックトレースシンボルを取得します。4)実行./a.out 2>&1 | c++filtしてかなりバックトレースシンボルを取得します。
Dan

2
「キャッチされなかった例外のクリーンアップは行われません。」-実際には、これは実装定義です。C ++仕様の15.3 / 9および15.5.1 / 2を参照してください。「一致するハンドラーが見つからない状況では、terminate()が呼び出される前にスタックが巻き戻されるかどうかは、実装によって定義されます。」それでも、コンパイラがサポートしていれば、これは優れたソリューションです。
Dan

1
((sig_ucontext_t *)userContext)->uc_mcontext.fault_address;私のARMターゲットで働いた
スティーブン

1
いくつかの注意事項:backtrace_symbols()はmallocを実行します...したがって、起動時にメモリブロックを事前に割り当ててから、たまたま発生したイベントでbacktrace_symbols()を呼び出す直前に、それをmy_terminate()で割り当て解除することができます。 std :: bad_alloc()例外の処理。。また、あなたは<cxxabi.h>含むことができ、その後、マングルされた部分文字列の何か有益なアウトをするために)(__cxa_demangle使用は、出力メッセージ[]の文字列に「+」「(」の間と表示
Kスコット・ピエル

51

あなたが言うように、gdbで 'catch throw'を使用して、スローされたすべての例外に対して '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上]


17

次のようなマクロを作成できます。

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

...そして例外がスローされた場所を提供します(確かにスタックトレースではありません)。上記のコンストラクターを受け取るいくつかの基本クラスから例外を派生させる必要があります。


18
-1あなたはしませんthrow new excation(...)が、throw exception(...)C ++はJavaではありません
Artyom

7
はい、修正しました。JavaとC ++の両方で動作するプログラマを許してください。
Erik Hermansen、2010年

私はこれを使用しましたが。それの問題は、それが実際に例外を投げた理由を知らないということです。たとえば、tryブロックに5つのstoi呼び出しがある場合、どれが実際に原因であるかはわかりません。
Banjocat 2015年

5

使用しているOS /コンパイラに関する情報を渡していない。

Visual Studio C ++では、例外を計測できます。

ddj.comの「Visual C ++例外処理インストルメンテーション」を参照してください

同じくddj.comにある私の記事事後デバッグ」には、ロギングなどにWin32構造化例外処理(インスツルメンテーションで使用)を使用するコードが含まれています。


彼はgdbを言った、それはWindows / Visual Studioをほとんど除外する。
ベンフォークト

2
まあ、彼は「gdbが不足している」何かが欲しいと言っていますが、OS /コンパイラについては明示的に言及していません。それはそのようなことを宣言しない人々の問題です。
RED SOFT ADAIR 2010年

5

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
}

}

問題に関する良い記事があります。


1

Windows / Visual Studioでこれを行うコードがあります。アウトラインが必要かどうかをお知らせください。しかし、dwarf2コードでそれを行う方法がわからない、クイックgoogleは、おそらく必要なものの一部であるlibgccに関数_Unwind_Backtraceがあることを示唆しています。


「概要が必要かどうかを私に知らせてください」は有用な答えではないためです。しかし、_Unwind_Backtraceは、補償。
トーマス

OPがgdbに言及していることに基づいて、Windowsは関連性がないと推測しました。もちろん、アレックスは自分の質問を自由に編集してWindowsと言うことができました。
Ben Voigt

1

このスレッドを確認してください。おそらく役立つでしょう:

未処理のC ++例外をすべてキャッチしますか?

私はそのソフトウェアで良い経験をしました:

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

未処理の例外について、スタックトレースをファイルに出力できます。


ポイントは、アレックスはexception thrown foo.c@54, ..., re-thrown bar.c@54, ....手動で行う必要がないようなスタックトレースを望んでいるということです。
VolkerK 2010年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.