Внедрить секундомер, используя c#, во все методы dll, используя Mono.Cecil, включая методы с несколькими операторами Return. - PullRequest
1 голос
/ 19 февраля 2020

Дело 1 Дело 2 Дело 3 Дело 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();
    }

1 Ответ

0 голосов
/ 27 февраля 2020

Вы были почти правы с вашим кодом. Было несколько изменений, которые нужно было сделать.

Не уверен, зачем вам нужен код операции ldstr, так как он нигде не нужен. Для вызова, в который вы хотите вставить до , первый код операции не после. Что касается последней инструкции, вы можете go с 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, который переходит на возврат, мы обновляем его вместо этого перейти к нашему звонку. Violà.

Execution before and after

Примечание: использовалось 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 и Instrumenting. NET сборки для измерения времени выполнения метода с помощью Mono.Cecil .

самореклама отключена

...