Quantcast
Channel: Mike Stall's .NET Debugging Blog
Viewing all 35 articles
Browse latest View live

Simple harness to print exceptions in an app

$
0
0

Several people have asked how to write something that runs some executable under a harness and then dumps all the exceptions that are thrown.
Back in November, I wrote  a similar harness to dump load module events using MDbg.  You can easily modify that to dump exceptions. See that blog entry for an explanation of how the code below works.

Here's sample code for a harness to print exceptions.

// Simple harness to dump exceptions.
// Be sure to reference MdbgCore.dll (which ships in the Whidbey beta 2 SDK)
using System;
using Microsoft.Samples.Debugging.MdbgEngine;

class Program
{
    static void Main(string[] args)
    {
        if (args == null || args.Length != 1)
        {
            Console.WriteLine("Usage: PrintEx <filename>");
            Console.WriteLine("   Will run <filename> and print all exceptions.");
            return;
        }
        Console.WriteLine("Run '{0}' and print all exceptions.", args[0]);

        MDbgEngine debugger = new MDbgEngine();
        debugger.Options.CreateProcessWithNewConsole = true;

        // Specify which debug events we want to receive.
        // The underlying ICorDebug API will stop on all debug events.
        // The MDbgProcess object implements all of these callbacks, but only stops on a set of them
        // based off the Options settings.
        // See CorProcess.DispatchEvent and MDbgProcess.InitDebuggerCallbacks for more details.
        debugger.Options.StopOnException = true;
        //Do 'debugger.Options.StopOnExceptionEnhanced = true;' to get additional exception notifications.
       
        // Launch the debuggee.
        MDbgProcess proc = debugger.CreateProcess(args[0], "", DebugModeFlag.Default, null);
       
        while(proc.IsAlive)
        {           
            // Let the debuggee run and wait until it hits a debug event.
            proc.Go().WaitOne();
            object o = proc.StopReason;

            // Process is now stopped. proc.StopReason tells us why we stopped.
            // The process is also safe for inspection.           
            ExceptionThrownStopReason m = o as ExceptionThrownStopReason;
            if (m != null)
            {
                try
                {
                    MDbgThread t = proc.Threads.Active;
                    MDbgValue ex = t.CurrentException;
                    MDbgFrame f = t.CurrentFrame;
                    Console.WriteLine("Exception is thrown:" + ex.TypeName + "(" + m.EventType +
                        ") at function " + f.Function.FullName + " in source file:"
                         + t.CurrentSourcePosition.Path + ":" + t.CurrentSourcePosition.Line);
                }
                catch(Exception e   )
                {
                    Console.WriteLine("Exception is thrown, but can't inspect it.");
                }
            }       
        }

        Console.WriteLine("Done!");
    }
} // end class

So compile the above (making sure you include the reference to mdbgcore.dll) and then run it on an app like:

using System;

class Foo
{
static void Main()
{
	System.Console.WriteLine("Hi!");
	try {
		throw new Exception("Bong!"); // line 11
	}
	catch(System.Exception)
	{
	}
	
	throw new Exception("Bong#2"); // line 15

}
}

And it prints:
Run 'c:\temp\t.exe' and print all exceptions.
Exception is thrown:System.Exception(DEBUG_EXCEPTION_FIRST_CHANCE) at function Foo.Main in source file:c:\temp\t.cs:11
Exception is thrown:System.Exception(DEBUG_EXCEPTION_FIRST_CHANCE) at function Foo.Main in source file:c:\temp\t.cs:15
Exception is thrown:System.Exception(DEBUG_EXCEPTION_UNHANDLED) at function Foo.Main in source file:c:\temp\t.cs:15
Done!
 

You could certainly make additional tweaks like:
1) printing the whole callstack instead of just the leaf-most method.
2) printing more of the exception (such as the message string, nested exceptions, or other fields)
3) printing other exception notifications (such as catch-handler-found or unwind notifications)
4) or support for attaching to an existing app.


MDbg Sample is updated for Beta 2 and RTM

$
0
0

The Mdbg (a managed debugging written in pure C#) sample, which includes full source, has now been updated and released. The previous sample was for beta 1. This new sample has been updated for Whidbey beta 2 and will also be compatible with the final Whidbey release. The old mdbg beta 1 source sample is now obsolete.
Update: The old URL is obsolete. The new URL appears to be here.

I just downloaded and built it to verify (make sure that you install it into its own directory and not on top of a previous Mdbg install). Many thanks should go to Jan Stranik and Rick Byers for their Mdbg work in making this happen.

It does not contain my updated Mdbg winforms gui. We want to roll that back into the sample, but plan to do so at a later date so that we didn't delay releasing this sample any longer.
To put this in perspective, we effectively have 2 Mdbg lines: the Sample and the SDK-version.
 

 The SampleThe SDK version
Primary Goal?Provide hobbyists sample usage of the ICorDebug API via managed wrappers. This sample uses completely public interfaces.Maintain feature parity with the old Cordbg that we removed from the SDK. (Remember this?)
What's included?Includes all source, VS buildable solutionBinaries only. Source not included
Prerequisites?Only requires that the .Net Frameworks are installed. Does not require the SDK.Part of the SDK, so the SDK is installed.
Deployed dlls?Multiple dlls (Corapi, CorApi2, MdbgEng, MdbgExt) since there is no IL linker in the redist. See here for details.
Does not include a native disassembler.
Dlls linked into MdbgCore.dll. Includes native-disassembly functionality (in MdbgDis.dll) on par with Cordbg's functionality. Since the deployed dlls are different, a single extension may not be able to target both the sample and the sdk. 
Extensions?Includes helpful extension dlls (such as primitive GUI and IL diassembler)No extension dlls included with (beyond MdbgDis.dll for native disassembly). This is because the SDK version was just trying to match old Cordbg feature parity
Release schedule?Whenever we feel like it. This should include after major CLR releases.  There are currently two releases here: The beta 1 sample, and now the beta2/RTM sample.Ships as part of SDK. There is currently 1 release here for the beta 2 SDK. It will remain in the SDK until it is deprecated (which currently won't happen until Windbg has real managed-debugging support)


So there are now several Mdbg's floating around:
1.) MDbg Beta 1 source-sample. This was the first release of Mdbg. This sample only works on Beta 1 and is now obsolete. The download link has been updated to point to the beta2 sample.
2.) MDbg in Beta 2 SDK. This was a binary-only sample (no source include) and marks that MDbg is now officially part of the CLR's SDK. This version also has a bunch of extra fixes on top of the Beta 1 release.
3.) MDbg Beta 2 source-sample. That's what I'm blogging about here.

I allude here to some issues upgrading extensions from beta 1 to beta 2 or between switching between the sample and the sdk version.

Converting a managed PDB into a XML file.

$
0
0

I wrote some C# sample code to get an ISymbolReader from a managed PDB (Program Database) file and then dump it back out as XML. 
The managed PDB stores all the source-level debugging information such as:
- the mapping between IL offsets and source lines.  (Normally a compiler builds up this mapping automatically, though C#'s #line directive lets you explicitly write it).
- the entry point method if present (eg, "Main" in C#). This does not exist in dlls.
- the names of local variables.
PDBs basically pick up where Reflection and Metadata leave off. That's because Reflection / Metadata just include information needed to execute the program and perform some runtime services. We didn't want to bloat managed executables with additional information beyond that. (In retrospect, we've learning that there's a niche set who really wish all this information was available from reflection).

ISymbolReader is the entry point interface into accessing information in a PDB. The interfaces are unfortunately defined in mscorlib. They actually wrap the unmanaged symbol store interfaces declared in CorSym.idl.  MDbg beta 2 provides implementations of these interfaces using COM-interop to access the underlying unmanaged interfaces.  It turns out the CLR redist includes an implementation too, but that implementation is lame. ILDasm uses the unmanaged interfaces to read PDBs (enable "View : Show Source Lines") so that it can print source-level information with the IL. 

Sample code is here, and it requires a reference to the MDbg beta 2 for the managed wrappers. It's also FxCop clean. [Update: 1/26/06: sample code is updated to handle post -beta2 MDbg breaking changes and not crash on methods without symbols]

Here's the demo!
Here's a test source file (test.cs) for which we'll inspect the pdb. Compile it as: csc test.cs /debug+

using System;classFoo{staticvoid Main()
{int x =4;
        {int y  = 5;
        }Console.WriteLine("Boo!");
}staticvoid Second()
{Console.WriteLine("second");for(int x = 5; x < 7; x++)Console.WriteLine(x);
}
}

And after running the tool:
    Pdb2Xml.exe test.exe test.xml
And here's the XML output:

<!--This is an XML file representing the PDB for 'c:\temp\test.exe'--><symbolsfile="c:\temp\test.exe"><!--This is a list of all source files referred by the PDB.--><files><fileid="1"name="c:\temp\test.cs" /></files><!--This is the token for the 'entry point' method, which is the method that will be called when the assembly is loaded. This usually corresponds to 'Main'--><EntryPoint><methodref>0x6000001</methodref></EntryPoint><!--This is a list of all methods in the assembly that matches this PDB.--><!--For each method, we provide the sequence tables that map from IL offsets back to source.--><methods><methodname="Foo.Main"token="0x6000001"><sequencepointstotal="7"><entryil_offset="0"start_row="5"start_column="1"end_row="5"end_column="2"file_ref="1" /><entryil_offset="1"start_row="6"start_column="2"end_row="6"end_column="11"file_ref="1" /><entryil_offset="3"start_row="7"start_column="2"end_row="7"end_column="3"file_ref="1" /><entryil_offset="4"start_row="8"start_column="3"end_row="8"end_column="14"file_ref="1" /><entryil_offset="6"start_row="9"start_column="2"end_row="9"end_column="3"file_ref="1" /><entryil_offset="7"start_row="10"start_column="2"end_row="10"end_column="28"file_ref="1" /><entryil_offset="18"start_row="11"start_column="1"end_row="11"end_column="2"file_ref="1" /></sequencepoints><locals><localname="x"il_index="0"il_start="0"il_end="19" /><localname="y"il_index="1"il_start="3"il_end="7" /></locals></method><methodname="Foo.Second"token="0x6000002"><sequencepointstotal="9"><entryil_offset="0"start_row="14"start_column="1"end_row="14"end_column="2"file_ref="1" /><entryil_offset="1"start_row="15"start_column="2"end_row="15"end_column="30"file_ref="1" /><entryil_offset="12"start_row="16"start_column="6"end_row="16"end_column="16"file_ref="1" /><entryil_offset="14"hidden="true" /><entryil_offset="16"start_row="17"start_column="3"end_row="17"end_column="24"file_ref="1" /><entryil_offset="23"start_row="16"start_column="24"end_row="16"end_column="27"file_ref="1" /><entryil_offset="27"start_row="16"start_column="17"end_row="16"end_column="22"file_ref="1" /><entryil_offset="32"hidden="true" /><entryil_offset="35"start_row="18"start_column="1"end_row="18"end_column="2"file_ref="1" /></sequencepoints><locals><localname="CS$4$0000"il_index="1"il_start="0"il_end="36" /><localname="x"il_index="0"il_start="12"il_end="35" /></locals></method></methods></symbols>

And just to confirm, here's the ILDasm output for Foo.Main:

.method /*06000001*/ private hidebysig static 
        void  Main() cil managed
{
  .entrypoint
  // Code size       19 (0x13)
  .maxstack  1
  .locals /*11000001*/ init ([0] int32 x,
           [1] int32 y)
  .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}'
// Source File 'c:\temp\test.cs' 
//000005: {
  IL_0000:  nop
//000006: 	int x =4;	
  IL_0001:  ldc.i4.4
  IL_0002:  stloc.0
//000007: 	{ 
  IL_0003:  nop
//000008: 		int y  = 5;
  IL_0004:  ldc.i4.5
  IL_0005:  stloc.1
//000009: 	}
  IL_0006:  nop
//000010: 	Console.WriteLine("Boo!");
  IL_0007:  ldstr      "Boo!" /* 70000001 */
  IL_000c:  call       void [mscorlib/*23000001*/]System.Console/*01000005*/::WriteLine(string) /* 0A000003 */
  IL_0011:  nop
//000011: }
  IL_0012:  ret
} // end of method Foo::Main

Running XML queries:
XML has lots of neat associated technologies. For example, you can then run XPath queries to answer certain questions about the PDB. This is horribly inefficient, but very cool. Here are more sample queries:
Get all locals that are active at line 2
    /symbols/methods/method/locals/local[@il_start<="2" and @il_end>="2"]/@name

Get all filenames references from the pdb:
    /symbols/files/file/@name"

Find all methods that have code in a given filename (t.cs).
    /symbols/methods/method[sequencepoints/entry/@file_ref=/symbols/files/file[@name="c:\temp\t.cs"]/@id]/@name

Get the name of all methods.
    /symbols/methods/method/@name

Find the name of the entry point token:
    /symbols/methods/method[@token=/symbols/EntryPoint/methodref]/@name

Name all locals in method Foo.Main
    /symbols/methods/method[@name="Foo.Main"]/locals/local/@name

Get start row for IL offset 4 in method Foo.Main
    /symbols/methods/method[@name="Foo.Main"]/sequencepoints/entry[@il_offset="0x4"]/@start_row

The sample code includes these queries and others.
 

Other commentary:
At first I was hoping to create an XmlReader over a PDB store. It turns out this is very difficult. The reader has 25+ abstract methods; and even if you create it as a XmlTextReader over a text stream, that's bad because if you're writing XML, you really want to write it with a structured writer like XmlWriter instead of writing a raw text stream. So I settled on just using an XmlWriter instead of a reader.
I also thought it would ultimately be better to expose the PDB via an XPathNavigator instead of an XmlReader/XmlWriter, due to the query-intensive purpose of a PDB. I did XmlWriter because that was easiest and I needed to start somewhere. Xml super gurus out there are certainly free to grab this sample and produce an XPathNavigator for it.

I think it would also be a great project to get a XML2PDB writer. And then you can use XSLT to transform PDBs. That's a project for another day. Offhand, I think the managed wrappers are not complete enough to do this. But since you've got the source for MDbg, you can always fix that.

There's a long list of things I'd like to get into regarding PDBs. My list for future blog entries include:
- why do managed PDBs need a metadata reader?
- how does the code to get an ISymReader work?
- what about PDB readers for in-memory modules?
- Why does MDbg have its own implementation of ISymReader?
- More on the PDB vs. metadata / reflection split.
- Other XML-isms (such as XSD, XSLT, serialization on the xml documents above).
 

Adding IronPython scripting engine to Mdbg

$
0
0

I hear IronPython is a great managed scripting language to embed in other managed apps, so I thought I'd try this out by writing an MDbg Extension to drop IronPython 0.9.1 into the MDBg Beta 2 sample. (both pieces are publicly available downloads). The experiment went pretty well.  I conclude IronPython is a very viable choice for scripting managed apps, I can already do cool things, and I'm excited at what I could do if I actually knew how to use Python.

I'll blog about this in a 2-parter: First is a focus on my actual results (the demo), and next I'll focus on how easy it was to get there. [update:] I wrote up the steps for how-to add IronPython support.
I also need to emphasize that I had never used python before this and am not on any python forums. I used http://diveintopython.org/toc/index.html as a basic python reference and got some tips from Martin Maly.  More comprehensive reference is  http://docs.python.org/tut. I can see that the python gurus could do a lot of amazing things here. 

All of the Python-Mdbg work was done in a single Mdbg extension and didn't require any modifications to either Python or Mdbg. In fact, my python extension even operates well with my Mdbg gui extension. This is very promising because if your existing managed app already has a plug-in model, you may be able to write a single Python plug-in that can then turn around and run arbitrary python scripts.

The C# source for my python extension is here. I also have a sample collection of my python scripts for mdbg here, which contains the python functions I mention below. The extension adds the following Mdbg commands:
[Update: 11/29/05] Note that this is an Mdbg extension, which means that you must compile the extension (source here) and then load it into Mdbg (via the "Load") command before you can use any of the commands below. Eg, if you compile the extension into "PythonExt.dll", then you can load it into Mdbg via "load pythonext.dll".

1) "python" command executes a single python statement.  Mdbg still owns the input console, so this is what forwards input from the Mdbg command prompt onto the python engine. This is the bread and butter.

mdbg> py 1+2
3

[p#:0, t#:0] mdbg> py Shell
<Microsoft.Samples.Tools.Mdbg.MDbgShell object at 0x021A74D4>

[p#:0, t#:0] mdbg> py PrintStack()
0) Foo.Second (test.cs:35)
1) Foo.Main (test.cs:29)
------------

The first case is a simple expression. The second case shows that the python interpreter has access to the Shell object, from which it can use reflection to access the rest of Mdbg. In the third case, we execute the python function PrintStack(), which I defined as:

def GetDebugger(): return Shell.Debugger;
def CurProc(): return GetDebugger().Processes.Active
def CurThread(): return CurProc().Threads.Active
def PrintStack():
	i = 0
	for x in CurThread().Frames: 		
		print "%d) %s" % (i, x)
		i = i + 1
	print '------------'

You can imagine that you could create powerful python commands to dump arbitrary data structures.

2) The "pyin" (python interactive) command enters a python interactive session similar to the standard python console. This lets you issue a bunch of python commands (useful for multiple-line stuff) without needing the mdbg "python" command before each one. The console input gets directly routed to the python engine input loop instead of to MDbg's input loop. You exit back to an Mdbg session by typing "pyout". 

[p#:0, t#:0] mdbg> pyin
Entering Python Interactive prompt.
Type 'pyout' leave PythonInteractive mode and get back to Mdbg prompt.
>>>
[p#:0, t#:0] mdbg> 1+2
3
>>>
[p#:0, t#:0] mdbg> PrintVal(CurLocals())
CS$4$0000=False(System.Boolean)
>>>
[p#:0, t#:0] mdbg> pyout
Leaving Python Interactive prompt.
[p#:0, t#:0] mdbg>

PrintVal() and CurLocals() are also functions I defined in Python, similar to PrintStack() above:

def CurLocals(): 
	return CurFrame().Function.GetActiveLocalVars(CurFrame())
def PrintVal(v):
	# Check for MDbgValue[]
	if str(type(v))=="<type 'Microsoft.Samples.Debugging.MdbgEngine.MDbgValue[]'>":
		for x in v:
			PrintVal(x)
	else:
		PrintValHelper(v, "")

# Helper to print with indent.
def PrintValHelper(v, indent):
	if (v.IsComplexType):
		print indent + v.Name + "=Complex Type: (" + v.TypeName + ")"
		for x in v.GetFields(): 
			PrintValHelper(x, indent+"  ")
	elif (v.IsArrayType):
		print indent + v.Name + "=array Type: (" + v.TypeName + ")"
		for x in v.GetArrayItems(): 
			PrintValHelper(x, indent+"  ")
	else:
		print indent + v.Name + "=" + v.GetStringValue(0, False) + "(" + v.TypeName + ")";

3) The "pimport" (Python Import) command loads a python module (some file with a .py extension that contains the same raw stuff you'd type into the console) into the default scope. This is key because it lets you build up libraries of all your great functions (like PrintVal, PrintStack, etc) in some script file.  It turns out that python has an "import" command, so you may be tempted to issue an MDbg command like "python import test". However, imported python modules won't have visibility to Mdbg variables like "Shell", and so they can't do interesting Mdbg stuff.  I needed a dedicated mdbg import command to import the python script into the right scope.

4) The "Pyb"  (Python Breakpoint) command which lets you attach a python expression to a breakpoint. In general, I think there's a lot of power from being able to use python expressions as predicates / filters throughout your app. In this case, if the expression returns a non-null result, that becomes the StopReason and Mdbg stops at the breakpoint. If the expression evaluates to null, the breakpoint is continued.
You can use this to implement conditional breakpoints.
The pyb syntax is: "pyb" <breakpoint location> "|" <python expression>
In the following example, we set a python conditional breakpoint inside the for loop. E2() is yet another python function I wrote which fetches the value of a local variable and returns it as a string.

[p#:0, t#:0] mdbg> sh 6
29  	Second(5);
30  	Console.WriteLine("Hoo!");
31  	Second(4);
32  }
33  
34  static void Second(int z)
35:*{
36  	Console.WriteLine("second");
37  	int x;
38  	for(x = 5; x < 8; x++)
39  		Console.WriteLine(x);   <-- We'll set breakpoint here
40  }
[p#:0, t#:0] mdbg> pyb 39 | E2("x")=="6" <--  Stop on line 39 when local expression "x" evaluates to "6".
Breakpoint #3 bound (line 39 in test.cs)(python: E2("x")=="6")
[p#:0, t#:0] mdbg> b
Current breakpoints:
Breakpoint #1 bound (Foo::Second(+0))
Breakpoint #3 bound (line 39 in test.cs)(python: E2("x")=="6") <--  See our breakpoint shows up in the list
[p#:0, t#:0] mdbg> g
Python BP hit: E2("x")=="6"
Result:False (of type=System.Boolean) <-- First iteration, x==5
Expression is false. continuing
Python BP hit: E2("x")=="6"
Result:True (of type=System.Boolean)  <-- Second iteation, x==6
Expression is true. Stopping at breakpoint
STOP True
39:		Console.WriteLine(x);
[p#:0, t#:0] mdbg> print xx=6
[p#:0, t#:0] mdbg> 

Some ways you could use Python in Mdbg:
There are lots of ways that you could wire up Mdbg with Iron Python script. For example:
1) fancy conditional breakpoint: You could have multiple breakpoints invoking different methods on the same python class and manipulating the same data. For example, you could imagine building some sort of compound breakpoint that had tentacles in multiple methods like AddRef() and Release(), did stack hashing at each hit, and helped track down ref leaks.  Or you could build an "instance" breakpoint that creates a strong handle to a given instance and then sets conditional breakpoints on all member functions which compare the current instance to the cached strong-handle. The possibilities are endless.
2) fancy exception filtering: Only want to stop on 1st chance exceptions thrown from a given module while function Foo() is on the stack, but function Bar() is not? Write a python expression to hook Mdbg's Exception notification callback.
3) texual visualizers: Need to dump the contents of your own hand-rolled collection? Take the PrintStack() example above a few steps further and you could write python script to inspect any arbitrary data structures.  You could even dump it as HTML to a file to get a rich view.
4) mass breakpoints:  You want to place a breakpoint on every single method described by a regular expression? Trivial! (Python handles regular expressions).
 

In conclusion: For not knowing anything about python when I first started, this went very smoothly.  The entire extension (source is here) is verbosely commented with lots of whitespace and still under 450 lines. I also found several Mdbg bugs and have recognized some things MDbg could be doing to cooperate with being more scriptable.
The thing that excites me the most is how scalable the solution is. Writing your own scripting engine is hard. Using the existing IronPython engine is easy and powerful. The python snippets can be arbitrary python expressions (including function calls with side-effects) and can share data with each other. If an app embeds the IronPython engine, it allows end users to easily wire things up in new and useful ways.
[update] Next up, I'll blog about the actual mechanics of how I went about hooking this up.

 

MDbg Sample temporarily broken in post-beta 2 builds.

$
0
0

Customers have alerted us that the MDBg sample is broken on post-beta 2 builds. Rick Byers has diagnosed the problem as an issue in the mdbg build and we're working to update it. I guess there was a VS change that exposed this problem. Rick may blog a more detailed explanation, but I just wanted to get a quick post out to let people know we're aware of this and it's being addressed.

It fails with an error like this:

Error 1 The type or namespace name 'NativeApi' does not exist in the namespace 'Microsoft.Samples.Debugging.CorDebug' (are you missing an assembly reference?) C:\Documents and Settings\Administrator\Desktop\Whidbey Stuff\mdbg\mdbg\corapi\AppDomainEnumerator.cs 8 44 corapi

In the meantime, Rick's suggested fix is:

To fix this, you need to add “call ” to the beginning of the post-build event for corapi2.  I.e., the post-build event should be:

call "$(DevEnvDir)\..\tools\vsvars32.bat" > NUL && cd "$(ProjectDir)" && ilasm /KEY=../mdbg.snk /DLL /DEBUG /OUT="$(TargetDir)\corapi2.dll" context.il cordblib.il corpublib.il cormetalib.il assemblyDef.il> NUL

[update]: Rick has posted in more detail with a fix here.
I'll post again when we update the sample with the fix.

MDbg extension to debug IL

$
0
0

I've updated the MDbg gui to provide IL debugging. I blogged here that the CLR actually lets you debug at the IL level (with some restrictions), but no debugger actually exposes this feature. People were skeptical, and now that we shipped whidbey, I've had enough time to go add this functionality to the MDbg gui and demonstrate that it is indeed possible.

Here's a screen shot stopped at a for-loop. The current IP is at IL_10.

I press F10 and now the current IP moves to IL_16, which is in the middle of a source line.

Note that the IL-level step couldn't stop at IL_11 because there was no actual native code for that. Since the CLR is ultimately debugging the underlying native code produced from the IL, it can't stop on IL opcodes that don't produce any native code. Efficient codegen means that not all IL opcodes have a direct mapping to native opcodes. I discuss that issue in more depth here.

What does it do?
The window stitches together 3 sources of data. The actual source is in red with source lines on the left. In this case, this is souce line 53. The corresponding IL opcodes are in blue, and the IL offsets are displayed as IL_<hex>. And then the native code ranges associated with each IL range are displayed below the IL ranges. So IL opcodes 0x10 and 0x11 map to 8 bytes of native code starting at native offset 0x2d.  IL offset 0x16 maps to 1 byte of native code starting at native offset 0x35.

//     53:		Console.WriteLine(x);  IL_10  :  ldloc.0
  IL_11  :  call System.Console.WriteLineIL:0x10,0x16 --> Native:0x2d,0x35  (N. size=0x8 bytes)

  IL_16  :  nop
    IL:0x16,0x17 --> Native:0x35,0x36  (N. size=0x1 bytes)

If I had a native disassembler lying around, I could print the real native opcodes instead of just the native ranges. If I had an IL disassembler with IL-to-Source mappings (I don't think reflector provides this), then I could step into a function without any source and decompile the IL.

It's also built on top of existing ICorDebug functionality. ICorDebug already provides the ability to get the raw IL bytes for a method, and then the gui just uses an Il disassembler (which we already provided in the mdbg sample) to decode those bytes into the strings you see. This doesn't require any modifications to the debuggee. Specifically, you don't need to do the trick where you round-trip it through ilasm. You don't need to adjust the DebuggableAttribute (see Rick's blog here). You don't need to run the debuggee under any special flags. You don't need to func-eval anything to use some IL VS visualizer.

It also exposes scenarios that we didn't target before. Specifically, we target source-level debugging. I've found some cases where it looks like the IL map is wrong for IL spots inbetween source-lines. So nobody would find these bugs just using a source-level debugger.

What doesn't it do?
This does have some restrictions:
1) Although the IL window binds F10 to a real IL level step, it doesn't support F9 setting IL-level breakpoints. There's no restriction here, I was just too lazy to deal with synchronizing breakpoints between multiple copies of the source and figured it wasn't needed to prove the point. .
2) It doesn't show the IL evaluation stack. It turns out ICorDebug doesn't support this functionality (despite what our interfaces may lead you to believe).
3) It doesn't have a full IL disassembler. I just used the IL disassembler from the the Mdbg extension, which handles most of the IL opcodes. I don't think it handles generics. And it doesn't use PDB info to give you local names instead of IL numbers.
 
 

More sceenshots:
And here's a screen shot of inside Console.WriteLine.

And another screen shot that's stopped somewhere. This shot shows how having multiple statements on a single line work and how having a single statement span multiple lines works.

We'll roll this back into the MDbg sample and release the full source at some future point. I will of course blog when that happens. But for now, I wanted to give folks a heads up. The basic gui and ildasm extension samples are already available. The only thing that's missing is stitching them altogether.

This update also fixes a bunch of other random issues in the gui, but I'll blog about that later.

Summary of the role of MDbg and Cordbg

$
0
0

I want to summarize in one place our views on Mdbg and Cordbg, and our plans for their future.

Our views on the different debuggers:
Cordbg/Mdbg - In general, we've never viewed Cordbg or MDbg as ideal production quality debuggers. We view Cordbg/Mdbg primarily as test tools and samples of how to use the API. They've lived in the SDK to provide a simple, primitive command line debugger for basic scenarios.
Visual Studio - We view Visual Studio (or other 3rd party debuggers) as the real debuggers. We recommend using Visual Studio over Cordbg/MDbg wherever possible. There's an entire team of great people dedicated to Visual Studio, whereas there are just a few folks randomly working on Mdbg in our free time. 
NTSD / CDB / WINDBG - these are Microsoft's "real" command line debuggers. Once NTSD supports real managed code debugging (and not just SOS), we would like to cease shipping Mdbg in the SDK altogether.

About Cordbg:
Cordbg was the first command line debugger for managed code. Cordbg was originally written as an internal testing tool for the ICorDebug APIs, and got sucked into shipping as a real product because there was a time (before VS supported the CLR) when it was the only debugger available. It was written in unmanaged C++, before COM-interop was operational, and it directly consumed the com-classic ICorDebug interfaces. Cordbg shipped in V1.0, and V1.1 SDK. The source shipped as a sample and was our primary documentation of the interfaces.  The unmanaged Cordbg's last ship date was in the VS 2005 Beta 1 SDK. In that release, it had some primitive (and untested) support for some new VS 2005 features. Cordbg may still ship in Rotor.

About Mdbg:
MDbg was written in V2, and has since replaced Cordbg. MDbg is written in C# and uses COM-interop to access ICorDebug. The initial motivation for writing MDbg was to leverage managed code's extensibility story to have a highly scriptable debugger that we could use for better automated testing of ICorDebug. MDbg now ships in the V2 SDK. Since MDbg was written from scratch, and Cordbg's feature set was not specified, MDbg is missing some unspecified features from Cordbg (like ca <type>). Unfortunately, this means MDbg is not at full feature parity with the V1.1 Cordbg. 
We also ship MDbg as full source sample. See here for a comparison of the MDbg sample versus the MDbg SDK.  The sample is intended as additional documentation for ICorDebug, and not as a production debugger.

Why the switch?
Because of MDbg's great extensibility story, we used it heavily internally for our own testing purposes. The CLR didn't want to maintain two command line debuggers (MDbg and Cordbg), we already had to maintain MDbg for testing, Mdbg was just plain cooler (being in C#), we solicited a lot of customer feedback (including here and here), and so we made the tough decision to cut Cordbg. Internally, we renamed the unmanaged CorDbg to "NativeTestDbg" and kept it around for legacy tests. All our our new test development is on Mdbg or VS.

The "Cordbg Skin":
To help ease compatibility for anticipated cordbg-withdrawal, we created a "cordbg skin", which is just a veneer on top of Mdbg that makes it look like Cordbg. This is the Cordbg.exe that now ships in V2. Thus we completely switched the implementation of cordbg.exe between VS2005 beta 1 (unmanaged) and beta 2 (managed cordbg skin). (You'll notice Cordbg.exe now shows up in its own "pro" command). Unfortunately, just as MDbg lacks full parity with the old Cordbg, the cordbg skin lacks full parity too. Thus in trying to do a good thing (release MDbg), we have regressed cordbg functionality from an end-user perspective. This was a hard decision, but customers encouraged us that it was the right call. Furthermore, customers have also let us know there are more valuable places for us to invest our time than the Cordbg skin. Thus we would like to kill off the cordbg-skin (perhaps in the next release). Cordbg.exe will eventually cease to ship at all.

The future?
Cordbg.exe is being phased out and we'd like to stop shipping it completely.
VS is the preferred managed debugger over Cordbg/MDbg. It has great features for managed code and will only keep getting better.
Once NTSD has real command line support, we would like to stop shipping MDbg in the SDK. It will likely live on as a sample.
We will improve the document for ICorDebug, and we will continue to ship MDbg as a sample at least in the near term.

 

MDbg Linkfest

$
0
0

MDbg is a debugger for managed code written entirely in C# (and IL), which started shipping in the CLR 2.0 SDK.
I have so many MDbg posts, that I'm losing track of them. And others have had some good posts too. For my own organization, I need to create an easy way to find all the links (particularly when I search for a link that I want to include in one of my other blogs). I have an MDbg category, but that's still too verbose to find everything. So here's my one stop link-fest of MDbg posts.  I plan to update this over time with new posts (both my own or others).

What is Mdbg and where can I get it?
Managed Debugger Sample (My first post on MDbg)
Download V2.1 MDbg?
MDbg Forum on MSDN (this is the place to ask your Mdbg questions)

Summary of the role of MDbg and Cordbg
We need your feedback on the fate of Cordbg.exe / BradA on killing Cordbg
Comparison of MDbg Sample vs. MDbg SDK (MDbg Sample is updated for Beta 2 and RTM)

MDbg + V1.1 (Everett / VS2003)
Problems for a managed debugger for v1.1?
Using metadata interfaces from managed code
Using MC++ wrappers to access ICorDebug from V1.1

MDbg + IronPython (now included in latest sample source)
Adding IronPython scripting engine to Mdbg
ShawnFa's IronPython-MDbg examples
How to embed IronPython script support in your existing app in 10 easy steps
Update for Iron Python 0.9.3, and later for Beta 1
Raw source for latest mdbg-IP extension
Harry Pierson's 'Writing a Debuger in Mdbg' series.

Tools built on top of MDbg:
Build tips: How to build these samples
Sample app to attach to target app, dump callstacks of all threads, and detach.
Sample app to print loaded modules (in C#) .  (In VB)
Simple harness to print exceptions in an app
How to modify those tools to attach instead of launch.
Converting a managed PDB into a XML file. (included in latest Mdbg sample)
Building your own MDbg extension.

MDbgGui + Threading
MDbg extension to debug IL,
Winforms + MDbg threading issue: Round1 (Bad: UI thread is MTA), Round 2 (Good: UI thread is now STA)
Source is available for MDbg Winforms GUI! (updates to the GUI)
Winforms gui on top of Managed debugger sample.

Update / Servicing notifications:
Mdbg Version 2.1 released (still targets .NET 2.0)
MDbg sample (2.0) is updated for Whidbey (.NET 2.0) RTM (fixes post-beta2 breaks)
MDbg Sample temporarily broken in post-beta 2 builds.
MDbg Sample is updated for Beta 2
Updating MDbg GUI for Beta 2

Other
:
Mdbgcore.dll and linking IL
Evidence that MDbgCore is easier to use than ICorDebug
Cross-platform, 32/64-bit stuff
Jan on MSDN TV

Other people's Mdbg work:
Felice Pollano's MDbg + Reflector project
Managed Stack Explorer -  productized tool to get stack snapshots (kind of like snapshot tool, but much cooler). This only builds on the low-level Mdbg wrappers (CorApi*).

 


Updated MDbg GUI

$
0
0

I've updated the MDbg GUI. We'll be integrating this into the actual MDbg sample and it will all be available in the MDbg download. Here's a list of what's improved. I'll certainly post when the download site gets updated.

IL support
1) Added integrated IL view! It shows IL disassembly + native ranges + Source together.
2) Added real IL-level stepping (via F10 in the IL view)
3) Add IL/Native offset to callstack window

UI issues:
4) UI thread is now STA, as UI threads are supposed to be. Since ICorDebug is MTA, the UI thread now does cross thread communication to access ICorDebug. This was a huge threading overhaul underneath. The old model is described here. I'll blog about the new model at some point.
5) Variable inspection windows now do lazy expansion of an object tree. Previously, it auto-expanded to 5-deep. This is huge perf win and allows expanding to an arbitrary depth. You can now leave the auto window open and step without the perf killing things.
6) Added Shift+F5 for “Stop Debugging”. This is the same key mapping as VS. The prior MDbg gui saw S+F5 as just F5 and would do a “go”.
7) Allow multi-line input for Gui input. This is very useful for things like the Python extension that implement the console.
8) Added “.menu” command which lets you invoke GUI menu items from the command line.

Using RichEditBox:
9) Implemented IMDbgIO2 which allows highlighting in output string.
10) Added fonts to the output GUI window for different output types (stdout, error, input, meta information from gui)

Other:
11) Attach dialog now includes version of target column.
12) The GUI is FXcop clean.

Bug fixes:
1) Fixed background color in source window
2) Can now view Open Source dialog again because the UI thread is STA.

 

I guess that's a lot of work for just a sample that we don't want people using in production scenarios. I started off by fixing the STA issue (#4), and then adding the IL window (since that was just cool), and then decided to play around with RichEditBox because it was easy. I guess I just like writing in C#.

Writing a debugger in VB

$
0
0

Some might consider writing a managed debugger in VB.Net to be an oxymoron. But maybe not. Here's a VB.Net snippet that serves as a highly-specialized debugger to launch an app and print all the modules that get loaded.

This is adapted from the C# sample snippet here that does the same thing. Although we know that VB.Net is theoretically powerful enough to do what C# does, I thought it would be nice to actually concretely see it in VB. 

Writing a whole debugger in VB?
We believe we could have written most of MDbg in VB (instead of C#).  However, just because it's possible doesn't mean that it's easy to do. One concern is that although this is technically possible, I doubt MDbg's usage conforms to the developer-paradigms / design rules that VB developers would expect. People don't want to worry about things like programming with multiple threads, manipulating metadata, and apartments.
However, since MDbg takes care of most of these problems, building  on top of MDbg (like the sample below shows) should be easier.

Other trivia:
FWIW, It only took about 15 minutes for me to port it over, and I don't actually know VB.Net. The IDE really helps you along, and I did a lot of it with Edit + Continue. The biggest problem I had was tracking down the "typeof" operator.  It also initially left off the MTAThread attribute and so it hung at the "proc.Go.WaitOne()" line, and then I remembered that ICorDebug is MTA, and so added the attribute.

'----------------------------------------------------
' Test harness to print all loaded modules
' Ported from C# version at:
' http://blogs.msdn.com/jmstall/archive/2004/11/19/267135.aspx
'----------------------------------------------------Imports SystemImports Microsoft.Samples.Debugging.MdbgEngineModule Module1<MTAThread()> Sub Main()' Get target filename from argsDim filename AsStringDim args AsString()
        args = System.Environment.GetCommandLineArgs()If (args.Length <> 2) Then            Console.WriteLine("Usage: PrintMods <filename>")
            Console.WriteLine("   Will run <filename> and print all loaded modules.")ReturnEndIf        filename = args(1)Dim debugger As MDbgEngine = New MDbgEngine()' Set options. Specifically need to request Module Loads        debugger.Options.CreateProcessWithNewConsole = True        debugger.Options.StopOnModuleLoad = TrueDim proc As MDbgProcess = debugger.CreateProcess(filename, "", DebugModeFlag.Default, Nothing)' Consume all debug events, sniffing for LoadModules events.While (proc.IsAlive)
            proc.Go().WaitOne()Dim o AsObject = proc.StopReasonIf (TypeOf o Is ModuleLoadedStopReason) ThenDim m As ModuleLoadedStopReason = CType(o, ModuleLoadedStopReason)
                Console.WriteLine("Module loaded:"& m.Module.CorModule.Name)EndIfEndWhile        Console.WriteLine("Done!")EndSub
EndModule

 

 

Tool to get snapshot of managed callstacks

$
0
0

I wrote a simple tool to take a snapshot of a running managed process and dump the output as an XML file. I'll post the full source as a sample on MSDN.
[Update 6/26/06] After great delay, source posted here. Also, check out Managed Stack Explorer, which is a more polished tool that has similar snap-shot gathering behavior.

The usage is pretty simple. To take a snapshot of the running process "hello.exe", run:
    SnapShot.exe -name:hello.exe

And then it dumps out an XML containing callstacks of all threads, including locals and arguments of each frame (see below). 

Comments on the tool:
The actual tool is trivial to write. It's under 500 lines, and the largest part is adding error checking for the command line options and breaking everything out into little xml tags. Here's a watered down basic version of the tool which just attaches (see here for details on attach) and dumps callstacks via MDbgFrame.ToString() (eg, the equivalent of MDbg's where command), and it's under 70 C# lines (Update: fix an issue with draining attach events, bumps the line count up from 50 to 70):

//-----------------------------------------------------------------------------
// Harness to snapshot a process's callstacks
// Built on MDbg, Needs a reference to MdbgCore.dll (ships in CLR 2.0 SDK).
// Author: Mike Stall (http://blogs.msdn.com/jmstall)
//-----------------------------------------------------------------------------using System;using Microsoft.Samples.Debugging.MdbgEngine;using System.Diagnostics;classProgram{// Skip past fake attach events. staticvoid DrainAttach(MDbgEngine debugger, MDbgProcess proc)
    {        bool fOldStatus = debugger.Options.StopOnNewThread;
        debugger.Options.StopOnNewThread = false; // skip while waiting for AttachComplete        proc.Go().WaitOne();Debug.Assert(proc.StopReason isAttachCompleteStopReason);

        debugger.Options.StopOnNewThread = true; // needed for attach= true; // needed for attach// Drain the rest of the thread create events.while (proc.CorProcess.HasQueuedCallbacks(null))
        {
            proc.Go().WaitOne();Debug.Assert(proc.StopReason isThreadCreatedStopReason);
        }

        debugger.Options.StopOnNewThread = fOldStatus;
    }

    // Expects 1 arg, the pid as a decimal stringstaticvoid Main(string[] args)
    {int pid = int.Parse(args[0]);MDbgEngine debugger = newMDbgEngine();MDbgProcess proc = null;try        {
            proc = debugger.Attach(pid);
            DrainAttach(debugger, proc);            MDbgThreadCollection tc = proc.Threads;Console.WriteLine("Attached to pid:{0}", pid);foreach (MDbgThread t in tc)
            {Console.WriteLine("Callstack for Thread {0}", t.Id.ToString());foreach (MDbgFrame f in t.Frames)
                {Console.WriteLine("  " + f);
                }
            }
        }finally        {if (proc != null) { proc.Detach().WaitOne(); }
        }

    }
}

Some sample output from that is:

Attached to pid:3784
Callstack for Thread 2384
  [Internal Frame, 'M-->U']
  System.IO.__ConsoleStream.ReadFileNative (source line information unavailable)
  System.IO.__ConsoleStream.Read (source line information unavailable)
  System.IO.StreamReader.ReadBuffer (source line information unavailable)
  System.IO.StreamReader.ReadLine (source line information unavailable)
  System.IO.TextReader.SyncTextReader.ReadLine (source line information unavailable)
  System.Console.ReadLine (source line information unavailable)
  Foo.Main (wait.cs:15)

Some technical notes:
This is doing an invasive attach, running the callstacks, and then doing a detach.  It is not taking a memory dump. Only 1 managed debugger can attach at a time (see here), and you can't do this if a native debugger is already attached (see here). The MDbgProcess.Detach() call is in a finally such that if the harness does crash in the middle, then it will at least detach from the target app.

Lines of Code vs. Functionality: C# vs. Mdbg script:
You could do the same thing with an MDbg script like:
    attach %1
    for where
    detach

Where %1 is the pid of interest.
This is a cute tangent about lines of code vs. functionality:
3 lines of Mdbg script provide the raw functionality of attach, get the callstacks, and detach. Though you don't get control over formatting, and it doesn't scale well to doing things differently.
We go up to 70 lines of C# to be able to run it from a C# harness without pulling in MDbg.exe proper.
We go up to 500 lines of C# once we add dumping values, a few fancy options (attach by name), error checks, and spew to XML instead of just using the default ToString().

Sample output:

Sample output from the real XML-based tool looks like this (I removed some redundant frames).  It has an arbitrarily policy to dump the first depth of fields for reference types. I plan to publish the source for this tool somewhere on MSDN.

<!--Snapshot of managed process taken from SnapShot gathering tool (built on MDbg).--><processpid="2144"><threadtid="3252"><callstack><framehint="C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll!System.IO.TextReader.SyncTextReader.ReadLine (source line information unavailable)"il="0"mapping="MAPPING_UNMAPPED_ADDRESS"><locals /><arguments><valuename="this"type="System.IO.TextReader.SyncTextReader"><fields><valuename="_in"type="System.IO.StreamReader">System.IO.StreamReader</value><valuename="Null"type="System.IO.TextReader"><null></value><valuename="__identity"type="System.Object"><null></value></fields></value></arguments></frame><framehint="C:\WINDOWS\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll!System.Console.ReadLine (source line information unavailable)"il="0"mapping="MAPPING_EPILOG"><locals /><arguments /></frame><framehint="C:\bugs\hello.exe!t.Main (hello.cs:133)"il="273"><locals><valuename="CS$1$0000"type="System.Int32">0</value><valuename="CS$4$0001"type="System.Boolean">False</value><valuename="x"type="System.Int32">3</value><valuename="t2"type="t"><fields><valuename="MyString"type="System.String">"hi!"</value><valuename="m_x"type="System.Int32">0</value></fields></value><valuename="tInt"type="System.RuntimeType"><fields><valuename="m_cache"type="System.IntPtr">0</value><valuename="m_handle"type="System.RuntimeTypeHandle">System.RuntimeTypeHandle</value><valuename="s_typeCache"type="System.RuntimeType.TypeCacheQueue"><null></value><valuename="s_typedRef"type="System.RuntimeType">System.RuntimeType</value><valuename="s_ActivatorCache"type="System.RuntimeType.ActivatorCache"><null></value><valuename="s_ForwardCallBinder"type="System.OleAutBinder"><null></value><valuename="FilterAttribute"type="System.Reflection.MemberFilter"><null></value><valuename="FilterName"type="System.Reflection.MemberFilter"><null></value><valuename="FilterNameIgnoreCase"type="System.Reflection.MemberFilter"><null></value><valuename="Missing"type="System.Object"><null></value><valuename="Delimiter"type="System.Char">\0</value><valuename="EmptyTypes"type="System.Type[]"><null></value><valuename="defaultBinder"type="System.Object"><null></value><valuename="valueType"type="System.Type"><null></value><valuename="enumType"type="System.Type"><null></value><valuename="objectType"type="System.Type"><null></value><valuename="m_cachedData"type="System.Reflection.Cache.InternalCache"><null></value></fields></value><valuename="fp1"type="t.FP1"><fields><valuename="_invocationList"type="System.Object"><null></value><valuename="_invocationCount"type="System.IntPtr">0</value><valuename="_target"type="t.FP1">t.FP1</value><valuename="_methodBase"type="System.Reflection.MethodBase"><null></value><valuename="_methodPtr"type="System.IntPtr">3416108</value><valuename="_methodPtrAux"type="System.IntPtr">9515312</value></fields></value><valuename="q"type="t[]">array [2]</value><valuename="s1"type="System.String">"abc"</value><valuename="s2"type="System.String">"abc"</value></locals><arguments><valuename="args"type="System.String[]">array [1]</value></arguments></frame></callstack></thread></process>
 

MDbg sample is updated for VS2005 RTM

$
0
0

We've updated the MDbg source sample (download here) for Visual Studio 2005. This latest round of updates  includes an integrated IL view, making the UI thread STA, fixing some build problems with VS2005 RTM and C# Express.

It took me under 3 minutes to downloaded it and build it all from scratch.

And just for kicks (and because I already have the jpg lying around), here's a screenshot of the GUI with integrated IL view:

Kudos to the usual suspects (Nick Hertl, Jan Stranik, Rick Byers, Jon Langdon)!

Mdbg IronPython Extension updated for Beta 1

$
0
0

You probably heard that Iron Python Beta 1 was released.  It has a small (but very worthy) breaking change, and I've updated the MDbg-IronPython extension accordingly.

I updated it previously for 0.9.3. The changes are related to redirecting the python console. Here is the progression:

        // Hook input + output. This is redirecting pythons 'sys.stdout, sys.stdin, sys.stderr'
        // This connects python's sys.stdout --> Stream --> Mdbg console.
        Stream s = newMyStream();
       
#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

I think the current plan of directly passing .NET Stream objects in is a much cleaner way to integrate into .NET.

MDbg UI Threading, round 2

$
0
0

The STA/MTA threading problem is (hopefully) fixed in the latest MDbg winforms Gui. (which also has some cool new features, including an IL disassembly window)

The original solution implemented in the original GUI (described here ) had the problem that the UI thread was MTA! This is very bad because UI threads are supposed to be STA. In fact, certain UI things, like the OpenFile dialog, must operate on an STA thread. (Winforms started enforcing this in Beta 2, leading to the problems many people identified here).  We did this because ICorDebug is MTA, and we wanted the UI thread to be able to access MDbg / ICorDebug directly because that greatly simplifies the code.


Basic properties of this solution:
Here are the basic properties of this new solution:
1) UI thread is STA (as it should be), and so it never touches MDbg objects (which are effectively MTA because they call on the MTA ICorDebug)
2) We have a worker thread dedicated to talking to MDbg.  All gui elements (including the text box used to input commands) post to this worker thread. 
3) UI thread has a variety of ways of accessing the worker thread. See below for details.
4) The worker thread can post asynchronous (non-blocking)  requests to update UI elements. The worker thread never blocks on the UI thread.

Consequences of those decisions:
Here's an explanation of why this system is correct and doesn't deadlock.
Mdbg has a worker thread that invokes a callback for ReadLine() when its command line is ready for input.  The worker returns from the callback, passing out a string to execute, and then the Mdbg executes that command. Once the command is done,  MDbg invokes the ReadLine() callback for the next command. When MDbg runs at the command prompt, this callback just forwards the call to Console.ReadLine().  The MDbg gui provides another callback that coordinates ReadLine() with the main GUI winform which gets the command content from a TextBox.

The threading implications are:
1) The worker thread is the only thread that can resume the debuggee, which it does by just returning from the ReadLine() callback.
2) The worker thread is blocked (inside of MDbg) while the debuggee is running. It will invoke the ReadLine() callback when the debuggee stops (eg, at a breakpoint or some other event).
Note that it doesn't matter whether the command actually resumes the debuggee (like "go", or "next") or whether it's just a quick command like "print".

The UI thread can attempt to post non-blocking text commands to the worker thread. Eg, press F5 in the UI, and it posts a "go" command to the worker thread.  The UI will not wait for this command to finish.
The UI thread can also post blocking work requests to get information used to fill in tool windows. Eg, the UI has a modeless callstack tool window, and it can have the worker thread produce a collection of frames, which the UI thread then puts into the callstack window.  These work requests can only gather information and can not resume the debuggee. All UI actions that could resume the debuggee must be done via a text command.
 

Enough background, here's the proof:
I must emphasize that I'm not a UI expert, so here's my best attempt at proving safety:

1) The only way we can deadlock is if the UI thread blocks on something. The UI thread calls modal dialogs, which are safe since the UI is STA and the dialogs will pump. The only other thing it even considers blocking on is the worker thread.

2) So this simplifies to: The only way we can deadlock is if the UI thread blocks on the worker thread, and then the worker blocks on something.
The worker only makes non-blocking calls (Control.BegionInvoke) to the UI thread, and so it can never block on that. The worker doesn't calls any blocking function on Mdbg. So the only remaining thing the worker can block on is waiting for the debuggee to hit a debug event. In other words, the the worker only blocks while the debuggee is running.

3) So this simplifies to: The only way we can deadlock is if the UI thread blocks on the worker while the debuggee is running.

4) The only way the UI blocks on the worker is by invoking a synchronous worker delegate to get the worker to fill out data for a tool window.
As a safety-check, this could have this block be with a timeout; that pops a "Unresponsive" dialog. That dialog could even have a button to include an async-break.

So this means we just need to ensure that the debuggee is stopped from the time the UI issues the worker delegate until the time the UI gets the delegate results.

5) The only way the worker thread can resume is if it returns from its callback. It can only do this if the UI tells it to via the UI posting a text command (eg, "Go" or "Step").  Thus the debuggee can't be resumed without the UI thread issuing the order. Also, the worker thread can notify the UI thread once the debuggee has stopped again. Thus, the UI thread knows a well-defined, thread safe, bounding window on when the debuggee is running. (This is tracked via the MainForm.IsProcessStopped property in GUIMainForm.cs).

6) The same UI thread issues worker delegates and triggers resuming the debuggee.  The UI thread has no reentrancy while issuing the delegate, and so there is guaranteed mutual exclusion between these two actions.

7) Thus the debuggee is indeed stopped while the UI thread is blocked on the delegate. And that means the worker thread is free to process the delegate. And thus worker completes and the UI thread doesn't deadlock.

Q.E.D.

If we really wanted to be thorough, we could ensure that the UI thread truly never blocked by having the UI would async post the delegate; and then the delegate would async post back to the UI thread to finish.

 

Ways that the Mdbg worker thread may need to access the UI thread:
The worker thread posts non-blocking requests to the UI thread. These get queued and executed in the order they were posted.  This is done by the worker thread calling Control.BeginInvoke (and not just Control.Invoke, which blocks).

1) Enable / Disable input box:
The UI has a Text Box that lets the user type in text commands and send to the Mdbg shell (think the GUI version of Console.ReadLine).
When the worker thread is ready for text input, it will tell the UI to enable the input edit box so that the user can type in a text command. When the worker thread is then about to block (actually, give control back to Mdbg) to execute the command, it signals the UI thread to disable the input box so that the user can't enter any new commands.

2) Writing output:
The worker thread needs to notify the UI if any commands write output. It's extremely significant that the write operations are queued

Ways the UI thread (STA) needs to access the MDbg layer (MTA) :
The UI needs to access MDbg in several different ways.

1) execute a shell command:  I discussed here that the GUI will want to implement many UI commands via shell commands. For example, pressing F10 maps to the "next" command, and pressing "F5" maps to the "go" command. 
Shell commands are significant because the debuggee may resume execution (such as "next", "go"), and that means the command may block indefinitely. Clearly, the UI  can't block as soon as the user presses F5. So the UI issues shell commands asynchronously. This worker thread then goes off and executes the command, and it will then notify the UI once it hits stopping event. Meanwhile, the UI thread is free to continuing painting.

2) collect Mdbg information to populate a tool window:  The UI has tool windows (such as callstack, module, etc) and it needs information from MDbg in order to fill these windows in. For this, the UI thread will make post synchronous (blocking) worker requests to the worker thread. UI thread will block waiting for worker to complete. This is safe because these query operations are guaranteed to not block.  But to be safe, the UI thread could block with timeout and then pop an error dialog on timeout.  Anonymous delegates provide a convenient way for the UI thread to make a cross-thread call to the worker thread.

3) Async break: This is a special case. All other UI access occurs when the debuggee is stopped. However, the UI also has a command to break into a running debuggee. For this, the UI thread can just spin up an extra thread to do the async break. The UI thread doesn't need to wait on the extra thread because as soon as the async break finishes, the worker thread will become free and notify the UI thread.

Sample Mdbg extension

$
0
0

Here is a template for playing around with an extension for the MDbg sample (Mdbg is the managed debuggeer written in C#). It adds an "Addition" command to MDbg that adds two numbers. Not very exciting, but it shows the plumbing.  You can download the MDbg sample here.  I'll likely update this if I think of a better sample command than just "addition".

These extensions target the MDbg sample, and not Mdbg in the SDK.

Please note that the MDbg sample is just a sample. We don't consider it a real production debugger (use VS2005 for that). The main goal for extensions is to let hobbyists play around with prototyping debugging ideas and exploring our APIs.


//-----------------------------------------------------------------------------
// Template for an MDbg extension 
//
// 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. 
//
// More about Mdbg here:
//   http://blogs.msdn.com/jmstall/archive/2005/11/08/mdbg_linkfest.aspx
// 
//
// HOW TO BUILD:
//    This must have a reference to the other Mdbg dlls (corapi, corapi2, mdbgeng, mdbgext)
//
//  Assuming this is caled "myfile.cs", compile like:
//    csc /t:library /debug+ myfile.cs /r:corapi.dll /r:corapi2.dll /r:mdbgeng.dll /r:mdbgext.dll
//
// HOW TO USE:
// You must then load this extension into Mdbg via the "load" command before
// you can use the commands in this extension. For example, at an Mdbg prompt, type:
//     load C:\fullpath\subdir\MyFile.dll
//-----------------------------------------------------------------------------using System;using System.Collections.Generic;using System.Text;using System.Diagnostics;using Microsoft.Samples.Tools.Mdbg;using Microsoft.Samples.Debugging.MdbgEngine;using Microsoft.Samples.Debugging.CorDebug;using System.Globalization;// extension class name must have [MDbgExtensionEntryPointClass] attribute on it 
// and implement a LoadExtension()[MDbgExtensionEntryPointClass(
    Url = "http://blogs.msdn.com/jmstall", // put your URL here    ShortDescription = "Eventing test extension."// your "help" description for group of commands)]publicclassMyMdbgExt : CommandBase{// This is called when the extension is first loaded.// Extensions are loaded via MDbg's "Load" command.publicstaticvoid LoadExtension()
    {// Add all the commands from this classMDbgAttributeDefinedCommand.AddCommandsFromType(Shell.Commands, typeof(MyMdbgExt));// You can do other initialization here, or write out to the MDbg console.// Use WriteOutput instead of "Console.WriteLine", because the Mdbg console may be redirected// (to a GUI or logfile, etc).         WriteOutput("My mdbg Extension loaded");
    }// Commands are identified by the "CommandDescription" attribute on a method.    [CommandDescription(
        CommandName = "addition",  // name of your command in the shell.// How many characters does the shell need to match? This can be used // as a "shortcut" to your command        MinimumAbbrev = 3, // Short help string that appears next to this command in the "Help" list.        ShortHelp = "adds two numbers",// Longer help string for when we explicitly ask about this command.    // It can include sample usage.        LongHelp = @"Adds two numbers.
Usage:
    add 2 4     // returns 6
"        )
    ]publicstaticvoid MyCommand(string argString)
    {   // This is a trivial command to add two numbers.// For more complex commands, see mdbgCommands.cs in the Mdbg project.ArgParser args = newArgParser(argString);if (args.Count != 2)
        {thrownewMDbgShellException("Expected 2 arguments");
        }int a = args.AsInt(0); // throws if not intint b = args.AsInt(1);
        WriteOutput(String.Format(CultureInfo.InvariantCulture, "Adding {0} + {1} = {2}", a, b, a+b));
    }

} // end class for my extensions.

 


MDbg, Managed-debugging, and 64 bit

$
0
0

V2 CLR added support for 64-bit (amd64 and ia64), and that includes managed-debugging support. So a 64-bit MDbg can debug a 64-bit managed app. But what about cross-platform stuff when your debugger and debuggee are different platforms? Here's how MDbg and managed-debugging work across the 32/64 bit divide. 

The quick story is that Mdbg is pure IL and thus could be built platform neutral. However, the implementation of ICorDebug (mscordbi.dll) is native code, and thus platform specific (ICorDebug is the underlying managed-debugging interfaces). The problem is how does Mdbg know which mscordbi to invoke when MDbg instantiates an ICorDebug object?  Answering that question means Mdbg takes a subtle dependency on the platform. Thus we actually compile MDbg with a specific bitness (using /platform), even though it is pure IL. There are bit-specific versions of the framework, and so each version gets the corresponding bit-specific version of MDbg.

About 64-bit and other platforms:
On 64-bit machines, both the 32-bit and 64-bit CLR may be installed on the machine in separate directories. A pure-IL managed app can thus run in either 64-bit or 32-bit mode. In 32-bit mode, it loads the 32-bit CLR, and pinvokes to system dlls will resolve against the 32-bit version of those dlls. In 64-bit mode, it's a 64-bit pipeline.  If you run an assembly as 32-bit on a 64-bit machine, it will run in the WOW susbsystem.   Josh Williams has more details here.

When compiling a C# assembly, you can set the bitness with the /platform compiler flag to CSC. Or you can use CorFlags to change the bitness on an already-compiled assembly.

How does Mdbg instantiate ICorDebug?
Mscordbi.dll is loaded via a pinvoke call to mscoree!CreateDebuggingInterfaceFromVersion. If MDbg is running in 32-bit mode, it will pinvoke into the 32-bit version of mscoree and thus instantiate the 32-bit version of mscordbi. If Mdbg is running in 64-bit mode, by the same reasoning it will instantiate the 64-bit version of mscordbi. This is why that even though Mdbg is pure-IL, it compiles with platform affinity.

Debugging across 32/64 bit boundaries
At the ICD level, all managed debugging is local (what VS calls "remote-debugging", CLR calls "remote UI"). This also includes not debugging across the 32/64-bit boundary. So a 32-bit ICorDebug can't attach to a 64-bit debuggee, and vice-versa. Trying to cross this boundary gives a CORDBG_E_UNCOMPATIBLE_PLATFORMS hresult..  This means that if you want to debug 32-bit processes, you must spawn the 32-bit version of Mdbg.

What about platform specific code in Mdbg?
Almost all of  MDbg is platform neutral because ICD absorbs the platform specific stuff. A few platform-specific things (like dumping registers, native-disassembly) are in a platform-specific satellite dll, MdbgDis.dll. Here's the platform dependencies for the different parts of the SDK-version of Mdbg:

dlllanguageplatform specific?description
mscordbi.dllnative C++YesPart of CLR redist for the platform specific implementation of ICorDebug
Mdbg.exe, MdbgCore.dll pure IL
C#, IL
NoBulk of Mdbg's functionality, including shell, all UI, and all commands.
MDbgDis.dllMC++
 
YesPlatform specific code for an Mdbg extension dll to provide native disassembly functions in the SDK version of MDbg.  MC++ helps it bridge the C++ disassembly functions to the managed Mdbg extension model. This is optional functioanlity.

What about EnC / Interop-debugging?
Edit-And-Continue (EnC) is only supported for x86. Thus to do EnC when developing on Mdbg on a 64-bit machine, you must be running it as a 32-bit app.  I believe the sample currently builds Mdbg in x86 mode, partly so that EnC is on by-default. Interop-debugging is also only supported on x86.

The bottom line:
If you want to debug a 32-bit app, you must launch a 32-bit MDbg. Ditto for 64-bit. This also means that a single MDbg instance can't debug processes from different architectures.

That may be annoying to the end-user to have to pick which version of the tool to launch in order to know what to debug. There are games you can play to get around that. For example, only your debug-engine needs to have the right platform-affinity. You could have a proxy, platform neutral shell, which then launched a separate process for the debug-engine in the proper bitness. The shell and debug-engine would need to communicate across processes.  That's all more complicated, but it would have a nicer end-user experience. I believe VS does some things like this.

Jan is on MSDN TV talking about MDbg

$
0
0

Jan Stranik is on MSDN TV talking about MDbg, the managed-debugging sample written in C#.  See the video here.  Jan wrote most of MDbg, and handle a lot of hard problems about getting a working debugger in managed code.

He gives a brief overview of managed-debugging and of Mdbg's architecture, including how Mdbg layers things on ICorDebug.
He also has the following demos:
1. creating an MDbg extension.  (I blogged about that here)
2. using the mdbg wrappers in your own tools. (Kind of like a combination of enumerating all processes, attaching to them, and listing their modules).

Check it out!

Why doesn't MDbg use generics?

$
0
0

If you've looked at the MDbg sources, you'll notice it looks a lot like a V1.1 app. There are few to no V2 (Whidbey) concepts in there: few anonymous delegates, very little (if any?) generics, no 'yield' keyword. Since MDbg shipped in V2, why didn't we use the greatest stuff from V2?

1. Bootstrapping: We started writing Mdbg early in V2; when V2 still looked a lot like V1. Generics weren't even available yet. This bootstrapping reason is also why V1.0 Cordbg was unmanaged instead of being managed with COM-interop. When we started writing Cordbg in V1.0, COM-interop wasn't ready yet.

2. In case we ever wanted to run Mdbg on V1.1: New V2 features like generics don't run on the V1 CLR; and so if we wanted to leave the door open for backporting Mdbg to V1.1, then we needed to only use the V1.1 feature set in V2. There are other problems for making a managed debugger in V1.1, so it's unlikely we'll ever actually do this. And the longer that V2 is out, the less attractive porting MDbg to V1 becomes.

Felice Pollano: Integrating a debugger into Reflector

$
0
0

Felice Pollano is working on project to make merge Reflector (an IL-->C# decompiler) and Mdbg (a managed debugger). See here for details.

I've said at many times this is possible, but have never had the cycles to go do it. (As a side note, there are actually a lot of things that are possible at the ICorDebug platform level but that the Visual Studio Debugger does not actually do. I've been meaning to blog more about this platform vs. application gap...)

So I'm excited to see somebody trying this out. The closest I got was adding the MDbg extension to debug IL. The MDbg source is public and uses public APIs, so brave folks could also try extending that sample to add Reflector support.

I've added Felice's project to the Mdbg link's page under "Other people's Mdbg work".  If you've been using Mdbg to do cool tools like that, feel free to ping me and I'll add your tool's URL to that page too.

Update [5/8/06]: Something's available here: http://www.felicepollano.com/PermaLink,guid,1c863e69-1b56-4cfa-a60b-203e1127b8bd.aspx

Managed Stack Explorer

$
0
0

Check out: Managed Stack Explorer. It's a tool on CodePlex that lets you automatically get stack snapshots of V2 managed processes.

Its debugging inspection functionality (viewing callstacks) is built on the CLR Debugging Services via ICorDebug, and access through managed code via the low-level managed wrappers from Mdbg (and so I've added it to the "Other people's MDbg work" list on the Mdbg linkfest  page). This is like a much fancier version of the Callstack Snapshot tool available. 

I think this is a great example of how debugging services can be useful for writing lightweight, specialized, diagnostic tools beyond just super-powerful debuggers. A big part of our motivation in publishing MDbg (and the managed wrappers) was to make these sort of tools easy to write.

The source is fully available, and you can download it and build it with VS. It currently has a bug where you need to either modify the Main() method or launch it outside of the debugger (Ctrl+F5).

Viewing all 35 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>