木曜日, 12月 27, 2007
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
/*********************************************************************
/*1208429829* Symbols can not be loaded because symbol path is not initialized. *
/*1208429830* *
/*1208429831* The Symbol Path can be set by: *
/*1208429832* using the _NT_SYMBOL_PATH environment variable. *
/*1208429833* using the -y argument when starting the debugger. *
/*1208429834* 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`1System.ServiceModel.Channels.InitialServerConnectionReader, System.ServiceModel
00000642801d2348 1 24 System.ServiceModel.Dispatcher.SortedBuffer`2+DefaultComparerSystem.ServiceModel.Dispatcher.MessageFilterTable`1+FilterTableEntry[[System.ServiceModel.EndpointAddress, System.ServiceModel, System.ServiceModel],[System.ServiceModel.Dispatcher.MessageFilterTable`1+TableEntryComparerSystem.ServiceModel.EndpointAddress, System.ServiceModel, System.ServiceModel]]
00000642801d1588 1 24 System.Collections.Generic.ObjectComparer`1System.ServiceModel.Dispatcher.FaultContractInfo, System.ServiceModel
000006428019b8f0 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.Channels.BaseUriWithWildcard, System.ServiceModel
000006428019b5e8 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.Channels.DirectionalAction, System.ServiceModel
000006428019a710 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.Dispatcher.MessageFilter, System.ServiceModel
000006428019a558 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.EndpointAddress, System.ServiceModel
000006428019a3a0 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.Description.DispatcherBuilder+ListenUriInfo, System.ServiceModel
0000064280192f80 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.ServiceModel.Description.OperationDescription, System.ServiceModel
0000064280192a50 1 24 System.Collections.Generic.ObjectEqualityComparer`1System.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内のスタックの状態がでる。 この辺でなんとかならんか。

パフォーマンスカウンタ
virtual bytes
「プロセスが使用している仮想アドレス空間の現時点のバイト数」
http://support.microsoft.com/kb/268343/ja
プロセスがもってるアドレス空間
32bitOSならこれが2Gを超えたあたりでおちる
(boot.iniで3GBスイッチつかってなければ)

private bytes
特定のプロセスに排他的に割り当てられた実メモリのサイズ

working set
private bytes + 共有バイト

page file bytes
プロセスが使ってるページファイルサイズ

なので
virtual bytes > workingset > private bytes
virtual bytes > workingset + page file bytes

Memory:Pages/sec
ページをディスクに書いたり、読んだりした回数。
ハードページフォルトが起こった回数。

Pages Output/sec
ディスクに書いた回数

Pages Input/sec
ディスクから読み込んだ回数

ソフトページフォルトとハードページフォルト
http://d.hatena.ne.jp/NyaRuRu/20051022/p4
http://d.hatena.ne.jp/NyaRuRu/20051022/p5

ソフトページフォルト
ちょっと遠い??メモリに退避されてるだけで、ディスクに吐き出されてはいない。
でもワーキングセットからは減る。

ハードページフォルト
ディスクに退避してしまった。とても復帰が遅くなる。

re: .NET Memory Leak Case Study: The Event Handlers That Made The Memory Baloon
I know that the !dumpheap has a 窶都hort parameter that can be used within .foreach in the windbg. Is there any other command that can be used with in .foreach like !dumpheap?

Example

.foreach obj { !dh -type System.String -gen 2 -short } { !gcroot ${obj} }
re: .NET Memory Leak Case Study: The Event Handlers That Made The Memory Baloon
Many of the sos commands have a -short param. !dumpaspnetcache for example has a -short parameter.

Even if they dont you can often use the .foreach command like above by skipping tokens with /pS and /ps. I m writing a blog post on automating debugging with .foreach now but your example above is excellent usage of the .foreach command.
re: .NET Memory Leak Case Study: The Event Handlers That Made The Memory Balo

SOS デバッガ拡張 SOS.dll

SOS デバッガ拡張 SOS.dll を使用して内部の共通言語ランタイム CLR: Common Language Runtime 環境に関する情報を渡すことにより、WinDbg.exe デバッガおよび Visual Studio でマネージ プログラムをデバッグできます。