この例外を投げたのは誰だー スタックトレースで遊ぼう
まぁ、不慮の例外といいますか、突然飛んでくる例外というのはいろいろあるわけで。
そりゃ、例外なんて名前がついているから、例外的に突然飛んでくるのは当たり前だろうとこともありますね。
では、この例外をだれが投げたかを C++ で追跡することはできるのでしょうか。
_ □□ _ ___、、、 //_ [][]// ,,-―''':::::::::::::::ヽヾヽ':::::/、 誰 投 こ // \\ // /::::::::::::::::::::::::::::::i l | l i:::::::ミ だ げ の  ̄  ̄  ̄/ /:::::::::,,,-‐,/i/`''' ̄ ̄ ̄ `i::;| あ た 例 ―`―--^--、__ /:::::::::=ソ / ヽ、 / ,,|/ っ の 外 /f ),fヽ,-、 ノ | 三 i <ニ`-, ノ /、-ニニ' 」') !! は i'/ /^~i f-iノ |三 彡 t ̄ 。` ソ ハ_゙'、 ̄。,フ | ) を ,,, l'ノ j ノ::i⌒ヽ;;|  ̄ ̄ / _ヽ、 ̄ ゙i ) ` '' - / ノ::| ヽミ `_,(_ i\_ `i ヽ、 ∧ ∧ ∧ ∧ /// |:::| ( ミ / __ニ'__`i | Y Y Y Y Y ,-" ,|:::ヽ ミ /-───―-`l | // | | // l::::::::l\ ||||||||||||||||||||||/ | // | / ____.|:::::::| 、 `ー-―――┴ / __,,..-'| /゙ー、,-―'''XXXX `''l::,/| ー- 、__ ̄_,,-"、_,-''XXXXX | /XX/ XXXXXXXXXX| | _, /ノXXXXXXXXXX|
この記事は、kernelvm advent calendar 用の記事でつ。
int f4(int a) { throw my_exception("あらいを作りました"); } int f3(int a) { try { return f4(3) + 3; } catch(const my_exception & e) { //この例外を読んだのはだれたー //ここでだれが throw したか知りたい } } int f2(int a) { return f3(2) + 2; } int f1(int a) { return f2(1) + 1; } int main() { return 0; }
f4関数 で飛ばされる例外を f3関数 の catchで取得します。
この時、f4が例外を飛ばしたということを f3関数のcatchの中だけで知ることができるでしょうか。
結論から言うと、無理です。
* * * + 無理です n ∧_∧ n + (ヨ(* ´∀`)E) Y Y *
orz
なんか、処理系とか特殊な条件とかが整うと、とれなくもないような気もしますが、、、
catch区の中だけでは、どこのだれがこの例外を飛ばしたか、追跡することができません。
これをするには、my_exception の時に stacktrace を保存しておく、ぐらいしかないです。
class my_exception { public: my_exception(const char * inMessage) { //ここでスタックトレースを保存する. //例えばこんな感じ this->Stacktrace.recordTrace(); } };
スタックトレース
しかし、一つ問題があります。
exception のたびに毎回重い処理を走らせるとパフォーマンス上の問題が発生する場合があります。
ふつー exceptionってそんなに発生しないからいいような気もしますが、どっかのコーヒー言語かぶれの人とかが実装した処理系とかだとなんでも何でも exceptionをガシガシなげてくるみたいな実装もあるから油断は禁物です。
//Unix系でスタックトレースを取得する. //関数にラップしているのは、この後の windowsとの互換性のためだよ int backtrace(void** buffer , int n) const { return ::backtrace(buffer , n); }
StackWalk APIってゆーのがあるんだけど、ちょっと癖があっていまいち使いづらい。
ふつーに呼ぶとスレッドコンテキスト取得してとかいろいろあって非常に面倒だし、オーバーヘッドが怖いです。
#自分自信のスレッドコンテキストが取れないので、一度別スレッド作ってとかやって死ぬほど遅くなった記憶がある。。。
で、代わりになるのが RtlCaptureStackBackTrace API です。
http://msdn.microsoft.com/en-us/library/ff552119.aspx
#このRtlCaptureStackBackTrace APIが早いかどうか議論するところでしょうけど。
//windowsで簡単にスタックトレース int backtrace(void** buffer , int n) const { if (n >= 63) { n = 62; } return (int) RtlCaptureStackBackTrace(0,n,buffer,NULL); }
RtlCaptureStackBackTrace は、windows XP 以降から使えるらしいAPIです。
Unix系だと backtrace みたく簡単にスタックトレースをかけることが可能です。
windows 2000ではないこともないらしいが、バグがあったりしてうまく動作しないことがあるらしいですが。
で、ここで問題になるのが、 windows2000を未サポートでもいいのかって問題。
10年以上も前のOSだから、そろそろサポートしなくてもいいんだろうけど、ビジネス的な問題で切れない場合もあるだろう。
そーゆーときは、スタックトレースは動かなくてもいいから、 windows 2000でメインの機能は動いてほしい、、、ぐらいで手を打つしかないのかも。
なんで、こんな風にダイナミックリンクで呼び出すようにする。
#include <windows.h> #include <stdlib.h> //dll読み込みヘルパー class LoadLibraryHelper { private: //DLL インスタンス. HMODULE DllInstance; public: LoadLibraryHelper() { this->DllInstance = NULL; } virtual ~LoadLibraryHelper() { if (this->DllInstance != NULL) { ::FreeLibrary(this->DllInstance); this->DllInstance = NULL; } } bool Load( const char* inDLLName )//std読んでいない化石環境とかのために const char* で作る. { if (this->DllInstance != NULL) { return false; } this->DllInstance = ::LoadLibraryA(inDLLName); return this->DllInstance != NULL; } FARPROC GetProcAddress(const char* inProcName) { if (!this->DllInstance) { return NULL; } return ::GetProcAddress(this->DllInstance,inProcName); } }; int backtrace(void** buffer , int n) { LoadLibraryHelper kernel32; if ( !kernel32.Load("kernel32.dll") ) { return 0; } //スタックトレースAPI typedef USHORT (WINAPI *RtlCaptureStackBackTraceDef) (ULONG FramesToSkip,ULONG FramesToCapture,PVOID *BackTrace,__out_opt PULONG BackTraceHash); RtlCaptureStackBackTraceDef RtlCaptureStackBackTraceProc; RtlCaptureStackBackTraceProc = (RtlCaptureStackBackTraceDef)kernel32.GetProcAddress("RtlCaptureStackBackTrace"); if (!RtlCaptureStackBackTraceProc) { return 0; } if (n >= 63) { n = 62; } return (int) RtlCaptureStackBackTraceProc(0,n,buffer,NULL); } //実行テスト用 int f2(int a) { void* stack[50]; int n = backtrace(stack , 50); for(int i = 0; i < n ; i ++) { printf("%p\r\n",stack[i]); } return a + 2; } int f1(int a) { return f2(1) + 1; } int main() { f1(1); return 0; }
あー、めちゃコードが長くなったね。
ともあれ、これで、バックトレースはできた。
こいつを動かすと、スタックトレースを取得してくれる。
000000013FA84517 000000013FA84581 000000013FA84626 000000013FA8465F 000000013FA82D4C 000000013FA82B9E 0000000076CEF56D 0000000076E23021 続行するには何かキーを押してください . . .
うーん、 16進数だけだと味気ないね。
一応、この16進数からコードの位置をデバックシンボルをもとに、デバッガーを使うなり、手計算するなりすれば、元のコードの位置を求めることはできるんだけど、なんか、めんどい。
つーわけで、プログラムで自動的に復元ささせるようにしてみよう。
ちゃんとデバックシンボルが埋め込まれたバイナリなら、関数名はもとより、コードのファイル名と行数まで取得できるよ。
アドレスから関数名を求める
windows系だと、dbghelp.dll にある関数でごにょごにょする。
Unix系だと、dladdr abi::__cxa_demangle bfd とかでごにょごにょするみたい。
あるアドレス address が割り当てられて、それをもとにコードの行数までを復元するコードはこんな感じになる。
Windows系、Unix系ともに初期化が必要なので、めんどいね。
あと、すべてが終わったら、一応、解放処理もしないとだめだ。
なんで、 singleton な感じで実装してみた。
#最近のOSは賢いから、プロセス終わったらたいていのリソースは解放してくれるけどさー
//Windows系 //本当は dbghelp.dllからダイナミックロード(+ 32/64bit対応)をしているんだけど、長くなるので適当にはしょる. #include <windows.h> #include <stdlib.h> #include <dbghelp.h> class BackTrace { //シンボルエンジンの準備ができているか bool IsSymbolEngineReady; //プロセスハンドル HANDLE Process; //初期化 BackTrace() { this->IsSymbolEngineReady = FALSE; //プロセスを記録. this->Process = ::GetCurrentProcess(); //シンボルエンジンの初期化. //行番号付きのデータをロードしてねとお願いする. SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); if (this->SymInitialize(this->Process, NULL, TRUE)) { //シンボルエンジン準備完了 this->IsSymbolEngineReady = true; } return ; } public: //解放 virtual ~BackTrace() { if (this->IsSymbolEngineReady) { this->SymCleanup(this->Process); this->IsSymbolEngineReady = false; } } //シンボルの解決 void addressToSymbolString(void* address ,char * outBuffer , int len) const { if ( ! this->IsSymbolEngineReady ) { //シンボルエンジンが準備できていない. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //モジュール名 IMAGEHLP_MODULE imageModule = { sizeof(IMAGEHLP_MODULE) }; BOOL r = SymGetModuleInfo(this->Process ,(DWORD) address , &imageModule); if (!r) { #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //シンボル情報格納バッファ. IMAGEHLP_SYMBOL * imageSymbol; char buffer[MAX_PATH + sizeof(IMAGEHLP_SYMBOL) ] = {0}; imageSymbol = (IMAGEHLP_SYMBOL*)buffer; imageSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); imageSymbol->MaxNameLength = MAX_PATH; //関数名の取得... DWORD disp = 0; r = SymGetSymFromAddr(this->Process , (DWORD)address , &disp , imageSymbol ); if (!r) {//関数名がわかりません. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #else _snprintf(outBuffer , len , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #endif return ; } //行番号の取得 IMAGEHLP_LINE line ={sizeof(IMAGEHLP_LINE)}; r = SymGetLineFromAddr(this->Process ,(DWORD) address , &disp , &line); if (!r) {//行番号が分かりません #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ %s @ %s+%d" ,address, imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s+%d" ,address,imageModule.ModuleName , imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #endif return ; } //行番号がわかりました. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer , len ,_TRUNCATE, "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #endif } //singletonの取得 static const BackTrace* Get() { static BackTrace s; return &s; } };
はい、ソース長いですね。
windowsはこんな感じで dbghelp.dllを使ってごにょごにょすればいろいろできます。
次は、Unix系を見てみましょう。
//Unix系 #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <cxxabi.h> //デマングル #include <bfd.h> //行番号まで取り出す //入っていないなら yum install binutils-devel とかしてね! // #include <bfd.h> 自体をコメントアウトすると機能全体をOFFにできるよ!! class BackTrace { //シンボルエンジンの準備ができているか bool IsSymbolEngineReady ; #ifdef __BFD_H_SEEN__ bfd* Abfd; asymbol** Symbols; int NSymbols; asection* Section; #endif //初期化 BackTrace() { #ifdef __BFD_H_SEEN__ this->IsSymbolEngineReady = false; this->Abfd = NULL; this->Symbols = NULL; this->NSymbols = 0; //see http://0xcc.net/blog/archives/000073.html this->Abfd = bfd_openr("/proc/self/exe", NULL); if (!this->Abfd) { return ; } bfd_check_format(this->Abfd, bfd_object); int size = bfd_get_symtab_upper_bound(this->Abfd); if (size <= 0) { return ; } this->Symbols = (asymbol**) malloc(size); if (!this->Symbols) { return ; } this->NSymbols = bfd_canonicalize_symtab(this->Abfd, this->Symbols); if (!this->NSymbols) { return ; } this->Section = bfd_get_section_by_name(this->Abfd, ".debug_info"); if (!this->Section) { return ; } //シンボルエンジンの初期化完了 this->IsSymbolEngineReady = true; #else //シンボルエンジンは利用できない!! this->IsSymbolEngineReady = false; #endif } public: //解放 virtual ~BackTrace() { #ifdef __BFD_H_SEEN__ if (this->Symbols) { free(this->Symbols); this->Symbols = NULL; } if (this->Abfd) { bfd_close (this->Abfd); this->Abfd = NULL; } #endif } //シンボルの解決 void addressToSymbolString(void* address ,char * outBuffer , int len) const { //backtrace_symbols で一発、、、なんてことをすると demangle されない場合があるそうな。 //だから、dladdr でバラしていく。 //see http://d.hatena.ne.jp/syuu1228/20100215/1266262848 Dl_info info; if (! dladdr(address, &info) ) { snprintf(outBuffer ,len , "0x%p @ ??? @ ??? @ ???:???" ,address ); return ; } if (!info.dli_sname) { snprintf(outBuffer ,len , "0x%p @ %s @ ??? @ ???:???" ,address , info.dli_fname ); return ; } if (!info.dli_saddr) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ ???:???" ,address , info.dli_fname , info.dli_sname ); return ; } //デマングルして関数名を読める形式に int status = 0; char * demangled = abi::__cxa_demangle(info.dli_sname,0,0,&status); //シンボルエンジンは使えるの? if (!this->IsSymbolEngineReady) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s+0x%p" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), (demangled ? demangled : info.dli_sname), (unsigned int) ((char *)address - (char *)info.dli_saddr) ); free(demangled); return ; } #ifdef __BFD_H_SEEN__ //ファイル名と行数を求める. const char* filename = NULL; const char* functionname = NULL; unsigned int line = 0; int found = bfd_find_nearest_line(this->Abfd, this->Section, this->Symbols, (long)address, &filename, &functionname, &line); if (found && filename != NULL && functionname != NULL) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s+0x%p" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), (demangled ? demangled : info.dli_sname), (unsigned int) ((char *)address - (char *)info.dli_saddr) ); free(demangled); return ; } snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s:%d" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), filename , line ); free(demangled); #endif return ; } //singletonの取得 static const BackTrace* Get() { static BackTrace s; return &s; } };
はい、こちらもソースが長いですね。
本当は Unix系も動的に読み込むようにしたほうがいいんだろうけど、、、だれか直して!!
さて、このコードを stacktrace した結果に対して実行すると、以下のようになる。
0x000000013FC9450B @ stacktrace @ f3 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:636
アドレスだけだったのが、モジュール名、関数名、ソースファイル名、行数が表示されている。
すべてを一つに
さてさて、断片的に書いたソースをすべてつくってけてすべてを一つにまとめるとこうなる。
windows 32bit / 64bit対応、 Unix対応とかが絡んでくるので結構巨大なソースになってしまった。
あと一応、 STL とかを組み込まないような過去の遺産プロジェクトでも導入できるように、べたC++で書いているw。
メモリ管理がめんどいネ。。。
#ifdef _MSC_VER #pragma once #include <windows.h> #include <stdlib.h> #include <dbghelp.h> #elif __GNUC__ #include <execinfo.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <cxxabi.h> //デマングル #include <bfd.h> //行番号まで取り出す //入っていないなら yum install binutils-devel とかしてね! // #include <bfd.h> 自体をコメントアウトすると機能全体をOFFにできるよ!! #endif class BackTrace { #ifdef _MSC_VER //dll読み込みヘルパー class LoadLibraryHelper { private: //DLL インスタンス. HMODULE DllInstance; public: LoadLibraryHelper() { this->DllInstance = NULL; } virtual ~LoadLibraryHelper() { if (this->DllInstance != NULL) { ::FreeLibrary(this->DllInstance); this->DllInstance = NULL; } } bool Load( const char* inDLLName )//std読んでいない化石環境とかのために const char* で作る. { if (this->DllInstance != NULL) { return false; } this->DllInstance = ::LoadLibraryA(inDLLName); return this->DllInstance != NULL; } FARPROC GetProcAddress(const char* inProcName) { if (!this->DllInstance) { return NULL; } return ::GetProcAddress(this->DllInstance,inProcName); } }; //スタックトレースAPI typedef USHORT (WINAPI *RtlCaptureStackBackTraceDef) (ULONG FramesToSkip,ULONG FramesToCapture,PVOID *BackTrace,__out_opt PULONG BackTraceHash); RtlCaptureStackBackTraceDef RtlCaptureStackBackTraceProc; //シンボルエンジンの準備ができているか bool IsSymbolEngineReady; //プロセスハンドル HANDLE Process; #if (_WIN64 || __x86_64__) //アドレスからモジュールを求める typedef BOOL (WINAPI *SymGetModuleInfo64Def) (HANDLE hProcess, DWORD64 dwAddr, PIMAGEHLP_MODULE64 ModuleInfo ); SymGetModuleInfo64Def SymGetModuleInfo64Proc; //アドレスからシンボルを求める typedef BOOL (WINAPI *SymGetSymFromAddr64Def) (HANDLE hProcess,DWORD64 Address,PDWORD64 Displacement,PIMAGEHLP_SYMBOL64 Symbol); SymGetSymFromAddr64Def SymGetSymFromAddr64Proc; //アドレスからファイルと行番号を求める typedef BOOL (WINAPI *SymGetLineFromAddr64Def) (HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PIMAGEHLP_LINE64 Line); SymGetLineFromAddr64Def SymGetLineFromAddr64Proc; #else //アドレスからモジュールを求める typedef BOOL (WINAPI *SymGetModuleInfoDef) (HANDLE hProcess, DWORD dwAddr, PIMAGEHLP_MODULE ModuleInfo ); SymGetModuleInfoDef SymGetModuleInfoProc; //アドレスからシンボルを求める typedef BOOL (WINAPI *SymGetSymFromAddrDef) (HANDLE hProcess,DWORD Address,PDWORD Displacement,PIMAGEHLP_SYMBOL Symbol); SymGetSymFromAddrDef SymGetSymFromAddrProc; //アドレスからファイルと行番号を求める typedef BOOL (WINAPI *SymGetLineFromAddrDef) (HANDLE hProcess, DWORD dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINE Line); SymGetLineFromAddrDef SymGetLineFromAddrProc; #endif //シンボルエンジンのオプション typedef BOOL (WINAPI *SymSetOptionsDef) ( DWORD SymOptions ); SymSetOptionsDef SymSetOptionsProc; //シンボルエンジンの初期化 typedef BOOL (WINAPI *SymInitializeDef) (HANDLE hProcess, PSTR UserSearchPath, BOOL fInvadeProcess ); SymInitializeDef SymInitializeProc; //シンボルエンジンの終了 typedef BOOL (WINAPI *SymCleanupDef) (HANDLE hProcess); SymCleanupDef SymCleanupProc; LoadLibraryHelper Kernel32Librsry; LoadLibraryHelper DbgHelpLibrsry; BackTrace() { this->IsSymbolEngineReady = FALSE; this->RtlCaptureStackBackTraceProc = NULL; this->Process = NULL; this->SymSetOptionsProc = NULL; this->SymInitializeProc = NULL; this->SymCleanupProc = NULL; #if (_WIN64 || __x86_64__) this->SymGetModuleInfo64Proc = NULL; this->SymGetSymFromAddr64Proc = NULL; this->SymGetLineFromAddr64Proc = NULL; #else this->SymGetModuleInfoProc = NULL; this->SymGetSymFromAddrProc = NULL; this->SymGetLineFromAddrProc = NULL; #endif if ( ! this->Kernel32Librsry.Load("kernel32.dll") ) { return ; } if ( ! this->DbgHelpLibrsry.Load("dbghelp.dll") ) { return ; } this->RtlCaptureStackBackTraceProc = (RtlCaptureStackBackTraceDef)this->Kernel32Librsry.GetProcAddress("RtlCaptureStackBackTrace"); #if (_WIN64 || __x86_64__) this->SymGetModuleInfo64Proc = (SymGetModuleInfo64Def)this->DbgHelpLibrsry.GetProcAddress("SymGetModuleInfo64"); this->SymGetSymFromAddr64Proc = (SymGetSymFromAddr64Def)this->DbgHelpLibrsry.GetProcAddress("SymGetSymFromAddr64"); this->SymGetLineFromAddr64Proc = (SymGetLineFromAddr64Def)this->DbgHelpLibrsry.GetProcAddress("SymGetLineFromAddr64"); #else this->SymGetModuleInfoProc = (SymGetModuleInfoDef)this->DbgHelpLibrsry.GetProcAddress("SymGetModuleInfo"); this->SymGetSymFromAddrProc = (SymGetSymFromAddrDef)this->DbgHelpLibrsry.GetProcAddress("SymGetSymFromAddr"); this->SymGetLineFromAddrProc = (SymGetLineFromAddrDef)this->DbgHelpLibrsry.GetProcAddress("SymGetLineFromAddr"); #endif this->SymSetOptionsProc = (SymSetOptionsDef)this->DbgHelpLibrsry.GetProcAddress("SymSetOptions"); this->SymInitializeProc = (SymInitializeDef)this->DbgHelpLibrsry.GetProcAddress("SymInitialize"); this->SymCleanupProc = (SymCleanupDef)this->DbgHelpLibrsry.GetProcAddress("SymCleanup"); if ( !this->RtlCaptureStackBackTraceProc && !this->SymSetOptionsProc && !this->SymInitializeProc && !this->SymCleanupProc #if (_WIN64 || __x86_64__) && !this->SymGetModuleInfo64Proc && !this->SymGetSymFromAddr64Proc && !this->SymGetLineFromAddr64Proc #else && !this->SymGetModuleInfoProc && !this->SymGetSymFromAddrProc && !this->SymGetLineFromAddrProc #endif ) { return ; } //プロセスを記録. this->Process = ::GetCurrentProcess(); //シンボルエンジンの初期化. //行番号付きのデータをロードしてねとお願いする. this->SymSetOptionsProc(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); if (this->SymInitializeProc(this->Process, NULL, TRUE)) { //シンボルエンジン準備完了 this->IsSymbolEngineReady = true; } return ; } public: virtual ~BackTrace() { if (this->IsSymbolEngineReady) { this->SymCleanupProc(this->Process); this->IsSymbolEngineReady = false; } } #elif __GNUC__ //シンボルエンジンの準備ができているか bool IsSymbolEngineReady ; #ifdef __BFD_H_SEEN__ bfd* Abfd; asymbol** Symbols; int NSymbols; asection* Section; #endif BackTrace() { #ifdef __BFD_H_SEEN__ this->IsSymbolEngineReady = false; this->Abfd = NULL; this->Symbols = NULL; this->NSymbols = 0; //see http://0xcc.net/blog/archives/000073.html this->Abfd = bfd_openr("/proc/self/exe", NULL); if (!this->Abfd) { return ; } bfd_check_format(this->Abfd, bfd_object); int size = bfd_get_symtab_upper_bound(this->Abfd); if (size <= 0) { return ; } this->Symbols = (asymbol**) malloc(size); if (!this->Symbols) { return ; } this->NSymbols = bfd_canonicalize_symtab(this->Abfd, this->Symbols); if (!this->NSymbols) { return ; } this->Section = bfd_get_section_by_name(this->Abfd, ".debug_info"); if (!this->Section) { return ; } //シンボルエンジンの初期化完了 this->IsSymbolEngineReady = true; #else //シンボルエンジンは利用できない!! this->IsSymbolEngineReady = false; #endif } public: virtual ~BackTrace() { #ifdef __BFD_H_SEEN__ if (this->Symbols) { free(this->Symbols); this->Symbols = NULL; } if (this->Abfd) { bfd_close (this->Abfd); this->Abfd = NULL; } #endif } #endif public: #ifdef _MSC_VER //スタックトレースを取得する. int backtrace(void** buffer , int n) const { if ( ! this->RtlCaptureStackBackTraceProc ) { //ロードされていない。 return 0; } if (n >= 63) { n = 62; } return (int) this->RtlCaptureStackBackTraceProc(0,n,buffer,NULL); } #if (_WIN64 || __x86_64__) //for64bit //シンボルの解決 void addressToSymbolString(void* address ,char * outBuffer , int len) const { if ( ! this->IsSymbolEngineReady ) { //シンボルエンジンが準備できていない. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //モジュール名 IMAGEHLP_MODULE64 imageModule = { sizeof(IMAGEHLP_MODULE64) }; BOOL r = this->SymGetModuleInfo64Proc(this->Process ,(DWORD64) address , &imageModule); if (!r) { #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //シンボル情報格納バッファ. IMAGEHLP_SYMBOL64 * imageSymbol; char buffer[MAX_PATH + sizeof(IMAGEHLP_SYMBOL64) ] = {0}; imageSymbol = (IMAGEHLP_SYMBOL64*)buffer; imageSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64); imageSymbol->MaxNameLength = MAX_PATH; //関数名の取得... DWORD64 disp = 0; r = this->SymGetSymFromAddr64Proc(this->Process , (DWORD64)address , &disp , imageSymbol ); if (!r) {//関数名がわかりません. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #else _snprintf(outBuffer , len , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #endif return ; } //行番号の取得 IMAGEHLP_LINE64 line ={sizeof(IMAGEHLP_LINE64)}; r = this->SymGetLineFromAddr64Proc(this->Process ,(DWORD64) address , &disp , &line); if (!r) {//行番号が分かりません #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ %s @ %s+%d" ,address, imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s+%d" ,address,imageModule.ModuleName , imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #endif return ; } //行番号がわかりました. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer , len ,_TRUNCATE, "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #endif } #else //for 32bit //シンボルの解決 void addressToSymbolString(void* address ,char * outBuffer , int len) const { if ( ! this->IsSymbolEngineReady ) { //シンボルエンジンが準備できていない. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //モジュール名 IMAGEHLP_MODULE imageModule = { sizeof(IMAGEHLP_MODULE) }; BOOL r = this->SymGetModuleInfoProc(this->Process ,(DWORD) address , &imageModule); if (!r) { #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ ??? @ ??? @ ???:???" ,address ); #else _snprintf(outBuffer , len , "0x%p @ ??? @ ??? @ ???:???" ,address ); #endif return ; } //シンボル情報格納バッファ. IMAGEHLP_SYMBOL * imageSymbol; char buffer[MAX_PATH + sizeof(IMAGEHLP_SYMBOL) ] = {0}; imageSymbol = (IMAGEHLP_SYMBOL*)buffer; imageSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL); imageSymbol->MaxNameLength = MAX_PATH; //関数名の取得... DWORD disp = 0; r = this->SymGetSymFromAddrProc(this->Process , (DWORD)address , &disp , imageSymbol ); if (!r) {//関数名がわかりません. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #else _snprintf(outBuffer , len , "0x%p @ %s @ ??? @ ???:???" ,address,imageModule.ModuleName ); #endif return ; } //行番号の取得 IMAGEHLP_LINE line ={sizeof(IMAGEHLP_LINE)}; r = this->SymGetLineFromAddrProc(this->Process ,(DWORD) address , &disp , &line); if (!r) {//行番号が分かりません #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer ,len , _TRUNCATE , "0x%p @ %s @ %s @ %s+%d" ,address, imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s+%d" ,address,imageModule.ModuleName , imageModule.ModuleName , imageSymbol->Name, imageSymbol->Name,(int) ((char*)address - (char*)line.Address) ); #endif return ; } //行番号がわかりました. #if __STDC_WANT_SECURE_LIB__ _snprintf_s(outBuffer , len ,_TRUNCATE, "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #else _snprintf(outBuffer , len , "0x%p @ %s @ %s @ %s:%d" ,address,imageModule.ModuleName , imageSymbol->Name , line.FileName , line.LineNumber); #endif } #endif //(_WIN64 || __x86_64__) #elif __GNUC__ //スタックトレースを取得する. int backtrace(void** buffer , int n) const { return ::backtrace(buffer , n); } //シンボルの解決 void addressToSymbolString(void* address ,char * outBuffer , int len) const { //backtrace_symbols で一発、、、なんてことをすると demangle されない場合があるそうな。 //だから、dladdr でバラしていく。 //see http://d.hatena.ne.jp/syuu1228/20100215/1266262848 Dl_info info; if (! dladdr(address, &info) ) { snprintf(outBuffer ,len , "0x%p @ ??? @ ??? @ ???:???" ,address ); return ; } if (!info.dli_sname) { snprintf(outBuffer ,len , "0x%p @ %s @ ??? @ ???:???" ,address , info.dli_fname ); return ; } if (!info.dli_saddr) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ ???:???" ,address , info.dli_fname , info.dli_sname ); return ; } //デマングルして関数名を読める形式に int status = 0; char * demangled = abi::__cxa_demangle(info.dli_sname,0,0,&status); //シンボルエンジンは使えるの? if (!this->IsSymbolEngineReady) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s+0x%p" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), (demangled ? demangled : info.dli_sname), (unsigned int) ((char *)address - (char *)info.dli_saddr) ); free(demangled); return ; } #ifdef __BFD_H_SEEN__ //ファイル名と行数を求める. const char* filename = NULL; const char* functionname = NULL; unsigned int line = 0; int found = bfd_find_nearest_line(this->Abfd, this->Section, this->Symbols, (long)address, &filename, &functionname, &line); if (found && filename != NULL && functionname != NULL) { snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s+0x%p" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), (demangled ? demangled : info.dli_sname), (unsigned int) ((char *)address - (char *)info.dli_saddr) ); free(demangled); return ; } snprintf(outBuffer ,len , "0x%p @ %s @ %s @ %s:%d" , address , info.dli_fname , (demangled ? demangled : info.dli_sname), filename , line ); free(demangled); #endif return ; } #endif //シンボルをまとめて解決 void addressToFullSymbolString(void** address ,int size , char * outBuffer , int len) const { int writesize = 0; int i = 0; for( i = 0 ; i < size ; i ++) { void * p = address[i]; if (p == NULL) { break; } this->addressToSymbolString(p,outBuffer + writesize, len - writesize); writesize += (int)strlen(outBuffer + writesize); if (len - writesize >= 2) { #if __STDC_WANT_SECURE_LIB__ strncat_s(outBuffer + writesize , len - writesize , "\r\n",2); #else strncat(outBuffer + writesize , "\r\n",2); #endif writesize += 2; } if (len <= writesize) { break; } } } //バックトレースの取得して画面に表示 void printBackTrace() const { void* stackBuffer[50]; char symbolBuffer[1024]; int stacksize = this->backtrace(stackBuffer , 50 ); this->addressToFullSymbolString(stackBuffer , stacksize , symbolBuffer , 1024); puts(symbolBuffer); } //singletonの取得 static const BackTrace* Get() { static BackTrace s; return &s; } }; class BackTraceAuto { void** StackBuffer; int StackBufferSize; public: BackTraceAuto() { this->StackBuffer = NULL; this->StackBufferSize = 0; } virtual ~BackTraceAuto() { free(this->StackBuffer); this->StackBuffer = NULL; } //バックトレースを記録 void recordTrace() { if (this->StackBuffer != NULL) { return ; } this->StackBuffer = (void**)malloc(sizeof(void*) * 50); if (this->StackBuffer == NULL) { return ; } this->StackBufferSize = BackTrace::Get()->backtrace(this->StackBuffer , 50); } //バックトレースを表示 void show() const { if (this->StackBuffer == NULL) { return ; } char symbolBuffer[1024]; BackTrace::Get()->addressToFullSymbolString(this->StackBuffer , this->StackBufferSize , symbolBuffer , 1024); puts(symbolBuffer); } }; class my_exception { BackTraceAuto Stacktrace; const char * Message; public: my_exception(const char * message) { //ここでスタックを保存する this->Stacktrace.recordTrace(); this->Message = message; } void show() const { puts(this->Message); //スタックのアドレスを関数名などに変換しながら表示する this->Stacktrace.show(); } }; void f5() { throw my_exception("aaaa"); } void f4() { f5(); } void f3() { int aaa = 0; try { f4(); } catch(my_exception const & e) { // BackTrace::Get()->printBackTrace(); e.show(); } } void f2() { f3(); } void f1() { f2(); } int main() { f1(); return 0; }
さっそく使ってみる。
//windows 64bit 0x000000013FC9150D @ stacktrace @ ?backtrace@BackTrace@@QEBAHPEAPEAXH@Z @ c:\use rs\rti\documents\my dropbox\stacktrace\stacktrace\stacktrace.cpp:282 0x000000013FC9149A @ stacktrace @ BackTraceAuto::recordTrace @ c:\users\rti\docu ments\my dropbox\stacktrace\stacktrace\stacktrace.cpp:583 0x000000013FC91561 @ stacktrace @ my_exception::my_exception @ c:\users\rti\docu ments\my dropbox\stacktrace\stacktrace\stacktrace.cpp:609 0x000000013FC94486 @ stacktrace @ f5 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:622 0x000000013FC944CA @ stacktrace @ f4 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:628 0x000000013FC9450B @ stacktrace @ f3 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:636 0x000000013FC9454A @ stacktrace @ f2 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:647 0x000000013FC9457A @ stacktrace @ f1 @ c:\users\rti\documents\my dropbox\stacktr ace\stacktrace\stacktrace.cpp:651 0x000000013FC945AA @
//linux //コンパイルオプション g++ -g -O0 stacktrace.cpp -rdynamic -ldl -lbfd -liberty //なんか、途中から行番号の取得に失敗している、、、なんでだろう。 0x0x4039b7 @ ./a.out @ BackTrace::backtrace(void**, int) const @ /home/rti/stacktrace/stacktrace/stacktrace.cpp:431 0x0x403a13 @ ./a.out @ BackTraceAuto::recordTrace() @ /home/rti/stacktrace/stacktrace/stacktrace.cpp:583 0x0x403a47 @ ./a.out @ my_exception::my_exception(char const*) @ my_exception::my_exception(char const*)+0x0x23 0x0x403055 @ ./a.out @ f5() @ f5()+0x0x25 0x0x40308f @ ./a.out @ f4() @ f4()+0x0x9 0x0x4030a7 @ ./a.out @ f3() @ f3()+0x0x15 0x0x403103 @ ./a.out @ f2() @ f2()+0x0x9 0x0x40310f @ ./a.out @ f1() @ f1()+0x0x9 0x0x40311b @ ./a.out @ main @ main+0x0x9 0x0x362981d994 @ /lib64/libc.so.6 @ __libc_start_main @ /proc/self/exe:0 0x0x402f69 @ ./a.out @ __gxx_personality_v0 @ /proc/self/exe:0
うまくできたー。
やったね。車輪ができたよ。
車輪?
え?車輪。
まずは、コーヒーでも飲めよ。
_、_ ( ,_ノ` ) ζ [ ̄]'E
'さて、何の話だったかな。
そう、車輪だ。
そう、これは再発明なんだよ。
世の中には Boost.Backtrace ってゆーのがあるんだよ。
http://d.hatena.ne.jp/faith_and_brave/20101022/1287731209
これを使うと、こんな感じで同じようなことができるんだ。
#include <boost/backtrace.hpp> #include <iostream> void f5() { throw boost::runtime_error("My Error"); } void f4() { f5(); } void f3() { int aaa = 0; try { f4(); } catch(boost::runtime_error const & e) { std::cerr << e.what() << std::endl; std::cerr << boost::trace(e); } } void f2() { f3(); } void f1() { f2(); } int main() { f1(); return 0; }
出力結果
My Error 00309E1F: boost::stack_trace::trace +0x3f 003025AB: boost::backtrace::backtrace +0x8b 00302352: boost::runtime_error::runtime_error +0x62 00302269: f5 +0x69 003029D3: f4 +0x23 00302A54: f3 +0x54 00302D23: f2 +0x23 00302DF3: f1 +0x23 00302EA3: main +0x23 0031092F: __tmainCRTStartup +0x1bf 0031075F: mainCRTStartup +0xf 75D73677: BaseThreadInitThunk +0x12 77009D42: RtlInitializeExceptionChain +0x63 77009D15: RtlInitializeExceptionChain +0x36
そうなんだ。
これだけで、できちゃうんだ。
ここまで読んでくれてすまないと思っている。
だけど、今回作った方がAPIを動的リンクで呼び出しているので下位互換性があるし、ソースの行数レベルでのトレースができるんだよ、それに、STLすら使っていない化石環境でも導入できる。
まぁ、それだけなんだけどネw
おまけ1 API対応表
windows | unix | 用途 |
RtlCaptureStackBackTrace | backtrace | スタックトレースを取得したい |
SymGetModuleInfo SymGetSymFromAddr |
dladdr | アドレスから モジュール名と関数名を取得したい |
SymGetLineFromAddr | bfd_find_nearest_line | アドレスから ソースファイル名と行数を取得したい |
SYMOPT_UNDNAMEオプション | abi::__cxa_demangle | デマングル |
dbghelp.h | dlfcn.h cxxabi.h bfd.h |
アドレスから復元するライブラリ |
おまけ2 catchでのスタックの状態
try { //esp 0x0020f400 //ebp 0x0020f4f8 00031E68 mov dword ptr [ebp-4],0 f4(); 00031E6F call f4 (312DAh) } 00031E74 jmp __catch$?f3@@YAXXZ$0+24h (31E9Ah) catch(int & e) { //esp 0x0020ebb0 まだ伸びっぱなしぢゃね? //ebp 0x0034f948 puts("123"); 00031E76 mov esi,esp 00031E78 push offset string "123" (40834h) 00031E7D call dword ptr [__imp__puts (455FCh)] 00031E83 add esp,4 00031E86 cmp esi,esp 00031E88 call @ILT+1215(__RTC_CheckEsp) (314C4h) } 00031E8D mov dword ptr [ebp-4],0FFFFFFFFh 00031E94 mov eax,offset $LN2 (31EA1h) 00031E99 ret
あと、catchの下に変数を配置すると、ちょっと変わる。
try { //esp 0x0034f844 //ebp 0x0034f948 010E1E68 mov dword ptr [ebp-4],0 f4(); 010E1E6F call f4 (10E12DAh) } 010E1E74 jmp __catch$?f3@@YAXXZ$0+24h (10E1E9Ah) catch(int & e) { //esp 0x0034eff4 まだ伸びっぱなしぢゃね? //ebp 0x0034f948 puts("123"); 010E1E76 mov esi,esp 010E1E78 push offset string "123" (10F0834h) 010E1E7D call dword ptr [__imp__puts (10F55FCh)] 010E1E83 add esp,4 010E1E86 cmp esi,esp 010E1E88 call @ILT+1215(__RTC_CheckEsp) (10E14C4h) } 010E1E8D mov dword ptr [ebp-4],0FFFFFFFFh 010E1E94 mov eax,offset __tryend$?f3@@YAXXZ$1 (10E1EA1h) //←これ! 010E1E99 ret //←これ! //esp 0x0034f844 戻る //ebp 0x0034f948 010E1E9A mov dword ptr [ebp-4],0FFFFFFFFh int bbb = 0; 010E1EA1 mov dword ptr [ebp-30h],0 }
ここら辺の仕様ってどうなってるの?
参考
普通のやつらの下を行け: BFDでデバッグ情報の取得
http://0xcc.net/blog/archives/000073.html
C++でbacktrace_symbols()してみたらmanglingされてて読めない件
http://d.hatena.ne.jp/syuu1228/20100215/1266262848
Getting the backtrace from the catch block
http://stackoverflow.com/questions/4283943/getting-the-backtrace-from-the-catch-block
How to Log Stack Frames with Windows x64
http://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64
//↓後で試す.
Windows の NTDLL.DLLの RtlCaptureStackBackTrace の バグ
http://blog.livedoor.jp/blackwingcat/archives/845435.html
Boost.Backtrace?
http://d.hatena.ne.jp/faith_and_brave/20101022/1287731209
obcheck
http://sourceforge.jp/projects/obcheck/
最後まで読んでくれてありがとう。