2016/06/16(木)LoadLibraryに渡されたDLLのパスをデバッガで見る x86
ロードされるDLLの一覧がほしいだけなら Dependency Walker 上で、プロファイリングすれば十分なのだけど。
Kernel32.dll は、すべてのプロセスで同じアドレス空間にロードされているため、
あるプログラムで、Kernel32.dll の API に対してGetProcAddress して得た関数アドレスは、実は他のプロセスでも有効。
これを利用して、LoadLibrary に渡される文字列をデバッガから調べてみることにする。
まずは、適当なプログラム(x86)で、LoadLibraryA と LoadLibraryW を GetProcAddress して、アドレスを調べておく。
調べた結果、それぞれ 0x7639A840 と 0x763A4BF0 だった。
デバッグ対象のEXEをVisual Studioでソリューションとして開き、「デバッグ→ステップイン」でプロセスを開始する。
EXEのエントリポイントで止まるので、この状態で「デバッグ→ウィンドウ→逆アセンブル」を開き、
アドレスに先ほど得た 0x7639A840 を、アドレスとして入力しLoadLibrary の先頭にブレークポイントを仕掛ける。
同様に 0x764A5BF0 にもブレークポイントを仕掛ける。
F5で実行を再開。
ブレークしたときLoadLibraryの先頭にいる。
x86の関数呼び出し規約によれば LoadLibrary の第1引数は、ESPレジスタの指しているアドレス+4の位置に格納されている。
LoadLibraryの第1引数はロードしたいDLLへのパスを格納している文字列へのポインタなので、
これを得るための式をウォッチウィンドウに追加することで、何を引数に関数が呼ばれたかがわかる。
(char**)(@esp+4) ← LoadLibraryAが呼ばれたとき (wchar_t**)(@esp+4) ← LoadLibraryWが呼ばれたとき
結果。*1
というようなことをやっていた。
↑のDSPプラグインを他のアプリから使いたかったんだけど、うまく動かないやつがあってどうしてだろうと思って。
ちなみに、ESPレジスタで思い出したけど、
上記のように関数が呼び出されたとき、ESPレジスタの指す先にはリターンアドレスが入っているので、
これを使うことで、関数の呼び出し元のアドレスを得て メモリリークの追跡もできたりした。
過去形なのは最近x64が台頭しつつあるから。*2
void *my_alloc(void *pCaller, size_t size) { ... } #define NAKED __declspec(naked) #define WITH_CALLER(...) { void *CALLER; { __asm mov eax, [esp] __asm push ebp __asm mov ebp, esp __asm mov CALLER, eax } __VA_ARGS__; { __asm pop ebp __asm ret } } NAKED void * operator new(size_t size) WITH_CALLER(my_alloc(CALLER, size));
// WITH_CALLER の 展開後イメージ __declspec(naked) void* operator new(size_t size) { void* pCALLER; __asm mov eax, [esp]; // 関数が呼び出された瞬間、ESPの指す先にあるリターンアドレスをEAXにメモ。 __asm push ebp; // VC++ がローカル変数へのアクセスをEBPを元に行うコードを生成するので、 __asm mov ebp, esp; // それと互換するようにEBPを準備する。 __asm mov pCALLER, eax; // メモったリターンアドレスをローカル変数へ移して my_alloc(pCALLER, size); // それを使って本命の関数呼び出し。 __asm mov esp, ebp; // 使ったものは __asm pop ebp; // 戻しておこう __asm ret; // my_alloc の戻り値がEAXに入ってるはずなので、それがそのまま戻る。 }
my_alloc は呼び出し元のアドレスをつけて呼び出されるので、線形リストなどに時刻・サイズ等とともにメモっておけばよい。
ビルド時 .map ファイルや、逆アセンブルを出力しておけば、__FILE__などなくとも関数を特定できるのでこれで十分。