WinDbg: プロセス起動直後、DLL がロードされ、エントリーポイントや main() に入るまでの動作を確認する
Windows のプロセスが起動し、DLL の暗黙的ロードが行われ、exe のエントリーポイントが呼び出され、main (WinMain) に入るまでの動作を観察してみます。
動作確認環境
- Windows 11 Home 22H2
- WinDbg 10.0
通常の手段では手遅れ
調査対象のプログラム(今回は MyMain.exe)を実行してから WinDbg でアタッチしても、
当然ながら、暗黙的ロードの DLL はすでに読み込まれてしまっています。
0:004> lm ★ ロードモジュールを表示
start end module name
00007ff7`69a70000 00007ff7`69b11000 MyMain ......
00007ffd`abc20000 00007ffd`abcc0000 MyDll2 ......
00007ffd`ac460000 00007ffd`ac500000 MyDll1 ......
00007ffd`e2750000 00007ffd`e2af4000 KERNELBASE ......
00007ffd`e34e0000 00007ffd`e35a4000 KERNEL32 ......
00007ffd`e50b0000 00007ffd`e52c4000 ntdll ......
WinDbg の引数に調査対象のプログラムを指定し WinDbg 経由で実行しても、
C:\> windbg MyMain.exe
デバッガが停止するのは DLL がロードされた後の、「ntdll!LdrpDoDebuggerBreak」という箇所です。
......
ModLoad: 00007ff7`69a70000 00007ff7`69b11000 MyMain.exe
ModLoad: 00007ffd`e50b0000 00007ffd`e52c4000 ntdll.dll
ModLoad: 00007ffd`e34e0000 00007ffd`e35a4000 C:\WINDOWS\System32\KERNEL32.DLL
ModLoad: 00007ffd`e2750000 00007ffd`e2af4000 C:\WINDOWS\System32\KERNELBASE.dll
ModLoad: 00007ffd`abc20000 00007ffd`abcc0000 C:\tp\MyDll64\MyDll1.dll
ModLoad: 00007ffd`aab10000 00007ffd`aabb0000 C:\tp\MyDll64\MyDll2.dll
(1a44.35a4): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30: ★ ここで停止。上記の通り (ModLoad)、各種 DLL はロード済み。
00007ffd`e518b304 cc int 3
......
すでに手遅れです。
コマンドラインオプションを付けて起動
もっと早い段階でプログラムを停止させるには、WinDbg にコマンドラインオプション「-xe cpr -xe ld」を付けて、調査対象のプログラムを起動します。「-xe cpr」はプロセス生成時(Create Process 時) に停止、「-xe ld」は DLL ロード 時(Load Module 時)に停止の意味です。
C:\> windbg -xe cpr -xe ld MyMain.exe
実行すると、MyMain.exe のイメージロードのタイミングで止まりました。
......
ModLoad: 00007ff7`69a70000 00007ff7`69b11000 MyMain.exe ★ MyMain.exe のロード
00007ffd`e510aa40 4883ec78 sub rsp,78h
......
実行を継続すると(g コマンド)、今度は「ntdll.dll」がロードされたタイミングで止まりました。
0:000> g
ModLoad: 00007ffd`e50b0000 00007ffd`e52c4000 ntdll.dll ★ ntdll.dll のロード
ntdll!RtlUserThreadStart:
00007ffd`e510aa40 4883ec78 sub rsp,78h
0:000> lm
start end module name
00007ff7`69a70000 00007ff7`69b11000 MyMain ......
00007ffd`e50b0000 00007ffd`e52c4000 ntdll ......
さらに実行を継続すると、「KERNEL32.DLL」がロードされたタイミングで止まり、
0:000> g
ModLoad: 00007ffd`e34e0000 00007ffd`e35a4000 C:\WINDOWS\System32\KERNEL32.DLL ★ KERNEL32.dll のロード
ntdll!NtMapViewOfSection+0x14:
00007ffd`e514f254 c3 ret
0:000> lm
start end module name
00007ff7`69a70000 00007ff7`69b11000 MyMain ......
00007ffd`e34e0000 00007ffd`e35a4000 KERNEL32 ......
00007ffd`e50b0000 00007ffd`e52c4000 ntdll ......
以下繰り返しで最終的に次の DLL がロードされ、
0:001> lm
start end module name
00007ff7`69a70000 00007ff7`69b11000 MyMain ......
00007ffd`abc20000 00007ffd`abcc0000 MyDll2 ......
00007ffd`ac460000 00007ffd`ac500000 MyDll1 ......
00007ffd`e2750000 00007ffd`e2af4000 KERNELBASE ......
00007ffd`e34e0000 00007ffd`e35a4000 KERNEL32 ......
00007ffd`e50b0000 00007ffd`e52c4000 ntdll ......
「-xe cpr -xe ld」オプションを指定しなかったときと同じ、「ntdll!LdrpDoDebuggerBreak」で停止しました。
0:001> g
(4f0c.192c): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffd`e518b304 cc int 3
この段階では、まだ .exe のエントリーポイントや main (WinMain) には入っていません。
それぞれにブレークポイントを仕掛けてみましょう。
0:000> !dh MyMain
......
2DC9 address of entry point ★ エントリーポイントのオフセット
......
0:000> bp MyMain+2DC9 ★ エントリーポイントにブレークポイントを仕掛ける
0:000> bp MyMain!main ★ main(MyMain はコンソールプログラム)にブレークポイントを仕掛ける
実行を継続すると、.exe のエントリーポイントで停止しました。このアドレスにあったのは、C 言語ランタイムライブラリの初期化ルーチンにジャンプする命令です。
0:000> g
Breakpoint 1 hit
MyMain!ILT+7620(mainCRTStartup):
00007ff7`69a72dc9 e936480000 jmp MyMain!mainCRTStartup (00007ff7`69a77604)
さらに実行を継続すると、main 関数の先頭で停止しました。
0:000> g
Breakpoint 2 hit
MyMain!main: ★ main() の先頭
00007ff7`69a77110 4883ec28 sub rsp,28h
このように、各 DLL のロードから main に入るまでを追うことができました。
GFlags でさらに詳しい情報を見る
WinDbg と同じフォルダに入っている GFlags (Global Flags, gflags.exe) ツールを利用すると、DLL ロード時の詳細情報を表示できます。
GFlags を起動し、調査対象のプログラムに「Show loader snaps」オプションを設定します。
さきほどと同様、WinDbg のコマンドラインオプション「-xe cpr -xe ld」を指定して、調査対象のプログラムを起動します。
C:\> windbg -xe cpr -xe ld MyMain.exe
何百行にも渡るので大幅に省略しますが、DLL を探したり(redirected to)初期化ルーチンを呼び出したり(Calling init routine)している様子が細かく表示されます。
......
ModLoad: 00007ff7`69a70000 00007ff7`69b11000 MyMain.exe
00007ffd`e510aa40 4883ec78 sub rsp,78h
0:000> g
ModLoad: 00007ffd`e50b0000 00007ffd`e52c4000 ntdll.dll
ntdll!RtlUserThreadStart:
00007ffd`e510aa40 4883ec78 sub rsp,78h
0:000> g
......
3c44:2868 @ 483934187 - LdrLoadDll - ENTER: DLL name: KERNEL32.DLL
3c44:2868 @ 483934187 - LdrpLoadDllInternal - ENTER: DLL name: KERNEL32.DLL
3c44:2868 @ 483934187 - LdrpFindKnownDll - ENTER: DLL name: KERNEL32.DLL
3c44:2868 @ 483934187 - LdrpFindKnownDll - RETURN: Status: 0x00000000
3c44:2868 @ 483934187 - LdrpMinimalMapModule - ENTER: DLL name: C:\WINDOWS\System32\KERNEL32.DLL
ModLoad: 00007ffd`e34e0000 00007ffd`e35a4000 C:\WINDOWS\System32\KERNEL32.DLL
ntdll!NtMapViewOfSection+0x14:
00007ffd`e514f254 c3 ret
0:000> g
3c44:2868 @ 483934937 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
3c44:2868 @ 483934937 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-rtlsupport-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
3c44:2868 @ 483934937 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-rtlsupport-l1-2-2.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
3c44:2868 @ 483934953 - LdrpFindKnownDll - ENTER: DLL name: KERNELBASE.dll
3c44:2868 @ 483934953 - LdrpFindKnownDll - RETURN: Status: 0x00000000
3c44:2868 @ 483934953 - LdrpMinimalMapModule - ENTER: DLL name: C:\WINDOWS\System32\KERNELBASE.dll
ModLoad: 00007ffd`e2750000 00007ffd`e2af4000 C:\WINDOWS\System32\KERNELBASE.dll
ntdll!NtMapViewOfSection+0x14:
00007ffd`e514f254 c3 ret
0:000> g
3c44:2868 @ 483935468 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
3c44:2868 @ 483935468 - LdrpPreprocessDllName - INFO: DLL api-ms-win-eventing-provider-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\kernelbase.dll by API set
3c44:2868 @ 483935468 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-apiquery-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\ntdll.dll by API set
3c44:2868 @ 483935468 - LdrpPreprocessDllName - INFO: DLL api-ms-win-core-processthreads-l1-1-0.dll was redirected to C:\WINDOWS\SYSTEM32\kernelbase.dll by API set
......
3c44:2868 @ 483935484 - LdrpGetProcedureAddress - INFO: Locating procedure "RtlReAllocateHeap" by name
3c44:2868 @ 483935484 - LdrpGetProcedureAddress - INFO: Locating procedure "RtlFreeHeap" by name
3c44:2868 @ 483935484 - LdrpGetProcedureAddress - INFO: Locating procedure "RtlAllocateHeap" by name
......
3c44:2868 @ 483935484 - LdrpInitializeNode - INFO: Calling init routine 00007FFDE278C2F0 for DLL "C:\WINDOWS\System32\KERNELBASE.dll"
3c44:2868 @ 483935921 - LdrGetDllHandleEx - ENTER: DLL name: ntdll.dll
3c44:2868 @ 483935921 - LdrpFindLoadedDllInternal - RETURN: Status: 0x00000000
3c44:2868 @ 483935921 - LdrGetDllHandleEx - RETURN: Status: 0x00000000
3c44:2868 @ 483935921 - LdrpGetProcedureAddress - INFO: Locating procedure "RtlDisownModuleHeapAllocation" by name
3c44:2868 @ 483935921 - LdrpInitializeNode - INFO: Calling init routine 00007FFDE34F2710 for DLL "C:\WINDOWS\System32\KERNEL32.DLL"
3c44:2868 @ 483935921 - LdrGetDllHandleEx - ENTER: DLL name: ntdll.dll
3c44:2868 @ 483935921 - LdrpFindLoadedDllInternal - RETURN: Status: 0x00000000
3c44:2868 @ 483935921 - LdrGetDllHandleEx - RETURN: Status: 0x00000000
3c44:2868 @ 483935921 - LdrpGetProcedureAddress - INFO: Locating procedure "RtlQueryFeatureConfiguration" by name
......
3c44:2868 @ 483935937 - LdrpMinimalMapModule - ENTER: DLL name: C:\tp\MyDll64\MyDll1.dll
3c44:448c @ 483935937 - LdrpMinimalMapModule - ENTER: DLL name: C:\tp\MyDll64\MyDll2.dll
ModLoad: 00007ffd`ac460000 00007ffd`ac500000 C:\tp\MyDll64\MyDll2.dll
ntdll!NtMapViewOfSection+0x14:
00007ffd`e514f254 c3 ret
0:001> g
ModLoad: 00007ffd`abc20000 00007ffd`abcc0000 C:\tp\MyDll64\MyDll1.dll
ntdll!NtMapViewOfSection+0x14:
00007ffd`e514f254 c3 ret
0:000> g
3c44:2868 @ 483945437 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
3c44:448c @ 483945437 - LdrpMinimalMapModule - RETURN: Status: 0x00000000
......
(3c44.2868): Break instruction exception - code 80000003 (first chance)
ntdll!LdrpDoDebuggerBreak+0x30:
00007ffd`e518b304 cc int 3
故障解析や OS の解析に役立ちそうです。
WinDbg 経由で実行できないとき
調査対象のプログラムが外部のプログラムから直接実行されているため WinDbg 経由で実行できない、という場合もあると思います。そんなときも、GFlags を使って WinDbg を割り込ませることができます。
具体的には、調査対象のプログラムに「Debugger」として「”C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe” -xe cpr -xe ld」を指定します(下の画像は加工しています/パスは環境に合わせて変更してください)。
上記設定を行うと、単に「MyMain.exe」を実行しただけなのに、「”C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\windbg.exe” -xe cpr -xe ld MyMain.exe」と実行したかのように動作します。
試してみましょう。
プログラムを実行します。
C:\> MyMain.exe
WinDbg が起動し、プログラムがロードされたタイミングで停止しました。ここから詳細な動作を調査できます。
なお、GFlags の設定が不要になったら明示的にクリアしてください。
おわりに
「プロセスの起動 → DLL のロード → .exe のエントリーポイント → main 」の動作を追う方法について説明しました。
その他、コマンドラインオプション「-xe ld:dllname」で特定の DLL がロードされたタイミングで停止、WinDbg 経由で起動後、WinDbg コマンドウィンドウから「sxe cpr」「sxe ld」「.restart」すれば「Windbg.exe -xe cpr -xe ld」と同様の効果あり、との情報をメモしておきます。