Mono.Cecil Заменить аргумент в методе

Задача:

Функция поиска всех звонков

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

в SomeCnsl.exe и обернуть аргумент 's' в функции ChangeString

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

Пример:

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

Для решения этой задачи я использую Mono.Cecil. И мое решение выглядит так:

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();
}

Чтобы найти строковый аргумент, я создаю рекурсивный метод 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);
    }
}

И это работа. Пока в аргументы WriteString не вставьте несколько строк, например:

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          

Итак, мой вопрос:

Могу ли я более точно определить все команды IL из второго аргумента функции WriteText? И если смогу, то как?


person WarFollowsMe    schedule 22.03.2016    source источник
comment
Вы нашли решение в другом месте?   -  person Sellorio    schedule 04.04.2020


Ответы (1)


Это не ответ на ваш вопрос, но он может помочь вам сделать это по-другому. Вы можете использовать Roslyn, чтобы переписать свой код по своему усмотрению.

Вы можете сделать это путем наследования CSharpSyntaxRewriting, а затем переопределить соответствующие методы. После этого вы получите новое синтаксическое дерево с измененным кодом, после чего вы сможете его скомпилировать и сохранить на диск. Взгляните, например, здесь.

person Dudi Keleti    schedule 29.03.2016