Tag Archives: windbg

Getting the disassembly and IL for a Jitted\NGENed .Net method using WinDbg and SOS.dll

If you didn’t understand the title, then this post isn’t for you.
If you think you understood, and you think that this may help you with your debugging – then turn back now, you’ve gone completely the wrong way.

I’ve put this disclaimer here since, unless you are interested in how the JIT works (in which case, skip this and read on!), the only reason you are getting the assembly from the JIT is because you believe that the JIT compiled something incorrectly (or, at least, that was why I was investigating this – although it turned out that we were hitting an issue due to Out of Order Execution which lead to a race condition which had a window of a couple of CPU instructions – which just goes to show how awesome our stress test is).

As a note (if you want to try this at home) this uses the SpotTheDefect[0] code, which has been modified to initialize _foo to null (ensuring that the application crashed) and I’m trying to get the IL and disassembly for the Thread1 method

So, first off, we need to load our good friend SOS:

0:004> .loadby sos clr

Then we can find the method table for the class that contains the method we want. In my case, I’m looking for the Program class which is under the SpotTheDefect1 namespace in the SpotTheDefect1 assembly:

0:004> !name2ee SpotTheDefect1!SpotTheDefect1.Program
Module:      000007fa66f82f80
Assembly:    SpotTheDefect1.exe
Token:       0000000002000002
MethodTable: 000007fa66f838a8
EEClass:     000007fa67092240
Name:        SpotTheDefect1.Program

Once we’ve gotten the address to the method table, we can then dump that out specifying the ‘-md’ option to get the addresses of the methods in that class:

0:004> !dumpmt -md 000007fa66f838a8
EEClass:         000007fa67092240
Module:          000007fa66f82f80
Name:            SpotTheDefect1.Program
mdToken:         0000000002000002
File:            C:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\bin\Debug\SpotTheDefect1.exe
BaseSize:        0x18
ComponentSize:   0x0
Slots in VTable: 9
Number of IFaces in IFaceMap: 0
MethodDesc Table
           Entry       MethodDesc    JIT Name
000007fac557a7c0 000007fac52037a0 PreJIT System.Object.ToString()
000007fac5624cb0 000007fac52037a8 PreJIT System.Object.Equals(System.Object)
000007fac56247a0 000007fac52037d0 PreJIT System.Object.GetHashCode()
000007fac5587420 000007fac52037e8 PreJIT System.Object.Finalize()
000007fa670a0090 000007fa66f838a0    JIT SpotTheDefect1.Program..cctor()
000007fa66f8c038 000007fa66f83898   NONE SpotTheDefect1.Program..ctor()
000007fa670a00e0 000007fa66f83868    JIT SpotTheDefect1.Program.Main(System.String[])
000007fa670a0270 000007fa66f83878    JIT SpotTheDefect1.Program.Thread1()
000007fa66f8c030 000007fa66f83888   NONE SpotTheDefect1.Program.Thread2()

You will notice that the methods are marked wither “JIT”, “PreJIT” or “NONE” – this indicates if the JIT has compiled the method (“JIT”), if NGEN compiled the method ahead of time (“PreJIT”) or if the method is yet to be compiled (“NONE”). I’m interested in Thread1, which has been compiled by the JIT, so now I can dump out the method descriptor (I’ll also show later on what happens if you try to get a method that is yet to be compiled).

0:004> !dumpmd 000007fa66f83878   
Method Name:  SpotTheDefect1.Program.Thread1()
Class:        000007fa67092240
MethodTable:  000007fa66f838a8
mdToken:      0000000006000002
Module:       000007fa66f82f80
IsJitted:     yes
CodeAddr:     000007fa670a0270
Transparency: Critical

From here I now know the method descriptor and the address of the code, so I can dump the IL (using the method descriptor) and the actual assembly (using the code address)

0:004> !dumpil 000007fa66f83878   
ilAddr = 00000000009e20a0
IL_0000: nop
IL_0001: ldsfld SpotTheDefect1.Program::_disposed
IL_0006: stloc.0
IL_0007: ldloc.0
IL_0008: brtrue.s IL_0017
IL_000a: nop
IL_000b: ldsfld SpotTheDefect1.Program::_foo
IL_0010: callvirt Foo::Bar
IL_0015: nop
IL_0016: nop
IL_0017: ret

0:004> !U 000007fa670a0270
Normal JIT generated code
Begin 000007fa670a0270, size 61

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 24:
>>> 000007fa`670a0270 4883ec38        sub     rsp,38h
000007fa`670a0274 c644242000      mov     byte ptr [rsp+20h],0
000007fa`670a0279 48b83034f866fa070000 mov rax,7FA66F83430h
000007fa`670a0283 8b00            mov     eax,dword ptr [rax]
000007fa`670a0285 85c0            test    eax,eax
000007fa`670a0287 7405            je      SpotTheDefect1!SpotTheDefect1.Program.Thread1()+0x1e (000007fa`670a028e)
000007fa`670a0289 e8c6cda45f      call    clr!TranslateSecurityAttributes+0x62a9c (000007fa`c6aed054) (JitHelp: CORINFO_HELP_DBG_IS_JUST_MY_CODE)
000007fa`670a028e 90              nop

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 25:
000007fa`670a028f 8a059e33eeff    mov     al,byte ptr [000007fa`66f83633]
000007fa`670a0295 88442420        mov     byte ptr [rsp+20h],al

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 26:
000007fa`670a0299 0fb6442420      movzx   eax,byte ptr [rsp+20h]
000007fa`670a029e 85c0            test    eax,eax
000007fa`670a02a0 7527            jne     SpotTheDefect1!SpotTheDefect1.Program.Thread1()+0x59 (000007fa`670a02c9)
000007fa`670a02a2 90              nop

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 27:
000007fa`670a02a3 48b83856e21200000000 mov rax,12E25638h
000007fa`670a02ad 488b00          mov     rax,qword ptr [rax]
000007fa`670a02b0 4889442428      mov     qword ptr [rsp+28h],rax
000007fa`670a02b5 488b442428      mov     rax,qword ptr [rsp+28h]
000007fa`670a02ba 803800          cmp     byte ptr [rax],0
000007fa`670a02bd 488b4c2428      mov     rcx,qword ptr [rsp+28h]
000007fa`670a02c2 e8b1bdeeff      call    SpotTheDefect1.Program+Foo.Bar() (000007fa`66f8c078) (SpotTheDefect1.Program+Foo.Bar(), mdToken: 0000000006000006)
000007fa`670a02c7 90              nop

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 28:
000007fa`670a02c8 90              nop

c:\Users\Daniel\Documents\Visual Studio 11\Projects\SpotTheDefect1\SpotTheDefect1\Program.cs @ 29:
000007fa`670a02c9 eb00            jmp     SpotTheDefect1!SpotTheDefect1.Program.Thread1()+0x5b (000007fa`670a02cb)
000007fa`670a02cb 90              nop
000007fa`670a02cc 4883c438        add     rsp,38h
000007fa`670a02d0 c3              ret

A couple of things to note: Firstly, the IL tends to match up with the actual code quite accurately (unless you are using compiler magic like yield return or await/async), and I’m using !U to dump the disassembly which (if you have the symbols) will show you what line of code generated which assembly commands (although the JIT may rearrange sections of code (especially for try/catch/finally statements) – so don’t expect the assembly to always work out as nicely as it did above). You may have also noticed that the assembly is quite verbose, contains debugging information (“CORINFO_HELP_DBG_IS_JUST_MY_CODE”) and some unnecessary NOPs (e.g. line 28 of my code was turned into a single NOP) – this is because I was using a debug build of SpotTheDefect1.exe, if I was using the release build, then a lot of these instructions would be optimized away (or not generated in the first place).

Finally, if we were interested in the assembly for Thread2, we could try to dump of the method descriptor

0:004> !dumpmd 000007fa66f83888  
Method Name:  SpotTheDefect1.Program.Thread2()
Class:        000007fa67092240
MethodTable:  000007fa66f838a8
mdToken:      0000000006000003
Module:       000007fa66f82f80
IsJitted:     no
CodeAddr:     ffffffffffffffff
Transparency: Critical

But the code address points to nowhere – which is because it hasn’t been compiled yet, so there is no assembly associated with the method (although you could still dump out the IL).

Tagged , , , , ,