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

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!

 

   

Next post: MANAGED DEBUGGING with WINDBG. Call Stacks. Part 3.

Index: MANAGED DEBUGGING with WINDBG. Introduction and Index.

 

Regards,

 

Alex (Alejandro Campos Magencio)