WinDbg + SOS/SOSEXで.Netアプリのメモリダンプを解析

WinDbgを使って.NETアプリをメモリダンプ解析することは滅多に無いのですが（コールスタックでメソッド名を見ればピンとひらめいて原因特定することが多いので、大抵はVisual Studioで事足りてます）、たまに使うたびに基本的なコマンドを忘れてしまい毎回ググってることに気づいたので、自分がよく使うWinDbg＋sos/sosexのコマンドを個人メモとして記録しておきます。随時追加予定。

#注意点
デバッグターゲットが対象プラットフォームに「`Any CPU`」を指定、かつ「`32 ビットを選ぶ`」を有効にしてビルドしたプログラムである場合、実行ホストが64ビットOSであっても32ビットプロセスとして実行可能であれば32ビットプロセスとして実行されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/773de37f-edae-13ca-91d6-ef3f86de71f3.png)

32ビットプロセスとして実行された場合、プロセスのダンプファイルは32bit版のタスクマネージャー（`%windir%\SysWOW64\taskmgr.exe`）から作成してください。また、WinDbgはWinDbg(x86)を、`sosex.dll`は32bit版を使用して下さい。

ダンプファイルは、タスクマネージャーの[詳細]タブからターゲットプロセスを選択して右クリックメニューから「ダンプファイルの作成」を選択して作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/51871/7ad4af7f-623e-b211-2b73-052235c125f3.png)

# sosex.dllの入手
事前に、[STEVE'S TECHSPOT](http://www.stevestechspot.com/)から`sosex.dll`を入手しておきます。ローカルの任意の場所に格納しておきます（本記事では`C:\local\sosex_32\sosex.dll`に格納）。

# sos.dll、sosex.dllのロード

WinDbgを起動後、ダンプファイルをWinDbgにドラッグ&ドロップしたのち、以下の一連のコマンドを実行します。

```
0:000> .loadby sos clr
0:000> .load C:\local\sosex_32\sosex.dll
0:000> !chain
```

`!chain`の実行結果に`sosex.dll`のパスが含まれていればOKです。

```
0:000> !chain
Extension DLL chain:
    C:\local\sosex_32\sosex.dll: image 4.6.0.0, API 1.0.0, built Mon Mar 14 23:10:18 2016
        [path: C:\local\sosex_32\sosex.dll]
    C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos: image 4.8.4400.0, API 1.0.0, built Fri Jun  4 01:26:38 2021
        [path: C:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.dll]
```
# 画面のクリア
`.cls`を実行します。

# インスタンスのメンバ変数の値を知る

まず、インスタンスのクラス名を指定して、`!DumpHeap -type クラス名`を実行します。

```:インスタンスのメモリアドレスの取得
0:000> !DumpHeap -type CSSandbox.SampleClass
 Address       MT     Size
01bb24b4 01975f10       20     

Statistics:
      MT    Count    TotalSize Class Name
01975f10        1           20 CSSandbox.SampleClass
Total 1 objects

```

`Address`フィールドに表示されたアドレス`01bb24b4`を指定して、`!DumpObj /d アドレス`を実行します。

```:インスタンスのメンバ変数の情報の表示
0:000> !DumpObj /d 01bb24b4
Name:        CSSandbox.SampleClass
MethodTable: 01975f10
EEClass:     01971644
Size:        20(0x14) bytes
File:        C:\Users\yz2cm\source\repos\CSSandbox\CSSandbox\bin\Release\CSSandbox.exe
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
7299ff7c  400000a        4 ...eading.Tasks.Task  0 instance 01bb82a0 t
01975f80  400000b        8 ...viceSandboxWorker  0 instance 01bb24c8 worker
7295878c  400000c        c       System.Boolean  1 instance        0 sampleFlag_1
7295878c  400000d        d       System.Boolean  1 instance        1 sampleFlag_2
72952734  4000008        4        System.Object  0   static 01bb823c SampleLock_1
72952734  4000009        8        System.Object  0   static 01bb8248 SampleLock_2
7295878c  400000e       25       System.Boolean  1   static        0 static_sampleFlag_1
7295878c  400000f       26       System.Boolean  1   static        1 static_sampleFlag_2
```

`dd アドレス`で、マネージドヒープをダンプできます。先頭にメソッドテーブルへのポインタの値`0x01975f10`が入るため、`0x04`バイトのオフセットを取っています。

```:マネージドヒープ
0:000> dd 0x01bb24b4+0x04
01bb24b8  01bb82a0 01bb24c8 00000100 00000000
01bb24c8  01975f80 00000000 80000000 729524e4
01bb24d8  00000008 00650053 00760072 00630069
01bb24e8  00310065 00000000 00000000 71d72720
01bb24f8  00000000 00000000 00000000 01bb2514
01bb2508  00000000 00000000 00000000 72952734
01bb2518  00000000 00000000 72952c60 00000008
01bb2528  00650053 00760072 00630069 00310065
```

# デッドロックを検出する

`!dlk`コマンドはデッドロックを検出します。

```:デッドロックの検出
0:000> !dlk
Examining SyncBlocks...
Scanning for ReaderWriterLock(Slim) instances...
Scanning for holders of ReaderWriterLock locks...
Scanning for holders of ReaderWriterLockSlim locks...
Examining CriticalSections...
Scanning for threads waiting on SyncBlocks...
*** WARNING: Unable to verify checksum for DeadLockSample.exe
Scanning for threads waiting on ReaderWriterLock locks...
Scanning for threads waiting on ReaderWriterLocksSlim locks...
Scanning for threads waiting on CriticalSections...
*** WARNING: Unable to verify checksum for System.ServiceProcess.ni.dll
*DEADLOCK DETECTED*
CLR thread 0x6 holds the lock on SyncBlock 01315580 OBJ:02018248[System.Object]
...and is waiting for the lock on SyncBlock 0131554c OBJ:0201823c[System.Object]
CLR thread 0x5 holds the lock on SyncBlock 0131554c OBJ:0201823c[System.Object]
...and is waiting for the lock on SyncBlock 01315580 OBJ:02018248[System.Object]
CLR Thread 0x6 is waiting at DeadLockSample.DeadlockSampleClass.RunWorker()(+0x2f IL,+0x7c Native) [C:\Users\yz2cm\source\repos\DeadLockSample\DeadLockSample\DeadlockSampleClass.cs @ 18,21]
CLR Thread 0x5 is waiting at DeadLockSample.DeadLockSampleClass.RunForever()(+0x4b IL,+0xb7 Native) [C:\Users\yz2cm\source\repos\CSharpSandboxDotNet45\DeadLockSample\DeadLockSampleClass.cs @ 32,21]

1 deadlock detected.
```

# スレッド一覧を表示する

```
0:000> !threads
ThreadCount:      8
UnstartedThread:  0
BackgroundThread: 4
PendingThread:    0
DeadThread:       3
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 555c 012ec5c8     2a020 Preemptive  020127BC:00000000 012e6748 0     MTA 
   2    2 44c8 012fa1a8     2b220 Preemptive  00000000:00000000 012e6748 0     MTA (Finalizer) 
XXXX    3    0 01324ea0     30820 Preemptive  00000000:00000000 012e6748 0     Ukn 
XXXX    4    0 01327090   1039820 Preemptive  00000000:00000000 012e6748 0     Ukn (Threadpool Worker) 
   3    5 54c0 01329ec0   3029220 Preemptive  02019538:00000000 012e6748 1     MTA (Threadpool Worker) 
   7    6 4204 01340bf8   3029220 Preemptive  0201C318:00000000 012e6748 1     MTA (Threadpool Worker) 
XXXX    7    0 013443d0   1039820 Preemptive  00000000:00000000 012e6748 0     Ukn (Threadpool Worker) 
   8    8 56a0 01345f08   102a220 Preemptive  00000000:00000000 012e6748 0     MTA (Threadpool Worker)
```

# スレッドの切り替えとスタックの表示
`!Threads`を実行して、調査対象のスレッドの番号を調べます。スレッドの番号は、`ID`列の値ではなく、`ID`列左にある一列目の値です。

```
0:000> !Threads
ThreadCount:      9
UnstartedThread:  0
BackgroundThread: 7
PendingThread:    0
DeadThread:       1
Hosted Runtime:   no
                                                                         Lock  
       ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception
   0    1 2230 012fc098     2a020 Preemptive  01BB27BC:00000000 012f5fe8 0     MTA 
   5    2 4174 0130dbd0     2b220 Preemptive  00000000:00000000 012f5fe8 0     MTA (Finalizer) 
XXXX    3    0 01337f28     30820 Preemptive  00000000:00000000 012f5fe8 0     Ukn 
   6    4 31b0 0133a118   1029220 Preemptive  01BC6418:00000000 012f5fe8 0     MTA (Threadpool Worker) 
   7    5 4a68 0133d218   3029220 Preemptive  01BB9538:00000000 012f5fe8 1     MTA (Threadpool Worker) 
  11    6 430c 01354728   3029220 Preemptive  01BBC318:00000000 012f5fe8 1     MTA (Threadpool Worker) 
  12    7 3c0c 013584c0   1029220 Preemptive  01BC0270:00000000 012f5fe8 0     MTA (Threadpool Worker) 
  13    8 4f4c 0135d348   102a220 Preemptive  00000000:00000000 012f5fe8 0     MTA (Threadpool Worker) 
  14    9 3d38 01337858   1029220 Preemptive  00000000:00000000 012f5fe8 0     MTA (Threadpool Worker) 
```

`~スレッドの番号s`を実行して、当該スレッドに切り替えます。

```
0:005> ~7s
eax=00000000 ebx=00000001 ecx=00000000 edx=00000000 esi=00000001 edi=00000001
eip=76f02f8c esp=044fef88 ebp=044ff118 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
ntdll!NtWaitForMultipleObjects+0xc:
76f02f8c c21400          ret     14h
```

`!ClrStack`を実行して、コールスタックを表示します。

```
0:007> !ClrStack
OS Thread Id: 0x4a68 (7)
Child SP       IP Call Site
044ff2e0 76f02f8c [GCFrame: 044ff2e0] 
044ff3c0 76f02f8c [GCFrame: 044ff3c0] 
044ff3dc 76f02f8c [HelperMethodFrame_1OBJ: 044ff3dc] System.Threading.Monitor.ReliableEnter(System.Object, Boolean ByRef)
044ff458 019c0b0f ServiceSandbox.ServiceSandboxCore.RunForever_SyncContextNull() [C:\Users\yz2cm\source\repos\CSharpSandboxDotNet45\ServiceSandbox\ServiceSandboxCore.cs @ 32]
044ff490 72d9d4bb System.Threading.Tasks.Task.InnerInvoke() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2884]
044ff49c 72d9b731 System.Threading.Tasks.Task.Execute() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2498]
044ff4c0 72d9b6fc System.Threading.Tasks.Task.ExecutionContextCallback(System.Object) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2861]
044ff4c4 72d38604 System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 980]
044ff530 72d38537 System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\executioncontext.cs @ 928]
044ff544 72d9b4b2 System.Threading.Tasks.Task.ExecuteWithThreadLocal(System.Threading.Tasks.Task ByRef) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2827]
044ff5a8 72d9b357 System.Threading.Tasks.Task.ExecuteEntry(Boolean) [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2767]
044ff5b8 72d9b29d System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() [f:\dd\ndp\clr\src\BCL\system\threading\Tasks\Task.cs @ 2704]
044ff5bc 72d0eb7d System.Threading.ThreadPoolWorkQueue.Dispatch() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 820]
044ff60c 72d0e9db System.Threading._ThreadPoolWaitCallback.PerformWaitCallback() [f:\dd\ndp\clr\src\BCL\system\threading\threadpool.cs @ 1161]
044ff82c 73ecf066 [DebuggerU2MCatchHandlerFrame: 044ff82c] 
```

# すべてのスレッドでコールスタックを表示する

`~*e!ClrStack`を実行します。

# スレッドの状態を表示する
`~スレッドの番号`を実行します。

```
0:011> ~7
   7  Id: 1840.4a68 Suspend: 0 Teb: 00d9a000 Unfrozen
      Start: clr!Thread::intermediateThreadProc (73f94be0)
      Priority: 0  Priority class: 32  Affinity: ff
```

`~*`で、すべてのスレッドの状態を表示します。

# 参考書籍・サイト
*  Sasha Goldshteinほか (2013)『[C#プログラマのための.NETアプリケーション最適化技法](https://www.amazon.co.jp/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9E%E3%81%AE%E3%81%9F%E3%82%81%E3%81%AE-NET%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E6%9C%80%E9%81%A9%E5%8C%96%E6%8A%80%E6%B3%95-Programmers-SELECTION-Sasha-Goldshtein/dp/4798131792/ref=tmm_other_meta_binding_swatch_0?_encoding=UTF8&qid=&sr=)』翔泳社
*  Mario Hewardt (2009)『[Advanced .NET Debugging](https://www.amazon.co.jp/Advanced-Debugging-Addison-Wesley-Microsoft-Technology/dp/0321578899/ref=tmm_pap_swatch_0?_encoding=UTF8&qid=&sr=)』Addison-Wesley Professional
* 「[メモリダンプから.NETのメモリ状態を探りたい](https://engineering.grani.jp/entry/2017/11/07/152107)」