WinDbg不能直接标出“此处发生死锁”,但能通过!syncblk、!threads、~*e !clrstack等命令精准定位阻塞线程、锁持有者及同步原语状态,需结合堆栈与对象引用交叉分析。

WinDbg 能否直接分析 .NET 死锁?
不能直接标出“此处发生死锁”,但能精准定位阻塞线程、锁持有者和同步原语状态。关键靠 !syncblk、!dlk(仅限 .NET Framework)和线程堆栈交叉比对。
注意:!dlk 在 .NET Core / .NET 5+ 中不可用,必须改用 !threads + ~*e !clrstack + 手动识别 Monitor.Enter、WaitHandle.WaitOne、lock 对应的 IL 或 JIT 后调用点。
抓取和加载正确的内存转储
死锁发生时进程仍在运行但无响应,推荐用 procdump -ma -e 1 -w 捕获;若已挂起,用任务管理器“创建转储文件”或 dotnet-dump collect -p (.NET Core+)。
加载转储后,先确认符号路径和 .NET 运行时版本:
.symfix .sympath+ C:\symbols .loadby sos clr // .NET Framework .loadby sos coreclr // .NET Core / .NET 5+ !peb
常见错误:符号未加载导致 !clrstack 显示 UNKNOWN;SOS 加载错版本(如用 clr 加载 .NET 6 进程)会报 Failed to load data access DLL。
定位阻塞线程和锁竞争点
执行 ~*e !clrstack 查看所有托管线程调用栈,重点关注含以下模式的线程:
Easily find JSON paths within JSON objects using our intuitive Json Path Finder
-
System.Threading.Monitor.Enter或Object.Wait卡在ntdll!NtWaitForMultipleObjects或coreclr!WaitForMultipleObjectsEx -
System.Threading.WaitHandle.WaitOne后无后续调用 - 重复出现相同锁对象地址(如
0x000001a2f8c4d038)在多个线程栈中
再用 !syncblk 查锁状态:
!syncblk
输出中关注 Index、SyncBlock、Thread 和 Object 列。若某 Object 地址被多个线程列为 WAITING,而只有一个线程显示 OWNED 且其栈停在 Monitor.Enter 或类似位置,基本就是死锁源头。
.NET Core / .NET 5+ 下替代 !dlk 的实操技巧
没有 !dlk 不代表没法查——重点是快速筛选可疑线程并比对锁对象引用:
- 用
~*e !dumpstack -EE快速过滤只含托管调用的线程(排除纯 native 线程) - 对每个疑似阻塞线程,执行
!dso(dump stack objects),找最近的System.Object实例地址,记下它 - 用
!do看该对象是否为Monitor关联对象(类型通常为业务类,但可结合源码确认) - 用
!dumpheap -type列出所有同类锁实例,再用!gcroot看谁在引用它
真正耗时的不是命令本身,而是把三四个线程的 !clrstack + !dso + !do 结果横向对照——漏掉一个持有锁却没卡住的线程,就可能误判。







