Sunday, May 22, 2011

Becoming a Console Programmer : Extending The Watch Window (debugger addin)

So previously we established how to use autoexp.dat for simple format changes and how to use a DLL to achieve those changes. There are major limitations to the DLL approach however in that we cannot access global symbols and thus any system that relies on global state for debug (as many do) cannot be debugged.


Here I will discuss how to create a VS2010 plugin using C# (plugins also work for VS2008 however not using C#).

The Development Tools Environment framework was setup around a decade ago to allow Visual Studio extensibility. I would recommend that any programmers read through what is possible using DTE however i'm not going to invest much time into that here; I will simply use one aspect of it to achieve our goal: global symbol access.

VS2010 has a great Wizard to help us here, Select File->New->Project then on the left hand side go to Other Project Types->Extensibility->Visual Studio Addin, click next and then choose option (default) Create an Add-in using Visual C#, leave application host as default, then give your addin a recognizable name. Set your addin to load when host application starts. The rest is default.

This provides us with a very simple addin with a LOT of C# goodness and very little code. I should note that when i wrote this project it was my first foray into C# and thus my knowledge in this area is shady at best, i had lots of help from experts local to me in setting this up.

If you read through the code you will find some relatively basic functionality. Your new addin has interfaces to intercept callbacks for various VS2010 events, OnConnection is the only default interface i used during which i installed new event handlers.

So lets extend the default a little:

  1. Add a member to the class of type "DebuggerEvents"
  2. instantiate it to "_applicationObject.Events.DebuggerEvents"
  3. setup handles for:
    1. OnEnterBreakMode
    2. OnEnterRunMode
    3. OnEnterDesignMode
  4. VS2010 should auto-complete and auto generate these for you but the basics should look like :
    _debugger_events.OnEnterBreakMode += new _dispDebuggerEvents_OnEnterBreakModeEventHandler(DebuggerEvents_OnEnterBreakMode);
    _debugger_events.OnEnterRunMode += new _dispDebuggerEvents_OnEnterRunModeEventHandler(_debugger_events_OnEnterRunMode);
    _debugger_events.OnEnterDesignMode += new _dispDebuggerEvents_OnEnterDesignModeEventHandler(_debugger_events_OnEnterDesignMode);
     
  5. For now ignore all but OnEnterBreakMode, this will be called whenever we enter any type of breakmode (stepping, breakpoints, exceptions etc). At this point we can do something quite nifty... any expression you can type into a watch window, can be evaluated here for example
    Debugger debugger = _application_object.Debugger;
    Expression exp = debugger.GetExpression("&g_my_global_foo,d");
    
    if (exp.IsValidValue)
    {
        Int64 value = Convert.ToInt64(exp.Value.ToString(), 10);
        System.Diagnostics.Debug.WriteLine(exp.Value.ToString());
    
        // do other fancy things here
    }
  6. At this point its really up to you to choose how you communicate this information to any external process. Personally my first simple solution was old school environment variable. This works perfectly well for a limited project, simply push your symbol address to the environment variable and let any other process (EEAddin for instance) read that environment variable and use it. Should you choose to publish the root address of your global systems for instance one might conceivably achieve full global memory addressing on your target.
  7. The OnEnterRunMode hook should be used to "create" any resources you need on events "Attach" and "Launch"
  8. The OnEnterDesignMode hook should be used to "destroy" those resources for events "Detach, EndProgram & Stop Debugging"
Debugging the debugger addin isn't difficult but is not obvious. Given the setup (Client Project) using (Debugger Addin) which launches Watch Window DLLS (EEAddin) the debugging method i used is:

  1. Open the Debugger Addin project and build/run. This should open up an instance of VS2010 with your Addin loaded (check under Tools->Addin Manager
  2. Within this second instance of VS2010 open up your Client Project, Build and run.
  3. At this point breakpoints within the Debugger Addin are possible and you should be able to trap the incoming events.


Notes:
  • using environment variables will work for very simple projects however for production code i would recommend using process specific memory mapped files or another less hacky method
  • VS2008 can be supported however this has to be done as a C++/ATL project and requires a LOT of boilerplate code. Jason Weiler @ Airtight discusses this here and played a large part in enlightening me to this process.
  • As previous with EEAddin, this framework is not very forgiving and will require you to write defensively in all aspects. C# does handle a lot of this for you however the C++/ATL version will not. If you are writing an addin framework for a large group of programmers i would suggest significant boilerplate on all code.
  • the DTE framework is VERY powerful, anyone using it should read through the docs thoroughly to appreciate everything it can do and share what you do if you can :D

No comments: