简体   繁体   中英

Mono.Cecil Replace argument in method

Task:

Find all calls function

public static void WriteString(int index0, string s, int index1)
{
    Console.WriteLine(s);
}

in SomeCnsl.exe and wrap argument 's' in function ChangeString

public static string ChangeText(string text)
{
    return text + "new";
}

Example:

original: WriteString(0,"hello",1);
wrap:     WriteString(0,ChangeText("hello"),1);

To solve this task I use Mono.Cecil. And my solution look like this:

private static AssemblyDefinition MainAssembly;
static void Main(string[] args)
{
    MainAssembly = AssemblyDefinition.ReadAssembly("SomeCnsl.exe");

    var changeTextMethod = typeof(SomeCnsl.Program).GetMethod("ChangeText");
    var changeTextMethodRef = MainAssembly.MainModule.Import(changeTextMethod);

    var mainMethod = MainAssembly.Modules.SelectMany(mod => ModuleDefinitionRocks.GetAllTypes(mod))
        .SelectMany(t => t.Methods)
        .Where(method => null != method.Body);

    foreach (var body in mainMethod.Select(m => m.Body))
    {
        var processor = body.GetILProcessor();
        var instructions = body.Instructions.Where(instr => instr.OpCode == OpCodes.Call && instr.ToString().Contains("WriteString")).ToList();
        foreach (var instr in instructions)
        {
            var stringEndArg = GetStringArgument(instr);
            var writeInstruction = processor.Create(OpCodes.Call, changeTextMethodRef);
            processor.InsertAfter(stringEndArg, writeInstruction);
        }
    }
    SavePatchedAssembly();
}

To find string argument I create recursive method GetStringArgument:

public static Instruction GetStringArgument(Instruction callDrawString)
{       
    if (callDrawString.Previous.OpCode == OpCodes.Ldstr || callDrawString.Previous.OpCode == OpCodes.Ldarg_1 ||
        (callDrawString.Previous.OpCode == OpCodes.Call && callDrawString.Previous.ToString().Contains("System.String::")) ||
        (callDrawString.Previous.OpCode == OpCodes.Callvirt && callDrawString.Previous.ToString().Contains("System.String::")) ||
        (callDrawString.Previous.OpCode == OpCodes.Callvirt && callDrawString.Previous.ToString().Contains("Generic.List`1<System.String>::get_Item")) ||
        (callDrawString.Previous.OpCode == OpCodes.Callvirt && callDrawString.Previous.ToString().Contains("Generic.Dictionary`2<") && callDrawString.Previous.ToString().Contains("System.String>::get_Item")) ||
        ((callDrawString.Previous.Operand as ParameterReference) != null && (callDrawString.Previous.Operand as ParameterReference).ParameterType.FullName == typeof(string).FullName) ||
        ((callDrawString.Previous.Operand as FieldReference) != null && (callDrawString.Previous.Operand as FieldReference).FieldType.FullName == typeof(string).FullName) ||
        ((callDrawString.Previous.Operand as PropertyReference) != null && (callDrawString.Previous.Operand as PropertyReference).PropertyType.FullName == typeof(string).FullName))
    {
        return callDrawString.Previous;
    }
    else
    {
        return GetStringArgument(callDrawString.Previous);
    }
}

And it's work. Until in arguments of WriteString put some strings, like this:

static Dictionary<string, int> listParam = new Dictionary<string, int> { { "first", 1 }, { "second", 2 }, { "third", 3 } };
static int index = 2;
static string indexString = "second";

static void Main(string[] args)
{
    while(true)
    {
        Thread.Sleep(1000);
        WriteString(index, indexString, listParam[indexString]);
    }        
}

ILCode WriteString call:
    IL_0022: ldsfld       int32 SomeCnsl.Program::index
    IL_0027: ldsfld       string SomeCnsl.Program::indexString
    IL_002c: ldsfld       class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32> SomeCnsl.Program::listParam
    IL_0031: ldsfld       string SomeCnsl.Program::indexString
    IL_0036: callvirt     instance !1/*int32*/ class [mscorlib]System.Collections.Generic.Dictionary`2<string, int32>::get_Item(!0/*string*/)
    IL_003b: call         void SomeCnsl.Program::WriteString(string, int32, int32)
    IL_0040: nop          

So, my question is:

Can I define all IL commands from second argument in function WriteText more precisely? And if I can, then how?

It's not answer on your question but it can help you to do it in another way. You can use Roslyn to rewrite your code as you want.

You can do it by inheritance CSharpSyntaxRewriting and then override the relevant methods. After that you will receive a new syntax tree with the modified code then you can compile it and save it to disk. Take a look here for example.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM