概述:IIS 网站点击无响应时,通过抓取 w3wp 进程 Full Dump,使用 WinDbg 分析线程池状态和线程栈,快速定位第三方模块阻塞等根因。
一、分析前准备
1.1 确认 Dump 类型
!analyze -v第一条必跑命令,自动分析异常类型。
重点确认:
- 是否为
Full UserDump(只有 full dump 才能看完整堆栈) - 异常代码(
0xc0000005崩溃 /0xc0000135状态停留 / 无异常只是挂起) Not Full UserDump→ 换一个 full dump,否则栈信息不全
1.2 加载 Symbols
.sympath srv*https://msdl.microsoft.com/download/symbols
.sympath+ <你的私有符号路径>
.symfix .
.reload /f必加的私有符号:
- IIS 模块:
%WinDir%\System32\inetsrv - 你的 .NET 程序集 PDB
- 第三方 DLL PDB(排查问题时往往关键)
.reload /i w3wp.exe二、线程池状态(第一步要看的)
!threadpool典型输出解读
CPU utilization: 6%
Worker Thread: Total: 3 Running: 3 Idle: 0 MaxLimit: 4095 MinLimit: 2
Work Request in Queue: 1
AsyncTimerCallbackCompletion TimerInfo@00e46250
诊断要点:
| 指标 | 含义 | 提示 |
|---|---|---|
| CPU 高 + Running 多 | 代码死循环或锁竞争 | 查 !runaway |
| CPU 低 + 全 busy | 线程在等外部资源 | 重点查栈 |
| Work Request in Queue > 0 | 任务积压,无线程处理 | 根因信号 |
| Idle = 0 且全 busy | 线程池饱和 | 看谁卡住了 |
黄金法则:低 CPU + 线程全 busy = 阻塞(等 DB、远程调用、文件锁),不是计算密集。
三、线程栈分析
3.1 快速全景:!stacks 2
!stacks 2高亮锁持有情况的彩色线程视图,能一眼看出死锁。
如果报错
Unable to get value of PsActiveProcessHead,说明内核符号未加载,跳过此步,用~* kb手动查。
3.2 完整栈:~* kb 2000
~* kb 2000Dump 所有线程的 Native 调用栈,每个线程逐一分析。
常用线程切换命令:
~0s ; 切到主线程
~Ns ; 切到第 N 个线程
~* e !clrstack ; 所有线程跑一遍托管栈3.3 托管栈:!clrstack
~<thread-id>s
!clrstack
kb 500对可疑线程同时跑 Native 栈(kb)和托管栈(!clrstack)。
四、关键分析维度
4.1 GC 和托管堆(.NET 应用)
!dumpheap -stat ; 托管堆概览,看哪类对象最多
!eeheap -gc ; GC 堆大小,是否逼近上限
!dumpheap -type HttpContext ; 有多少请求对象堆积
!dumpobj <obj-address> ; 看单个对象详情常见问题特征:
- GC 堆逼近上限 → 内存泄漏
- 大量
HttpContext滞留 → 请求队列阻塞 - 数据库连接池耗尽,线程全挂等连接
4.2 同步锁分析
!locks ; 列出所有 CRITICAL_SECTION
!syncblk ; 托管对象同步块
!stacks 2彩色输出能直接看到哪个线程持有了哪个锁,一眼定位死锁。
4.3 句柄和资源泄漏
!heap -s ; 进程堆使用
!handle ; Handle 表4.4 特殊等待:CLRSemaphore
; 线程栈中出现这些调用表示在等线程池信号量
clr!CLRSemaphore::Wait
clr!ThreadpoolMgr::UnfairSemaphore::Wait
clr!ThreadpoolMgr::WorkerThreadStart信号量等待本身正常,但如果超时参数很大(00004e20 等),说明工作项没有正常入队。
五、常见无响应根因速查表
| 症状 | 关键命令 | 常见原因 |
|---|---|---|
| CPU 100%,线程全 busy | ~*kb / !runaway | 代码死循环、同步锁竞争 |
| CPU 低,线程全等外部资源 | !locks / !stacks 2 | 等待 DB 连接、远程 API、文件锁 |
大量 HttpContext 对象 | !dumpheap -type HttpContext | 请求队列满,并发阻塞 |
| GC 频繁,堆逼近上限 | !eeheap -gc | 内存泄漏、大对象未释放 |
| Handle 持续增长 | !handle | 句柄泄漏(文件/进程/Event) |
| 第三方 DLL 线程卡住 | ~* kb + 模块名 | 该 DLL 初始化/回调阻塞 |
六、实战案例:第三方 DLL 导致的网站无响应
6.1 现象
!threadpool:CPU 6%,3 个 Worker 全 busy,1 个任务积压~* kb 2000:多个线程卡在第三方模块的Initialize函数
6.2 栈关键帧
Thread 27:
第三方模块!Initialize+0x4e614 ← 卡在此处
→ kernel32!BaseThreadInitThunk
→ ntdll!_RtlUserThreadStart
Thread 28:
第三方模块!Initialize+0x5af42
→ rpcrt4!RpcServerListen
→ rpcrt4!RPC_SERVER::WaitForStopServerListening
→ kernel32!BaseThreadInitThunk
→ ntdll!_RtlUserThreadStart
6.3 结论
- 多个线程在该模块的初始化函数里阻塞
- 该 DLL 不在 System32,可能是自研或第三方注入模块
- 初始化时等待外部资源(配置/远程服务),超时导致永久阻塞
6.4 下一步排查
; 确认 DLL 路径、版本、签名
lmv m <模块名>
!lmi <模块名>
; 定位具体卡住代码行
~<tid>s
u <模块名>!Initialize+<offset> L 20
; 确认属于哪个 AppPool
!iis
!apppool七、快速启动命令流
!analyze -v
!threadpool
!runaway
!stacks 2 ; 可能报错,跳过
~* kb 2000 ; 找可疑线程
~<tid>s; !clrstack; kb
!dumpheap -stat
!locks
八、相关笔记
本文档由 M-Claw 🐙 整理自 2026-05-06 实操案例 · 2026-05-07 归档至 wiki/调试技术