| Programming | Software Engineering | Web Design | Database | Operating Systems

Debugging Dynamically Generated Code (Reflection.Emit)

Mike Stall
Keywords: Debugging,Dynamically Generated Code
From: http://blogs.msdn.com/jmstall/archive/2005/02/03/366429.aspx
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 Code

Joel 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) :

1

2

3

4

// Test

xyz = "hello";

Write(xyz);

return;

 

 

Here’s the C# code to emit that. I’ve highlighted the additions related to debugging information.

 

// Sample of emitting debugging information for dynamic modules

// Reflection emit example adopted from http://blogs.msdn.com/joelpob/archive/2004/01/21/61411.aspx

using System;

using System.Reflection;

using System.Reflection.Emit;

using System.Threading;

using System.Diagnostics.SymbolStore;

 

public class EmitHelloWorld

{

static void Main(string[] args)

{

// create a dynamic assembly and module

AssemblyName assemblyName = new AssemblyName();

assemblyName.Name = "HelloWorld";

AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

ModuleBuilder module;

module = assemblyBuilder.DefineDynamicModule("HelloWorld.exe", true); // <-- pass 'true' to track debug info.

// Tell Emit about the source file that we want to associate this with.

ISymbolDocumentWriter doc = module.DefineDocument(@"Source.txt", Guid.Empty, Guid.Empty, Guid.Empty);

 

// create a new type to hold our Main method

TypeBuilder typeBuilder = module.DefineType("HelloWorldType", TypeAttributes.Public | TypeAttributes.Class);

 

// create the Main(string[] args) method

MethodBuilder methodbuilder = typeBuilder.DefineMethod("Main", MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(string[]) });

 

// generate the IL for the Main method

ILGenerator ilGenerator = methodbuilder.GetILGenerator();

 

// Create a local variable of type ‘string’, and call it ‘xyz’

LocalBuilder localXYZ = ilGenerator.DeclareLocal(typeof(string));

localXYZ.SetLocalSymInfo("xyz"); // Provide name for the debugger.

 

// Emit sequence point before the IL instructions. This is start line, start col, end line, end column,

 

// Line 2: xyz = "hello";

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

ilGenerator.Emit(OpCodes.Ldstr, "Hello world!");

ilGenerator.Emit(OpCodes.Stloc, localXYZ);

 

// Line 3: Write(xyz);

MethodInfo infoWriteLine = typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) });

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

ilGenerator.Emit(OpCodes.Ldloc, localXYZ);

ilGenerator.EmitCall(OpCodes.Call, infoWriteLine, null);

 

// Line 4: return;

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

ilGenerator.Emit(OpCodes.Ret);

 

// bake it

Type helloWorldType = typeBuilder.CreateType();

 

// This now calls the newly generated method. We can step into this and debug our emitted code!!

helloWorldType.GetMethod("Main").Invoke(null, new string[] { 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:

helloWorldType.GetMethod("Main").Invoke(null, new string[] { null }); // ß step into this call

 

Make sure Source.txt is loaded in VS. And then step in (via F11).

You’ll see the VS debugger stepped into our dynamically generated code.

 

You’ll notice:

-         we’re doing source-level debugging of the emitted code.

-         our declared local (‘xyz’) shows up in the locals window

-         our code shows up on the callstack.

 

 

You can also debug it in Mdbg (or any 3rd-party debugger). In fact, Mdbg’s ildasm extension can even show you the disassembly:

[t#:0] mdbg> print

xyz ="Hello world!"

unnamed_param_0=<null>

 

[t#:0] mdbg> w

Thread [#:0]

*0. HelloWorldType.Main (Source.txt:3)

1. System.RuntimeMethodHandle.InvokeMethodFast (source line information unavailable)

2. System.Reflection.RuntimeMethodInfo.Invoke (source line information unavailable)

3. System.Reflection.MethodBase.Invoke (source line information unavailable)

4. EmitHelloWorld.Main (program.cs:56)

 

[t#:0] mdbg> ild

code size: 13

current IL-IP: 6

mapping : MAPPING_EXACT

URL: HelloWorldType.Main

IL_0 : ldstr "Hello world!"

IL_5 : stloc.0

* IL _6 : ldloc.0

IL_7 : call System.Console.WriteLine

IL_C : ret

 

 

 

 

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. 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.

 

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.


Related Article
  • Determining the NetCF version while debugging
  • Debugging any .Net language
  • CLR Debugging vs. CLR Profiling
  • Debugging IL
  • Managed Debugging doesn't support Fibers

  • Comment
    zzz Post At: 2006-12-8 12:22:59





    warsaw hotels Post At: 2006-12-12 6:51:45
    Your site is really very interesting. This is mine: http://www.apartments.waw.pl/
    zzz Post At: 2006-12-13 10:20:45





    ټ Post At: 2006-12-18 12:46:29

    ټ Post At: 2007-1-15 2:14:49

    ټ Post At: 2007-1-23 2:11:15

    zzz Post At: 2007-2-23 17:08:47

    Դ Post At: 2007-6-26 3:24:51
    00
    1001 Post At: 2007-7-29 16:06:12

    3545
    1001 Post At: 2007-8-28 4:34:24
    abc
    163 Post At: 2007-9-30 19:27:53
    1

    1 Post At: 2007-12-9 1:16:00
    We sell store online.
    zzy Post At: 2007-12-11 22:53:43
    come
    zzy Post At: 2008-3-20 23:55:07
    abc.
    Add Your Comment:
    Your Name:      
    Your Comment:
    Note: After you post comment,please refresh the browser to show you comment.
    Search In YeYan.CN:
     

    Home | Privacy Policy | Copyright Policy | Contact Us | Site Map
    Copyright © 2006 YeYan.CN, All Rights Reserved.