#navi_header|技術| WinDBG参考図書: #amazon||> ||< #amazon||> ||< - "AWD"公式HP : Welcome to Advanced Windows Debugging, windows debugging portal, ... -- http://advancedwindowsdebugging.com/ - Windowsデバッグの極意 -- http://ascii.asciimw.jp/books/books/detail/978-4-04-867608-3.shtml ※"AWD"サンプルコードのシンボルパスについて 本書中で紹介されているURLは2011年1月の時点でアクセス不能になっている。 http://www.advancedwindowsdebugging.com/symbols/symstore.pri このため、"AWD"公式HPからダウンロードしたプライベートシンボルファイルをローカルPC上に展開し、そちらへのパスをシンボルパスに設定する必要がある。 ---- WinDBGやWindowsデバッグ関連参考サイト: - Driver Developer Resources: Debugging Tools for Windows -- http://www.microsoft.com/whdc/devtools/debugging/default.mspx - Ntdebugging Blog - Site Home - MSDN Blogs -- http://blogs.msdn.com/b/ntdebugging/ - Welcome to WinDbg.info -- http://windbg.info/ --- 日本語訳された"WinDbg From A to Z!"は分かりやすいし、"Common WinDbg Commands"は使い始めの頃は印刷して手元で確認出来ると便利。"CrashMe"はWinDbgに入門する時のサンプルアプリに使う。 #more|| ---- #outline|| ---- * sympath, _NT_SYMBOL_PATHの注意点 本やWeb記事からのコピペで済ませると後々「シンボルキャッシュが複数箇所にコピーされてしまう」「VC++側でデバッグ実行時にやたら遅くなる」などトラブルにつながるので注意。 シンボルサーバーを使うための基本: symsrv*ServerDLL*DownstreamStore*Servers ショートカット: sym*DownstreamStore*Servers = symsrv*symsrv.dll*DownstreamStore*Servers "Servers"にはセミコロンで区切って一つ以上のシンボルサーバー・シンボルストアのPATHを指定できる。 "DownstreamStore"には"Servers"からダウンロードしたシンボルファイルをキャッシュする場所を指定する。アスタリスクで区切ることで「カスケード」できる。 以下、設定のバリエーションと落とし穴を紹介。 ** ローカルキャッシュ(DownstreamStore)無し ・Microsoftのシンボルサーバーから都度DL、ローカルキャッシュ無し。一番シンプルで一番非効率。 srv*http://msdl.microsoft.com/download/symbols ・Microsoftのシンボルサーバー+自社イントラネット内の共有フォルダをシンボルストアとして指定。 srv*http://msdl.microsoft.com/download/symbols;\\MyCorpShare\SymStore ''落とし穴1:シンボルストアを複数指定する場合はセミコロンで区切ること。アスタリスクで区切ってしまうと、後述のDownstreamStoreのカスケード設定になってしまい、シンボルキャッシュが複数箇所にコピーされてしまうトラブルにつながる。'' ** ローカルキャッシュ(DownstreamStore)有り ・MicrosoftのシンボルサーバーからDLして C:\localsymcache にキャッシュ。 srv*C:\localsymcache*http://msdl.microsoft.com/download/symbols ・↑+共有フォルダからもキャッシュ。 srv*C:\localsymcache*http://msdl.microsoft.com/download/symbols;\\MyCorpShare\SymStore ・デバッガのデフォルトのローカルキャッシュを使う。 srv**http://msdl.microsoft.com/download/symbols →アスタリスク2つでデフォルトのローカルキャッシュを使うようになる。デフォルトのローカルキャッシュは、デバッガのホームディレクトリのsymディレクトリ。ホームディレクトリはデフォルトではデバッガのインストールディレクトリになっており、"!homedir"コマンドで変更できる。 ** ローカルキャッシュのカスケード 例: srv*C:\localsymcache*\\MyCorpShare\SymCache*http://msdl.microsoft.com/download/symbols + "C:\localsymcache"でシンボル発見 → ロード + "C:\localsymcache"にシンボル無し → "\\MyCorpShare\SymCache" を探索 ++ "\\MyCorpShare\SymCache"でシンボル発見 → ロード + "C:\localsymcache"にコピー ++ "\\MyCorpShare\SymCache"にシンボル無し → Microsoftを検索 +++ Microsoftでシンボル発見 → ロード + "C:\localsymcache", "\\MyCorpShare\SymCache" の ''両方にコピー'' +++ Microsoftにシンボル無し → 失敗 ''落とし穴2:キャッシュとして使うことを意図していないシンボルストアを間違ってカスケードしてしまうと、意図しないシンボルのコピーによりディスク領域の消費や、あるいは書き込み権限絡みのエラーが発生する可能性がある。'' ** "cache"によるローカルキャッシュ srv*...;cache*localsymcache;srv*... といった書式でより細かいローカルキャッシュ制御が可能。 ''ただし _NT_SYMBOL_PATH に組み込んでVC++でデバッグ実行をすると、数分以上のwaitが発生する。'' 参考:[[920]] ''落とし穴3:上記の通り、_NT_SYMBOL_PATH環境変数 + VC++の併用ケースでは実用上の問題で非推奨となる。'' ** まとめ - Servers と ローカルキャッシュ(DownstreamStore) とその区切り文字を混同しない。 -- Serversの区切り:セミコロン -- DownstreamStoreの区切り:アスタリスク - _NT_SYMBOL_PATH環境変数とVC++を併用するときは"cache"を使わない。 - ''"Debugging Tools for Windows" ヘルプファイルの "Advanced SymSrv Use" をよく読みましょう。'' * 簡単なBATファイルを作っておくと便利 デバッグ対象のEXEのあるディレクトリなど、適当なディレクトリにそのプロジェクト専用のWinDbg起動用BATファイルを作っておくと便利かもしれない。"_NT_"で始まる実行イメージやソース、シンボルディレクトリの環境変数を定義したり、windbgのコマンドラインオプションを埋め込んでおく。 windbg.bat: #pre||> @echo off cd /d %~dp0 set PATH=C:\WinDDK\7600.16385.1\Debuggers;%PATH% set _NT_SYMBOL_PATH=srv*C:\temp\symcache*http://msdl.microsoft.com/download/symbols;. set _NT_EXECUTABLE_IMAGE_PATH=. set _NT_SOURCE_PATH=. set _NT_DEBUG_LOG_FILE_APPEND=windbg.log start windbg.exe -srcpath %_NT_SOURCE_PATH% -loga %_NT_DEBUG_LOG_FILE_APPEND% -WF foo.wew debugee.exe arg1 arg2 ... ||< "_NT_SYMBOL_PATH"については各自の環境に応じて調整する。 わざわざ"-srcpath"を指定しているのは、環境変数で"_NT_SOURCE_PATH"を指定してもGUI("File" -> "Source File Path ...")に反映ず、"-srcpath"で指定すればちゃんと反映されたため。 "-WF"でWorkspace設定を読み込ませるようにしているが、WinDbg終了時に"File" -> "Save Workspace"を実行しておかないと保存されない=意味無しので注意。終了時に自動保存してくれると楽なんだけど。 * VMware上のゲストWindowsOSのカーネルデバッグ接続(Named Pipe) : ホストOS側での設定 : VMwareマシンの設定でシリアルポートを追加する。「接続」で「名前付きパイプを使用する」を設定。「この端末はサーバです。」「接続先はアプリケーションです。」の組み合わせに設定する。 : ゲストOS側での設定 : [[706]] を参考にゲストOS側を COM1 でカーネルデバッガの接続を有効にして起動する。 : 接続 :#block||> ホストOS側: windbg -k com:pipe,port=\\.\pipe\xxxxyyyy ||< * カーネルデバッグ中にEXEファイルのシンボルをロードしたい AWDの第三章では、プロセスが例外やDebugBreakし、カーネルデバッガに制御が渡るシナリオが紹介されています。 サンプルとして提示されているコマンド実行結果では、バックトレースの中に例外を発行した02sample.exeのシンボルが表示されています。 実際に試してみると、カーネルデバッガにブレークした時点では特定のプロセスのEXEファイル(今回なら02sample.exe)のシンボルをロード出来ませんでした。こんなエラーになっちゃいます: #pre||> kd> !reload -f 02sample.exe "02sample.exe" was not found in the image list. Debugger will attempt to load "02sample.exe" at given base 00000000. Please provide the full image name, including the extension (i.e. kernel32.dll) for more reliable results.Base address and size overrides can be given as .reload =,. DBGENG: 02sample.exe - Partial symbol image load missing image info ... Unable to add module at 00000000 ||< 色々試行錯誤してみたところ、".thread"メタコマンドに"/p"オプションを付けてコンテキストを切り替えると上手くいきました。 多分、特定プロセスのコンテキストに切り替わったためにEXEのメモリイメージを認識し、シンボルファイルを検索できるようになったんだと思います。 例:"AWD"第二章で使われている02sample.exeのシンボルファイルをKD上でロードしてみます。 1.まずスレッドアドレスを見つけます。 #pre||> kd> !process 0 4 02sample.exe PROCESS 81e4c020 SessionId: 0 Cid: 0474 Peb: 7ffdf000 ParentCid: 0278 DirBase: 09bc0420 ObjectTable: e1837878 HandleCount: 7. Image: 02sample.exe THREAD 8203a810 Cid 0474.0ee0 Teb: 7ffde000 Win32Thread: 00000000 WAIT ^^^^^^^^^^^^^^^ ||< 2.".thread /p"でコンテキストを切り替えます。ついでに"/r"も付けて、ロード出来るシンボルファイルはロードさせちゃいます。 #pre||> kd> .thread /p /r 8203a810 Implicit thread is now 8203a810 Implicit process is now 81e4c020 .cache forcedecodeuser done Loading User Symbols .... (ntdll.dllのシンボルファイルのロードメッセージ。 "!sym noisy"に設定してたから出力されたのかも。) ||< 3.バックトレースの表示をトリガーにして、02sample.exeのシンボルをロードしてみる。 #pre||> kd> k *** Stack trace for last set context - .thread/.cxr resets it ChildEBP RetAddr b1f5a42c 80502cf0 nt!KiSwapContext+0x2e b1f5a438 804fbd72 nt!KiSwapThread+0x46 b1f5a460 8063afc4 nt!KeWaitForSingleObject+0x1c2 b1f5a540 8063c099 nt!DbgkpQueueMessage+0x17c b1f5a564 8063c1cb nt!DbgkpSendApiMessage+0x45 b1f5a5f0 804feccc nt!DbgkForwardException+0x8f b1f5a9b0 8050245b nt!KiDispatchException+0x37e b1f5ad34 80542ec1 nt!KiRaiseException+0x175 b1f5ad50 8053f668 nt!NtRaiseException+0x31 b1f5ad50 7c812afb nt!KiFastCallEntry+0xf8 (kernel32.dllのシンボルロードメッセージ) 0006ff04 77bd272c kernel32!RaiseException+0x53 (02sample.exeのシンボルロードメッセージ) 0006ff44 010016eb msvcrt!_CxxThrowException+0x36 0006ff58 01001d63 02sample!RaiseCPP+0x1b [c:\awd\chapter2\sample.cpp @ 50] 0006ff70 01001c7b 02sample!AppInfo::Loop+0xb3 [c:\awd\common\menu.h @ 47] 0006ff7c 01002038 02sample!wmain+0x1b [c:\awd\chapter2\sample.cpp @ 228] 0006ffc0 7c817077 02sample!__wmainCRTStartup+0x102 [d:\vistartm\base\crts\crtw32\dllstuff\crtexe.c @ 711] 0006fff0 00000000 kernel32!BaseProcessStart+0x23 ||< 02sampleのシンボルがロードされシンボル解決出来たことが分かりました。 * オンライン(=Live)カーネルデバッグでユーザープロセス・スレッドにコンテキストを切り替えたい 基本の流れ: + "!session"コマンドで現在ログイン中のセッションを確認 + "!sprocess" or "!process"コマンドでプロセスを検索 + ".process"コマンドでプロセスコンテキストを切り替え + ".thread"コマンドでスレッドコンテキストを切り替え + スタック、レジスタの確認、ブレークポイント設定など 例1:AWDの第二章サンプル(02sample.exe)のプロセスにコンテキストを切り替えてみる: #pre||> ######## ログインセッションは一つしか無かったので、"!process"コマンドで02sample.exeを検索 kd> !process 0 0 02sample.exe PROCESS 81cfb268 SessionId: 0 Cid: 0b68 Peb: 7ffda000 ParentCid: 09cc DirBase: 09c40360 ObjectTable: e226bd20 HandleCount: 9. Image: 02sample.exe PROCESS 820a7340 SessionId: 0 Cid: 0bec Peb: 7ffde000 ParentCid: 09cc DirBase: 09c40280 ObjectTable: e1c44c98 HandleCount: 9. Image: 02sample.exe ######## Process Address (EPROCESS) 0x820a7340 に切り替えてみる。 kd> .process 820a7340 Implicit process is now 820a7340 kd> .context User-mode page directory base is 9c40280 ######## Process Environment Block (PEB)を見てみる。ちゃんと02sample.exeになってる。 kd> !peb PEB at 7ffde000 InheritedAddressSpace: No ReadImageFileExecOptions: No BeingDebugged: No ImageBaseAddress: 01000000 Ldr 00181e90 Ldr.Initialized: Yes Ldr.InInitializationOrderModuleList: 00181f28 . 00182070 Ldr.InLoadOrderModuleList: 00181ec0 . 00182060 Ldr.InMemoryOrderModuleList: 00181ec8 . 00182068 Base TimeStamp Module 1000000 472187dc Oct 26 15:23:24 2007 C:\awdbin\WinXP.x86.chk\02sample.exe ######## "!process"コマンドで直接EPROCESSを指定して確認してみる。 kd> !process 820a7340 2 PROCESS 820a7340 SessionId: 0 Cid: 0bec Peb: 7ffde000 ParentCid: 09cc DirBase: 09c40280 ObjectTable: e1c44c98 HandleCount: 9. Image: 02sample.exe THREAD 81c35b90 Cid 0bec.082c Teb: 7ffdd000 ... 81c35d84 Semaphore Limit 0x1 kd> !thread 81c35b90 THREAD 81c35b90 Cid 0bec.082c Teb: 7ffdd000 ... 81c35d84 Semaphore Limit 0x1 Waiting for reply to LPC MessageId 00009d87: Current LPC port e113ec50 Not impersonating DeviceMap e1884720 ######## Thread Address (ETHREAD) 0x81c35b90 に切り替えてみる。 kd> .thread 81c35b90 Implicit thread is now 81c35b90 ######## スタックトレースを確認:ちゃんと02sample.exeのスタックになっている。 kd> k *** Stack trace for last set context - .thread/.cxr resets it ChildEBP RetAddr b1946c68 80502cf0 nt!KiSwapContext+0x2e ... 0006ff70 01001c7b 02sample!AppInfo::Loop+0x70 [c:\awd\common\menu.h @ 38] 0006ff7c 01002038 02sample!wmain+0x1b [c:\awd\chapter2\sample.cpp @ 228] ... ||< 例2:手っ取り早くスタックトレースを確認してみる。 #pre||> ########### まず02sample.exeのEPROCESS, ETHREADを確認。 kd> !process 0 2 02sample.exe PROCESS 81cfb268 SessionId: 0 Cid: 0b68 Peb: 7ffda000 ParentCid: 09cc DirBase: 09c40360 ObjectTable: e226bd20 HandleCount: 9. Image: 02sample.exe THREAD 81be5b80 Cid 0b68.0b6c Teb: 7ffdf000 Win32Thread: 00000000 WAIT: (WrLpcReply) UserMode Non-Alertable 81be5d74 Semaphore Limit 0x1 PROCESS 820a7340 SessionId: 0 Cid: 0bec Peb: 7ffde000 ParentCid: 09cc DirBase: 09c40280 ObjectTable: e1c44c98 HandleCount: 9. Image: 02sample.exe THREAD 81c35b90 Cid 0bec.082c Teb: 7ffdd000 Win32Thread: 00000000 WAIT: (WrLpcReply) UserMode Non-Alertable 81c35d84 Semaphore Limit 0x1 ########## ";" でつなげて一気にプロセス・スレッド両コンテキストを切り替える。 kd> .process /p 820a7340; .thread /p 81c35b90 ; .context Implicit process is now 820a7340 .cache forcedecodeuser done Implicit thread is now 81c35b90 Implicit process is now 820a7340 .cache forcedecodeuser done User-mode page directory base is 9c40280 ########## スタックトレースの確認→02sample.exeのものになっている。 kd> k *** Stack trace for last set context - .thread/.cxr resets it ChildEBP RetAddr b1946c68 80502cf0 nt!KiSwapContext+0x2e ... 0006ff70 01001c7b 02sample!AppInfo::Loop+0x70 [c:\awd\common\menu.h @ 38] 0006ff7c 01002038 02sample!wmain+0x1b [c:\awd\chapter2\sample.cpp @ 228] ... ||< ".process", ".thread" で "/p" オプションがあったりなかったりしますが、WinDBGのヘルプを見てみると、オンラインデバッグの場合は"/p"オプションを付けたほうが良いようです。例ではEPROCESSやETHREAD・スタックトレースを確認出来れば良いので、付けたり付けなかったりしてます。 * オンライン(=Live)カーネルデバッグでユーザープロセスにブレークポイント設定→ステップ実行 指定のプロセス・スレッドに切り替えるところまでは上と同じです。一点違うのは、".process"コマンドに"/i"オプションを付けることです。これが無いと、ユーザープロセスがブレークポイントを踏んだ時にカーネルデバッガがbreakしてくれませんでした。 #pre||> kd> !process 0 2 02sample.exe PROCESS 81bd1020 SessionId: 0 Cid: 0ff0 Peb: 7ffdf000 ParentCid: 09cc DirBase: 09c40360 ObjectTable: e20bfad8 HandleCount: 7. Image: 02sample.exe THREAD 81d1e020 Cid 0ff0.0ff4 Teb: 7ffde000 ... 81d1e214 Semaphore Limit 0x1 PROCESS 820c29f0 SessionId: 0 Cid: 03d4 Peb: 7ffdf000 ParentCid: 09cc DirBase: 09c40280 ObjectTable: e1d34bb0 HandleCount: 7. Image: 02sample.exe THREAD 81cff450 Cid 03d4.01d8 Teb: 7ffde000 ... 81cff644 Semaphore Limit 0x1 kd> .process /i 820c29f0; .thread 81cff450; .context You need to continue execution (press 'g' ) for the context to be switched. When the debugger breaks in again, you will be in the new process context. Implicit thread is now 81cff450 User-mode page directory base is b1f000 kd> k *** Stack trace for last set context - .thread/.cxr resets it ChildEBP RetAddr b1a56c68 80502cf0 nt!KiSwapContext+0x2e ... 0006ff70 01001c7b 02sample!AppInfo::Loop+0x70 [c:\awd\common\menu.h @ 38] 0006ff7c 01002038 02sample!wmain+0x1b [c:\awd\chapter2\sample.cpp @ 228] ... ||< 02sample.exeのOutputDebug関数にブレークポイントを設定してみます。"/p"と"/t"でProcessAddress, ThreadAddress両方を指定する必要がありました。 #pre||> kd> x 02sample!OutputDebug 01001c30 02sample!OutputDebug (void) kd> bp /p 820c29f0 /t 81cff450 02sample!OutputDebug kd> bl 0 e 01001c30 0001 (0001) 02sample!OutputDebug Match thread data 81cff450 Match process data 820c29f0 kd> g ########### ".process /i"オプションにより一旦breakしますので、再度"g"で再開させます。 Break instruction exception - code 80000003 (first chance) nt!RtlpBreakWithStatusInstruction: 80529c0c cc int 3 kd> g ||< これで、指定されたプロセス・スレッドがOutputDebug関数に入るとカーネルデバッガにbreakします。あとは"t"や"p"コマンドなど一般的なデバッグコマンドを使ってユーザープロセスをデバッグできます。 #navi_footer|技術|