Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Saving C# Expression Tree to a File

I want to debug an expression and save the expression tree to a file:

var generator = DebugInfoGenerator.CreatePdbGenerator();
var document = Expression.SymbolDocument(fileName: "MyDebug.txt");
var debugInfo = Expression.DebugInfo(document, 6, 9, 6, 22);
var expressionBlock = Expression.Block(debugInfo, fooExpression);
var lambda = Expression.Lambda(expressionBlock, parameters);
lambda.CompileToMethod(method, generator);
var bakedType = type.CreateType();
return (type)bakedType.GetMethod(method.Name).Invoke(null, parameters);

How can I find or save "MyDebug.txt"?

like image 388
user2341923 Avatar asked Sep 15 '13 14:09

user2341923


1 Answers

You didn't comprehend what SymbolDocument() is...

var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("foo"), System.Reflection.Emit.AssemblyBuilderAccess.RunAndSave);
var mod = asm.DefineDynamicModule("mymod", true);
var type = mod.DefineType("baz", TypeAttributes.Public);
var method = type.DefineMethod("go", MethodAttributes.Public | MethodAttributes.Static);

Expression fooExpression = Expression.Divide(Expression.Constant(0), Expression.Constant(0));
var parameters = new ParameterExpression[0];
var generator = DebugInfoGenerator.CreatePdbGenerator();
var document = Expression.SymbolDocument(fileName: "MyDebug.txt");
var debugInfo = Expression.DebugInfo(document, 6, 9, 6, 22);
var expressionBlock = Expression.Block(debugInfo, fooExpression);
var lambda = Expression.Lambda(expressionBlock, parameters);

lambda.CompileToMethod(method, generator);
var bakedType = type.CreateType();
Func<int> method2 = (Func<int>)Delegate.CreateDelegate(typeof(Func<int>), bakedType.GetMethod(method.Name));

try
{
    int res = method2();
}
catch (Exception e)
{
    Console.WriteLine(e.StackTrace);
}

The Expression I generated is something like return 0/0;. I execute it and trap the exception (note that I don't use the Invoke method, because the Invoke method would put the DivisionByZeroException as the InnerException).

The stack trace I output is something like:

in baz.go() in MyDebug.txt:row 6 in ConsoleApplication85.Program.Test()

You see the MyDebug.txt:row 6? They are your SymbolDocument and your DebugInfo.

A more complete example, taken from Debugging Dynamically Generated Code (Reflection.Emit).

static void Test()
{
    // create a dynamic assembly and module 
    AssemblyName assemblyName = new AssemblyName("HelloWorld");
    AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

    // Mark generated code as debuggable. 
    // See https://docs.microsoft.com/en-us/archive/blogs/rmbyers/debuggableattribute-and-dynamic-assemblies for explanation.        
    Type daType = typeof(DebuggableAttribute);
    ConstructorInfo daCtor = daType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });
    CustomAttributeBuilder daBuilder = new CustomAttributeBuilder(daCtor, new object[] { 
    DebuggableAttribute.DebuggingModes.DisableOptimizations | 
    DebuggableAttribute.DebuggingModes.Default });
    assemblyBuilder.SetCustomAttribute(daBuilder);

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

    var doc = Expression.SymbolDocument(@"Source.txt");

    // 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("MyMethod", MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.Public, typeof(void), new Type[] { typeof(string[]) });

    // Create a local variable of type 'string', and call it 'xyz'
    var localXYZ = Expression.Variable(typeof(string), "xyz"); // Provide name for the debugger. 

    var generator = DebugInfoGenerator.CreatePdbGenerator();
    var debugInfo1 = Expression.DebugInfo(doc, 2, 1, 2, 100);

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

    // Line 2: xyz = "hello"; 
    var assign = Expression.Assign(localXYZ, Expression.Constant("Hello world!"));

    // Line 3: Write(xyz); 
    MethodInfo infoWriteLine = typeof(System.Console).GetMethod("WriteLine", new Type[] { typeof(string) });
    var debugInfo2 = Expression.DebugInfo(doc, 3, 1, 3, 100);

    var write = Expression.Call(infoWriteLine, localXYZ);

    // Line 4: return; 
    var debugInfo3 = Expression.DebugInfo(doc, 4, 1, 4, 100);

    var block = Expression.Block(new ParameterExpression[] { localXYZ }, new Expression[] { debugInfo1, assign, debugInfo2, write, debugInfo3 });
    var lambda = Expression.Lambda<Action<string[]>>(block, Expression.Parameter(typeof(string[])));

    // bake it 
    lambda.CompileToMethod(methodbuilder, generator);
    Type helloWorldType = typeBuilder.CreateType();

    // This now calls the newly generated method. We can step into this and debug our emitted code!! 
    var dm = (Action<string[]>)Delegate.CreateDelegate(typeof(Action<string[]>), helloWorldType.GetMethod("MyMethod"));
    dm(new string[] { null }); // <-- step into this call 
}

Save this somewhere as Source.txt

1|  // Test
2|  xyz = "hello"; 
3|  Write(xyz); 
4|  return;

then put a breakpoint on the dm(new string[] { null }), and press F11 when on the bp. It will ask you for the Source.txt. Note that if you don't select it, you'll have to delete the .suo file to get the window a second time.

Note the use of the DebuggableAttribute. It seems it's the trick to mark the dynamic assembly as debuggable.

like image 62
xanatos Avatar answered Sep 18 '22 18:09

xanatos