从头开始使用WinDbg 第二部分
从头开始使用WinDbg 第二部分
这是前面富有想像力的名字为“从头开始使用WinDbg 第一部分”的内容的继续,我已经假设你已经读过了它,因此,如果你还没有阅读过,我强烈建议你先读一下。我们会继续使用前面的dump例子,我非常乐意从上次我们停下来的地方继续前进。
更多有用的命令
上一次我们使用了一些来自SOS扩展的非常好的命令来查看运行堆栈调用、请求、cpu中运行着的进程等等。我们也向更深处挖掘了一些信息,我们会继续使用这些命令,还会有一些其他命令。
!dumpstackobjects (!dso)
假设我们正在查看一个特殊的进程,我们想要知道被当前栈引用的所有托管对象。我们有什么方法可以这么做呢? 非常肯定我们有这样一个命令:!dumpstackobjects ,简称:!dso。
我们运行这个命令,我们就会看到当前进程的当前调用堆栈的所有对象的列表。输出会像下面这样:
因为篇幅关系仅列了一部份。
0:050> !dso OS Thread Id: 0x1e ESP/REG Object Name System.DirectoryServices.Protocols.SEC_WINNT_AUTH_IDENTITY_EX System.DirectoryServices.Protocols.SEC_WINNT_AUTH_IDENTITY_EX System.DirectoryServices.Protocols.SEC_WINNT_AUTH_IDENTITY_EX |
当我们想要看被当前进程单独引用的所有对象的时候,这个命令非常的有用。如果你想要就某一个对象,你仅需要把地址拷贝下来,然后用 !dumpoubect 命令来查看。
0:050> !do 271fdfe0 Name: System.DirectoryServices.Protocols.LdapConnection MethodTable: EEClass: 149daf08 Size: 56(0x38) bytes (C:\WINDOWS\assembly\GAC_MSIL\System.DirectoryServices.Protocols\ Fields: MT Field Offset 1202af88 1466fe50 0fb896d8 4000238 28 System.IntPtr 0 instance 564180944 ldapHandle 0fd314bc >> Domain:Value 0019daf0:NotInit 11b42540:073fe504 << >> Domain:Value 0019daf0:NotInit 11b42540:073fe 0fd314bc >> Domain:Value 0019daf0:NotInit 11b42540:073fe610 << >> Domain:Value 0019daf0:NotInit 11b42540:073fe678 << 12305e94 4000241 30 ....ManualResetEvent 0 shared static waitHandle >> Domain:Value 0019daf0:NotInit 11b42540:073fe >> Domain:Value 0019daf0:NotInit 11b42540:073fe |
!dumparray (!da)
你可能已经注意到,我们有再栈上一双对象数组。查找 System.Object[] 类型,你会看到它们。 如果你对这个数组来执行 !dumpobject 命令,你只能看到数组本身的一些信息,而不是数组的内容。必须使用 !dumparray 简称 !da 命令来查看才可以。
0:050> !do 27239b98 Name: System.Object[] MethodTable: EEClass: Size: 24(0x18) bytes Array: Rank 1, Number of elements 2, Type CLASS Element Type: System.String Fields: None 0:050> !da 27239b98 Name: System.String[] MethodTable: EEClass: Size: 24(0x18) bytes Array: Rank 1, Number of elements 2, Type CLASS Element Methodtable: [0] [1] |
你可以看到 !dumparray 命令给了我们多一点的信息。该数组包含了两个System.String 的元素,并且有它们的地址。所以我们可以使用 !dumpobject 来查看这些String对象。
!objsize
你看上面列出的数组对象的大小是24B,这里的大小仅仅是这个System.Object[] 这个对象自己的大小,仅代表这个结构的大小,并不包含它里面的内容的大小。这个System.Object[]对象包含两个String 对象,这些String 对象是独立的对象,它们有
要取得一个对象的大小我们使用如下命令:
0:050> !objsize 27239b98 sizeof(27239b98) = 348 ( 0x |
这个命令会遍历这个对象所引用的的儿子对象和孙子对象等等。这个数组的精确的尺寸是包含了它子代的对象,大小是348B。
如果里面有很多的子代对象,使用 !objsize 这个命令来计算对象的大小是很慢的。你还有一点也是必须知道的,这个命令不是和你想象的这么智能的。比如:你有一个自定义按钮控件,它引用了父对象aspx 页面对像,那么你会得到aspx 页面的大小和它里面子控件的大小。这样是错误的,你得到的 objsize会非常可笑得很大,你需要使用 !dumpobject 来手动的确认一下引用的对象。
!dumpheap
这是另外一个非常有用的命令。只用这个命令的时候你需要加一个参数,没有参数的输出会是整个堆上的所有对象。所以我一般都加上 –stat 这个参数。它自己内部会写很多信息,但最后会有一个统计信息,下面是一段截取的输出。
0:050> !dumpheap -stat ------------------------------ Heap 0 total 2754508 objects ------------------------------ Heap 1 total 2761329 objects ------------------------------ total 5515837 objects Statistics: MT Count TotalSize Class Name ……. 14ef4718 1 12 System.Net.HttpRequestCreator .. |
它是一个按照对象类型的大小来排列的,一般strings 都会排在最后面,因为它是用的最多的。另外比较有用的参数是 –type 和 –mt ,使用它们你能看到特别类型的对象。例如,你想看HttpRequestCreators这个对象,你可以使用如下方式:
0:050> !dumpheap -mt 14ef4718 ------------------------------ Heap 0 0342ccf8 14ef4718 12 total 1 objects ------------------------------ Heap 1 total 0 objects ------------------------------ total 1 objects Statistics: MT Count TotalSize Class Name 14ef4718 1 12 System.Net.HttpRequestCreator |
他会给我们这个对象的地址,你想要更深入的了解这个对象,就可以使用 !dumpobject 这个命令。
!dumpheap –type 的工作也非常的相似,不同的是你这次是用类名,它会进行子字符串匹配,所以,如果你输入:!dumpheap -type System.Web ,你就会得到类名中包含“System.Web”的所有的类。
另外几个比较有忧的参数是 –min 和 –max , 后面跟上一个代表对像大小的代表字节的数字。这个在你解决字符串问题时非常有用。另外,!dumpheap -stat -min 85000 会列出所有的在LOH上的对象。-min 含义是除了 比 85000更小的。
把工具拿来使用
我会是用前面介绍的这些命令来进行一些实际的演示。这个dump文件是从前面一个case中得到的。这个有问题的应用程序是运行在2个工作进程的web 园模式下。Session状态是被放在sqlServer中的。客户在测试性能的问题,对出现的问题的描述也是很模棱两可的。我有好几吨的dump要分析!因此,我大概的看看我能发现什么。一件事,我在乐意很早就开始做的是查看cache。根据客户所说,它们根本没有使用cache。但我认为通常最好再检查一下。
为了找出有多少数据被放在了cache中,我首先需要查找 System.Web.Caching.Cache 类。我运行 !dumpheap -stat -type System.Web.Caching.Cache ,注意我使用了 –stat 参数,否则我会得到很长的信息,包括 Caching.CacheKeys 和Caching.CacheEntrys等,下面是输出。
0:050> !dumpheap -type System.Web.Caching.Cache -stat ------------------------------ Heap 0 total 665 objects ------------------------------ Heap 1 total 1084 objects ------------------------------ total 1749 objects Statistics: MT Count TotalSize Class Name …… …….. 123063fc 832 16640 System.Web.Caching.CacheKey 12306820 732 52704 System.Web.Caching.CacheEntry Total 1749 objects |
我现在知道了 cache 对象,知道了Method Table ,我现在使用 !dumpobject 来列出这个对象
0:050> !dumpheap -mt ------------------------------ Heap 0 03392d20 total 1 objects ------------------------------ Heap 1 total 0 objects ------------------------------ total 1 objects Statistics: MT Count TotalSize Class Name Total 1 objects |
现在知道了地址,那我就可以运行 !objsize 来知道它到底有多少大,花费了一些时间,因为cache很复杂,而且还有很多子对象。结果出来了。
0:050> !objsize 03392d20 sizeof(03392d20) = 266640828 ( 0xfe49dbc) bytes (System.Web.Caching.Cache) |
看,cache 是 266MB ,这是非常多的,但事实中,客户说它们并没有使用cache。
那什么被缓存了呢?
为了抽取一些样本看看什么被缓存了,我看了看 CacheEntrys。我已经有了MethodTable,刚才在执行 !dumpheap -type System.Web.Caching.Cache –stat(前面)的时候已经有了这个MT,所以有下面的输出:
0:050> !dumpheap -mt 12306820 ------------------------------ Heap 0 033950bc 12306820 72 033da ....etc... 03e total 382 objects ------------------------------ total 732 objects |
另外一个可以使用的命令,有相同的输出的,我已经给出的,当然是:
!dumpheap -type System.Web.Caching.CacheEntry。
OK,让我们看看这个CacheEntrys,抽取一些样本,我执行了:
0:050> !do 03b Name: System.Web.Caching.CacheEntry MethodTable: 12306820 EEClass: Size: 72(0x48) bytes (C:\WINDOWS\assembly\GAC_32\System.Web\ Fields: MT Field Offset 0fb 0fd3da00 4001329 8 System.Int32 0 instance -1314181915 _hashCode 120219d0 4001331 120219d0 4001332 24 System.DateTime 1 instance 03b |
根据已经dump出来的结果,我最感兴趣的是 _value,因此我把地址拷贝下来,然后运行:
0:000> !do 03e Name: System.Web.SessionState.InProcSessionState MethodTable: 14dbad EEClass: 14e43af8 Size: 48(0x30) bytes (C:\WINDOWS\assembly\GAC_32\System.Web\ Fields: MT Field Offset 0fd3da00 4001d8b c System.Int32 0 instance 20 _timeout 120219d0 4001d8d 0fd3da00 4001d8e 10 System.Int32 0 instance 1 _lockCookie 1202bf60 4001d 0fd3da00 4001d90 14 System.Int32 0 instance 0 _flags |
这里我发现了很有趣的一个东西,被缓存的东西是一个InProcSessionState对象,这个东西以前可能不知道是放在缓存里的吧。有了这个对象,那么客户说的它们使用sqlServer来保存Session是不正确的。
结果是这样的:客户临时为了概要性地检测把它改成了In-process 模式,但是忘记把它改成SqlServer模式了。