Welcome to MSDN Blogs Sign in | Join | Help

Sample code for MDbg-IronPython extension

//-----------------------------------------------------------------------------
// Simple extension to hookup Iron Python to Mdbg
//
// Mike Stall:  http://blogs.msdn.com/jmstall 
// IronPython from here: http://workspaces.gotdotnet.com/ironpython   
// Mdbg here: http://blogs.msdn.com/jmstall/archive/2005/11/08/mdbg_linkfest.aspx 
// 
// This targets the IronPython Beta 1 release (which has breaking changes from .0.9.3)
//
// To use this extension, you must build it as a dll, and then load it
// into mdbg via the "Load" command. 
//
// This requires a reference to the usual culprit of Mdbg extension dlls, 
// as well as to IronPython.dll
//
// You can build this in Visual Studio 2005 by adding a new Class Library project
// to the Mdbg sample, and then adding the proper references. 
//-----------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Reflection;

using Microsoft.Samples.Tools.Mdbg;
using Microsoft.Samples.Debugging.MdbgEngine;
using Microsoft.Samples.Debugging.CorDebug;

using System.Text.RegularExpressions;
using System.Globalization;
using System.IO;
using SS = System.Diagnostics.SymbolStore;


// extension class name must have [MDbgExtensionEntryPointClass] attribute on 
// it and implement a LoadExtension()
[MDbgExtensionEntryPointClass( Url = "http://blogs.msdn.com/jmstall", ShortDescription = "Eventing test extension." )] public class PythonExt : CommandBase { // Adapter to expose some methods to Python // avoid static methods. class Util { public void ExecuteCommand(string arg) { CommandBase.ExecuteCommand(arg); } } // This is called when the python extension is first loaded. public static void LoadExtension() { MDbgAttributeDefinedCommand.AddCommandsFromType(Shell.Commands, typeof(PythonExt)); WriteOutput("IronPython-Mdbg Extension loaded"); m_python = new IronPython.Hosting.PythonEngine(); // Set =true to avoid having IronPython output files for everything it compiles. //IronPython.AST.Options.DoNotSaveBinaries = true; // Add the current directory to the python engine search path. // @todo - could add the symbol path Debugger.Options.SymbolPath here too. m_python.AddToPath(Environment.CurrentDirectory); // Tell Python about some key objects in Mdbg. Python can then reflect over these objects. // These variables live at some special "main" scope. Python Modules imported via python "import" command // can't access them. Use PythonEngine.ExecuteFile() to import files such that they can access these vars. WriteOutput("Adding variable 'Shell' to python main scope"); m_python.SetVariable("Shell", Shell); m_python.SetVariable("MDbgUtil", new Util()); // See MyResolveHandler for reasons why we hook this. // Only need this for "import" command. AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(MyResolveHandler); // Hook console. The Python Console refers to the pysical input + output from the python UI and // is separate from output of actual python commands (like "print"). // We need this to enter interactive mode. m_python.MyConsole = new MyMdbgConsole(); // Hook input + output. This is redirecting pythons 'sys.stdout, sys.stdin, sys.stderr' // This connects python's sys.stdout --> Stream --> Mdbg console. Stream s = new MyStream(); #if false // This is for versions before .0.9.3 // IronPython.Modules.sys.stdin = // IronPython.Modules.sys.stderr = // IronPython.Modules.sys.stdout = new IronPython.Objects.PythonFile(s, "w", false); #elif false // 0.9.3. breaks the above line because it adds a "name" string parameter. Here's what it should be in 0.9.3: //IronPython.Objects.Ops.sys.stdin = new IronPython.Objects.PythonFile(s, "stdin", "r", false); //IronPython.Objects.Ops.sys.stderr = new IronPython.Objects.PythonFile(s, "stderr", "w", false); //IronPython.Objects.Ops.sys.stdout = new IronPython.Objects.PythonFile(s, "stdout", "w", false); #else // Beta 1 breaks the above again. IMO, this integrates into .NET much cleaner: m_python.SetStderr(s); m_python.SetStdin(s); m_python.SetStdout(s); #endif } // Stream to send Python Output to Mdbg console. // This can be used to redirect python's sys.stdout. class MyStream : Stream { #region unsupported Read + Seek members public override bool CanRead { get { return false; } } public override bool CanSeek { get { return false; } } public override bool CanWrite { get { return true; } } public override void Flush() { // nop } public override long Length { get { throw new NotSupportedException("Seek not supported"); } // can't seek } public override long Position { get { throw new NotSupportedException("Seek not supported"); // can't seek } set { throw new NotSupportedException("Seek not supported"); // can't seek } } public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException("Reed not supported"); // can't read } public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException("Seek not supported"); // can't seek } public override void SetLength(long value) { throw new NotSupportedException("Seek not supported"); // can't seek } #endregion public override void Write(byte[] buffer, int offset, int count) { // Very bad hack: Ignore single newline char. This is because we expect the newline is following // previous content and we already placed a newline on that. if (count == 1 && buffer[offset] == '\n') return; // Code update from ShawnFa to fix case for '\r' StringBuilder sb = new StringBuilder(); while (count > 0) { char ch = (char)buffer[offset]; if (ch == '\n') { Shell.IO.WriteOutput("STDOUT", sb.ToString()); sb.Length = 0; // reset. } else if (ch != '\r') { sb.Append(ch); } offset++; count--; } // Dump remainder. @todo - need some sort of "Write" to avoid adding extra newline. if (sb.Length > 0) Shell.IO.WriteOutput("STDOUT", sb.ToString()); } } // Console hook to redirect IronPython output to MDbg's console. // This allows us to enter PythonInteractive mode. // This is very important if MDbg's console is redirected (eg, to a GUI) class MyMdbgConsole : IronPython.Hosting.IConsole { #region IConsole Members // The keyword they type in to exit the console. // They enter via "Pyin", so "Pyout" seems a good choice. Don't want to pick something like "exit" // that would cause Mdbg to exit if accidentally used at the Mdbg prompt. public static readonly string ExitKeyword = "pyout"; // Sends the 'end-of-file' via returning null. public string ReadLine() { string text; Shell.IO.ReadCommand(out text); // If they type the exit keyword, we'll translate that to EOF and // back out of the python interactive shell. if (text == ExitKeyword) { text = null; } return text; } // !!! Oh no!! How do we map Write() when we only have WriteLine()? // Queue all Writes and then flush them? Still doesn't account for styles. public void Write(string text, IronPython.Hosting.Style style) { WriteLine(text, style); } public void WriteLine(string text, IronPython.Hosting.Style style) { WriteOutput(text); } // @todo - Map from IronPython.Hosting.Style --> Mdbg console. #endregion } // If Python Options.DoNotSaveBinaries=false (the default), then it will try to load the newly // generated assembly using LoadFrom(), and likely fail to find resolve the references. // Need this hack to cover that up. // @todo - there has to be a better way... static Assembly MyResolveHandler(object sender, ResolveEventArgs args) { string name = args.Name; string[] x = name.Split(','); string n = x[0]; if (n == "IronPython") { Type t = typeof(IronPython.Hosting.PythonEngine); return t.Assembly; } return null; } // The main python engine. static IronPython.Hosting.PythonEngine m_python; // Command to import python modules into the same scope that we called SetVariable // on during initialization. This lets these modules access the key Mdbg vars // and thus traverse the Mdbg tree. // You can reload a file by just reimporting it. // // args is the filename to load [ CommandDescription( CommandName = "pimport", MinimumAbbrev = 2, ShortHelp = "Import python script", LongHelp = @" Imports a python script into the evaluation scope so that it can access Mdbg vars. You can reload a file by importing it multiple times. " ) ] public static void PythonImport(string args) { m_python.ExecuteFile(args); } // Enter Python-interactive mode. // This drops us to a python prompt: // - so we don't need to use the "python" mdbg command to execute python commands). // - It also lets us type multi-line python commands (like defining functions). [ CommandDescription( CommandName = "pyinteractive", MinimumAbbrev = 4, ShortHelp = "Go into python interactive mode", LongHelp = "Enter python interactive mode, like what you'd have in a python shell" ) ] public static void PythonInteractive(string args) { WriteOutput("Entering Python Interactive prompt."); WriteOutput("Type '" + MyMdbgConsole.ExitKeyword + "' leave PythonInteractive mode and get back to Mdbg prompt."); // This will call back via our user supplied IConsole interface (MyMdbgConsole class). // It keep fetching commands via IConsole.ReadLine() and not return until that yields null. m_python.RunInteractive(); WriteOutput("Leaving Python Interactive prompt."); } // Execute a python command. Args is the command to execute. // Commands can include expressions as well as defining functions. [ CommandDescription( CommandName = "python", MinimumAbbrev = 2, ShortHelp = "Execute a single python command", LongHelp = @"Execute a single python command or expression. Example: py 1+2 py def MyAdd(a,b): return a + b " ) ] public static void PythonCommand(string args) { // Executes 1 Python command. m_python.Execute(args); } // Eval a python expression. Args is the command to execute. [ CommandDescription( CommandName = "peval", MinimumAbbrev = 2, ShortHelp = "Eval python expression", LongHelp = @"Execute a single python expression." ) ] public static void PyEval(string args) { // Executes 1 Python command. object o = m_python.Evaluate(args); PrintPythonResult(o); } // Helper to print a result static void PrintPythonResult(object o) { if (o == null) { WriteOutput("Result:null"); } else { WriteOutput("Result:" + o.ToString() + " (of type=" + o.GetType() + ")"); } } #region Python Conditional Breakpoint // Conditional breakpoint which executes an IronPython expression when hit. class PythonBreakpoint : MDbgFunctionBreakpoint { public PythonBreakpoint(string pythonCommand, MDbgBreakpointCollection breakpointCollection, ISequencePointResolver location) : base(breakpointCollection, location) { m_pythonCommand = pythonCommand; } string m_pythonCommand; // Return null to continue // Return non-null for a stop-reason to stop the shell. public override object OnHitHandler(CustomBreakpointEventArgs e) { WriteOutput("Python BP hit:" + m_pythonCommand); { // Compensate for MDbg bug. Need to refresh stack because we're in a callback. Debugger.Processes.Active.Threads.RefreshStack(); } object o = null; try { // Execute the python expression to determine if we should stop or not. o = m_python.Evaluate(m_pythonCommand); PrintPythonResult(o); if (o == null) { WriteOutput("Null stop reason - continuing process from breakpoint."); } if (o is bool) { // Special case common scenario for boolean expressions if ((bool)o) { WriteOutput("Expression is true. Stopping at breakpoint"); return true; } else { WriteOutput("Expression is false. continuing"); return null; } } else { WriteOutput("Non-null stop reason. Halting process at breakpoint."); } return o; } catch (System.Exception ex) { o = "Exception thrown:" + ex.Message; WriteOutput("Exception thrown. Stopping at breakpoint."); return o; } } public override string ToString() { return base.ToString() + "(python:" + m_pythonCommand + ")"; } } [ CommandDescription( CommandName = "pyb", MinimumAbbrev = 3, ShortHelp = "Conditional Python Breakpoint", LongHelp = @"Usage: pyb <breakpoint args> '|' <python expression> Creates a conditional breakpoint at the given location. When the BP is hit, the python expression is evaluated for a stop-reason. Iff it is null or False, the bp continues. Breakpoint syntax is the same as the 'break' command. example: pyb 23 | func(1) " ) ] public static void PythonBreakpointCmd(string args) { // We're adding a breakpoint. Parse the argument string. MDbgProcess p = Debugger.Processes.Active; MDbgBreakpointCollection breakpoints = p.Breakpoints; // Use a bar '|' to split breakpoint location from python string. BP loc comes // first because it doesn't contain a bar, so that simplifies parsing. Everything // after the bar is a python expression. int idx = args.IndexOf('|'); if (idx < 0) { throw new ArgumentException("Expected '|' to separate python expression from breakpoint location."); } string stLoc = args.Substring(0, idx - 1); string stPythongExpression = args.Substring(idx + 1); ISequencePointResolver bploc = Shell.BreakpointParser.ParseFunctionBreakpoint(stLoc); if (bploc == null) { throw new Exception("Don't understand the syntax"); } PythonBreakpoint bp = new PythonBreakpoint(stPythongExpression, breakpoints, bploc); WriteOutput(bp.ToString()); } #endregion Python Conditional Breakpoint } // end class for my extensions.
Published Wednesday, August 31, 2005 7:34 PM by jmstall

Comments

# Adding IronPython scripting engine to Mdbg

Wednesday, August 31, 2005 9:49 PM by Mike Stall's .NET Debugging Blog
I hear IronPython is a great managed scripting language to embed in other managed apps, so I thought...

# How to embed IronPython script support in your existing app in 10 easy steps

Thursday, September 01, 2005 11:05 AM by Mike Stall's .NET Debugging Blog
Previously, I added IronPython scripting support to a real existing application, MDbg (a managed debugger...

# IronPython + MDbg == good times

Friday, September 02, 2005 5:02 PM by .Net Security Blog
Mike Stall recently completed a project to embed IronPython into the MDbg debugger as an MDbg extension.&amp;nbsp;...

# IronPython + MDbg = good times

Friday, September 02, 2005 5:38 PM by .Net Security Blog
Mike Stall recently completed a project to embed IronPython into the MDbg debugger as an MDbg extension.&amp;nbsp;...

# IronPython + MDbg = good times

Friday, September 02, 2005 7:07 PM by .Net Security Blog
Mike Stall recently completed a project to embed IronPython into the MDbg debugger as an MDbg extension.&amp;nbsp;...

# IronPython + MDbg = good times

Friday, September 02, 2005 8:41 PM by .Net Security Blog
Mike Stall recently completed a project to embed IronPython into the MDbg debugger as an MDbg extension.&amp;nbsp;...
New Comments to this post are disabled
 
Page view tracker