WOW64 上でのデバッグ [2/2] ~ .effmach コマンドによるコンテキストのスイッチ

WOW64 上のデバッグ

前のページでは WOW64 とは何か、ということを説明しました。
ここでは実例を挙げて、プログラムのデバッグを実践してみましょう。

テストプログラムは次のようなプログラムです。エディット・ボックスとボタンがあります。エディットボックスに何か文字を入力し、ボタンを押下します。

すると、エディット・ボックスに入力した文字がメッセージボックスに表示される。ただ、それだけのプログラムです。

ちなみに、このプログラムはターゲットを x86 としてビルドしてあります。ですから、前のページで見たようにこのプロセスは WOW64 上で走っているはずです。 このような単純なプログラムで、メッセージボックスに渡した文字がデバッガで確認できるかどうか試してみましょう。

引数、戻り値、エラーコードの確認。これらはデバッガ操作の基本ですからね。
これが出来ないとデバッグをすることは非常に難しいです。

さて、上記のプログラムは dlgwin32.exe というプログラムです。
これをタスクマネージャで見てみましょう。

タスクマネージャでは 32 ビットコードを実行しているプロセスには、*32 というマークが付きます。 下のスクリーンショットのように、確かに dlgwin32.exe にも *32 というマークが付いています。

現在、メッセージボックスを表示して停止している状態なので、この状態のままデバッグできるかどうか試してみましょう。

タスクマネージャからプロセス ID (PID) が 3064 であることもわかったので、PID = 3064 のプロセスにデバッガをアタッチします。 CDB をアタッチするには次のようにします。

> cdb -p 3064

スクリーンショットは次のようになります。

シンボルパスが設定されていなかったので、シンボルパスを設定します。

0:003> .sympath srv*C:\websymbols*http://msdl.microsoft.com/download/symbols
Symbol search path is: srv*C:\websymbols*http://msdl.microsoft.com/download/symbols
Expanded Symbol search path is: 
srv*c:\websymbols*http://msdl.microsoft.com/download/symbols

シンボルがわからないという人は、当サイト内の デバッグにあると便利なシンボルとは? などをご覧ください。

シンボルパスを設定したら、シンボルをリロードします。

0:003> .reload
Reloading current modules
...................................

このままの状態でスタックバックトレースを見てみます。

0:003> ~*kn 100

   0  Id: bf8.660 Suspend: 1 Teb: 00000000`7efdb000 Unfrozen
 # Child-SP          RetAddr           Call Site
00 00000000`0008ebf8 00000000`72ed2d92 wow64cpu!CpupSyscallStub+0x9
01 00000000`0008ec00 00000000`72f4d07e wow64cpu!Thunk0Arg+0x5
02 00000000`0008ecc0 00000000`72f4c549 wow64!RunCpuSimulation+0xa
03 00000000`0008ed10 00000000`76e984c8 wow64!Wow64LdrpInitialize+0x429
04 00000000`0008f260 00000000`76e97623 ntdll!LdrpInitializeProcess+0x17e2
05 00000000`0008f760 00000000`76e8308e ntdll! ?? ::FNODOBFM::`string'+0x2bea0
06 00000000`0008f7d0 00000000`00000000 ntdll!LdrInitializeThunk+0xe

   1  Id: bf8.1074 Suspend: 1 Teb: 00000000`7efad000 Unfrozen
 # Child-SP          RetAddr           Call Site
00 00000000`01c5f0f8 00000000`72ed282c wow64cpu!CpupSyscallStub+0x9
01 00000000`01c5f100 00000000`72f4d07e wow64cpu!WaitForMultipleObjects32+0x32
02 00000000`01c5f1c0 00000000`72f4c549 wow64!RunCpuSimulation+0xa
03 00000000`01c5f210 00000000`76ecd177 wow64!Wow64LdrpInitialize+0x429
04 00000000`01c5f760 00000000`76e8308e ntdll! ?? ::FNODOBFM::`string'+0x2bfe4
05 00000000`01c5f7d0 00000000`00000000 ntdll!LdrInitializeThunk+0xe

   2  Id: bf8.15ec Suspend: 1 Teb: 00000000`7efa1000 Unfrozen
 # Child-SP          RetAddr           Call Site
00 00000000`055cf0f8 00000000`72ed2bcd wow64cpu!CpupSyscallStub+0x9
01 00000000`055cf100 00000000`72f4d07e wow64cpu!Thunk0ArgReloadState+0x1a
02 00000000`055cf1c0 00000000`72f4c549 wow64!RunCpuSimulation+0xa
03 00000000`055cf210 00000000`76ecd177 wow64!Wow64LdrpInitialize+0x429
04 00000000`055cf760 00000000`76e8308e ntdll! ?? ::FNODOBFM::`string'+0x2bfe4
05 00000000`055cf7d0 00000000`00000000 ntdll!LdrInitializeThunk+0xe

#  3  Id: bf8.f7c Suspend: 1 Teb: 00000000`7efaa000 Unfrozen
 # Child-SP          RetAddr           Call Site
00 00000000`01d3fc78 00000000`76f48638 ntdll!DbgBreakPoint
01 00000000`01d3fc80 00000000`76eb39cb ntdll!DbgUiRemoteBreakin+0x38
02 00000000`01d3fcb0 00000000`00000000 ntdll!RtlUserThreadStart+0x25

残念ながらここには、ntdll と wow64*.dll の呼び出ししか見えておらず、windlg32.exe は全く見えてきません。 現在、メッセージボックスを表示中のはずなのですが、メッセージボックスに関わる何かも全く見えません。

このままでは、デバッグは困難です。

ここで役に立つのが、.effmach コマンドです。

WOW64 エミュレーションモードでは、それぞれのスレッドは二つのコンテキストを保持しています。 ひとつはネイティブコンテキストで、もうひとつは 32 ビットコンテキストです。(それぞれのスレッドに TEB も二つあり、 WOW64 プロセスの PEB も二つになります. これらは !wow64exts.straddr コマンドでみれます)

.effmach コマンドを利用すると 32 ビットのコンテキストにスイッチすることができるのです。

では、実際に .effmach x86 コマンドの後に、スタックバックトレースを確認してみましょう。

0:003> .effmach x86
Effective machine: x86 compatible (x86)

0:003:x86> ~*kbn 100

   0  Id: bf8.660 Suspend: 1 Teb: 7efdb000 Unfrozen
 # ChildEBP RetAddr  Args to Child
00 0018f528 74f12674 00480764 00000000 00000001 USER32!NtUserWaitMessage+0x15
01 0018f564 74f1288a 006f034e 00480764 00000000 USER32!DialogBox2+0x222
02 0018f590 74f4f8d0 74ee0000 0027a740 00480764 USER32!InternalDialogBox+0xe5
03 0018f644 74f4fbac 00000040 00000000 00401000 USER32!SoftModalMessageBox+0x757
04 0018f79c 74f4fcaf 0018f7a8 00000028 00480764 USER32!MessageBoxWorker+0x269
05 0018f808 74f4fea5 00480764 0018f860 0041219c USER32!MessageBoxTimeoutW+0x52
06 0018f828 74f4fee7 00480764 0018f860 0041219c USER32!MessageBoxExW+0x1b
07 0018f844 0040119d 00480764 0018f860 0041219c USER32!MessageBoxW+0x18
08 0018fa68 004010d9 00480764 000003e9 00140c3a dlgwin32!OnCommand+0x6d
09 0018fa8c 74ef6238 00480764 00000111 000003e9 dlgwin32!DialogProc+0xd9
0a 0018fab8 74f212a1 00401000 00480764 00000111 USER32!InternalCallWinProc+0x23
0b 0018fb34 74f210e2 00000000 00401000 00480764 USER32!UserCallDlgProcCheckWow+0x10f
0c 0018fb84 74f244b1 008209c0 00000000 00000111 USER32!DefDlgProcWorker+0xb7
0d 0018fbc0 74efcd81 008209c0 00000000 76e739e8 USER32!SendMessageWorker+0x42f
0e 0018fbe4 74f35f4b 00480764 00000111 000003e9 USER32!SendMessageW+0x7f
0f 0018fbfc 74f3608c 00749e10 00000000 00000000 USER32!xxxButtonNotifyParent+0x66
10 0018fc24 74f22eec 0024e070 00000000 00000001 USER32!xxxBNReleaseCapture+0x138
11 0018fcc0 74f37042 00749e10 00000000 00000202 USER32!ButtonWndProcWorker+0xa07
12 0018fce8 74ef6238 00140c3a 00000202 00000000 USER32!ButtonWndProcW+0x54
13 0018fd14 74ef68ea 74f36fee 00140c3a 00000202 USER32!InternalCallWinProc+0x23
14 0018fd8c 74ef7d31 00000000 76ed939c 00140c3a USER32!UserCallWinProcCheckWow+0x109
15 0018fdec 74ef7dfa 76ed939c 00000000 0018fe28 USER32!DispatchMessageWorker+0x3bc
16 0018fdfc 74f12292 0018fe44 00000001 008209c0 USER32!DispatchMessageW+0xf
17 0018fe28 74f12715 00480764 00000000 00000000 USER32!IsDialogMessageW+0x5f6
18 0018fe6c 74f1288a 00480764 00000000 00000000 USER32!DialogBox2+0x15f
19 0018fe98 74f127b8 00400000 00417060 00000000 USER32!InternalDialogBox+0xe5
1a 0018feb8 74f12aa1 00400000 00417060 00000000 USER32!DialogBoxIndirectParamAorW+0x37
1b 0018fedc 00401118 00400000 00000065 00000000 USER32!DialogBoxParamW+0x3f
1c 0018fef8 00401370 00400000 00000000 00234e33 dlgwin32!WinMain+0x18
1d 0018ff88 768c3677 7efde000 0018ffd4 77069d72 dlgwin32!__tmainCRTStartup+0x113
1e 0018ff94 77069d72 7efde000 4e8b3da0 00000000 KERNEL32!BaseThreadInitThunk+0xe
1f 0018ffd4 77069d45 004013db 7efde000 00000000 ntdll_77030000!__RtlUserThreadStart+0x70
20 0018ffec 00000000 004013db 7efde000 00000000 ntdll_77030000!_RtlUserThreadStart+0x1b

   1  Id: bf8.1074 Suspend: 1 Teb: 7efad000 Unfrozen
 # ChildEBP RetAddr  Args to Child
00 0214fdf4 77091dab 00000003 00262728 00000001 
ntdll_77030000!NtWaitForMultipleObjects+0x15
01 0214ff88 768c3677 00000000 0214ffd4 77069d72 ntdll_77030000!TppWaiterpThread+0x33d
02 0214ff94 77069d72 002626f8 4c873da0 00000000 KERNEL32!BaseThreadInitThunk+0xe
03 0214ffd4 77069d45 77091c7f 002626f8 00000000 ntdll_77030000!__RtlUserThreadStart+0x70
04 0214ffec 00000000 77091c7f 002626f8 00000000 ntdll_77030000!_RtlUserThreadStart+0x1b

   2  Id: bf8.15ec Suspend: 1 Teb: 7efa1000 Unfrozen
 # ChildEBP RetAddr  Args to Child
00 056cfec8 74c10816 000000e8 00000000 056cff10 ntdll_77030000!NtWaitForSingleObject+0x15
01 056cff34 768c1184 000000e8 00000bb8 00000000 KERNELBASE!WaitForSingleObjectEx+0x98
02 056cff4c 768c1138 000000e8 00000bb8 00000000 
KERNEL32!WaitForSingleObjectExImplementation+0x75
03 056cff60 72932b44 000000e8 00000bb8 00000000 KERNEL32!WaitForSingleObject+0x12
04 056cff80 72932b88 056cff94 768c3677 02194958 
imjp10k!IImeConvert::SearchDicThread+0x1aa
05 056cff88 768c3677 02194958 056cffd4 77069d72 imjp10k!SearchDicThreadFunc+0x19
06 056cff94 77069d72 02194958 4bff3da0 00000000 KERNEL32!BaseThreadInitThunk+0xe
07 056cffd4 77069d45 72932b6f 02194958 00000000 ntdll_77030000!__RtlUserThreadStart+0x70
08 056cffec 00000000 72932b6f 02194958 00000000 ntdll_77030000!_RtlUserThreadStart+0x1b

#  3  Id: bf8.f7c Suspend: 1 Teb: 7efaa000 Unfrozen
 # ChildEBP RetAddr  Args to Child
00 0497fff4 00000000 00000000 00000000 00000000 ntdll_77030000!RtlUserThreadStart

確かに 32 ビット上のスタックバックトレースと同様の結果が表示されました。

ここから、メッセージボックスの文字が本当に拾えるか確認してみましょう。

スレッド 0 にて、dlgwin32 のコードから MessageBoxW を呼び出しています。 MessageBoxW の第二引数はメッセージテキストですよね?ですから、それを持ってきて表示しましょう。
MessageBox の W ですから、それは Unicode のはずですから、du コマンドで表示します。

0:003:x86> du 0018f860
0018f860  "テスト"

確かに 「テスト」 という文字が出てきました。

このようにして、WOW64 上のコードを調べれば良いのです。

尚、32 ビットにスイッチした後、元のモードに戻るには .effmach . (ドット) とすれば OK です。

0:003:x86> .effmach .
Effective machine: x64 (AMD64)

0:003>

おまけ ~ !wow64exts デバッガエクステンションのコマンドを利用する

他の方法として、!wow64exts デバッガエクステンションを利用することも出来ます。!wow64exts.k コマンドを使って次のようにスタックを見ることも出来ます。

0:003> ~0s
wow64cpu!CpupSyscallStub+0x9:
00000000`72ed2dd9 c3              ret
0:000> !wow64exts.k
Walking 64bit Stack...
Child-SP          RetAddr           Call Site
00000000`0008ebf8 00000000`72ed2d92 wow64cpu!CpupSyscallStub+0x9
00000000`0008ec00 00000000`72f4d07e wow64cpu!Thunk0Arg+0x5
00000000`0008ecc0 00000000`72f4c549 wow64!RunCpuSimulation+0xa
00000000`0008ed10 00000000`76e984c8 wow64!Wow64LdrpInitialize+0x429
00000000`0008f260 00000000`76e97623 ntdll!LdrpInitializeProcess+0x17e2
00000000`0008f760 00000000`76e8308e ntdll! ?? ::FNODOBFM::`string'+0x2bea0
00000000`0008f7d0 00000000`00000000 ntdll!LdrInitializeThunk+0xe
Walking 32bit Stack...
ChildEBP RetAddr
0018f528 74f12674 USER32!NtUserWaitMessage+0x15
0018f564 74f1288a USER32!DialogBox2+0x222
0018f590 74f4f8d0 USER32!InternalDialogBox+0xe5
0018f644 74f4fbac USER32!SoftModalMessageBox+0x757
0018f79c 74f4fcaf USER32!MessageBoxWorker+0x269
0018f808 74f4fea5 USER32!MessageBoxTimeoutW+0x52
0018f828 74f4fee7 USER32!MessageBoxExW+0x1b
0018f844 0040119d USER32!MessageBoxW+0x18
0018fa68 004010d9 dlgwin32!OnCommand+0x6d
0018fa8c 74ef6238 dlgwin32!DialogProc+0xd9
0018fab8 74f212a1 USER32!InternalCallWinProc+0x23
0018fb34 74f210e2 USER32!UserCallDlgProcCheckWow+0x10f
0018fb84 74f244b1 USER32!DefDlgProcWorker+0xb7
0018fbc0 74efcd81 USER32!SendMessageWorker+0x42f
0018fbe4 74f35f4b USER32!SendMessageW+0x7f
0018fbfc 74f3608c USER32!xxxButtonNotifyParent+0x66
0018fc24 74f22eec USER32!xxxBNReleaseCapture+0x138
0018fcc0 74f37042 USER32!ButtonWndProcWorker+0xa07
0018fce8 74ef6238 USER32!ButtonWndProcW+0x54
0018fd14 74ef68ea USER32!InternalCallWinProc+0x23
0:000>

また 32 ビットモードにスイッチする、!wow64exts.sw コマンドというのもあります。

0:000> !wow64exts.sw
Switched to 32bit mode
0:000:x86> ~*kbn

.  0  Id: bf8.660 Suspend: 1 Teb: 7efdb000 Unfrozen
 # ChildEBP RetAddr  Args to Child
00 0018f528 74f12674 00480764 00000000 00000001 USER32!NtUserWaitMessage+0x15
01 0018f564 74f1288a 006f034e 00480764 00000000 USER32!DialogBox2+0x222
02 0018f590 74f4f8d0 74ee0000 0027a740 00480764 USER32!InternalDialogBox+0xe5
03 0018f644 74f4fbac 00000040 00000000 00401000 USER32!SoftModalMessageBox+0x757
... (同様なので省略)

0:000:x86>

.effmach も内部的には !wow64exts.sw でモードを切り替えているので結果は同様になります。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説