C++ で例外がキャッチされた後にそのソースを見つけますか?
-
09-06-2019 - |
質問
MS VC++で答えを探しています。
大規模な C++ アプリケーションをデバッグする場合、残念ながら C++ 例外が非常に広範囲に使用されます。実際に望むよりも少し遅れて例外をキャッチすることがあります。
疑似コードの例:
FunctionB()
{
...
throw e;
...
}
FunctionA()
{
...
FunctionB()
...
}
try
{
Function A()
}
catch(e)
{
(<--- breakpoint)
...
}
デバッグ時にブレークポイントを使用して例外をキャッチできます。ただし、例外が発生したかどうかを追跡することはできません FunctionA()
または FunctionB()
, 、または他の関数。(広範な例外の使用と上記の例の巨大なバージョンを想定しています)。
私の問題に対する 1 つの解決策は、呼び出しスタックを特定して保存することです。 例外コンストラクター内で (すなわち、捕まる前に)。ただし、これには、この基本例外クラスからすべての例外を派生する必要があります。また、多くのコードが必要になり、プログラムの速度が低下する可能性があります。
作業量が少なくて済む、もっと簡単な方法はありますか?大規模なコードベースを変更する必要はありませんか?
他の言語でこの問題に対するより良い解決策はありますか?
解決
例外がどこから発生したかだけを知りたい場合は、次のような単純なマクロを作成するだけです。
#define throwException(message) \
{ \
std::ostringstream oss; \
oss << __FILE __ << " " << __LINE__ << " " \
<< __FUNC__ << " " << message; \
throw std::exception(oss.str().c_str()); \
}
これにより、ファイル名、行番号、および関数名が例外テキストに追加されます (コンパイラーがそれぞれのマクロを提供する場合)。
次に、次を使用して例外をスローします
throwException("An unknown enum value has been passed!");
他のヒント
コード内のブレークポイントを指しました。デバッガーを使用しているため、例外クラスのコンストラクターにブレークポイントを設定することも、スローされたすべての例外でブレークするように Visual Studio デバッガーを設定することもできます ([デバッグ] -> [例外] C++ 例外をクリックし、スローされたオプションとキャッチされなかったオプションを選択します)。
John Robbins が書いた素晴らしい本があり、多くの難しいデバッグの問題に取り組んでいます。その本はと呼ばれます Microsoft .NETおよびMicrosoft Windows用のアプリケーションのデバッグ. 。タイトルに反して、この本にはネイティブ C++ アプリケーションのデバッグに関する多くの情報が含まれています。
この本には、スローされた例外の呼び出しスタックを取得する方法についての長いセクションがあります。私の記憶が正しければ、彼のアドバイスには次のようなものがあります。 構造化例外処理 (SEH) C++ 例外の代わりに (または C++ 例外に加えて)。この本はあまりお勧めできません。
例外オブジェクト コンストラクターにブレークポイントを置きます。例外がスローされる前にブレークポイントを取得します。
例外の原因を見つける方法はありません 後 投げるときにその情報を含めない限り、キャッチされます。例外をキャッチするまでに、スタックはすでに巻き戻されており、スタックの以前の状態を再構築する方法はありません。
コンストラクターにスタック トレースを含めるという提案が最善の策です。確かに、構築中には時間がかかりますが、これが懸念されるほど頻繁に例外をスローする必要はないでしょう。すべての例外を新しいベースから継承させることも、必要以上の作業になる可能性があります。関連する例外を単純に継承させ (ありがとう、多重継承)、それらを個別にキャッチすることができます。
使用できます スタックトレース64 トレースを構築する関数 (他の方法もあると思います)。チェックアウト この記事 たとえばコード。
GCC ライブラリを使用して C++ でそれを行う方法は次のとおりです。
#include <execinfo.h> // Backtrace
#include <cxxabi.h> // Demangling
vector<Str> backtrace(size_t numskip) {
vector<Str> result;
std::vector<void*> bt(100);
bt.resize(backtrace(&(*bt.begin()), bt.size()));
char **btsyms = backtrace_symbols(&(*bt.begin()), bt.size());
if (btsyms) {
for (size_t i = numskip; i < bt.size(); i++) {
Aiss in(btsyms[i]);
int idx = 0; Astr nt, addr, mangled;
in >> idx >> nt >> addr >> mangled;
if (mangled == "start") break;
int status = 0;
char *demangled = abi::__cxa_demangle(mangled.c_str(), 0, 0, &status);
Str frame = (status==0) ? Str(demangled, demangled+strlen(demangled)) :
Str(mangled.begin(), mangled.end());
result.push_back(frame);
free(demangled);
}
free(btsyms);
}
return result;
}
例外のコンストラクターは、この関数を呼び出してスタック トレースを保存するだけで済みます。パラメータが必要です numskip
私はスタック トレースから例外のコンストラクターを切り出すのが好きなためです。
これを行う標準的な方法はありません。
さらに、呼び出しスタックは通常、例外が発生した時点で記録される必要があります。 投げられた;一度そうなったら つかまった スタックが展開されるため、スローされた時点で何が起こっていたのかわかりません。
Win32/Win64 上の VC++ では、 かもしれない コンパイラー組み込み _ReturnAddress() からの値を記録し、例外クラスのコンストラクターが __declspec(noinline) であることを確認することで、使用可能な十分な結果を取得します。デバッグ シンボル ライブラリと併用すると、おそらく SymGetLineFromAddr64 を使用して戻りアドレスに対応する関数名 (および .pdb に行番号が含まれている場合は行番号) を取得できると思います。
ネイティブ コードでは、 ベクトル化された例外ハンドラー. 。VC++ は SEH 例外に加えて C++ 例外を実装し、ベクトル化された例外ハンドラーがフレーム ベースのハンドラーの前に最初に与えられます。ただし、ベクトル化された例外処理によって引き起こされる問題は診断が難しい場合があるので、十分に注意してください。
また マイク・ストールがいくつかの警告を発している マネージド コードを含むアプリでの使用について。最後に読んでください マット・ピエトレックの記事 これを試す前に、SEH とベクトル化された例外処理を理解していることを確認してください。(追加したコードの重大な問題を追跡することが、重大な問題の追跡に役立つことほど気分が悪いものはありません。)
MSDev では例外がスローされたときにブレークポイントを設定できると思います。
あるいは、例外オブジェクトのコンストラクターにブレーク ポイントを置きます。
IDE からデバッグしている場合は、「デバッグ」->「例外」に移動し、「C++ 例外のスロー」をクリックします。
他の言語?Java では e.printStackTrace(); を呼び出します。これほど簡単なことはありません。
興味のある方のために付け加えておきますが、同僚がこの質問に電子メールで返信してくれました。
アルテムはこう書いています。
MiniDumpWriteDump() には、より適切なクラッシュ ダンプを実行できるフラグがあり、すべてのグローバル変数などを含む完全なプログラムの状態を確認できるようになります。コールスタックに関しては、最適化によってこれ以上改善できるとは思えません...(おそらく一部の) 最適化をオフにしない限り。
また、インライン関数を無効にしてプログラム全体を最適化することもかなり役立つと思います。
実際、ダンプ タイプは多数あります。十分小さいものの、より多くの情報が得られるダンプ タイプを選択することもできます。http://msdn.microsoft.com/en-us/library/ms680519(VS.85).aspx
ただし、これらの型はコール スタックには役に立ちません。表示できる変数の量に影響するだけです。
これらのダンプ タイプの一部は、私たちが使用している dbghelp.dll バージョン 5.1 ではサポートされていないことに気付きました。ただし、最新の 6.9 バージョンに更新することはできますが、MS デバッグ ツールの EULA を確認したところです -- 最新の dbghelp.dll は再配布しても問題ありません。
私は独自の例外を使用します。非常に簡単に処理できます。テキストも含まれています。私は次の形式を使用します。
throw Exception( "comms::serial::serial( )", "Something failed!" );
また、2 番目の例外形式もあります。
throw Exception( "comms::serial::serial( )", ::GetLastError( ) );
これは、FormatMessage を使用して DWORD 値から実際のメッセージに変換されます。where/what 形式を使用すると、どの機能で何が起こったのかが表示されます。