Original URL: | https://blogs.msdn.microsoft.com/alejacma/2009/08/05/managed-debugging-with-windbg-call-stacks-part-2/ |
Post name: | MANAGED DEBUGGING with WINDBG. Call Stacks. Part 2 |
Original author: | Alejandro Campos Magencio |
Posting date: | 2009-08-05T02:32:00+00:00 |
Hi all,
This post is a continuation of MANAGED DEBUGGING with WINDBG. Call Stacks. Part 1.
CALL STACKS. Part 2
· We can see the source code of a method in the call stack: First of all, Source Code mode has to be enabled. We’ll need symbols and source code files correctly configured. We can choose the frame of the method we want to see: 0:000> knL100 # ChildEBP RetAddr 00 0027e600 79f071ac KERNEL32!RaiseException+0x58 01 0027e660 79f9293a mscorwks!RaiseTheExceptionInternalOnly+0x2a8 02 0027e698 7a129a34 mscorwks!UnwindAndContinueRethrowHelperAfterCatch+0x70 03 0027e738 00371aad mscorwks!JIT_RngChkFail+0xb0 04 0027e780 003719fe WindowsApplication1!WindowsApplication1.Form1.PlayWithArray(Int32[])+0x55 05 00000000 7b062c9a WindowsApplication1!WindowsApplication1.Form1.Button6_Click(System.Object, System.EventArgs)+0x86 06 0027e7e8 7b11cb29 System_Windows_Forms_ni!System.Windows.Forms.Control.OnClick(System.EventArgs)+0x6a ... 0:000> .frame 4 04 0027e780 003719fe WindowsApplication1!WindowsApplication1.Form1.PlayWithArray(Int32[])+0x55 [C:\__WORKSHOP\Demos\BuggyNETApp\Form1.vb @ 135]
WinDbg will automatically open the source code file and locate where in the function we are in this call stack: Private Function PlayWithArray(ByVal array As Integer()) As Integer Dim result As Integer Try For i As Integer = 0 To 3 result += array(i)
Next Catch ex As Exception result = -1 End Try Return result End Function
· We can see the MSIL (Microsoft Intermediate Language) of a method in the call stack: When compiling to managed code, the compiler translates our source code into Microsoft intermediate language (MSIL), which is a CPU-independent set of instructions that can be efficiently converted to native code. MSIL includes instructions for loading, storing, initializing, and calling methods on objects, as well as instructions for arithmetic and logical operations, control flow, direct memory access, exception handling, and other operations. Before code can be run, MSIL must be converted to CPU-specific code by a just-in-time (JIT) compiler. To see the IL, we first need to get the method descriptor like this: 0:000> !CLRStack OS Thread Id: 0x1f3c (0) ESP EIP 0027e6f0 77a1b09e [HelperMethodFrame: 0027e6f0] 0027e740 00371aad WindowsApplication1.Form1.PlayWithArray(Int32[]) 0027e788 003719fe WindowsApplication1.Form1.Button6_Click(System.Object, System.EventArgs) 0027e7a8 7b062c9a System.Windows.Forms.Control.OnClick(System.EventArgs) ... 0:000> !IP2MD 00371aad
MethodDesc: 00116e28 Method Name: WindowsApplication1.Form1.PlayWithArray(Int32[]) Class: 00380a30 MethodTable: 00116ecc mdToken: 06000039 Module: 00112c3c IsJitted: yes m_CodeOrIL: 00371a58
Or directly with: 0:000> !DumpStack -EE OS Thread Id: 0x1f3c (0) Current frame: ChildEBP RetAddr Caller,Callee 0027e738 00371aad (MethodDesc 0x116e28 +0x55 WindowsApplication1.Form1.PlayWithArray(Int32[])) ...
Additionally, if we just hit a breakpoint, we can also use the eip (Instruction Pointer) register for this purpose: 0:000> !IP2MD @eip MethodDesc: 00116e28 Method Name: WindowsApplication1.Form1.PlayWithArray(Int32[]) ...
Now we can take a look to IL code for that method: 0:000> !DumpIL 00116e28 ilAddr = 00d73298 IL_0000: nop IL_0001: nop .try { IL_0002: ldc.i4.0 IL_0003: stloc.2 IL_0004: ldloc.1 IL_0005: ldarg.1 IL_0006: ldloc.2 IL_0007: ldelem.i4 IL_0008: add.ovf IL_0009: stloc.1 IL_000a: nop IL_000b: ldloc.2 IL_000c: ldc.i4.1 IL_000d: add.ovf IL_000e: stloc.2 IL_000f: ldloc.2 IL_0010: ldc.i4.3 IL_0011: stloc.s VAR OR ARG 4 IL_0013: ldloc.s VAR OR ARG 4 IL_0015: ble.s IL_0104 IL_0017: leave.s IL_002a } // end .try .catch { IL_0019: dup IL_001a: call Microsoft.VisualBasic.CompilerServices.ProjectDat::SetProjectError IL_001f: stloc.3 IL_0020: nop IL_0021: ldc.i4.m1 IL_0022: stloc.1 IL_0023: call Microsoft.VisualBasic.CompilerServices.ProjectDat::ClearProjectError IL_0028: leave.s IL_002a } // end .catch IL_002a: nop IL_002b: ldloc.1 IL_002c: stloc.0 IL_002d: br.s IL_002f IL_002f: ldloc.0 IL_0030: ret
Tip for Reading IL: The CLR is stack-based. A two-operand operation such as Add pops the two top values from the stack and adds them.
ILDASM.exe (Visual Studio.NET) will show the same IL code that we’ve just seen, plus the following declarations just before it: // Code size 49 (0x31) .maxstack 3 .locals init ([0] int32 PlayWithArray, [1] int32 result, [2] int32 i, [3] class [mscorlib]System.Exception ex, [4] int32 VB$CG$t_i4$S0)
And Reflector.exe will show our function like this: Private Function PlayWithArray(ByVal array As Integer()) As Integer Dim result As Integer Try Dim VB$CG$t_i4$S0 As Integer Dim i As Integer = 0 Do result = (result + array(i)) i += 1 VB$CG$t_i4$S0 = 3 Loop While (i <= VB$CG$t_i4$S0) ProjectData.SetProjectError(exception1) Dim ex As Exception = exception1 result = -1 ProjectData.ClearProjectError End Try Return result End Function
All this may help us to understand the correspondence between our code and its IL version.
· We can write an image loaded in memory to a file: You can save a module after patching it on the fly when live debugging, or get the original DLLs or EXEs when debugging a dump. If we save a managed binary, we can use the file with ILDASM.exe or Reflector.exe, for instance. We save a module like this: 0:004> lm start end module name 00a20000 00a2c000 WindowsApplication1 (deferred) … 79e70000 7a3ff000 mscorwks (private pdb symbols) c:\symbols\mscorwks.pdb\62286AFFFC674D5198883B98518936FF2\mscorwks.pdb ... 7afd0000 7bc6c000 System_Windows_Forms_ni (deferred) 0:004> !SaveModule 79e70000 c:\mscorwks.dll 5 sections in file section 0 - VA=1000, VASize=53abd2, FileAddr=400, FileSize=53ac00 section 1 - VA=53c000, VASize=9, FileAddr=53b000, FileSize=200 section 2 - VA=53d000, VASize=189d0, FileAddr=53b200, FileSize=16000 section 3 - VA=556000, VASize=670, FileAddr=551200, FileSize=800 section 4 - VA=557000, VASize=37bc4, FileAddr=551a00, FileSize=37c00
· We can see the assembly code of a method in the call stack: If method is jitted, we can see its assembly code as we will do it with any other unmanaged method: 0:000> !DumpStack -EE OS Thread Id: 0x1f3c (0) Current frame: ChildEBP RetAddr Caller,Callee 0027e738 00371aad (MethodDesc 0x116e28 +0x55 WindowsApplication1.Form1.PlayWithArray(Int32[])) ... 0:000> !DumpMD 0x116e28 Method Name: WindowsApplication1.Form1.PlayWithArray(Int32[]) Class: 00380a30 MethodTable: 00116ecc mdToken: 06000039 Module: 00112c3c IsJitted: yes m_CodeOrIL: 00371a58 0:000> uf 00371a58 WindowsApplication1!WindowsApplication1.Form1.PlayWithArray(Int32[]) [C:\__WORKSHOP\Demos\BuggyNETApp\Form1.vb @ 129]: 129 00371a58 55 push ebp 129 00371a59 8bec mov ebp,esp 129 00371a5b 57 push edi 129 00371a5c 56 push esi 129 00371a5d 53 push ebx 129 00371a5e 83ec34 sub esp,34h 129 00371a61 33c0 xor eax,eax 129 00371a63 8945e8 mov dword ptr [ebp-18h],eax ... ... WindowsApplication1!WindowsApplication1.Form1.PlayWithArray(Int32[])+0xb5 [C:\__WORKSHOP\Demos\BuggyNETApp\Form1.vb @ 144]: 144 00371b0d 8b45d8 mov eax,dword ptr [ebp-28h] 144 00371b10 8d65f4 lea esp,[ebp-0Ch] 144 00371b13 5b pop ebx 144 00371b14 5e pop esi 144 00371b15 5f pop edi 144 00371b16 5d pop ebp 144 00371b17 c3 ret
Note that if we have symbols we can tell the line number in the source code file. We have a .NET version of uf command which understands managed code: 0:000> !u 00371a58 Normal JIT generated code WindowsApplication1.Form1.PlayWithArray(Int32[]) Begin 00371a58, size c0 >>> 00371a58 55 push ebp 00371a59 8bec mov ebp,esp 00371a5b 57 push edi 00371a5c 56 push esi 00371a5d 53 push ebx 00371a5e 83ec34 sub esp,34h 00371a61 33c0 xor eax,eax 00371a63 8945e8 mov dword ptr [ebp-18h],eax ... 00371b16 5d pop ebp 00371b17 c3 ret
You can pass to this command any address within that method and it will show the same assembly and where we are in it (>>>). Note that we may run this command with an address within an unmanaged method and if we have private symbols and source code, it shows all the assembly in that method along with the source code itself!
Catch exception1 As Exception
Next post: MANAGED DEBUGGING with WINDBG. Call Stacks. Part 3.
Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.
Regards,
Alex (Alejandro Campos Magencio)
Comments: