Delegates in .NET are a very handy addition to the language. With the introduction of LINQ they did become mainstream and everyone is using them or is at least finding them cool. But what the CLR is really doing to them under the covers remains largely unexplored. This is ok as long as you never get any memory dumps from the field with angry customers waiting for a solution. Delegates are heavily used to e.g. send window messages from an arbitrary thread to the main UI thread. Or you have an thread scheduler (TPL anyone?) which does execute delegates from a queue on one or more threads. These things do all work with a custom data structure where besides scheduling information the delegate to be called is stored. When you want to examine a data structure which does contain delegate fields and you need to know to which method this delegate does point to things become less obvious if you do not have the VS debugger on a live process attached. If you dump the contents of a Func<string,bool> delegate you will see in Windbg something like this:
0 : 000 > !DumpObj / d 0272b8d8 Name
: System
.Func`2 [[System.String, mscorlib],
[
System.Boolean, mscorlib
]] MethodTable : 6e4edbac EEClass : 6e211748 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 6e52f744 4000076 4 System.Object 0 instance 0272b8d8 _target
->Target object to call to 6e52f744 4000077 8 System
.Object 0 instance 00000000 _methodBase –>
is null except if VS is attached 6e52ab88 4000078 c System
.IntPtr 1 instance 5b0a18 _methodPtr
->Trampoline code to dispatch the method 6e52ab88 4000079 10 System
.IntPtr 1 instance 40c068 _methodPtrAux –>
If not null and invocationCount ==
0 it does contain the MethodPtr of
a static method.6e52f744 400007a 14 System
.Object 0 instance 00000000 _invocationList –>
When it is an event there the list of delegates to call are
stored 6e52ab88 400007b 18 System
.IntPtr 1 instance 0 _invocationCount –>
Number of delegates stored in invocationList
We do know the type of the object where one of its methods is called by inspecting the _target field which gives us the instance on which non static methods are called. If the method is static _target does contain only the delegate instance (Func<string,bool>) which is of no help. When a delegate to a static method is declared the value which is normally stored in _methodPtr is stored in _methodPtrAux. This _methodPtrxxxx stuff does not point to data structures but trampolin code that does retrieve the target MethodDescriptor (not via reflection of course). I have found this stuff by creating a little sample app which defines some delegate instances and then goes to sleep to allow me to break into with Windbg quite easy.
using System;
using System.Linq;
using System.Threading;
using System.Runtime.InteropServices;
namespace ConsoleApplication10 {
class Program {
static void Main(string[] args) {
new Program().Start();
}
private void Start() {
Func<string, bool> func = Filter;
Func<string, bool> staticFunc = FilterStatic;
Thread.Sleep(5000);
func("");
}
static bool FilterStatic(string str) {
return true;
}
bool Filter(string str) {
throw new Exception();
}
The algorithm how the CLR does retrieve the MethodDescriptor when a delegate has been called is the same since .NET 2.0 up to .NET 4.5. There are even no differences between x86 and x64 (except for the pointer size of a MethodDescriptor). The algorithm can best be seen in x64 assembly code how it does retrieve the method descriptor. The _MethodPtr+5 is the base value in eax.
clr!PrecodeFixupThunk + 0x1 : 000007fe`edf113f1 4c0fb65002 movzx r10,
byte ptr[rax + 2] // load byte from eax+2
000007fe`edf113f6 4c0fb65801 movzx r11,
byte ptr[rax + 1] // load byte from eax+1
000007fe`edf113fb 4a8b44d003 mov rax,
qword ptr[rax + r10 * 8 + 3] // retrieve base MethodDescriptor
000007fe`edf11400 4e8d14d8 lea r10,
[rax + r11 * 8] // Add method slot offset to get the right one
From this disassembly we can now create a Windbg script to dump for any MethodPtr the actual method.
0:000> !DumpObj /d 01ca2328
Name: System.Func`2[[System.String, mscorlib],[System.Boolean, mscorlib]]
MethodTable: 5cf43c6c
EEClass: 5cb73718
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
5cef8194 400002d 4 System.Object 0 instance 01ca231c _target
5cef8194 400002e 8 System.Object 0 instance 00000000 _methodBase
5cefb8fc 400002f c System.IntPtr 1 instance 26c048 _methodPtr
5cefb8fc 4000030 10 System.IntPtr 1 instance 0 _methodPtrAux
5cef8194 4000031 14 System.Object 0 instance 00000000 _invocationList
5cefb8fc 4000032 18 System.IntPtr 1 instance 0 _invocationCount
0:000> $$>a< "c:\source\DelegateTest\Resolve.txt" 26c048
Method Name: ConsoleApplication10.Program.Filter(System.String)
Class: 002612fc
MethodTable: 00263784
mdToken: 06000005
Module: 00262e7c
IsJitted: yes
CodeAddr: 002c05f0
Transparency: Critical
The code for the Resolve.txt Windbg script is
r $t0 = ${$arg1}+5
r $t1 = $t0 + 8*by($t0+2) + 3
r $t2 = 8*by($t0+1)
r $t3 = poi($t1) + $t2
!DumpMD $t3
This does allow you to retrieve the called method from any delegate instance from .NET 2.0-4.5 regardless of its bitness. The offsets used in the script seem to point to IL structures which have the same layout across all platforms. For x86 code there is a even shorter shortcut. It seems that the two offsets are 0 normally which boils the whole calculation down to
!DumpMD poi(_methodPtr + 8)
to get the desired information. For x64 though you need the script to get the right infos back. I have tried to load a dump of the short demo application into VS11 to see if VS can resolve the target methods already (would be a fine thing) but nope you will not get much infos out of the dump if you stick to VS. Windbg still remains the (only) one tool of choice to do post mortem debugging with memory dumps. You can either tell your customers that you did not find the bug and you need to install VS on their machines or you take a deep breath and remove they layers of abstractions (C#, IL, Assembler, … Quantum Physics) one by one until you can solve your problem.
If you try this out and come back to me that this script does not work it could be that you did try to dump the _methodPtr from a multicast delegate which has an invocation count > 0. The _methodPtr does then contain the code to loop through the array and calls them one by one. All you need to do is to use the script on one of the delegates contained in the _invocationList array.
To make Windbg more user friendly you should type as first command always
.prefer_dml 1
to enable “hyperlink” support for many commands in WinDbg. With .NET 4.0 the sos.dll does also support this hyperlink syntax which makes it much easier to dump the contents of objects. Whenever you click on one of the blue underlined links with the mouse a useful command like dump object contents is executed.
When you have this enabled it becomes also easier to load the right sos.dll. Many of you know the trick to use the .loadby command to load the sos dll from the same location as the clr.dll (.NET 4.0) or mscorwks (pre .NET 4.0). For some reasons this loadby trick does not work in all cases. Another nearly as fast solution is to use the lm command to display the loaded modules. When you then click on the module where you want to know the path you can then copy it into your command line and append sos.dll to your load command and you are done.
For the ones of you who can remember all shortcuts you can also use
!DumpMD poi(_methodPtr + 8) 0 : 000 > lm i m clr Browse full module list start
end module name
5dba0000 5e21f000 clr(export symbols) C
:\Windows\Microsoft.NET\Framework\v4 .0.30319\clr.dll
to show the full path of all modules which match clr as pattern.
posted on Sunday, May 20, 2012 10:39 AM Print