プログラムで構造化例外が出た時に、スタックバックトレースを見たくなったのですが、その時に作った簡単な動作検証用コードを載せておきます。
構造化例外が出るコード
まずはわざと構造化例外が発生するプログラムを用意します。
一番下のmain
関数から始まって、test2
関数で構造化例外が発生します。
#include "pch.h" #include <iostream> #include "StackBackTrace.h" void test2() { char *a = nullptr; memcpy(a, "abc", 4); } void test1() { test2(); } void se_translator(unsigned int u, _EXCEPTION_POINTERS *e) { printf_s("In se_translator. cEip = 0x%x\n", e->ContextRecord->Eip); StackBackTrace stackBackTrace; printf_s(stackBackTrace.build().c_str()); } int main() { _set_se_translator(se_translator); try { test1(); } catch (...) {} return 0; }
例外が発生したら、se_translator
関数でStackBackTrace
クラスを使ってスタックバックトレースを表示するようにしています。
なお、このプログラムを実行するためにはコンパイラの設定で「C++の例外を有効にする」を「はい - SEHの例外あり(/EHa)」にしておく必要があります。
StackBackTraceクラス
スタックバックトレースを表示するクラスでは、CaptureStackBackTrace
関数で取得したアドレスを使ってSymFromAddr
関数でシンボル情報を取得しています。
本筋とは関係ありませんが、new
するのが嫌だったので、あらかじめ必要なサイズ分確保したsymbol
変数を取りまわす方法を取っています。
#include "StackBackTrace.h" #include <dbghelp.h> StackBackTrace::StackBackTrace() : _hProcess(GetCurrentProcess()) , _symInitialized(SymInitialize(_hProcess, NULL, TRUE)) {} StackBackTrace::~StackBackTrace() { if (_symInitialized) { SymCleanup(_hProcess); } } std::string StackBackTrace::build() { void *symbol[sizeof(SYMBOL_INFO) + MAX_SYMBOL_NAME_LEN]; reinterpret_cast<SYMBOL_INFO *>(symbol)->SizeOfStruct = sizeof(SYMBOL_INFO); reinterpret_cast<SYMBOL_INFO *>(symbol)->MaxNameLen = MAX_SYMBOL_NAME_LEN; std::string result; void *stack[MAX_FRAMES_TO_CAPTURE]; const WORD frames = CaptureStackBackTrace( 0 , MAX_FRAMES_TO_CAPTURE , stack , NULL ); for (WORD i = 0; i < frames; i++) { SymFromAddr( _hProcess , reinterpret_cast<DWORD64>(stack[i]) , 0 , reinterpret_cast<SYMBOL_INFO *>(symbol) ); char buf[1024]; sprintf_s( buf , "%05d: %s - 0x%llx\n" , frames - i - 1 , reinterpret_cast<SYMBOL_INFO *>(symbol)->Name , reinterpret_cast<SYMBOL_INFO *>(symbol)->Address ); result += buf; } return result; }
なお、取得したアドレスをシンボル情報と関連付ける際は、該当プログラムをビルドするときに同時にできるPDBファイル(Program Databaseファイル)が必要になります。
PDBファイルは、該当プログラムのカレントディレクトリか、_NT_SYMBOL_PATH環境変数、または_NT_ALTERNATE_SYMBOL_PATH環境変数が指すディレクトリに置いておく必要がありますが、以下いずれかの方法によりそのパスを指定することもできます。
- SymSetSearchPath関数でパスを指定する
- SymInitialize関数の第二引数にパスを指定する
それぞれ以下のような形になります。
SymSetSearchPath(_hProcess, "C:\hogehoge\;C:\fugafuga\"); SymInitialize(_hProcess, "C:\hogehoge\;C:\fugafuga\", TRUE);
参考:
https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetsearchpath
https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize
実行結果
サンプルプログラムの実行結果です。下から追っていくことで、実行経路を追うことができます。
サンプルコードのダウンロード
本記事で紹介したサンプルコード全体を以下にアップロードしました。
ご自身の責任の下で、自由にご利用ください。
-
GitHub - fuket/StackBackTraceSample: C++/WindowsでPDBを使ってスタックバックトレースを表示するサンプル
C++/WindowsでPDBを使ってスタックバックトレースを表示するサンプル. Contribute to fuket/StackBackTraceSample development by cre ...
続きを見る