Debugging Dynamically Generated Code (Reflection.Emit)

The CLR supports the ability to generate code at runtime (Reflection.Emit). This is great for the many dynamic languages targeting the runtime (such as Iron Python).

We also support that ability to provide debugging information for that code so that you can debug it when it’s executed. This means that even the dynamic languages written for .Net get free debugging support.

Sample CodeJoel Pobar had a great sample here of using Reflection.Emit to print out “Hello World!”. I’ve extended his example to demonstrate emitting debugging information.

In my example, the code we emit is from the pseudo code in the file ‘Source.txt’ (download from here) :

// This now calls the newly generated method. We can step into this and debug our emitted code!! helloWorldType.GetMethod(“Main”).Invoke(null, newstring[] { null }); // <– step into this call }
}

The key take away here is that judging by the small amount of highlighting, it’s very easy to make your reflection-emit code debuggable. Reflection-Emit provides nice integration and wrappers for emitting the interesting debugging information. This makes naming local variables, parameters, etc trivial.

The other interesting thing is the call to ILGenerator.MarkSequencePoint. Sequence points associate IL instructions with source files. Let’s dissect the call:

ilGenerator.MarkSequencePoint(doc, 2, 1, 2, 100);

In this case, the ilGenerator object keeps track of the IL offset for us. The ‘doc’ parameter to MarkSequencePoint refers back to the DefineDocument call earlier which associates this method with the file “Source.txt”. The next 4 numbers are the starting line, starting column, ending line, and ending column (all 1-based). You can verify that these match back up to Source.txt. I pick the column range (1,100) to specify the whole line.

The one other thing to note is that we emit the sequence points before the opcodes they correspond to.

See the MSDN reference here for more information about making reflection.emit debuggable.

Proof that it’s debuggable

As proof that it’s debuggable, run the sample in Visual Studio. Place a breakpoint at the last call to Invoke, on this line:

Update: The Debuggable AttributeThe System.Diagnostics.DebuggableAttribute specifies some jit properties such as if the code is jitted optimized or not. If we want to be able to debug the code, we may want to explicitly use this attribute to disable optimizations. Rick Byers talks more about that here and I’ve updated the sample with his comments.

One caveat:V2.0 offers light-weight-codegen (LCG) which is a very fast reflection-emit that lets you just create methods without having to create modules, assemblies, types, or metadata. The methods also get garbage-collected when they are no longer used (which is much nicer than waiting handling an appdomain unload). Unfortunately, we don’t yet support debugging info for LCG. This was primarily a scheduling problem (it was effectively cut). The problem is that the whole managed debugging API is effectively an extension to the metadata API, and so debugging LCG without metadata breaks our model. We’ll find a way to fix this in V3.0. Update (10/25/05): Here are some tips for debugging LCG. Here is a visualizer to display the IL for a dynamic method.

In the meantime, if you want LCG methods to be debuggable, you could consider having a debug mode that obtains the ILGenerator from traditional emit (as demonstrated above), instead of DynamicMethod.GetILGenerator.

I have been able to debug this puppy using PEBrowse Interactive and to step into the Main method of the HelloWorld program! But, it takes some effort (and exposed a few bugs in my code — thanx!) You run the Main from EmitHelloWorld and continue execution to just before the code calls System.Reflection.MethodBase::Invoke. Then you enable PI’s Debug/Break on JIT Event option and continue running until a method shows up with no method name. This is the Main of the emitted code. Not pretty, but it works. I see that this does not run as a "normal" managed executable (and does not show up in TaskManager.)

I tried this with the latest release of Visual C# Express and found that it hit the breakpoint just as you describe. I now want to go a step further and get Edit & Continue working on the emited code. I just want to get it working with C#, I have no plans to implement my own language!

I tried changing Source.txt to Source.cs and replacing it with valid C# code. I then fixed the MarkSequencePoint calls to make them correlate (roughly) with the new code. I also changed DefineDocument to use SymLanguageType.ILAssembly, SymLanguageVendor.Microsoft, SymDocumentType.Text.

I was hoping this would allow me to use Edit & Continue on my generated code. No such luck though, the sequence points just dissapear when I edit the code. Is there a trick to let Visual Studio know that generated code is C# and a valid target for E&C?

Russ (Smidgeonsoft) – It’s cool that PEBrowse can handle this. Nice! Reflection emit is not creating a new executable, and so won’t show up as a new process in taskman. This is all in a single process.

Jamie – EnC doesn’t work w/ Ref-emit. I think that deserves its own post. Stay tuned.