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)