windbgとsosと.Netアプリ

○目的
スレッドがデッドロックしてたり、メモリリークっぽかったり、妙なバグがでた時にWindbgとsosで何とかできたらいいな。
そのためにWindbg+sosで何ができるか調べておく。
javaにはスレッドダンプとかPrintClassHistogramがあってうらやましいなぁと思いながら変わりを探してたら見つけた。

○いるもの
Windbgはダウンロードしていれる。場所はググれば出てくるでしょう。
sosはVS入れたら入ってくるらしい。

○使い方
1.起動
2.デバッグするプロセスをアタッチ
3.sosをロード
4.あとはコマンドでいろいろやる。

○具体例
サンプルアプリはコレ。
アタッチするようにServer.exeを起動して、一回クライアントからアクセスもしておく。

Windbgを起動する
画面は殺風景

プロセスをアタッチ
Server.exeをアタッチ
なんか表示される。

sosをロード
.load C:\WINDOWS\microsoft.net\Framework64\v2.0.50727\sos.dll
でロード

あるオブジェクトのインスタンスについて調べる
dumpheap
でヒープの内容を見てみる。
先にヒープの内容が表示されて後の方にタイプの情報(これをメソッドテーブルっていうのかな??)

前半に表示されるヒープの内容はこんな感じ
0:009> !dumpheap
*********************************************************************
* Symbols can not be loaded because symbol path is not initialized. *
* *
* The Symbol Path can be set by: *
* using the _NT_SYMBOL_PATH environment variable. *
* using the -y argument when starting the debugger. *
* using .sympath and .sympath+ *
*********************************************************************
PDB symbol for mscorwks.dll not loaded
Address MT Size
00000642ab500460 00000642788c0cd8 24 
00000642ab500478 00000642788c1ad0 48 
00000642ab5004a8 00000642788c1ad0 72
・
・
・

アドレス\tメソッドテーブル\tサイズ
の順に表示されている。これだけ見ても何がなにやらわからない。

後半に表示されるタイプの情報がこんな感じ。
MT Count TotalSize Class Name
00000642801d3130 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Channels.InitialServerConnectionReader, System.ServiceModel]]
00000642801d2348 1 24 System.ServiceModel.Dispatcher.SortedBuffer`2+DefaultComparer[[System.ServiceModel.Dispatcher.MessageFilterTable`1+FilterTableEntry[[System.ServiceModel.EndpointAddress, System.ServiceModel]], System.ServiceModel],[System.ServiceModel.Dispatcher.MessageFilterTable`1+TableEntryComparer[[System.ServiceModel.EndpointAddress, System.ServiceModel]], System.ServiceModel]]
00000642801d1588 1 24 System.Collections.Generic.ObjectComparer`1[[System.ServiceModel.Dispatcher.FaultContractInfo, System.ServiceModel]]
000006428019b8f0 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Channels.BaseUriWithWildcard, System.ServiceModel]]
000006428019b5e8 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Channels.DirectionalAction, System.ServiceModel]]
000006428019a710 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Dispatcher.MessageFilter, System.ServiceModel]]
000006428019a558 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.EndpointAddress, System.ServiceModel]]
000006428019a3a0 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Description.DispatcherBuilder+ListenUriInfo, System.ServiceModel]]
0000064280192f80 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.ServiceModel.Description.OperationDescription, System.ServiceModel]]
0000064280192a50 1 24 System.Collections.Generic.ObjectEqualityComparer`1[[System.Xml.XmlQualifiedName, System.Xml]]
0000064280016780 1 24 Service.statefulServiceImpl
0000064280016300 1 24 Service.StatelessServiceImpl
・
・
・

こっちはまだわかるな。
メソッドテーブル\tヒープにあるインスタンスのカウント(多分)\tサイズ(メモリサイズかなぁ。よくわからん)\tクラスの名前
でならんでる。

ここではService.statefulServiceImplについて調べてみる。
まず、メソッドテーブルは0000064280016780だとわかっているので、ここを手がかりにいろいろと調べる。

dumpmtでメソッドテーブルの詳しい情報が見える。-mdオプションをつけるとクラスにあるメソッドの一覧まで見える。
0:009> !dumpmt -md 0000064280016780
EEClass: 0000064280144b58
Module: 0000064280015a30
Name: Service.statefulServiceImpl
mdToken: 02000005 (C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\WCF\Server\bin\Release\Service.dll)
BaseSize: 0x18
ComponentSize: 0x0
Number of IFaces in IFaceMap: 2
Slots in VTable: 8
--------------------------------------
MethodDesc Table
Entry MethodDesc JIT Name
000006427809f660 0000064278930388 PreJIT System.Object.ToString()
000006427809dc60 0000064278930390 PreJIT System.Object.Equals(System.Object)
000006427809dc90 00000642789303a8 PreJIT System.Object.GetHashCode()
0000064278092ca0 00000642789303b0 PreJIT System.Object.Finalize()
00000642801516d0 0000064280016720 JIT Service.statefulServiceImpl.SetName(System.String)
0000064280151750 0000064280016728 JIT Service.statefulServiceImpl.GetName()
000006428001c0e8 0000064280016730 NONE Service.statefulServiceImpl.Dispose()
0000064280151690 0000064280016738 JIT Service.statefulServiceImpl..ctor()

これでEEClassがわかった。
次はdumpclassでさらにいろいろ見てみる。
0:009> !dumpclass 0000064280144b58
Class Name: Service.statefulServiceImpl
mdToken: 0000000002000005 (C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\WCF\Server\bin\Release\Service.dll)
Parent Class: 00000642788c0c30
Module: 0000064280015a30
Method Table: 0000064280016780
Vtable Slots: 7
Total Method Slots: 8
Class Attributes: 100001
NumInstanceFields: 1
NumStaticFields: 0
MT Field Offset Type VT Attr Value Name
00000642788c1ad0 4000002 8 System.String 0 instance name_

名前がname_のインスタンス変数がひとつあることがわかった。
では実際のService.statefulServiceImplのインスタンスのname_の値を見てみる。

dumpheapは-mtオプションでメソッドテーブルで絞った表示ができる。
Service.statefulServiceImplのメソッドテーブルは0000064280016780なので、これでしぽる。
0:009> !dumpheap -mt 0000064280016780
Address MT Size
0000000002bdc688 0000064280016780 24 
total 1 objects
Statistics:
MT Count TotalSize Class Name
0000064280016780 1 24 Service.statefulServiceImpl
Total 1 objects

前半に表示されているのが実際にヒープに取られているインスタンスなのだと思う。
アドレスは0000000002bdc688

dumpobjでこのヒープの情報を見てみる。
0:009> !dumpobj 0000000002bdc688
Name: Service.statefulServiceImpl
MethodTable: 0000064280016780
EEClass: 0000064280144b58
Size: 24(0x18) bytes
(C:\Documents and Settings\Administrator\My Documents\Visual Studio 2008\Projects\WCF\Server\bin\Release\Service.dll)
Fields:
MT Field Offset Type VT Attr Value Name
00000642788c1ad0 4000002 8 System.String 0 instance 0000000002bdd820 name_

名前がname_で値は0000000002bdd820らしい。
0000000002bdd820ってなんだ??
変数name_のTypeはStringなので、ヒープのどこかに文字列が確保されて、それへのポインタが保持されているのかな??

というわけでさらにdumpobjで0000000002bdd820の値を見てみる。
0:009> !dumpobj 0000000002bdd820
Name: System.String
MethodTable: 00000642788c1ad0
EEClass: 00000642788c19d8
Size: 30(0x1e) bytes
(C:\WINDOWS\assembly\GAC_64\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)
String: 太郎
Fields:
MT Field Offset Type VT Attr Value Name
00000642788ca770 4000096 8 System.Int32 1 instance 3 m_arrayLength
00000642788ca770 4000097 c System.Int32 1 instance 2 m_stringLength
00000642788c4fe8 4000098 10 System.Char 1 instance 592a m_firstChar
00000642788c1ad0 4000099 20 System.String 0 shared static Empty
>> Domain:Value 0000000000168cb0:0000064278878f70 <<>> Domain:Value 0000000000168cb0:0000000002a81708 << 

ビンゴ!? String: 太郎 ってでてる。

時間がないので後はざっくり。

メモリリーク
dumpheapで出るcountの情報をみたらリークしているクラスが何かわかるのではないか?

デッドロック
threadでスレッドの一覧がでる。
~[番号]sでカレントのスレッドを変更した上でclrstackでClr内のスタックの状態がでる。 この辺でなんとかならんか。