65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WinDbg 从 EventHandler 获取方法名

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (6投票s)

2012年9月28日

CPOL

4分钟阅读

viewsIcon

29805

本文介绍如何使用WinDbg从EventHandler获取方法名称。

引言

如果您正在使用WinDbg进行托管调试,我相信您应该非常熟悉!gcroot命令输出中的这种引用关系。

07b3ebd4(System.EventHandler)->
0375891c(SomeWPFApp.MainWindow)->

SomeWPFApp中的对象MainWindow有很多事件,对象07b3ebd4(System.EventHandler)究竟处理了哪个确切的事件?

我将向您展示如何使用WinDbg来获得答案。

背景

我经常帮助客户使用WinDbg和SQL Server Profiler查找应用程序性能问题。最近,我研究了一些WPF和SilverLight性能问题。在大多数情况下,开发人员忘记从事件中取消引用EventHandler。因此,最重要的是找到哪个方法处理哪个事件?然后,我们可以简单地使用-=在某些方法(通常是Dispose方法)中删除此关系。

循序渐进

这是一个实时调试,而不是事后调试,这意味着我正在使用WinDbg附加我要研究的进程。

加载SOS

首先,这是一个.NET应用程序,所以我应该首先加载SOS扩展。对于不同的.NET Framework版本,我们必须使用不同的加载语法。

  • 对于.NET 1.0和.NET 1.1,只需键入.load clr10\sos.dll
  • 对于.NET 2.0,.NET 3.0和.NET 3.5,键入.loadby sos mscorwks.loadby sos mscorsvr
  • 对于.NET 4.0或更高版本,键入.loadby sos clr

那么,如何查找.NET Framework版本呢?我喜欢使用lmvm命令。

0:016> lmvm mscorwks
start    end        module name

哦,什么也没有。也许我试试其他的DLL。

0:016> lmvm clr
start    end        module name
79140000 797ae000   clr        (deferred)             
    Image path: C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\clr.dll
    Image name: clr.dll
----------------------Skip--------------------------------

好的,这是一个.NET 4.0应用程序。因此,我将通过相应的.NET Framework路径加载SOS。

0:016> .loadby sos clr 

检索排在Finalizable队列中的对象

如果您不使用本机资源(例如Win32 API调用或本机对象的托管包装器(DirectoryEntryFileStream、GDI+对象等)),最好**不要**实现“~ClassName”方法。

即使您创建了一个空析构函数方法(在.NET世界中名为Finalize),CLR也会以不同的方式管理此类对象的生命周期。

好的,让我们回到这篇文章。在这个演示中,我将从终结队列中找到一些东西。

0:016> !finalizequeue
SyncBlocks to be cleaned up: 0
MTA Interfaces to be released: 0
STA Interfaces to be released: 0
----------------------------------
generation 0 has 260 finalizable objects (0bfe14f4->0bfe1904)
generation 1 has 15 finalizable objects (0bfe14b8->0bfe14f4)
generation 2 has 6442 finalizable objects (0bfdb010->0bfe14b8)
Ready for finalization 0 objects (0bfe1904->0bfe1904)
Statistics for all finalizable objects (including all objects ready for finalization): 
--------------SKIP---------------- 
79b99fbc      362353        5797648 System.WeakReference
--------------SKIP----------------

我感兴趣的是System.WeakReference对象。到目前为止,内存中有362353个这样的对象,这太疯狂了!

为什么会有这么多WeakReference对象?为什么GC不释放它们?

对于问题1,通常是分配太多而释放太少。对于问题2,通常某些东西引用了这个对象,而这些“东西”又引用了ROOT对象,例如CPU寄存器、静态变量。

让我们从托管堆转储对象。

0:017> !dumpheap -mt 79b99fbc

 Address       MT     Size
----------Skip many objects----------------
04410400 79b99fbc       16     
04410454 79b99fbc       16     
044104a8 79b99fbc       16     
044104fc 79b99fbc       16     
04410550 79b99fbc       16     
044105c4 79b99fbc       16     
04410618 79b99fbc       16     
----------Skip many objects---------------- 

然后随机选择一个对象,我在这里选择了“044104fc”。

这个对象的引用关系是什么?

我们可以使用SOS扩展命令“!gcroot”找到引用路径。注意,有时命令不会向您显示任何内容。

0:000> !gcroot 044104fc 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 0 OSTHread 888
---------Skip too many root information-----------------------
ESP:12e9dc:Root:  0dbc9030(System.Windows.Threading.DispatcherOperation)->
  0dbc9010(System.Windows.Threading.DispatcherOperationCallback)->
  07b3eb98(System.Windows.Threading.DispatcherTimer)->
  07b3ebd4(System.EventHandler)->
  0375891c(SomeWPFApp.MainWindow)->
  079e7a94(SomeWPFApp.PermissionListBox)->
  07987f54(System.Windows.Controls.ControlTemplate)->
  079884cc(System.Windows.TemplateContent)->
  03700cf0(System.Windows.Baml2006.Baml2006SchemaContext)->
  03700ea4(System.Windows.Baml2006.WpfSharedBamlSchemaContext)->
  04409188(System.Collections.Concurrent.ConcurrentDictionary`2[[System.Reflection.Assembly, mscorlib],[System.Xaml.MS.Impl.XmlNsInfo, System.Xaml]])->
  04411314(System.Object[])->
  04411650(System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Reflection.Assembly, mscorlib],[System.Xaml.MS.Impl.XmlNsInfo, System.Xaml]])->
  04411578(System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Reflection.Assembly, mscorlib],[System.Xaml.MS.Impl.XmlNsInfo, System.Xaml]])->
  04411530(System.Collections.Concurrent.ConcurrentDictionary`2+Node[[System.Reflection.Assembly, mscorlib],[System.Xaml.MS.Impl.XmlNsInfo, System.Xaml]])->
  044104d0(System.Xaml.MS.Impl.XmlNsInfo)->
  044104fc(System.WeakReference)
---------Skip too many root information-----------------------
Command cancelled at the user's request. 

好的,从输出底部,我们找到了我们的幸运儿044104fc。此对象由一个XmlNsInfo持有,而该XmlNsInfo由一个泛型字典持有,依此类推。然后,一个SomeWPFApp.MainWindow实例由一个System.EventHandler持有。

为什么EventHandler会引用MainWindowEventHandler是什么?MainWindow中处理此事件的确切方法名称是什么?

转储方法名称

关键点是EventHandler对象。

0:000> !do 07b3ebd4
Name:        System.EventHandler
MethodTable: 79b92a30
EEClass:     79882d28
Size:        32(0x20) bytes
File:        C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
      MT    Field   Offset                 Type VT     Attr    Value Name
79b9f744  4000076        4        System.Object  0 instance 0375891c _target
79b9f744  4000077        8        System.Object  0 instance 00000000 _methodBase
79b9ab88  4000078        c        System.IntPtr  1 instance  7454518 _methodPtr
79b9ab88  4000079       10        System.IntPtr  1 instance        0 _methodPtrAux
79b9f744  400007a       14        System.Object  0 instance 00000000 _invocationList
79b9ab88  400007b       18        System.IntPtr  1 instance        0 _invocationCount 

事实上,第一个粗体目标地址是0375891c;如果您向上滚动,您会发现这是MainWindow对象。

第二个_methodPtr会告诉我们一些信息。这是一个IntPtr,指向某物的指针。地址是7454518。通常,这指向方法描述符。所以让我们使用SOS命令转储信息。

0:000> !ip2md 7454518 
Failed to request MethodData, not in JIT code range  

这是什么?这意味着该方法**可能尚未**进行JIT编译。或者,这是指向实际方法的入口点。让我们检查反汇编代码。

0:000> !u 7454518 
Unmanaged code
07454518 e93336a703      jmp     0aec7b50
0745451d 5f              pop     edi
0745451e 0000            add     byte ptr [eax],al
07454520 c45dc6          les     ebx,fword ptr [ebp-3Ah]
07454523 0500000000      add     eax,0
07454528 e8bbddce71      call    clr!PrecodeFixupThunk (791422e8)
0745452d 5e              pop     esi
0745452e 0000            add     byte ptr [eax],al
07454530 58              pop     eax
07454531 5d              pop     ebp 

第一行是jmp,它跳转到地址0aec7b50。实际上,这个地址指向一个真实的托管方法。

我们可以使用!ip2md转储描述符信息。

0:000> !ip2md 0aec7b50
MethodDesc:   05c65dc4
Method Name:  SomeWPFApp.MainWindow.dispatcherTimer_Tick(System.Object, System.EventArgs)
Class:        06117abc
MethodTable:  05c6689c
mdToken:      06000054
Module:       03572ea4
IsJitted:     yes
CodeAddr:     0aec7b50
Transparency: Critical
*** WARNING: Unable to verify checksum for C:\SomeFolder\SomeWPFApp.
Source file:  E:\work\SomeFolder\SomeWPFApp\MainWindow.xaml. @ 1300 

好了,我们现在得到了方法名称。在MainWindow类中,我们有一个名为dispatcherTimer_Tick的方法,此方法处理DispatcherTimer对象的Tick事件。开发人员应该在代码中的某些地方(例如Closing事件或Dispose方法)使用“-=”来删除此事件处理程序。然后,当GC开始工作时,这些对象就可以被回收了。

注意,您并非总是可以通过此方法找到方法描述符。如果代码尚未进行JIT编译,则此方法无效。您应该使用dd <address>检查托管堆,并尝试使用!dumpmd <3rd dd address>使用第三个双字值。您可以参考Mark撰写的这篇文章,http://julmar.com/blog/mark/?p=79

© . All rights reserved.