Внедрить секундомер с помощью c# во все методы dll с помощью Mono.Cecil, включая методы с несколькими операторами Return.

Случай 1 Случай 2 Случай 3 Случай 4

Цель: Используя код инжектора, я пытаюсь внедрить методы секундомера (которые находятся в dll секундомера) в нужном месте кода целевой dll, чтобы рассчитать время, затрачиваемое каждым методом в целевом объекте. dll, который может быть или не быть методом void и может иметь несколько операторов возврата.

Целевая 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();
    }

person john_2416    schedule 19.02.2020    source источник
comment
Что вы пробовали?   -  person Cyril Durand    schedule 19.02.2020
comment
Я попытался ввести stopwatch_start() и stopwatch_stop() в начало и конец каждого метода соответственно в целевой dll. Проблема в том, что в методе void, который имеет тело if, метод stopwatch_stop() вставляется в конец самого тела if, а не в конец метода @Cyril   -  person john_2416    schedule 19.02.2020
comment
var процессор2 = метод_.Body.GetILProcessor(); // Это создает новый процессор var last_instruction = method_.Body.Instructions.Last(); //Переход к последней строке кода var call2 = processing2.Create(OpCodes.Call, method_.Module.Import(stop_method)); // Это импортирует мой метод sendstop. //Здесь я перехожу к последней инструкции кода и использую call2 для вызова stopwatch_Stop(); функция   -  person john_2416    schedule 19.02.2020


Ответы (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);
    }
}

но это не сработает с некоторыми ранними доходами. Почему? Ранние возвраты кодируются как код операции br или br_s для 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;
}

Итак, что мы здесь делаем, так это сканируем все коды операций и смотрим, есть ли у нас br или br_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. Вы можете найти его Написание простого трассировщика выполнения .NET с помощью Mono.Cecil и Инструментирование сборок .NET для измерения времени выполнения метода с помощью Mono.Cecil.

самореклама выключена

person Paweł Łukasik    schedule 27.02.2020