Windbg调试新手入门
此文仅记录本人初次使用Windbg调试IIS,方便本人或有需要的同学参考!
-------------------------------------------------------------------------
1. WinDbg下载和安装
Install Debugging Tools for Windows 32-bit Version
http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx
Install Debugging Tools for Windows 64-bit Versions
http://www.microsoft.com/whdc/devtools/debugging/install64bit.mspx
建议不要下载winsdk_web.exe在线安装,超慢无法忍受,可直接下载 Windows Driver Kit (WDK) ISO image安装包(600多M好大),安装后在安装目录打开Debuggers/windbg.exe。
2. 设置并下载Windbg下载符号,请按以下步骤在Windbg命令行输入指令(注意下面的.不能去掉):
1) .sympath srv*G:\WinDDK\7600.16385.1\Debuggers\Symbols*http://msdl.microsoft.com/download/symbols (* 注意前面的绝对路径必须存在,不存在自己建立)。
2) !sym noisy
3) .reload
3.抓取DUMP
推荐使用Procdump进行抓取(详细使用请猛击这里:好用的抓取dump的工具-ProcDump),也可以直接在任务管理器中右键IIS进程w3wp.exe,然后选择“创建转储文件”生成DUMP。
4.分析DUMP
因为需要解决的是高CPU的问题,思路是分析某个线程在进程启动后占用的cpu时间。所以需要取多个dump,看"高CPU时间段"内"占用cpu时间增长最多"的是哪个线程,最终得到的两个文件如下(分别在任务管理器中在不同的时间段取两次DUMP):
打开第一个DUMP,运行!runaway命令可以看到各线程的CPU占用总时间:
Thread Time
18:fdc 0 days 1:20:28.390
19:1370 0 days 1:16:36.359
21:538 0 days 1:08:28.765
22:698 0 days 1:07:55.968
20:1180 0 days 0:58:22.046
138:1284 0 days 0:56:53.890
136:f9c 0 days 0:49:38.609
9:1094 0 days 0:44:26.312
147:db8 0 days 0:25:16.234
149:6f4 0 days 0:22:00.687
148:c8c 0 days 0:20:29.156
13:1108 0 days 0:01:31.562
12:d24 0 days 0:01:27.593
14:5e8 0 days 0:01:26.203
11:ce0 0 days 0:01:06.703
打开第二个DUMP,运行!runaway命令可以看到各线程的CPU占用总时间:
Thread Time
18:fdc 0 days 1:21:09.125
19:1370 0 days 1:20:20.468
21:538 0 days 1:08:43.140
22:698 0 days 1:08:28.812
20:1180 0 days 1:03:01.078
138:1284 0 days 0:57:49.281
136:f9c 0 days 0:55:01.250
9:1094 0 days 0:44:50.781
146:db8 0 days 0:27:10.062
147:c8c 0 days 0:25:17.828
148:6f4 0 days 0:25:03.656
13:1108 0 days 0:01:32.328
将两个DUMP中相同编号的线程结果减一下,可以得出18线程在这段时间内增长得最快,也就是说cpu这段时间内都在完成18线程的事情,那它肯定就是高cpu的原因了。
为了查看18线程对应的托管堆栈,执行以下命令:
1) 加载sos扩展,输入.load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\SOS.dll(32位系统相应改)。
2) 运行~18 s 切换到18线程。
3) 运行!clrstack查看堆栈。
从代码可以看出是StripHTML方法有问题,该方法执行一个正则式剔除所有HTML(取自cnblogs),可见该方法效率十分低下!
剩下的事情就好办了,重写或弃用此方法!
查找内存占用过高的根源
执行 !eeheap -gc 查看托管堆的总信息
0:004> !eeheap -gc Number of GC Heaps: 1 generation 0 starts at 0x0000000002a26220 generation 1 starts at 0x0000000002a21cf0 generation 2 starts at 0x0000000002a11000 ephemeral segment allocation context: none segment begin allocated size 0000000002a10000 0000000002a11000 0000000002af2238 0xe1238(922168) Large object heap starts at 0x0000000012a11000 segment begin allocated size 0000000012a10000 0000000012a11000 0000000018e171e0 0x64061e0(104882656) Total Size: Size: 0x64e7418 (105804824) bytes. ------------------------------ GC Heap Size: Size: 0x64e7418 (105804824) bytes.
看来大对象堆占用最多内存
执行 !dumpheap -min 200 -stat 获取占用堆内存的各对象的统计信息
0:004> !dumpheap -min 200 -stat total 0 objects Statistics: MT Count TotalSize Class Name 000007fef1369750 1 216 System.AppDomain 000007fef13745d0 2 432 System.Globalization.NumberFormatInfo 000007fef1373148 1 432 System.Collections.Generic.Dictionary`2+Entry[[System.Type, mscorlib],[System.Security.Policy.EvidenceTypeDescriptor, mscorlib]][] 000007fef136b7b0 2 1056 System.Globalization.CultureData 000007fef136c7e8 2 1128 System.Int32[] 000007fef136b328 3 3256 System.Char[] 000007fef136d110 1 4752 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.String, mscorlib]][] 0000000000664be0 22 31784 Free 000007fef136ae78 5 34072 System.Object[] 000007fef1370bc0 11 104858384 System.Byte[] Total 50 objects
Byte数组类型占用最多内存
执行 !dumpheap -type Byte[] -min 200 看各Byte数组占用堆内存的详细信息
0:004> !dumpheap -type Byte[] -min 200 Address MT Size 0000000002a25290 000007fef1370bc0 544 0000000012a17048 000007fef1370bc0 10485784 0000000013417060 000007fef1370bc0 10485784 0000000013e17078 000007fef1370bc0 10485784 00000000148170a8 000007fef1370bc0 10485784 00000000152170d8 000007fef1370bc0 10485784 0000000015c17108 000007fef1370bc0 10485784 0000000016617138 000007fef1370bc0 10485784 0000000017017168 000007fef1370bc0 10485784 0000000017a17198 000007fef1370bc0 10485784 00000000184171c8 000007fef1370bc0 10485784 total 0 objects Statistics: MT Count TotalSize Class Name 000007fef1370bc0 11 104858384 System.Byte[] Total 11 objects
随便挑一个大的对象出来
执行 !gcroot <Byte数组对象地址> 看对象引用关系
0:004> !gcroot 0000000012a17048 Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info. Scan Thread 0 OSTHread a54 RSP:54e958:Root: 0000000002a21e60(System.Threading.ThreadStart)-> 0000000002a21cb0(ConsoleApplication1.Program)-> 0000000002a21cc8(System.Collections.Generic.List`1[[System.Byte[], mscorlib]])-> 0000000002a21dc0(System.Byte[][])-> 0000000012a17048(System.Byte[])
跟踪到Program类的List类型成员了
执行 !do <Program对象地址> 查看对象的详细信息
0:004> !do 0000000002a21cb0 Name: ConsoleApplication1.Program MethodTable: 000007ff00044140 EEClass: 000007ff00152350 Size: 24(0x18) bytes File: C:\Users\Administrator\Desktop\ConsoleApplication1\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 000007ff00032600 4000001 8 ...yte[], mscorlib]] 0 instance 0000000002a21cc8 _list
就是_list了。有源代码文件的话这一步其实就可以略过,就算没有源代码文件也可以利用!dumpdomain与!SaveModule命令导出模块,然后使用Reflector进行反编译。
====================== Windbg的其它指令参考 ======================
!threadpool,查看线程池CPU使用量
!runaway,查看线程占用CPU时间,可以从中找到哪个线程占用时间更高。
~number s,number为具体哪个线程的ID。
!clrstack,到具体某个线程后,查看当前线程的托管代码
!name2ee ,找到哪个托管代码模块后,查看MethodTable,EEClass等信息。
!dumpmt,找到相关MethodTable处的相关信息。
!dumpmd,根据MethodDesc找到相关模块信息,比如MethodTable.
!dumpdomain,显示所有域里的程序集,或者根据参数获取指定域。
!savemodule,根据具体程序集地址,把当前程序集的代码生成到指定文件
查看占用内存过高的命令比如:
!eeheap,查看堆中信息,可以查看到大对象。
!dumpheap,查看堆中信息,一般带-min,-stat,-type等参数。
!gcroot,根据堆地址,查看相关模块引用代码信息。
lmvm clr 查看CLR调试版本
其他命令当然还非常多,也非常有用,需要的时候再翻资料,如果需要很精通windbg+sos,还是老老实实仔细看吧。
本文参考以下文章:
http://www.cnblogs.com/h-hello/articles/1502493.html
http://www.cnblogs.com/Jesses/archive/2011/05/03/2035560.html
http://www.cnblogs.com/Lawson/archive/2011/01/23/1942692.html