How to extract class IL code from loaded assembly

2019-06-10 11:02发布

问题:

How would I go about extracting the IL code for classes that are generated at runtime by reflection so I can save it to disk? If at all possible. I don't have control of the piece of code that generates these classes.

Eventually, I would like to load this IL code from disk into another assembly.

I know I could serialise/deserialise classes but I wish to use purely IL code. I'm not fussed with the security implications.

Running Mono 2.10.1

回答1:

Or better yet, use Mono.Cecil.

It will allow you to get at the individual instructions, even manipulating them and disassembling them (with the mono decompiler addition).

Note that the decompiler is a work in progress (last time I checked it did not fully support lambda expressions and Visual Basic exception blocks), but you can have pretty decompiled output in C# pretty easily as far as you don't hit these boundary conditions. Also, work has progressed since.

Mono Cecil in general let's you write the IL to a new assembly, as well, which you can then subsequently load into your appdomain if you like to play with bleeding edge.

Update I came round to trying this. Unfortunately I think I found what problem you run into. It turns out there is seems to be no way to get at the IL bytes for a generated type unless the assembly happened to get written out somewhere you can load it from.

I assumed you could just get the bits via reflection (since the classes support the required methods), however the related methods just raise an exception The invoked member is not supported in a dynamic module. on invocation. You can try this with the code below, but in short I suppose it means that it ain't gonna happen unless you want to f*ck with Marshal::GetFunctionPointerForDelegate(). You'd have to binary dump the instructions and manually disassemble them as IL opcodes. There be dragons.

Code snippet:

using System;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Reflection.Emit;
using System.Reflection;

namespace REFLECT
{
    class Program
    {
        private static Type EmitType()
        {
            var dyn = AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("Emitted"), AssemblyBuilderAccess.RunAndSave);
            var mod = dyn.DefineDynamicModule("Emitted", "Emitted.dll");
            var typ = mod.DefineType("EmittedNS.EmittedType", System.Reflection.TypeAttributes.Public);
            var mth = typ.DefineMethod("SuperSecretEncryption", System.Reflection.MethodAttributes.Public | System.Reflection.MethodAttributes.Static, typeof(String), new [] {typeof(String)});

            var il = mth.GetILGenerator();
            il.EmitWriteLine("Emit was here");
            il.Emit(System.Reflection.Emit.OpCodes.Ldarg_0);    
            il.Emit(System.Reflection.Emit.OpCodes.Ret);
            var result = typ.CreateType();
            dyn.Save("Emitted.dll");
            return result;
        }

        private static Type TestEmit()
        {
            var result = EmitType();
            var instance = Activator.CreateInstance(result);
            var encrypted = instance.GetType().GetMethod("SuperSecretEncryption").Invoke(null, new [] { "Hello world" });
            Console.WriteLine(encrypted); // This works happily, print "Emit was here" first

            return result;
        }

        public static void Main (string[] args)
        {
            Type emitted = TestEmit();

              // CRASH HERE: even if the assembly was actually for SaveAndRun _and_ it 
              // has actually been saved, there seems to be no way to get at the image
              // directly:
            var ass = AssemblyFactory.GetAssembly(emitted.Assembly.GetFiles(false)[0]);

              // the rest was intended as mockup on how to isolate the interesting bits
              // but I didn't get much chance to test that :)
            var types = ass.Modules.Cast<ModuleDefinition>().SelectMany(m => m.Types.Cast<TypeDefinition>()).ToList();
            var typ = types.FirstOrDefault(t => t.Name == emitted.Name);

            var operands = typ.Methods.Cast<MethodDefinition>()
                .SelectMany(m => m.Body.Instructions.Cast<Instruction>())
                .Select(i => i.Operand);

            var requiredTypes = operands.OfType<TypeReference>()
                .Concat(operands.OfType<MethodReference>().Select(mr => mr.DeclaringType))
                .Select(tr => tr.Resolve()).OfType<TypeDefinition>()
                .Distinct();
            var requiredAssemblies = requiredTypes
                .Select(tr => tr.Module).OfType<ModuleDefinition>()
                .Select(md => md.Assembly.Name as AssemblyNameReference);

            foreach (var t in types.Except(requiredTypes))
                ass.MainModule.Types.Remove(t);

            foreach (var unused in ass.MainModule
                     .AssemblyReferences.Cast<AssemblyNameReference>().ToList()
                     .Except(requiredAssemblies))
                ass.MainModule.AssemblyReferences.Remove(unused);

            AssemblyFactory.SaveAssembly(ass, "/tmp/TestCecil.dll");
        }
    }
}


回答2:

If all you want is the IL for your User class, you already have it. It's in the dll that you compiled it to.

From your other assembly, you can load the dll with the User class dynamically and use it through reflection.

UPDATE:

If what you have is a dynamic class created with Reflection.Emit, you have an AssemblyBuilder that you can use to save it to disk.

If your dynamic type was instead created with Mono.Cecil, you have an AssemblyDefinition that you can save to disk with myAssemblyDefinition.Write("MyAssembly.dll") (in Mono.Cecil 0.9).