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.WriteLine
IL:0x10,0x16 --> Native:0x2d,0x35 (N. size=0x8 bytes)
IL_16 : nopIL: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.
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.