BackTrace
バックトレースとは呼び出し履歴を取得する処理の事です。
呼び出し履歴(コールスタック)を取得するのでスタックトレースとも呼ばれます。
プログラムのデバッグ作業などをしていると特定の状況に陥った時のバックトレースを知りたい時があります。
例えばアサートに引っかかった時やメモリリークが発生した時等ですね。
そういう時にプログラム上からバックトレースを取得するAPIが用意されています。
最近の環境では大半の環境下で用意されているかと思います。
今回はWindows上でバックトレースを取得してコンソール上に表示する方法を記載していきます。
呼び出し履歴の取得
Windows環境でバックトレースを取得するときは次の関数(マクロ)を使います。
CaptureStackBackTrace
この定義はマクロになっていて実際には下記が呼び出されます。
NTSYSAPI USHORT RtlCaptureStackBackTrace( ULONG FramesToSkip, ULONG FramesToCapture, PVOID *BackTrace, PULONG BackTraceHash );
- FramesToSkip
何番目の履歴からキャプチャーするかを指定できます。
0だと呼び出した箇所から。
1だと呼び出した関数を呼び出した箇所(1つ上の履歴)からキャプチャーされます。 - FramesToCapture
最大でいくつまでの履歴をキャプチャーするかを指定できます。 - BackTrace
呼び出し元の履歴(アドレス)を返します。 - BackTraceHash
ハッシュ値を計算するために使用されます。
NULLを入れておけばハッシュ値は計算されません。 - 戻り値
戻り値で実際にキャプチャーできた履歴の数が取得できます。
文字列への変換
CaptureStackBackTraceを呼び出すことで呼び出し履歴は取得できます。
ただこれで取得できるのは呼び出し元のアドレスだけです。
これでは人が見ても直観的に理解することができません。
なのでアドレスから文字列へ変換する必要があります。
文字列に変換するにはいくつかの手順が必要になります。
シンボルハンドラの初期化
アドレスから文字列を取得する場合にはシンボル情報が必要になります。
※シンボル情報とはプログラムのコード上で扱われる識別子等の事です。
Windowsにはシンボル情報を扱うためのデバッグ機能がdbghelp.hに用意されています。
使用する際はインクルードとライブラリのリンクが必要です。
#include <dbghelp.h> #pragma comment(lib, dbghelp.lib)
そして次にシンボルハンドラの初期化を行います。
HANDLE process = GetCurrentProcess(); SymInitialize(process, NULL, TRUE);
これでシンボル情報にアクセスできるようになります。
シンボル情報の取得
シンボル情報にアクセスできるようになれば次はアドレスを元に文字列を取得します。
const int MaxNameLen = 256; char Name[MaxNameLen]; ULONG line = 0; // シンボル情報を格納するためのメモリ領域を確保する void* pMemory = malloc(sizeof(SYMBOL_INFO) + sizeof(char) * MaxNameLen); // 取得するシンボル情報の設定を行う SYMBOL_INFO* pSymbol = reinterpret_cast<SYMBOL_INFO*>(pMemory); pSymbol->MaxNameLen = MaxNameLen; pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); DWORD64 disp; // pAddrで指定されたアドレスのシンボル情報を取得 SymFromAddr(process, reinterpret_cast<DWORD64>(pAddr), &disp, pSymbol); // 取得したシンボル情報をNameに格納 strcpy_s(Name, MaxNameLen, pSymbol->Name); // 行数の取得 DWORD dispLine; IMAGEHLP_LINE64 line64; SymGetLineFromAddr64(process, pSymbol->Address + disp, &dispLine, &line64); line = line64.LineNumber; // シンボル情報の格納領域を解放 free(pMemory);
これでシンボル情報からコールスタックの情報を取得できます。
シンボル情報は一つずつ取得することになるので、取得したコールスタックの数だけ呼び出せばVisual Studioの呼び出し履歴と同じ結果が得られるはずです!
バックトレースはデバッグ作業の効率に大きくかかわってくる機能なので是非とも開発中の環境に取り入れて見てください!