WinDbg is a powerful tool to debug applications. Lately, its use has been extended to managed debugging as well through an extension called sos.dll (Son of Strike). Sos.dll is shipped with both .NET Framework 2.0 and .NET 1.1 Framework and is a WinDbg extension which allows WinDbg to read managed data structures such as Method Tables, Method Descriptors etc. The power of WinDbg is ultimate in detecting memory leaks, deadlocks and unexpected application crashes where Visual Studio debugger cannot reach.
In this post, we will try to debug and inspect the Jitted code for a very simple .NET console application. Note that WinDbg is new to me as well, so if there is any better or shorter way to do this, kindly leave a comment to me.
namespace ConsoleApplication1
{
class Test
public int i;
public int ri()
i++;
return i;
}
class Program
static void Main(string[] args)
Test t = new Test();
int y = t.ri();
Console.Write(y);
Upon loading this ConsoleApplication1.exe into debugger, WinDbg breaks at the default debug breakpoint. At this point .NET runtime is not loaded into memory, so SOS cannot be loaded at this moment. We will set a breakpoint on Module load of mscorlib.dll, so that we can load SOS.
Entering the command sxe ld:mscorlib sets this breakpoint. Once it is set, we will go ahead with F5 to run the application. WinDbg will break upon loading on mscorlib.dll :
ModLoad: 790c0000 79bf6000 C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\32e6f703c114f3a971cbe706586e3655\mscorlib.ni.dll eax=00000001 ebx=00000000 ecx=00000001 edx=00000000 esi=7ffdf000 edi=20000000 eip=7c82ed54 esp=0012e9d0 ebp=0012ea14 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!KiFastSystemCallRet: 7c82ed54 c3 ret
At this point, SOS can be loaded into memory, with the command .loadby sos mscorwks
Once the SOS is loaded, we can inspect the running threads using !threads
!threadsPDB symbol for mscorwks.dll not loaded ThreadCount: 2 UnstartedThread: 1 BackgroundThread: 1 PendingThread: 1 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 1c4 00181fd0 220 Enabled 00000000:00000000 0014cf28 1 Ukn XXXX 2 9d4 001533b8 1400 Enabled 00000000:00000000 0014cf28 0 Ukn (Finalizer)
!threads
PDB symbol for mscorwks.dll not loaded ThreadCount: 2 UnstartedThread: 1 BackgroundThread: 1 PendingThread: 1 DeadThread: 0 Hosted Runtime: no PreEmptive GC Alloc Lock ID OSID ThreadOBJ State GC Context Domain Count APT Exception 0 1 1c4 00181fd0 220 Enabled 00000000:00000000 0014cf28 1 Ukn XXXX 2 9d4 001533b8 1400 Enabled 00000000:00000000 0014cf28 0 Ukn (Finalizer)
We can see from the output above that there is one AppDomain at 0014cf28 loaded for the current running process, which makes sense since it is a very simple application.
Lets dump the domain information using !dumpdomain 014cf28
!dumpdomain 014cf28Domain 1: 0014cf28 LowFrequencyHeap: 0014cf4c HighFrequencyHeap: 0014cfa4 StubHeap: 0014cffc Stage: OPEN SecurityDescriptor: 0014e258 Name: None
!dumpdomain 014cf28
Domain 1: 0014cf28 LowFrequencyHeap: 0014cf4c HighFrequencyHeap: 0014cfa4 StubHeap: 0014cffc Stage: OPEN SecurityDescriptor: 0014e258 Name: None
Dumping the domain gives us information about the locations of heaps. Note that it also gives the managed loaded modules in the process, but there is none listed since no managed module has been loaded yet (mscorlib is about to be loaded). We can set another breakpoint in WinDbg till the jitting engine (mscorjit.dll) is loaded using sxe ld:mscorjit
The breakpoint is hit as soon as mscorjit.dll is loaded:
ModLoad: 79060000 790b6000 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\mscorjit.dll eax=00000001 ebx=00000000 ecx=00000001 edx=00000000 esi=7ffdf000 edi=20000000 eip=7c82ed54 esp=0012e554 ebp=0012e598 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 ntdll!KiFastSystemCallRet: 7c82ed54 c3 ret
Now, if we dump the domain again,
Domain 1: 0014cf28 LowFrequencyHeap: 0014cf4c HighFrequencyHeap: 0014cfa4 StubHeap: 0014cffc Stage: OPEN SecurityDescriptor: 0014e258 Name: ConsoleApplication1.exe Assembly: 00190558 [C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll] ClassLoader: 001905f0 SecurityDescriptor: 0018cdc8 Module Name 790c2000 C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll Assembly: 00194388 [C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe] ClassLoader: 00193720 SecurityDescriptor: 001942f0 Module Name 008f2c3c C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe
We can see that it lists, the mscorlib and our application(ConsoleApplication1.exe) as one of the loaded modules. Lets proceed onto setting the breakpoint on the Main() function. First we will need to get the address of method table of class Program by first dumping our module using !dumpmodule command. Once we get the method table, we can dump it using !dumpmt command to get the method descriptor for the Main() function.
!dumpmodule -mt 008f2c3c
!dumpmodule -mt 008f2c3cName: C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe Attributes: PEFile Assembly: 00194388 LoaderHeap: 00000000 TypeDefToMethodTableMap: 008f00c0 TypeRefToMethodTableMap: 008f00cc MethodDefToDescMap: 008f0118 FieldDefToDescMap: 008f012c MemberRefToDescMap: 008f0134 FileReferencesMap: 008f017c AssemblyReferencesMap: 008f0180 MetaData start address: 00402098 (1548 bytes) Types defined in this module MT TypeDef Name ------------------------------------------------------------------------------ 008f3038 0x02000003 ConsoleApplication1.Program Types referenced in this module MT TypeRef Name ------------------------------------------------------------------------------ 790fd0f0 0x01000001 System.Object
Name: C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe Attributes: PEFile Assembly: 00194388 LoaderHeap: 00000000 TypeDefToMethodTableMap: 008f00c0 TypeRefToMethodTableMap: 008f00cc MethodDefToDescMap: 008f0118 FieldDefToDescMap: 008f012c MemberRefToDescMap: 008f0134 FileReferencesMap: 008f017c AssemblyReferencesMap: 008f0180 MetaData start address: 00402098 (1548 bytes) Types defined in this module MT TypeDef Name ------------------------------------------------------------------------------ 008f3038 0x02000003 ConsoleApplication1.Program Types referenced in this module MT TypeRef Name ------------------------------------------------------------------------------ 790fd0f0 0x01000001 System.Object
We can dump the method table of ConsoleApplication1.Program using !dumpmt -md 008f3038. The parameter -md is for dumping MethodDesc table.
!dumpmt -md 008f3038EEClass: 008f1278 Module: 008f2c3c Name: ConsoleApplication1.Program mdToken: 02000003 (C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 6 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 79371278 7914b928 PreJIT System.Object.ToString() 7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object) 7936b3d0 7914b948 PreJIT System.Object.GetHashCode() 793624d0 7914b950 PreJIT System.Object.Finalize() 008fc010 008f3028 NONE ConsoleApplication1.Program.Main(System.String[]) 008fc01c 008f3030 NONE ConsoleApplication1.Program..ctor()
!dumpmt -md 008f3038
EEClass: 008f1278 Module: 008f2c3c Name: ConsoleApplication1.Program mdToken: 02000003 (C:\Documents and Settings\Administrator\My Documents\Visual Studio 2005\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe) BaseSize: 0xc ComponentSize: 0x0 Number of IFaces in IFaceMap: 0 Slots in VTable: 6 -------------------------------------- MethodDesc Table Entry MethodDesc JIT Name 79371278 7914b928 PreJIT System.Object.ToString() 7936b3b0 7914b930 PreJIT System.Object.Equals(System.Object) 7936b3d0 7914b948 PreJIT System.Object.GetHashCode() 793624d0 7914b950 PreJIT System.Object.Finalize() 008fc010 008f3028 NONE ConsoleApplication1.Program.Main(System.String[]) 008fc01c 008f3030 NONE ConsoleApplication1.Program..ctor()
We can see that method descriptor for Main resides at 008f3028.
We can set the breakpoint on Program.Main() method using !bpmd -md 008f3028
MethodDesc = 008f3028 Adding pending breakpoints...
As we run the application, the breakpoint is hit and instruction pointer is placed on the first instruction of the jitted Main() method.
(a0c.1c4): CLR notification exception - code e0444143 (first chance) JITTED ConsoleApplication1!ConsoleApplication1.Program.Main(System.String[]) Setting breakpoint: bp 00C20070 [ConsoleApplication1.Program.Main(System.String[])] Breakpoint 0 hit eax=008f3028 ebx=0012f4ac ecx=01241ec0 edx=00000000 esi=00181fd0 edi=00000000 eip=00c20070 esp=0012f484 ebp=0012f490 iopl=0 nv up ei pl nz ac po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212 00c20070 56 push esi
Finally, to see the generated machine code, use the Unassemble instruction to unassemble the instructions starting from current.
!u eip
1: Normal JIT generated code
2: ConsoleApplication1.Program.Main(System.String[])
3: Begin 00c20070, size 35
4: >>> 00c20070 56 push esi
5: 00c20071 b9b0308f00 mov ecx,8F30B0h (MT: ConsoleApplication1.Test)
6: 00c20076 e8a11f7dff call 003f201c (JitHelp: CORINFO_HELP_NEWSFAST)
7: 00c2007b 8bd0 mov edx,eax
8: 00c2007d ff4204 inc dword ptr [edx+4]
9: 00c20080 8b7204 mov esi,dword ptr [edx+4]
10: 00c20083 833d8c10240200 cmp dword ptr ds:[224108Ch],0
11: 00c2008a 750a jne 00c20096
12: 00c2008c b901000000 mov ecx,1
13: *** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v2.0.50727_32\mscorlib\32e6f703c114f3a971cbe706586e3655\mscorlib.ni.dll
14: 00c20091 e8ce587478 call mscorlib_ni+0x2a5964 (79365964) (System.Console.InitializeStdOutError(Boolean), mdToken: 06000770)
15: 00c20096 8b0d8c102402 mov ecx,dword ptr ds:[224108Ch]
16: 00c2009c 8bd6 mov edx,esi
17: 00c2009e 8b01 mov eax,dword ptr [ecx]
18: 00c200a0 ff5074 call dword ptr [eax+74h]
19: 00c200a3 5e pop esi
20: 00c200a4 c3 ret
Here are some interesting observations of this jitted code:
PingBack from http://wordnew.acne-reveiw.info/?p=9414
Thanks for sxe ld:mscorjit command. It solved my puzzle that I can't stop at the first line code of Main. It sounds to me that !bpmd program namespace.classname.method doesn't worl at all