プログラムで構造化例外が出た時に、スタックバックトレースを見たくなったのですが、その時に作った簡単な動作検証用コードを載せておきます。
構造化例外が出るコード
まずはわざと構造化例外が発生するプログラムを用意します。
一番下の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 ...
続きを見る

