MANAGED DEBUGGING with WINDBG. Call Stacks. Part 2
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)
Catch exception1 As Exception
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
12900371a5b 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!
Next post: MANAGED DEBUGGING with WINDBG. Call Stacks. Part 3.
Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.
Regards,
Alex (Alejandro Campos Magencio)