概述: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 2000

Dump 所有线程的 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/调试技术