WinDbg Bug: Disassembling JITed Code

 
As a follow up to this post, it would seem that the disassembler in version 6.6.7.5 of WinDbg appears to be broken when it comes to disassembling normal JITed code. I haven't noticed any other issues, but this one is a killer for me, so I've reverted to using version 6.6.3.5, which does not suffer from disassembler bug.
 
Disassembling native or pre-JITed code also appears to work. The bug seems to be isolated to the disassembly of normal JITed code. So if you're doing much work in windbg with managed code that involves looking at the disassembled output of managed methods, you might consider reverting to version 6.6.3.5. If you need the new/updated features/fixes of version 6.6.7.5, be advised that the disassembly you see for normal JITed methods may be buggy.
 
Note that you'll see the same buggy output using the windbg u command as well as the sos extension's u command. That's because sos.u doesn't implement its own disassembler - it just defers to the active debugger's disassembler; providing CLR-aware annotations to the output. As a result, if you use the !sos.u command within the context of the Visual Studio 2005 debugger (which does not appear to have this disassembler bug), you'll see valid output.
 
Ironically, if you look at the what's new page for version 6.6.7.5 of windbg, you'll find this line item:
 
Updated disassembler with new instruction support.
 
Thanks, but no thanks.
 
My test case is a simple C# console application that looks like so:
class Program {
  static void Main() {
    System.Console.WriteLine(GetText());
  }
  static string GetText() {
    return ("Hello, buggy windbg!");
  }
}
Using the !sos.bpmd extension command to break when Program.Main is called shows the following disassembly in version 6.6.3.5 of windbg (using !sos.u so that we can see the CLR-aware annotations):
0:000> !u eip
Normal JIT generated code
Program.Main()
Begin 01200070, size 23
01200070 56               push    esi
01200071 833dc82da20000   cmp     dword ptr [00a22dc8],0x0
01200078 7405             jz      0120007f
0120007a e87f22e978       call    mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
0120007f 90               nop
01200080 ff152c30a200     call    dword ptr [00a2302c] (Program.GetText(), mdToken: 06000002)
01200086 8bf0             mov     esi,eax
01200088 8bce             mov     ecx,esi
0120008a e855901b78       call    mscorlib_ni+0x2f90e4 (793b90e4) (System.Console.WriteLine(System.String), mdToken: 06000764)
0120008f 90               nop
01200090 90               nop
01200091 5e               pop     esi
01200092 c3               ret
In particular, note that the disassembled code shown above for instruction address 0120008a. The raw instruction sequence at that address is e855901b78. As I described in my previous post, the 1st byte (e8) is the Intel 'call' instruction, and the following 4 bytes (55901b78) indicate the target of the call instruction, computed relative to the instruction pointer, and not counting the 5 byte instruction itself. So we can manually compute the target of the call instruction in windbg like so:
0:000> ? 0120008a + 5 + poi(0120008a + 1)
Evaluate expression: 2033946852 = 793b90e4
The above calculation shows that the target of the call instruction is address 793b90e4, which matches windbg's output:
...
0120008a e855901b78       call    mscorlib_ni+0x2f90e4 (793b90e4) (System.Console.WriteLine(System.String), mdToken: 06000764)
...
Repeating the same experiment with version 6.6.7.5 of windbg (the buggy version) shows the following:
0:000> !u eip
Normal JIT generated code
Program.Main()
Begin 01200070, size 23
01200070 56              push    esi
01200071 833dc82da20000  cmp     dword ptr ds:[0A22DC8h],0
01200078 7405            je      0120007f
0120007a e87f22e978      call    mscorwks!JIT_DbgIsJustMyCode (7a0922fe)
0120007f 90              nop
01200080 ff152c30a200    call    dword ptr ds:[0A2302Ch]
01200086 8bf0            mov     esi,eax
01200088 8bce            mov     ecx,esi
0120008a e855901b78      call    MSVCR80!_NULL_IMPORT_DESCRIPTOR+0x2cdd (781b9055) (MSVCR80!_NULL_IMPORT_DESCRIPTOR)
0120008f 90              nop
01200090 90              nop
01200091 5e              pop     esi
01200092 c3              ret
There are a couple of things wrong with this output:
  • the sos.u command doesn't realize that the 2nd call instruction goes to Program.GetText
  • it incorrectly reports that the 3rd call instruction will land somewhere in MSVCR80.DLL (at address 781b9055).
Not likely.
 
Performing our same call decoding math for instruction 0120008a (the supposed call to address 781b9055 in MSVCR80.DLL) yields the following:
0:000> ? 0120008a + 5 + poi(0120008a + 1)
Evaluate expression: 2033946852 = 793b90e4
Note that our manual decoding of the call instruction yields a different target address: 793b90e4; as opposed to 781b9055 as shown by the !u command. If you disassemble that address, we'll see where execution will actually transfer:
0:000> !u eip
preJIT generated code
System.Console.WriteLine(System.String)
Begin 793b90e4, size 35
793b90e4 57              push    edi
793b90e5 56              push    esi
793b90e6 8bf9            mov     edi,ecx
793b90e8 ba86000000      mov     edx,86h
793b90ed b901000000      mov     ecx,1
793b90f2 e8d1a9ab00      call    mscorwks!JIT_Writeable_Thunks_Buf+0x198 (79e73ac8) (JitHelp: CORINFO_HELP_GETSHARED_GCSTATIC_BASE)
793b90f7 8bf0            mov     esi,eax
793b90f9 837e6800        cmp     dword ptr [esi+68h],0
793b90fd 750a            jne     mscorlib_ni+0x2f9109 (793b9109)
793b90ff b901000000      mov     ecx,1
793b9104 e80347f9ff      call    mscorlib_ni+0x28d80c (7934d80c) (System.Console.InitializeStdOutError(Boolean), mdToken: 0600070f)
793b9109 8b4e68          mov     ecx,dword ptr [esi+68h]
793b910c 8bd7            mov     edx,edi
793b910e 8b01            mov     eax,dword ptr [ecx]
793b9110 ff90d8000000    call    dword ptr [eax+0D8h]
793b9116 5e              pop     esi
793b9117 5f              pop     edi
793b9118 c3              ret
And, in fact, that's exactly what the C# code we're looking at does at that address - call System.Console.WriteLine.
 
So be advised - the disassembly included in WinDbg version 6.6.7.5 is broken when it comes to decoding normal JITed code.

Posted Jan 15 2007, 02:31 PM by mike-woodring

Comments

Volker von Einem wrote re: WinDbg Bug: Disassembling JITed Code
on 01-16-2007 2:37 AM
Thanks for the hint. You should post this in microsoft.public.windbg.

Regards Volker
Mike wrote re: WinDbg Bug: Disassembling JITed Code
on 01-16-2007 6:32 AM
I sent a note to windbgfb@microsoft.com instead, as that was the most expedient thing for me to do at the time. Someone has already sent me a note back saying they're looking into it.
Some Assembly Required wrote WinDbg Bug Update
on 01-22-2007 8:49 AM
fgmjnfg wrote re: WinDbg Bug: Disassembling JITed Code
on 08-27-2007 3:27 PM
http://4testslinks2.com
<a href="http://4testslinks2.com">4testslinks2.com</a>

Add a Comment

(required)  
(optional)
(required)  
Remember Me?