IronPython + MDbg = good times

Mike Stall recently completed a project to embed IronPython into the MDbg debugger as an MDbg extension.  IronPython's hosting interface is pretty slick, in fact it took Mike only 10 steps to get IronPython running inside MDbg and expose the debugger functionality to Python scripts via a variable named Shell.

After playing with Mike's extension for a few hours, I'm absolutely amazed by not only how easy it is to use, but how seamless the integration is.  For instance, I haven't used Python since college, and have never written an MDbg extension before.  However, less than an hour after I sat down to play with this I was able to write a script (which is less than 125 lines of code) that pops up a tree view displaying the processes being debugged, its AppDomains (and the assemblies loaded into them), and the threads with call stacks and locals:

PythonExt State Window

It took only about 10-15 minutes to write a quick script that enables you to set the debugger options via a GUI, which should cut down on the amount of time I spend trying to remember which letter combinations I want to type after "catch" :-)

PythonExt Options GUI

Since IronPython allows you to use most managed code, including the majority of the frameworks themselves from within your scripts, I used the System.Diagnostics.Process class to kick off a native debugger in order to snap a minidump of the process being debugged:

def MiniDump(path = "%TEMP%\\mdbg.dmp.cab", options = "/ma /ba", symbols = "srv**https://msdl.microsoft.com/download/symbols", debugger = "%WINDIR%\\\\System32\\\\ntsd.exe"):
    pid = Shell.Debugger.Processes.Active.CorProcess.Id
    path = Environment.ExpandEnvironmentVariables(path)
    executeString = ".dump %s %s; .detach; q" % (options, path)
    commandLine = '-c "%s" -pv -y %s -p %d' % (executeString, symbols, pid)

    print "Writing minidump %s" % path
    debugger = Process.Start(Environment.ExpandEnvironmentVariables(debugger), commandLine)
    debugger.WaitForExit()

Of course not wanting to leave well enough alone, I made a couple of modifications to the extension (original source code here). First, I added some new paths to the Python path:

  • %AllUsersProfile%\ApplicationData\MDbg\Scripts
  • %AppData%\MDbg\Scripts
  • The path the MDbg.exe is running in

// Add the all user app data\mdbg\scripts, appdata\mdbg\scripts, mdbg.exe path, and current directory
// to the python engine search path.
// @todo - could add the symbol path Debugger.Options.SymbolPath here too.
string scriptPathSuffix = Path.DirectorySeparatorChar +
    Assembly.GetEntryAssembly().GetName().Name +
    Path.DirectorySeparatorChar +
    "Scripts";
string[] pythonPath = new string[]
{
    Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + scriptPathSuffix,
    Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData) + scriptPathSuffix,
    new FileInfo(Assembly.GetEntryAssembly().Location).DirectoryName,
    Environment.CurrentDirectory
};
m_python.AddToPath(Environment.CurrentDirectory);
        
foreach(string path in pythonPath)
    m_python.AddToPath(path);

After that, I added the ability to have an autorun script, Startup.py.  As the last step of initialization, I simply look for a Startup.py in each of the directories on the path, and if I find it kick it off:

    // Finally, run each Startup.py
    WriteOutput("Running startup scripts");
    foreach(string path in pythonPath)
    {
        string startup = path + Path.DirectorySeparatorChar + "Startup.py";
        if(File.Exists(startup))
            m_python.ExecuteFile(startup);
    }
} // end of PythonExt::LoadExtension

Finally, I added a PythonExtVersion property to the MDbgUtil variable, which allows a script to find out what version of the MDbg python extension it's running under.

class Util
{
    private static Version version = new Version(1, 1, 0, 0);
       
    public Version PythonExtVersion
    {
        get { return version; }
    }

This is pretty powerful stuff -- I can easily see not wanting to run MDbg without the ability to lo PythonExt for anything but trivial debugging.  Considering how easy it was to get this up and running, if you've got a decent object model to expose to users, embedding IronPython into your application really opens up new doors for your users to use your application in ways you might not have thought of (or didn't have time to implement yourself) -- yet makes their experience much better.

Updated 4:06pm: Added links to StateWindow.py and OptionsGui.py