Welcome to MSDN Blogs Sign in | Join | Help
Undocked window crash QFE now available

A fix for a widely reported crash in the Visual Studio 2008 SP1 IDE is now available.  This fixes a crash that occurs after changing layouts and then changing modes.  The layout changes can be undocking/docking windows, and the mode changes are starting/stopping debugging or changing to/from Full Screen.  This bug was introduced in Service Pack 1 and the fix for this is now available for download via Code Gallery at:  http://code.msdn.microsoft.com/KB960075

Debugger QFE for VS 2008 SP1 released

The Visual Studio Debugger team has just released a QFE that fixes several problems with the debugger in VS 2008. This fixes several issues while debugging multi-threaded applications in both managed and native code as well as some issues with breakpoints.

It can be downloaded at this location:

http://code.msdn.microsoft.com/KB957912/Release/ProjectReleases.aspx?ReleaseId=1796

 

 

Adding Stepping Support to a Debug Engine

One feature that is clearly missing from the current debug engine sample is stepping. The reason it is missing is it only requires one AD7 method to initiate the step and one AD7 event when the step is complete. However, the actual implementation of stepping is very complex and requires support from the underlying platform to implement. Since the purpose of the sample is to supplement the existing documentation of AD7, I decided stepping was one thing that could be left out. Real world debug engines will require stepping support. So, the question becomes, how do you add it.  First, let's talk a little bit about AD7's support for stepping, and then I'll give a high-level descriptoin of what is required to implement a stepping engine in x86.

 When a user initiates a step in Visual Studio, the Debugger UI (and thus the SDM) make a call to the current IDebugProgram2's Step method. Step's signature looks like this:

HRESULT Step( 
   IDebugThread2*  pThread,
   STEPKIND        sk,
   STEPUNIT        step
);

The pThread parameter is the thread to step. The STEPKIND parameter describes what type of step to do (step-in, step-over, step-out, step-backwards). I've never actually seen an engine that supports step-backwards, but the other three are pretty much required for any engine that supports stepping.

STEPUNIT describes what "unit" to step. This can be statement, line, or instruction. Statement is used for languages such as VB or C# that allow the user to step through sub-expressions in a statement (such as a for loop initializer and its condition). Line is for languages that don't support that (such as C++). Instruction is used for stepping in the disassembly window.

When the engine receives the call to step, it should do the required setup in the debuggee to make the step occur, and then continue the last stopping event. This will allow the debugee to run. Once the engine detects that the step has completed successfully, it should send an instance of IDebugStepCompleteEvent2 to the SDM. IDebugStepCompleteEvent2 is a stopping event. Like all stopping events, the debuggee should remain in break state until the UI makes a call to Continue or to Execute.

So, what hardware support is required on a native x86 machine to enable stepping? The debug engine will make use of the trap-flag and breakpoints. The trap-flag is used to execute a single-instruction in the debuggee and then stop in break-mode. It is bit 8 in EFlags (one of the x86 control registers). When the next instruction completes executing in the debuggee with the trap flag enabled, a single-step exception (code 0x80000004) is thrown to the debugger if one is present.  

Breakpoints are used to avoid stopping on every instruction in cases that will be too expensive or for instructions that do no honor the trap-flag. For instance, when an instruction has the repeat prefix on it, a debug engine may choose to set a breakpoint at the next instruction after the repeated instruction and then continue. This is to avoid single-stepping over the same instruction repeatedly.

So, the most-basic stepping algorithm to perform a step on x86 looks like this:

1) Look at the source-line information in the symbol file. This can map the current instruction pointer to a line in source and tell the debugger how many instructions make up the source line.

2) Enable single stepping unless current instruction is one where the trap-flag cannot or should-not be used.

3) Continue the debuggee to allow it to run

4) Once the current instruction executes, receive the single-step exception. Check to see if the step is complete. If not, repeat from step 2 continuing the single-step exception handed.

5) Once the step completes, send an instance of IDebugStepComplete2.

Obviously, I'm missing a lot of details. For instance, how do you implement Step over if the stepping range contains a call instruction? How do you implement step-out? etc... Perhaps I'll get to those in a later post. Hopefully, this is enough to get some people started on the execution control portion of their debug engine.

 

What to do if your debug engine doesn't create real processes?

One thing we get a lot of reports about from people writing custom debug engines is if the engine is doing engine launch (using LaunchSuspended like the engine sample does) and the engine doesn't launch a true win32 process, the debugger will fail the launch and not call ResumeProcess. This is a common scenario for simulators or interpreters.

The SDM assumes that if IDebugProcess::GetPhysicalProcessId returns an AD_PROCESS_ID_SYSTEM for the pid and the process is local, it can actually access the real win32 process on the local machine. To make this work in your engine, use AD_PROCESS_ID_GUID instead (http://msdn.microsoft.com/en-us/library/bb145053.aspx)

 

 

Walkthrough3: Breakpoints and Stopping Events now available
I have uploaded another walkthrough for the new debug engine sample. This one explains breakpoints, breakpoint binding and stopping events in the sample engine. It is available along-side the other walkthroughs and the actual sample download at http://code.msdn.microsoft.com/debugenginesample

 

SOS in Visual Studio Part 2

I found this old  blog post on my box today. I had never gotten around to posting it.Hopefully, this will be useful to someone. 

 

In my last SOS post I promised to give a discussion on the CLR data structures you need to debug managed code with SOS. There is a great detailed article available here: http://msdn.microsoft.com/msdnmag/issues/05/05/JITCompiler/default.aspx
which explains this in good detail. However, I thought I’d still write an overview of the information and how it applies to SOS in VS.

 

Sos has a larger learning curve than VisualStudio, and I have found that this scares a lot of developers away from it. However, it really is worth time to learn to use it. Why? For two reasons: It has some very powerful features not available directly in Visual Studio and it enables you to debug managed code in crash dumps. (Yes, it is true that the Visual Studio debugger does not currently support debugging managed code from a crash dump).

 

There are three CLR data structures I think are most important when debugging using SOS. Figures 9 in the above MSDN article shows an excellent graphical representation of these types. These are data structures are:

 

1. MethodTable –Information about a class / type. This includes static class data, a table of method descriptors, pointers to the corresponding EEClass,  pointers to other MethodTables for other VTables (such as interfaces implemented by the class) and a pointer to the class constructor. MethodTable is paired with an EEClass which together define the complete type.

 

2. EEClass – More static information about a class / type. Logically, EEClass and MethodTable are the same data structure.

 

3. MethodDesc – Information specific to a particular method such as the IL or Jit’ed instructions.

 

 

So, when an instance of an object appears in managed code, it is referenced through an OBJECTREF. An OBJECTREF is actually a handle (indirect pointer) to the instance of the class. This object instance contains instance data (fields), string literals, and a handle to the type’s MethodTable. If a managed method creates an instance of a type like this:

 

public void MoveCar()

{

    Wheel w = new Wheel()   

}

 

The variable “w” appears as an objectref on the stack. Using this OBJECTREF, a developer using SOS can examine the variables instance data, static data, and type information.

 

As an example of debugging a small application using Visual Studio and SOS, I have enclosed a  small application at the end of this post that contains two classes, Program (the driver) and Squid. The application will create an instance of Squid, set its length and color, and have it swim to a new location. Create a new C# console application in Visual Studio and replace the code in program.cs with the text in the enclosed file. Enable native debugging by right-mouse clicking the project node, and choosing Properties->Debug->Enable Unmanaged Code Debugging. Set a breakpoint in the line: Squid s = new Squid("Red", 10); and start debugging. Once the breakpoint is hit, step into the constructor of new instance of Squid. Step over the initialization of some of Squid’s fields. Next, Load SOS:

 

  1. Open the immediate window (Debug->Windows->Immediate)
  2. type “.load c:\windows\microsoft.net\framework\v2.50727\sos.dll” and hit enter.

 

Let’s examine the instance of Squid. Since the debugger is currently sitting in the constructor for squid, type !DumpStackObjects in the immediate window. SOS should print the following output:

 

OS Thread Id: 0x1c4 (452)

ESP/REG  Object   Name

0013f068 01319e8c System.String    Red

0013f08c 01319e8c System.String    Red

0013f094 01319ea4 ConsoleApplication6.Squid

0013f0c8 01319ea4 ConsoleApplication6.Squid

0013f0d0 01319ea4 ConsoleApplication6.Squid

0013f1c4 01319e8c System.String    Red

0013f1c8 01319ea4 ConsoleApplication6.Squid

0013f1d4 01319ea4 ConsoleApplication6.Squid

0013f42c 01319ea4 ConsoleApplication6.Squid

0013f444 01319e7c System.Object[]    (System.String[])

0013f45c 01319e7c System.Object[]    (System.String[])

0013f534 01319e7c System.Object[]    (System.String[])

0013f6e0 01319e7c System.Object[]    (System.String[])

0013f708 01319e7c System.Object[]    (System.String[])

 

These are the managed objects currently on the stack. We want to look at squid, so copy the value of the Object field next to squid (01319ea4 in my example). This is the value of the objectref handle on the stack. Type !DumpObj 01319ea4 in the immediate window. SOS will now print:

 

Name: ConsoleApplication6.Squid

MethodTable: 0090321c

EEClass: 00901458

Size: 24(0x18) bytes

 (C:\Documents and Settings\j\My Documents\Visual Studio 2005\Projects\ConsoleApplication6\ConsoleApplication6\bin\Debug\ConsoleApplication6.exe)

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

790fa3e0  4000001        4        System.String  0 instance 01319e8c _color

790fed1c  4000002        8         System.Int32  0 instance       10 _length

790fed1c  4000003        c         System.Int32  0 instance        0 posX

790fed1c  4000004       10         System.Int32  0 instance        0 posY

790fed1c  4000001       1c         System.Int32  0   static        1 numRefs

 

SOS has just dumped the type of the object (ConsoleApplication6.Squid), a pointer to the method table and EEClass, the size of the object in bytes, the path to the module it is contained in, and the values of the instance and static fields.

 

Integral types such as int are easy to get the value of. For instance, the value of _length is 10. However, the value of the color string must be obtained by dumping its object as well. Copy its value (01319e8c) to the clipboard, and do another !dumpobj. SOS should now print something similar to this:

Name: System.String

MethodTable: 790fa3e0

EEClass: 790fa340

Size: 24(0x18) bytes

 (C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll)

String: Red

Fields:

      MT    Field   Offset                 Type VT     Attr    Value Name

790fed1c  4000096        4         System.Int32  0 instance        4 m_arrayLength

790fed1c  4000097        8         System.Int32  0 instance        3 m_stringLength

790fbefc  4000098        c          System.Char  0 instance       52 m_firstChar

790fa3e0  4000099       10        System.String  0   shared   static Empty

    >> Domain:Value  0015bf68:790d6584 <<

79124670  400009a       14        System.Char[]  0   shared   static WhitespaceChars

    >> Domain:Value  0015bf68:012a18a8 <<

 

The value of the string is in the “String” field. You can see the value is “Red”.

 

Alright, so now you know how to load SOS, and examine the state of variables in classes. What about getting a stack trace? SOS has three commands for getting a managed stack trance:

  1. !CLRStack – dump only the managed code on the stack for the current thread.
  2. !DumpStack – dump the managed and native stack for the current thread
  3. !EEStack – Execute !DumpStack on all threads.

 

!CLRStack is interesting if you are only looking at managed code. !DumpStack is far more verbose. I recommend turning on the symbol server to point to microsoft’s public symbols if you want to get an accurate stack from !DumpStack. !CLRStack will give you the values of parameters and locals if you pass the –p and –l flags when calling it. Let’s give it a try. From the constructor of Squid, execute !CLRStack. The output should look like this:

OS Thread Id: 0x754 (1876)

ESP       EIP    

0013f3f4 00e30133 ConsoleApplication6.Squid..ctor(System.String, Int32)

0013f440 00e300b2 ConsoleApplication6.Program.Main(System.String[])

0013f69c 79e88f63 [GCFrame: 0013f69c]

 

Now, do the same thing but passing –p and –l for parameters:

!CLRStack -p -l

OS Thread Id: 0x754 (1876)

ESP       EIP    

0013f3f4 00e30133 ConsoleApplication6.Squid..ctor(System.String, Int32)

    PARAMETERS:

        this = 0x01319ea4

        color = 0x01319e8c

        length = 0x0000000a

 

0013f440 00e300b2 ConsoleApplication6.Program.Main(System.String[])

    PARAMETERS:

        args = 0x01319e7c

    LOCALS:

        <CLR reg> = 0x00000000

 

0013f69c 79e88f63 [GCFrame: 0013f69c]

 

If you execute !DumpStack with the symbol path pointed to http://msdl.microsoft.com/download/symbols, you’ll get a much larger callstack. Here is an abbreviated version on my machine:

!DumpStack

OS Thread Id: 0x9fc (2556)

Current frame: _KiFastSystemCallRet@0 [d:\xpsprtm\base\ntos\rtl\i386\userdisp.asm:363]

ChildEBP RetAddr  Caller,Callee

0013ec9c 7c90e9c0 _ZwWaitForSingleObject@12+0xc [d:\xpsprtm\base\ntdll\daytona\obj\i386\usrstubs.asm:2291]

0013eca0 7c8025cb rewait+0xd [d:\nt\base\win32\client\synch.c:1312], calling _ZwWaitForSingleObject@12 [d:\xpsprtm\base\ntdll\daytona\obj\i386\usrstubs.asm:2291]

0013ece4 7a00a929 EEJitManager::JitTokenToMethodHotSize+0x18 [f:\rtm\ndp\clr\src\vm\codeman.h:1540]

0013ed04 79e77fd1 CLREventWaitHelper+0x2f [f:\rtm\ndp\clr\src\vm\synch.cpp:699]

0013ed48 79e77f9a CLREvent::WaitEx+0x117 [f:\rtm\ndp\clr\src\vm\synch.cpp:798], calling CLREventWaitHelper [f:\rtm\ndp\clr\src\vm\synch.cpp:689]

0013ed7c 79f6e717 BaseWrapper<long volatile *,FunctionBase<long volatile *,&CounterIncrease,&CounterDecrease,0>,0,&CompareDefault<long volatile *>,0>::~BaseWrapper<long volatile *,FunctionBase<long volatile *,&CounterIncrease,&CounterDecrease,0>,0,&CompareDefault<long volatile *>,0>+0x23, calling _InterlockedDecrement@4 [d:\nt\base\win32\client\i386\critsect.asm:126]

0013ed98 79e77f50 CLREvent::Wait+0x17 [f:\rtm\ndp\clr\src\vm\synch.cpp:715], calling CLREvent::WaitEx [f:\rtm\ndp\clr\src\vm\synch.cpp:717]

0013eda8 7a0dd76f Thread::WaitSuspendEventsHelper+0xc7 [f:\rtm\ndp\clr\src\vm\threads.cpp:13986], calling CLREvent::Wait [f:\rtm\ndp\clr\src\vm\synch.cpp:714]

0013eddc 7c80261a $L84827 [d:\nt\base\win32\client\synch.c:1323], calling RtlDeactivateActivationContextUnsafeFast [d:\xpsprtm\base\ntdll\sxsctxact.c:614]

0013edf8 7c80a027 SetEvent+0x10 [d:\nt\base\win32\client\synch.c:423], calling _ZwSetEvent@8 [d:\xpsprtm\base\ntdll\daytona\obj\i386\usrstubs.asm:1875]

0013ee2c 7a0dd890 Thread::WaitSuspendEvents+0x16 [f:\rtm\ndp\clr\src\vm\threads.cpp:14025], calling Thread::WaitSuspendEventsHelper [f:\rtm\ndp\clr\src\vm\threads.cpp:13948]

0013ee3c 7a0ddfc8 Thread::RareEnablePreemptiveGC+0x84 [f:\rtm\ndp\clr\src\vm\threads.cpp:10155], calling Thread::WaitSuspendEvents [f:\rtm\ndp\clr\src\vm\threads.cpp:14008]

0013ee48 79f75d16 Thread::RareDisablePreemptiveGC+0xdd [f:\rtm\ndp\clr\src\vm\threads.cpp:9192], calling Thread::EnablePreemptiveGC [f:\rtm\ndp\clr\src\vm\threads.h:1896]

0013ee64 79e74411 ClrFlsSetValue+0x57 [f:\rtm\ndp\clr\src\inc\clrhost.h:179], calling _EH_epilog3 [f:\rtm\vctools\crt_bld\self_x86\crt\prebuild\eh\i386\ehprolg3.c:537]

0013ee68 79e74435 DecCantStopCount+0x10 [f:\rtm\ndp\clr\src\vm\util.hpp:933], calling ClrFlsSetValue [f:\rtm\ndp\clr\src\inc\clrhost.h:167]

0013ee80 7a2a593c PostSendEvent_Internal+0x34 [f:\rtm\ndp\clr\src\debug\ee\debugger.cpp:676]

0013ee88 7a2b95e0 DebuggerController::DispatchPatchOrSingleStep+0x184 [f:\rtm\ndp\clr\src\debug\ee\controller.cpp:2959], calling PostSendEvent_Internal [f:\rtm\ndp\clr\src\debug\ee\debugger.cpp:655]

0013eee8 7a2b973d DebuggerController::DispatchNativeException+0xf2 [f:\rtm\ndp\clr\src\debug\ee\controller.cpp:4369], calling DebuggerController::DispatchPatchOrSingleStep [f:\rtm\ndp\clr\src\debug\ee\controller.cpp:2789]

0013ef28 7a2a4cb9 Debugger::FirstChanceNativeException+0x37 [f:\rtm\ndp\clr\src\debug\ee\debugger.cpp:5005], calling DebuggerController::DispatchNativeException [f:\rtm\ndp\clr\src\debug\ee\controller.cpp:4222]

0013ef60 7a05f8dd CLRVectoredExceptionHandlerPhase2+0x97 [f:\rtm\ndp\clr\src\vm\excep.cpp:7287]

0013ef88 79e74411 ClrFlsSetValue+0x57 [f:\rtm\ndp\clr\src\inc\clrhost.h:179], calling _EH_epilog3 [f:\rtm\vctools\crt_bld\self_x86\crt\prebuild\eh\i386\ehprolg3.c:537]

0013efa0 79f97730 CLRVectoredExceptionHandler+0xd3 [f:\rtm\ndp\clr\src\vm\excep.cpp:7202], calling CLRVectoredExceptionHandlerPhase2 [f:\rtm\ndp\clr\src\vm\excep.cpp:7206]

0013efd0 79f9484a CPFH_FirstPassHandler+0x5a [f:\rtm\ndp\clr\src\vm\i386\excepx86.cpp:1300], calling CLRVectoredExceptionHandler [f:\rtm\ndp\clr\src\vm\excep.cpp:7098]

0013efec 7c93799c RtlLookupFunctionTable+0xae [d:\xpsprtm\base\ntos\rtl\lookup.c:539], calling RtlCaptureImageExceptionValues [d:\xpsprtm\base\ntos\rtl\lookup.c:320]

0013effc 79f949a2 COMPlusFrameHandler+0x120 [f:\rtm\ndp\clr\src\vm\i386\excepx86.cpp:1722], calling CPFH_FirstPassHandler [f:\rtm\ndp\clr\src\vm\i386\excepx86.cpp:1279]

0013f01c 7c9037bf ExecuteHandler2@20+0x26 [d:\xpsprtm\base\ntos\rtl\i386\xcptmisc.asm:224]

0013f040 7c90378b ExecuteHandler@20+0x24 [d:\xpsprtm\base\ntos\rtl\i386\xcptmisc.asm:184], calling ExecuteHandler2@20 [d:\xpsprtm\base\ntos\rtl\i386\xcptmisc.asm:200]

0013f064 7c937860 RtlDispatchException+0xb1 [d:\xpsprtm\base\ntos\rtl\i386\exdsptch.c:400], calling _RtlpExecuteHandlerForException@20 [d:\xpsprtm\base\ntos\rtl\i386\xcptmisc.asm:99]

0013f0a8 7c96e0d4 $L38802+0x8 [d:\xpsprtm\base\ntos\rtl\heapdbg.c:905], calling __SEH_epilog [d:\xpsprtm\base\crts\crtw32\misc\i386\sehprolg.asm:56]

0013f0ac 7c94a5d0 RtlFreeHeapSlowly+0x37 [d:\xpsprtm\base\ntos\rtl\heap.c:3960], calling RtlDebugFreeHeap [d:\xpsprtm\base\ntos\rtl\heapdbg.c:803]

0013f0b8 7c926abe $L40961+0x80 [d:\xpsprtm\base\ntos\rtl\heap.c:4334], calling __SEH_epilog [d:\xpsprtm\base\crts\crtw32\misc\i386\sehprolg.asm:56]

0013f0c8 7c91056d $L41223+0x30 [d:\xpsprtm\base\ntos\rtl\heap.c:3895], calling __SEH_epilog [d:\xpsprtm\base\crts\crtw32\misc\i386\sehprolg.asm:56]

0013f0f0 7c90eafa _KiUserExceptionDispatcher@8+0xe [d:\xpsprtm\base\ntos\rtl\i386\userdisp.asm:219], calling RtlDispatchException [d:\xpsprtm\base\ntos\rtl\i386\exdsptch.c:504]

0013f3f4 00de010e (MethodDesc 0x9030b0 +0x2e ConsoleApplication6.Squid..ctor(System.String, Int32)) ====> Exception cxr@13f128

 

 

 

 

Simple Test App:

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication6
{
    class Squid
    {
        public static int numRefs;       
        private string _color;
        private int _length;
        private int posX;
        private int posY;

        public Squid(string color, int length)
        {
            this.Color = color;
            this.Length = length;
            posX = 0;
            posY = 0;
            Squid.numRefs++;
        }

        public string Color
        {
            get
            {
                return _color;
            }
            set
            {
                _color = value;
            }
        }

        public int Length
        {
            get
            {
                return _length;
            }
            set
            {
                _length = value;
            }
        }

        public void Swim(int newX, int newY)
        {

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Squid s = new Squid("Red", 10);
            s.Swim(15, 15);
        }
    }
}

VSIP Debug Engine Sample Walkthrough2

I have uploaded another walkthrough for the new debug engine sample. This one explains threads and modules in the sample engine, the AD7 events corresponding to them, and the objects that implement them. It is available along-side the other walkthroughs and the actual sample download at http://code.msdn.microsoft.com/debugenginesample

 

Walkthrough1 now available for Debug Engine Sample

I have written a guided walkthrough for the new Debug Engine Sample available in VSIP. The walkthrough contains a high-level architecture description and a source code walkthrough of how the sample engnes launches debug processes. It demonstrates: launching the process and sending debug events. I plan on adding some more of these walkthroughs as I have time.

I also posted an update the sample itself to fix a setup issue and a race condtion. Version 1.1 supercedes the original verison.

 The walkthrough and the new sample release is available here:

http://code.msdn.microsoft.com/debugenginesample

New Debug Engine Sample!

The Visual Studio Debugger team has just released a new sample for writing a Visual Studio Debug Engine. The new engine sample is available at: http://code.msdn.microsoft.com/debugenginesample

A debug engine is a component that plugs into Visual Studio's debugger UI and actually does the debugging for whatever is being debugged. Each debug engine is responsible for debugging a particular architecture or runtime. Why so much interest in writing one? Each runtime architecture, whether it be native code running on a processor, a jit compiled runtime, or an interpreter, requires a debug engine that understands that architecture.

A custom debug engine is required when adding debugging support for a new platform technology into Visual Studio. .Net language implementers should implement an Expression Evaluator instead which integrates into the existing .Net debug engine.

The Visual Studio Debugger is split up architecturally into a few pieces:

1)      The debugger UI: the windows and commands the user actually interacts with.  A good example of this is the watch window or the little red circle that appears in the text editor as a breakpoint.

2)      The SDM (session debug manager): “ A debug engine multiplexer” Admittedly, that’s a confusing explanation.
Essentially, the SDM’s job to combine all of the events and commands for the various debug engines into one unified stream for the UI.  The debugger UI only displays one ”view” of what is being debugged at a time.  Even if the user is debugging multiple processes or threads, they are only looking at one of them.

3)      Debug Engines – the components that perform the actual debugging of a debuggee.  For instance, a native debug engine would be responsible for debugging native win32 applications. A script debug engine would be responsible for debugging jscript or vbscript. A CLR debug engine would be responsible for debugging .Net applications running on the CLR.  A hypothetical Perl engine could be responsible for debugging Perl… and on and on

Visual Studio Debug Engines implement and interact with a set of interfaces called AD7 which stands for Active Debugging 7. AD7 is publically documented here: http://msdn2.microsoft.com/en-us/library/bb147088.aspx

This new debug engine sample demonstrates the majority of AD7 and debugs native win32 applications. It currently supports: launching, attaching, breakpoints, basic callstack walking, module and thread enumeration, and debug properties (parameters and locals). It does not implement more advanced features such as conditional breakpoints and tracepoints.

The sample is split into three separate projects:

1)      A front end implemented in C# which implements the AD7 interfaces and interacts with the SDM. The root object of this project is AD7Engine which implements IDebugEngine2. This project is called Microsoft.VisualStudio.Debugger.SampleEngine.

2)      A back end implemented in mixed mode C++ which interacts with the win32 debugging API and DIA, the public symbol interfaces. This project is called Microsoft.VisualStudio.Debugger.SampleEngineWorker.

3)      A Visual Studio Addin which is used to launch Visual C++ projects using the new engine. This project is named ProjectLauncher. It adds a new command button “ProjectLauncher” to the Visual Studio Tools menu which will launch a form that displays the current projects in Visual Studio that can be launched with the new engine.

Currently, the documentation for the new engine is the comments in the code. We will be adding new blog entries and walkthroughs as time allows. Please check back with my blog as well as the sample page for future updates and walkthroughs.

 

Using SOS in Visual Studio

One little known feature of Visual Studio 2005 is its ability to load and use the SOS debugging extension. SOS (Son of Strike) is an extension for the debuggers available in  Debugging Tools for Windows  that makes it possible to symbolically debug managed .Net applications using debuggers such as Windbg. Although I am capable of using Windbg and the like for debugging native code, I definitely prefer VS when it comes to common debugging tasks such as breakpoints and viewing data. Loading SOS in VS gives you the best of both worlds: the relaxed VS debugging environment and the power of SOS.

Sos has a larger learning curve than VisualStudio, and I have found that this scares a lot of developers away from it. However, it really is worth time to learn to use it. Why? For two reasons: It has some very powerful features not available directly in Visual Studio and it enables you to debug managed code in crash dumps. (Yes, it is true that the Visual Studio debugger does not currently support debugging managed code from a crash dump).

How do you go about using SOS in VS? Well first, you have to have the native debugging engine enabled. This is because SOS examines the CLR data structures from the debugger process so it must have access to the native address space. To do this from a console application project or winforms project, right mouse click on the project in solution explorer, choose properties, switch to the Debug tab, and make sure "Enable Unmanaged Code Debugging" is selected. With that done, close the property page add a breakpoint somewhere in your project, and hit F5. Once you've hit your breakpoint, the real fun begins.

1) Open the Immediate window (Debug->Windows->Immediate)

2) Type ".load C:\windows\Microsoft.NET\Framework\v2.0.50727\sos.dll" and hit enter (you may need to change the path to point to a different location on your machine). The immediate window should respond by saying "extension C:\windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded"

3) Type "!help" sos should dump its help contents to the immediate window. It is now ready to use.

So, what are some of those super powerful commands that SOS gives you? Well, for one thing, it gives you a "closer to the metal" view of your managed application. For a lot of these operations you'll need to understand a little bit about the data structure the CLR uses to represent your app while its running. I'll save that for the next post. One example of such a command is that SOS can dump the MSIL for a particular method. MSIL is the intermediate "assembly" language that the managed compilers all create from your source code. This is a feature I've heard several people ask for (if only to satisfy their own curiosity). The JIT compiler in the CLR converts the MSIL into native assembly language while your application runs. Visual Studio's dissassembly window dumps the native (post-jit instructions) and doesn't currently provide a way to see the MSIL.

How do you dump the MSIL for the current managed location?

1) with SOS loaded and your application in Break mode (sitting at a breakpoint) type !CLRStack. Sos will respond by printing the managed callstack for the current thread. (this is basically the equivalent of what the callstack would display). It might look something like:

!clrstack

OS Thread Id: 0xffc (4092)

ESP        EIP

0013f43c 00e3009d ConsoleApplication4.Program.Main(System.String[])

0013f69c 79e88f63 [GCFrame: 0013f69c]

2) The numbers under the EIP column are the addresses of the instruction pointer at that frame. Highlight the first one (00e3009d) and copy it to the clipboard.

3) Next, execute "!ip2md 00e3009d" where 00e3009d is the address you copied to the clipboard. SOS will respond with:

!ip2md 00e3009d

MethodDesc: 00903120

Method Name: ConsoleApplication4.Program.Main(System.String[])

Class: 009012e8

MethodTable: 00903130

mdToken: 06000001

Module: 00902d5c

IsJitted: yes

m_CodeOrIL: 00e30070

This information contains the addresses of several CLR data structures and some information about the frame (such as whether or not it has been jit-compiled). We are interested in the MethodDesc address for this operation so copy its value (00903120) to the clipboard.

4) Finally, execute "!DumpIL 00903120"  where 00903120 is the address of the methoddesc field. Sos will now dump:

!dumpil 00903120

ilAddr = 00402050

IL_0000: nop

IL_0001: ldc.i4.0

IL_0002: stloc.0

IL_0003: ldloc.0

IL_0004: ldc.i4.1

IL_0005: add

IL_0006: stloc.0

IL_0007: ldloc.0

IL_0008: dup

IL_0009: ldc.i4.1

IL_000a: add

IL_000b: stloc.0

IL_000c: stloc.1

IL_000d: ldloca.s VAR OR ARG 1

IL_000f: call System.Int32::ToString

IL_0014: call System.Console::WriteLine

IL_0019: nop

IL_001a: ret

And there you have it. The MSIL for the method.

In my next posting, I plan to go over some of these data structures more and explain some more of SOS's commands. For those who are curious, you can read the documentation for any sos command using the !help command. For instance, you can read about !DumpIL by executing !help DumpIL in the immediate window when SOS is loaded.

Jackson

This posting is provided "AS IS" with no warranties, and confers no rights.

 

Page view tracker