【问题标题】:Inject stopwatch using c# into all dll methods using Mono.Cecil, including methods with multiple Return statements将使用 c# 的秒表注入使用 Mono.Cecil 的所有 dll 方法中,包括具有多个 Return 语句的方法
【发布时间】:2020-02-28 03:38:29
【问题描述】:

Case 1Case 2Case 3Case 4

目标: 使用注入器代码,我试图将秒表方法(在秒表 dll 中)注入目标 dll 的所需代码位置,以便计算目标 dll 中每个方法所花费的时间,这可能是也可能不是无效的方法,它可能有多个返回语句。

目标 dll

public class targetDll
{

void func1(){
     //Inject Stopwatch_start(); method here
        int a = 3;
        int b = 4;
        int temp;
        temp = a;
        a = b;
        b =temp;
        if (a + b > 2)
        {
            Console.WriteLine("function____1");
        }
      #Stopwatch_stop()  //Inject stop time here
    }

String func2(){
      //Inject Stopwatch_start(); method here
        int a = 3;
        int b = 4;
        int c = 5;
        int temp;
        temp = a;
        a = b;
        b = c;
        c = temp;
        if (a + b > 5)
        {
            Console.WriteLine("function____2");
        //inject Stopwatch_stop() method here
          return ;
        }
        a = temp;
      //inject Stopwatch_stop(); method here
          return;
  }
}

源 dll(秒表 dll)

 public  static class stopwatch_class
{
  static System.Diagnostics.Stopwatch stopwatch_obj = new System.Diagnostics.Stopwatch();

    public static void stopwatch_start()
    {
        stopwatch_obj.Start();
    }

    public static  void stopwatch_stop()
    {            
        stopwatch_obj.Stop();
        Console.WriteLine(stopwatch_obj.ElapsedMilliseconds);            
     }        
    }
 }

注入器代码

 class Trial_injector
{
    static void Main(string[] args)
    {
        var start_method = (dynamic)null;
        var stop_method = (dynamic)null;

        AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("targetDll.dll", 
        new ReaderParameters { ReadWrite = true });

        var target_modules = target_assembly.MainModule;
        TypeDefinition[] target_module = target_modules.Types.ToArray();

        AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new 
        ReaderParameters { ReadWrite = true });

        var source_modules = source_assembly.MainModule;
        TypeDefinition[] source_module = source_modules.Types.ToArray();


        foreach (var type in source_module)
        {
            foreach (var method in type.Methods)
            {
                if (method.Name == "stopwatch_start")
                {
                    start_method = method;
                }

                if (method.Name == "stopwatch_stop")
                {
                    stop_method = method;
                }
            }
        }

        foreach(var module_ in target_module)
        {
            foreach(var method_ in module_.Methods)
            {
               String stg="hello_world";
                var processor2 = method_.Body.GetILProcessor();
                var first_instruction = method_.Body.Instructions.First();
                var last_instruction = method_.Body.Instructions.Last();
                var ldstr = processor2.Create(OpCodes.Ldstr, stg);

                var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
                var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
                processor2.InsertBefore(first_instruction, ldstr);
                processor2.InsertAfter(first_instruction, call);

                processor2.InsertBefore(last_instruction, ldstr);
                processor2.InsertBefore(last_instruction, call2);
            }
        }
        target_assembly.Write();
    }

【问题讨论】:

  • 你尝试了什么?
  • 我尝试将 stopwatch_start() 和 stopwatch_stop() 分别注入目标 dll 中每个方法的开头和结尾。问题是,在具有 "if body" 的 void 方法中,stopwatch_stop() 方法被插入到 "if body" 本身的末尾,而不是 @Cyril 方法的末尾
  • var processor2 = method_.Body.GetILProcessor(); //这会创建一个新的处理器 var last_instruction = method_.Body.Instructions.Last(); //这移动到代码的最后一行 var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method)); //这会导入我的 sendstop 方法 processor2.InsertBefore(last_instruction, call2); //在这里,我将转到代码的最后一条指令并使用 call2 调用 stopwatch_Stop();功能

标签: c# dll mono mono.cecil


【解决方案1】:

您的代码几乎是正确的。需要做的修改很少。

不确定为什么需要ldstr 操作码,因为它在任何地方都不需要。对于您希望在之前插入第一个操作码而不是之后的呼叫。至于最后一条指令,您可以使用InsertBefore。所以最终的代码可能是这样的:

foreach (var module_ in target_module)
{
    foreach (var method_ in module_.Methods)
    {
        var processor2 = method_.Body.GetILProcessor();
        var first_instruction = method_.Body.Instructions.First();
        var last_instruction = method_.Body.Instructions.Last();
        var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
        var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
        processor2.InsertBefore(first_instruction, call);

        processor2.InsertBefore(last_instruction, call2);
    }
}

但这不适用于一些早期回报。为什么?早期返回被编码为brbr_s 操作码到过程结束时的ret,如果我们在 ret 之前注入我们的call,那些早期返回将跳过它。在您的示例中,不需要它,因为此代码已转换为 if-else 并且我们在两种情况下都有正确匹配的分支。但是图像我们有这样的代码:

int a = 3;
if (a == 3)
{
  return; // very early return here
}
// the rest as in original one

我们不会看到此方法打印的经过时间,因为 return 将在我们注入调用之后指导执行。我们在这里需要做的是更新所有负责提前返回的分支指令(因此它们跳转到ret操作码)并将它们指向我们的调用。我们可以通过以下方式做到这一点:

foreach (var bodyInstruction in method_.Body.Instructions)
{
    if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
    if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;

    bodyInstruction.Operand = call2;
}

所以我们在这里所做的是扫描所有操作码,看看我们是否有一个 brbr_s 跳转到返回,我们更新它以跳转到我们的调用。中提琴。

注意:使用Elapsed 而不是ElapsedMilliseconds,因为前者是全零。

完整代码:

var start_method = (dynamic) null;
var stop_method = (dynamic) null;

AssemblyDefinition target_assembly = AssemblyDefinition.ReadAssembly("target.exe", new ReaderParameters {ReadWrite = true});

var target_modules = target_assembly.MainModule;
TypeDefinition[] target_module = target_modules.Types.ToArray();


AssemblyDefinition source_assembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", new ReaderParameters {ReadWrite = true});

var source_modules = source_assembly.MainModule;
TypeDefinition[] source_module = source_modules.Types.ToArray();

foreach (var type in source_module)
{
  foreach (var method in type.Methods)
  {
    if (method.Name == "stopwatch_start")
    {
      start_method = method;
    }

    if (method.Name == "stopwatch_stop")
    {
      stop_method = method;
    }
  }
}

foreach (var module_ in target_module)
{
  foreach (var method_ in module_.Methods)
  {
    var processor2 = method_.Body.GetILProcessor();
    var first_instruction = method_.Body.Instructions.First();
    var last_instruction = method_.Body.Instructions.Last();
    var call = processor2.Create(OpCodes.Call, method_.Module.Import(start_method));
    var call2 = processor2.Create(OpCodes.Call, method_.Module.Import(stop_method));
    processor2.InsertBefore(first_instruction, call);

    processor2.InsertBefore(last_instruction, call2);

    foreach (var bodyInstruction in method_.Body.Instructions)
    {
      if (bodyInstruction.OpCode != OpCodes.Br && bodyInstruction.OpCode != OpCodes.Br_S) continue;
      if (((Instruction)bodyInstruction.Operand).OpCode != OpCodes.Ret) continue;

      bodyInstruction.Operand = call2;
    }
  }
}
target_assembly.Write();

自我推销

我碰巧用 Mono.Cecil 录制了两个关于这样做(以有点不同的方式)的视频。你可以找到它Writing simple .NET execution tracer with Mono.CecilInstrumenting .NET assemblies to measure method's execution time in with Mono.Cecil

自我推销关闭

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 2021-07-18
    • 1970-01-01
    • 2013-12-19
    相关资源
    最近更新 更多