Mono.Cecil 替换方法中的参数

Posted

技术标签:

【中文标题】Mono.Cecil 替换方法中的参数【英文标题】:Mono.Cecil Replace argument in method 【发布时间】:2016-07-09 04:07:21 【问题描述】:

任务:

查找所有调用函数

public static void WriteString(int index0, string s, int index1)

    Console.WriteLine(s);

在 SomeCnsl.exe 中并在函数 ChangeString 中包装参数“s”

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          

所以,我的问题是:

我可以更精确地定义函数 WriteText 中第二个参数的所有 IL 命令吗?如果可以,那怎么办?

【问题讨论】:

您在其他地方找到了解决方案吗? 【参考方案1】:

这不是对您的问题的回答,但它可以帮助您以另一种方式做到这一点。 您可以根据需要使用 Roslyn 重写代码。

你可以通过继承CSharpSyntaxRewriting来实现,然后重写相关方法。之后,您将收到一个带有修改后代码的新语法树,然后您可以编译它并将其保存到磁盘。 以here 为例。

【讨论】:

以上是关于Mono.Cecil 替换方法中的参数的主要内容,如果未能解决你的问题,请参考以下文章

巧用Mono.Cecil反射加载类型和方法信息

使用 Mono.Cecil 在 C# 程序集中注入方法

如何使用 Mono.Cecil 获取 IL 指令的源代码/行号

如何以编程方式替换 .NET 程序集中的嵌入式资源?

Mono.Cecil - 0.6

使用 Mono.Cecil 辅助 Unity3D 手游进行性能测试(续)