.NET Frameworkには「SOS デバッガー拡張」という機能があり、デバッガからCLRの状態を追いかけることができる。これを使うと、重いVisual Studioを使わなくても、WinDbgで.NET Frameworkのアプリケーションをデバッグすることができる。今回は、.NETアプリが異常終了したときにその状態を調べるという方法について。
この役に立たないダイアログも、価値のあるものになるはず。
設定編
1. WinDbgをインストールする
今回はWinDbgを使うことにするので、WinDbgをインストールする。WinDbgをインストールするには「
Windows 用デバッグ ツールのダウンロードとインストール」にある通り、WDKあるいはWindows SDKをインストールすればついてくる。途中でコンポーネント選択の画面があるので、デバッガのみ選べば、他のコンポーネントをインストールしないことも可能。
2. 自動デバッガの設定をする
上の「役に立たないダイアログ」の「プログラムをデバッグします」で起動するプログラムを変更する。やりかたは「
Configuring Automatic Debugging」に書いてある通り、レジストリキー「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug」にある「Debugger」文字列値を追加/変更し、以下のようにする。
"C:\Program Files\Windows Kits\8.0\Debuggers\x86\windbg.exe" -p %ld -e %ld -g
x64版Windowsの場合は、SOS拡張を利用するには、実行するアプリケーションと同じアーキテクチャのwindbg.exeを使う必要があるので、上記のキーはx64版のwindbg.exeに向ける。
"C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\windbg.exe" -p %ld -e %ld -g
x86版のアプリを起動したとき向けに、WOW64側のレジストリキー「HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows NT\CurrentVersion\AeDebug」の「Debugger」文字列値をx86版windbg.exeに向けておく。
"C:\Program Files (x86)\Windows Kits\8.0\Debuggers\x64\windbg.exe" -p %ld -e %ld -g
一応、上記のパスにインストールされていた場合(=Windows8 SDKでWinDbgをデフォルト設定でインストールした場合)に使えるregファイルを置いておいた。
x64用 /
x86用
Visual Studioがインストールされている場合、Visual Studioのデバッガを使うようすでに構成されているため、注意が必要(上記の設定をするとこのダイアログからVisual Studioが使えなくなる)。
これで設定は完了。
実行編
MSBuild Launcher v0.1.1には、「存在しないエディタが設定されていると、異常終了する」というバグがあるので、これを例にする。なお、ポイントとなりそうな部分は勝手に太字にしてある。
基本的には、「
SOS.dll (SOS デバッガー拡張)」を見てコマンドを選ぶことになる。
1. 異常終了+デバッガ起動
MSBuild Launcherの「Settings」ボタンをクリックし、「Editor」テキストボックスに存在しないパスを入力する。その後、適当なmsbuildファイルを開き、「Edit」ボタンを押すと、異常終了し、上の役に立たないダイアログが出てくるので、「プログラムをデバッグします」をクリックすると、WinDbgが立ちあがる。ワークスペースを保存するか聞かれるので、Noを選ぶと、以下の画面になる。
2. SOS拡張のロード
上記の状態になったら以下の通り入力する。
0:000> .loadby sos clr
これは「clr.dllの横にあるsos.dllをロードする」という意味。今回のケースではすでにclr.dllがロードされているので、特にメッセージなく終了するはず。なお、「clr」とするのは.NET Framework 4の場合で、.NET Frameworkの2.0/3.xの場合「clr」は「mscorwks」とする。
今回のケースとはずれるが、ロードされていない場合、例えば起動直後の場合、
0:000> sxe ld clr
としてclrのロードで止めるよう設定して
0:000> g
で進めたあと「.loadby~」を行うか、「.load」で直接DLLの場所を指定してもOK(この場合、.NETのバージョンやアーキテクチャに注意する)。
0:000> .load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll
これで、SOS拡張がロードされた。
3. Exceptionの内容を表示する(!pe)
「CLR exception - code e0434352」が出ている通り、Exceptionが発生しているので、最後のExceptionの内容を表示する!pe (!PrintException)を実行する。
0:000> !pe
PDB symbol for clr.dll not loaded
Exception object: 0000000003078d10
Exception type: System.ComponentModel.Win32Exception
Message: 指定されたファイルが見つかりません。
InnerException:
StackTrace (generated):
SP IP Function
00000000001BD130 000007FEEB0CCBF3 System_ni!System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo)+0x4a3
00000000001BD220 000007FEEB0CD03C System_ni!System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo)+0x3c
00000000001BD260 000007FEE9BD1C51 PresentationCore_ni!System.Windows.EventRoute.InvokeHandlersImpl(System.Object, System.Windows.RoutedEventArgs, Boolean)+0x271
00000000001BD490 000007FEE9BB8BBC PresentationCore_ni!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject, System.Windows.RoutedEventArgs)+0x15c
00000000001BD520 000007FEE91EC26D PresentationFramework_ni!System.Windows.Controls.Button.OnClick()+0xad
00000000001BD590 000007FEE914B868 PresentationFramework_ni!System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs)+0x148
(中略)
00000000001BE9D0 000007FEE85486DF PresentationFramework_ni!System.Windows.Application.RunInternal(System.Windows.Window)+0x12f
00000000001BEA40 000007FEE8547CEB PresentationFramework_ni!System.Windows.Application.Run()+0xbb
00000000001BEA90 000007FF0015024F MsbuildLauncher!MsbuildLauncher.App.Main()+0x12f
StackTraceString:
HResult: 80004005
これでもう「System.Diagnostics.Process.StartWithShellExecuteEx」を呼んだ結果「System.ComponentModel.Win32Exception」が発生し「指定されたファイルが見つかりません。」というメッセージが出ていることがわかった。MSBuild Launcherにおいて「System.Windows.Controls.Button.OnClick」から「System.Diagnostics.Process.Start」を呼ぶところなんて限られてくるので、もうわかったも同然なのだが、もう少し調べてみる。
4. スタックのオブジェクトを一覧する(!dso)
今回の場合「InnerException」がnoneなので、そこは調べられない。SOS拡張では、!dso (!DumpStackObjects)で、スタックのオブジェクトを一覧できるので、表示してみる。
0:000> !dso
OS Thread Id: 0x1208 (0)
RSP/REG Object Name
r15 0000000002e69448 System.Windows.Input.NotifyInputEventArgs
00000000001BCE68 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BCF08 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BCF50 0000000003074ca0 System.Windows.EventRoute
00000000001BCF68 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BCF80 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BCF98 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BCFA0 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BD030 0000000003074ca0 System.Windows.EventRoute
00000000001BD048 0000000003078db0 System.Text.StringBuilder
00000000001BD080 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BD090 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BD0B0 0000000003074ca0 System.Windows.EventRoute
00000000001BD0B8 0000000002e69448 System.Windows.Input.NotifyInputEventArgs
00000000001BD120 0000000003078c40 Microsoft.Win32.NativeMethods+ShellExecuteInfo
00000000001BD130 0000000003078d10 System.ComponentModel.Win32Exception
00000000001BD168 0000000003078360 System.Windows.RoutedEventArgs
00000000001BD1D0 0000000003078c40 Microsoft.Win32.NativeMethods+ShellExecuteInfo
00000000001BD1F8 0000000003078860 System.Diagnostics.ProcessStartInfo
00000000001BD200 0000000003078360 System.Windows.RoutedEventArgs
00000000001BD210 0000000003078b28 System.Diagnostics.Process
00000000001BD220 0000000003078b28 System.Diagnostics.Process
(略)
太字の 「System.Diagnostics.ProcessStartInfo」オブジェクトを見ればもうすこしわかりそうなので、表示してみることにする。
5. オブジェクトの内容を表示する(!do)
オブジェクトの内容を表示するには、!do (!DumpObj)コマンドを使う。引数には、先程の「System.Diagnostics.ProcessStartInfo」のオブジェクトアドレスを指定する。
0:000> !do 0000000003078860
Name: System.Diagnostics.ProcessStartInfo
MethodTable: 000007feeab807a0
EEClass: 000007feea86a628
Size: 128(0x80) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System\v4.0_4.0.0.0__b77a5c561934e089\System.dll
Fields:
MT Field Offset Type VT Attr Value Name
000007feeb866738 4002fc9 8 System.String 0 instance 0000000003078808 fileName
000007feeb866738 4002fca 10 System.String 0 instance 0000000003078aa8 arguments
000007feeb866738 4002fcb 18 System.String 0 instance 0000000000000000 directory
000007feeb866738 4002fcc 20 System.String 0 instance 0000000000000000 verb
000007feeabb2ae8 4002fcd 68 System.Int32 1 instance 0 windowStyle
000007feeb86d450 4002fce 6c System.Boolean 1 instance 0 errorDialog
000007feeb873318 4002fcf 60 System.IntPtr 1 instance 0 errorDialogParentHandle
000007feeb86d450 4002fd0 6d System.Boolean 1 instance 1 useShellExecute
000007feeb866738 4002fd1 28 System.String 0 instance 0000000000000000 userName
000007feeb866738 4002fd2 30 System.String 0 instance 0000000000000000 domain
000007feeb87e6d0 4002fd3 38 ...rity.SecureString 0 instance 0000000000000000 password
000007feeb86d450 4002fd4 6e System.Boolean 1 instance 0 loadUserProfile
000007feeb86d450 4002fd5 6f System.Boolean 1 instance 0 redirectStandardInput
000007feeb86d450 4002fd6 70 System.Boolean 1 instance 0 redirectStandardOutput
000007feeb86d450 4002fd7 71 System.Boolean 1 instance 0 redirectStandardError
000007feeb873738 4002fd8 40 System.Text.Encoding 0 instance 0000000000000000 standardOutputEncoding
000007feeb873738 4002fd9 48 System.Text.Encoding 0 instance 0000000000000000 standardErrorEncoding
000007feeb86d450 4002fda 72 System.Boolean 1 instance 0 createNoWindow
000007feeb887510 4002fdb 50 System.WeakReference 0 instance 0000000000000000 weakParentProcess
000007feeab80948 4002fdc 58 ....StringDictionary 0 instance 0000000000000000 environmentVariables
「fileName」が使えそうなので、さらに表示してみる。
0:000> !do 0000000003078808
Name: System.String
MethodTable: 000007feeb866738
EEClass: 000007feeb3eed68
Size: 86(0x56) bytes
File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: C:\Windows\invalid-notepad.exe
Fields:
MT Field Offset Type VT Attr Value Name
000007feeb86c620 4000103 8 System.Int32 1 instance 30 m_stringLength
000007feeb86b160 4000104 c System.Char 1 instance 43 m_firstChar
000007feeb866738 4000105 10 System.String 0 shared static Empty
>> Domain:Value 00000000002dafa0:0000000002a11420 <<
これで、「C:\Windows\invalid-notepad.exe」という文字列がfileNameに入っていることがわかった。これでは実行できそうもない。
---
と、このような感じで、異常終了時の例外情報を調べるくらいならわりと簡単にできる。.NETプログラマなら覚えておくのがおすすめ。
本当は、AppDomainの中を旅するだけの機能もついているのだけど、それはまた機会があれば。