Here's the code that does the method cloning:
private static AssemblyDefinition CloneTargetMethod(ModuleDefinition
originalModule, MethodDefinition targetMethod)
{
var assemblyNameDefinition = new
AssemblyNameDefinition("TestAssembly", new Version(1, 0));
var testAssembly =
AssemblyDefinition.CreateAssembly(assemblyNameDefinition,
originalModule.Name, ModuleKind.Dll);
var name = targetMethod.FullName;
var module = testAssembly.MainModule;
var returnType = module.Import(targetMethod.ReturnType);
var globalType = module.Types[0];
var attributes = MethodAttributes.Public |
MethodAttributes.Static;
var clonedMethod = new MethodDefinition(targetMethod.Name,
attributes, returnType);
globalType.Methods.Add(clonedMethod);
foreach (var parameter in targetMethod.Parameters)
{
clonedMethod.Parameters.Add(new
ParameterDefinition(parameter.ParameterType));
}
var methodStubParameter = new
ParameterDefinition(module.Import(typeof(IMethodStub)));
clonedMethod.Parameters.Add(methodStubParameter);
// Add the IMethodStub parameter so that we can stub out
// all of the method calls
var body = clonedMethod.Body;
body.InitLocals = true;
body.MaxStackSize = 8;
var getMethodFromHandle =
module.ImportMethod<MethodBase>("GetMethodFromHandle",
typeof(RuntimeMethodHandle), typeof(RuntimeTypeHandle));
var stackCtor =
module.ImportConstructor<Stack<object>>(new Type[0]);
var pushMethod =
module.ImportMethod<Stack<object>>("Push");
var toArray =
module.ImportMethod<Stack<object>>("ToArray");
var execute = module.ImportMethod<IMethodStub>("Execute");
var currentArguments =
clonedMethod.AddLocal<Stack<object>>("__arguments");
var currentArgument =
clonedMethod.AddLocal<object>("__currentArgument");
var currentMethod =
clonedMethod.AddLocal<MethodBase>("__currentMethod");
var target = clonedMethod.AddLocal<object>("__target");
var processor = body.GetILProcessor();
// Create the shim for calling the IMethodStub interface
var methodBody = targetMethod.Body;
var originalInstructions = new
List<Instruction>(methodBody.Instructions.ToArray());
body.Instructions.Clear();
// Create the stack that will hold the method
arguments
foreach (var instruction in originalInstructions)
{
var opCode = instruction.OpCode;
if (opCode.FlowControl == FlowControl.Branch)
{
Console.WriteLine();
}
if (opCode != OpCodes.Callvirt && opCode !=
OpCodes.Call)
{
processor.Append(instruction);
continue;
}
processor.Emit(OpCodes.Nop);
processor.Emit(OpCodes.Newobj, stackCtor);
processor.Emit(OpCodes.Stloc, currentArguments);
var operand = instruction.Operand;
var method = (MethodReference)operand;
foreach (var param in method.Parameters)
{
// Save the current argument
var parameterType = param.ParameterType;
if (parameterType.IsValueType || parameterType is
GenericParameter)
processor.Emit(OpCodes.Box, parameterType);
processor.Emit(OpCodes.Stloc, currentArgument);
processor.Emit(OpCodes.Ldloc, currentArguments);
processor.Emit(OpCodes.Ldloc, currentArgument);
processor.Emit(OpCodes.Callvirt,
pushMethod);
}
// Save the target if necessary
if (method.HasThis)
processor.Emit(OpCodes.Stloc, target);
var declaringType = method.DeclaringType;
// Instantiate the generic type before determining
// the current method
if (declaringType.GenericParameters.Count > 0)
{
var genericType = new
GenericInstanceType(declaringType);
foreach (var parameter in
declaringType.GenericParameters)
{
genericType.GenericArguments.Add(parameter);
}
declaringType = genericType;
}
processor.Emit(OpCodes.Ldtoken, method);
processor.Emit(OpCodes.Ldtoken, declaringType);
processor.Emit(OpCodes.Call,
getMethodFromHandle);
processor.Emit(OpCodes.Ldarg,
methodStubParameter);
// The stack should be balanced at this point
// but for some reason I get the 'branch out of
method' error
// even though I haven't changed any branch
instructions
processor.Emit(OpCodes.Pop);
processor.Emit(OpCodes.Pop);
//processor.Emit(OpCodes.Ldloc, currentMethod);
//processor.Emit(OpCodes.Ldloc, target);
//processor.Emit(OpCodes.Ldloc, currentArguments);
//processor.Emit(OpCodes.Callvirt, toArray);
//processor.Emit(OpCodes.Callvirt, execute);
//processor.PackageReturnValue(returnType);
}
return testAssembly;
}
Here's the PEVerify output:
Microsoft (R) .NET Framework PE Verifier. Version 4.0.30319.1
Copyright (c) Microsoft Corporation. All rights reserved.
[IL]: Error: [H:\Development\MethodCloning\MethodCloning\bin\Debug
\output.dll : <Module>::Print][offset 0x0000004D] Branch out of the
method.
1 Error(s) Verifying output.dll
...and here's the dump of the Print method:
.method public static void Print(int32 A_0,
class
[MethodCloning]MethodCloning.IMethodStub A_1) cil managed
{
// Code size 214 (0xd6)
.maxstack 2
.locals init (class
[System]System.Collections.Generic.Stack`1<object> V_0,
object V_1,
class [mscorlib]System.Reflection.MethodBase V_2,
object V_3)
IL_0000: nop
IL_0001: ldarg.1
IL_0002: ldc.i4.3
IL_0003: rem
IL_0004: brtrue.s IL_0011
IL_0006: ldarg.1
IL_0007: ldc.i4.5
IL_0008: rem
IL_0009: ldc.i4.0
IL_000a: ceq
IL_000c: ldc.i4.0
IL_000d: ceq
IL_000f: br.s IL_0012
IL_0011: ldc.i4.1
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: brtrue.s IL_004f
IL_0016: nop
IL_0017: ldstr "FizzBuzz"
IL_001c: nop
IL_001d: newobj instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
IL_0022: stloc V_0
IL_0026: stloc V_1
IL_002a: ldloc V_0
IL_002e: ldloc V_1
IL_0032: callvirt instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
IL_0037: ldtoken method void
[mscorlib]System.Console::WriteLine(string)
IL_003c: ldtoken [mscorlib]System.Console
IL_0041: call class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
valuetype [mscorlib]System.RuntimeTypeHandle)
IL_0046: ldarg A_1
IL_004a: pop
IL_004b: pop
IL_004c: nop
IL_004d: br.s IL_ffffffd5
IL_004f: ldarg.1
IL_0050: ldc.i4.3
IL_0051: rem
IL_0052: ldc.i4.0
IL_0053: ceq
IL_0055: ldc.i4.0
IL_0056: ceq
IL_0058: stloc.0
IL_0059: ldloc.0
IL_005a: brtrue.s IL_0092
IL_005c: ldstr "Fizz"
IL_0061: nop
IL_0062: newobj instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
IL_0067: stloc V_0
IL_006b: stloc V_1
IL_006f: ldloc V_0
IL_0073: ldloc V_1
IL_0077: callvirt instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
IL_007c: ldtoken method void
[mscorlib]System.Console::WriteLine(string)
IL_0081: ldtoken [mscorlib]System.Console
IL_0086: call class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
valuetype [mscorlib]System.RuntimeTypeHandle)
IL_008b: ldarg A_1
IL_008f: pop
IL_0090: pop
IL_0091: nop
IL_0092: ldarg.1
IL_0093: ldc.i4.5
IL_0094: rem
IL_0095: ldc.i4.0
IL_0096: ceq
IL_0098: ldc.i4.0
IL_0099: ceq
IL_009b: stloc.0
IL_009c: ldloc.0
IL_009d: brtrue.s IL_00d5
IL_009f: ldstr "Buzz"
IL_00a4: nop
IL_00a5: newobj instance void class
[System]System.Collections.Generic.Stack`1<object>::.ctor()
IL_00aa: stloc V_0
IL_00ae: stloc V_1
IL_00b2: ldloc V_0
IL_00b6: ldloc V_1
IL_00ba: callvirt instance void class
[System]System.Collections.Generic.Stack`1<object>::Push(!0)
IL_00bf: ldtoken method void
[mscorlib]System.Console::WriteLine(string)
IL_00c4: ldtoken [mscorlib]System.Console
IL_00c9: call class [mscorlib]System.Reflection.MethodBase
[mscorlib]System.Reflection.MethodBase::GetMethodFromHandle(valuetype
[mscorlib]System.RuntimeMethodHandle,
valuetype [mscorlib]System.RuntimeTypeHandle)
IL_00ce: ldarg A_1
IL_00d2: pop
IL_00d3: pop
IL_00d4: nop
IL_00d5: ret
} // end of method 'Global Functions'::Print
Now for some reason, Cecil tries to branch out of the method with the
following instruction:
IL_004d: br.s IL_ffffffd5
When I step through the code with the debugger and look at the
original branch instructions, none of those branch instructions ever
try to jump out of the method. Am I doing something wrong here, or is
this a bug in Cecil?
--
--
mono-cecil