Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Get method name from an eventhandler with WinDbg

4.86/5 (6 votes)
27 Sep 2012CPOL4 min read 29.2K  
This article describes how to get the method name from an EventHandler with WinDbg.

Introduction

If you're doing managed debugging with WinDbg, I believe you should be very familiar with such reference relationships from the !gcroot command output.

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

The object MainWindow in SomeWPFApp has many events, what exact method is handing what exact event obtained by the object 07b3ebd4(System.EventHandler)?

I'll show you how to use WinDbg to get the answer.

Background

I often help customers find their application performance issues with WinDbg and the SQL Server Profiler. Recently, I did some research on WPF and SilverLight performance issues. In most cases, the developer forgot to de-reference the EventHandler from an event. Thus the most important thing is to find which method is handling which event? Then, we can simply use -= to remove the relationship in some method, usually the Dispose method.

Step by step

This is a live debug instead of a post-mortem debugging, which means I'm using WinDbg to attach the process I want to research.

Load SOS

First, this is a .NET application, so I should load the SOS extensions first. For different .NET framework versions, we have to use different load grammars.

  • For .NET 1.0 and .NET 1.1, simply type .load clr10\sos.dll
  • For .NET 2.0, .NET 3.0, and .NET 3.5, type .loadby sos mscorwks or .loadby sos mscorsvr
  • For .NET 4.0 or higher, type .loadby sos clr

Then, how to find the .NET Framework version? I like using the lmvm command.

0:016> lmvm mscorwks
start    end        module name

Ooh, nothing. May I'll try the other 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--------------------------------

OK, this is a .NET 4.0 application. So, I'll load the SOS by the corresponding .NET Framework path.

0:016> .loadby sos clr 

Retrieve objects queued in the Finalizable queue

It's a best practice to not implement the "~ClassName" method if you do not use native resources, such as a Win32 API call or a managed wrapper for native objects (DirectoryEntry, FileStream, GDI+ objects, etc.).

Even if you create an empty destructor method (named Finalize in the .NET world), the CLR will manage such kinds of objects' life cycle in a different way.

OK, let's go back to this article. In this demo, I'll find something from the finalize queue.

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----------------

I'm interested in the System.WeakReference object. There're 362353 such objects in memory until now, it's crazy!

Why so much WeakReference objects? Why does GC not free them?

For question 1, usually allocates too much and frees too less. For question 2, usually something has the reference on this object, and such "something" has references to the ROOT object, such as the CPU register, static variables.

Let's dump the objects from the managed heap.

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---------------- 

And then randomly select an object, I selected "044104fc" here.

What's the reference relationship on this object?

We can find the reference path by using the SOS extension command "!gcroot". Note, some times the command will show nothing to you.

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. 

OK, from the output bottom, we find our lucky baby, 044104fc. This object is held by an XmlNsInfo, and such an XmlNsInfo is held by a generic dictionary, and so on. Then, a SomeWPFApp.MainWindow instance is held by a System.EventHandler.

Why did the EventHandler have a reference to MainWindow? What's the EventHandler? What's the exact method name in the MainWindow that is handling such an event?

Dump the method name

The key point is the EventHandler object.

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 

In fact, the first bold target address is 0375891c; if you scroll up, you'll find this is the MainWindow object.

The second _methodPtr will tell us something. This is an IntPtr, a pointer to something. The address is 7454518. Usually, this points to a Method Descriptor. So let's dump the information by a SOS command.

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

What's this? This means the method may not be JITted yet. Or, this is the entry point to a real method. Let's check the disassemble code.

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 

The first line is a jmp, it jumps to address 0aec7b50. In fact, this address is pointed to a real managed method.

We can use !ip2md to dump the descriptor information.

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 

OK, we get the method name now. In class MainWindow, we have a method named dispatcherTimer_Tick, this method handles the event Tick of a DispatcherTimer object. The developer should use "-=" to remove this event handler at some place in the code, such as the Closing event or Dispose method. Then when the GC begins work, these objects can be collected.

Notice, you'll not always find the method descriptor via this method. If the code is not JITted yet, this method doesn't work. You should check the managed heap by dd <address> and try the third double word value by using !dumpmd <3rd dd address>. You can reference this article written by Mark, http://julmar.com/blog/mark/?p=79.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)