使用Cecil在函数周围插入开始/结束块

时间:2017-07-25 10:55:18

标签: cil mono.cecil

这个简单的代码工作正常,允许在每个BeginSample/EndSample函数周围添加Update/LateUpdate/FixedUpdate调用。但是,它没有考虑早期返回指令,例如由于条件的原因。你是否知道如何编写一个类似的函数来考虑早期的返回,以便在每种情况下执行EndSample调用?

请注意,我不是塞西尔专家,我现在才开始学习。在我看来,Cecil会在调用InsertBefore和类似函数后自动更新返回的操作。因此,如果BR操作码先前跳转到特定的指令地址,则插入后将更新地址以跳转到原始指令。在大多数情况下都可以,但在我的情况下,这意味着if语句将跳过最后一次插入的操作,因为BR操作仍将直接指向最终的Ret指令。请注意,UpdateLateUpdateFixedUpdate都是无效函数。

foreach (var method in type.Methods)
{
    if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
        method.HasParameters == false)
    {
        var beginMethod =
            module.ImportReference(typeof (Profiler).GetMethod("BeginSample",
                                                               new[] {typeof (string)}));
        var endMethod =
            module.ImportReference(typeof (Profiler).GetMethod("EndSample",
                                                               BindingFlags.Static |
                                                               BindingFlags.Public));

        Debug.Log(method.Name + " method found in class: " + type.Name);

        var ilProcessor = method.Body.GetILProcessor();

        var first = method.Body.Instructions[0];
        ilProcessor.InsertBefore(first,
                                 Instruction.Create(OpCodes.Ldstr,
                                                    type.FullName + "." + method.Name));
        ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));

        var lastRet = method.Body.Instructions[method.Body.Instructions.Count - 1];
        ilProcessor.InsertBefore(lastRet, Instruction.Create(OpCodes.Call, endMethod));

        changed = true;
    }
}

作为奖励,如果您可以向我解释EmitAppend之间使用相同操作数创建的新指令之间的区别。是否附加执行Emit或更多内容?

1 个答案:

答案 0 :(得分:0)

我可能已经找到了解决方案,至少显然它有效。我从这里按照用于解决类似问题的代码:

https://groups.google.com/forum/#!msg/mono-cecil/nE6JBjvEFCQ/MqV6tgDCB4AJ

我根据自己的目的调整了它,但它似乎有效,尽管我可能会发现其他问题。这是完整的代码:

 static bool ProcessAssembly(AssemblyDefinition assembly)
 {
     var changed = false;

     var moduleG = assembly.MainModule;

     var attributeConstructor =
             moduleG.ImportReference(
                 typeof(RamjetProfilerPostProcessedAssemblyAttribute).GetConstructor(Type.EmptyTypes));
     var attribute = new CustomAttribute(attributeConstructor);
     var ramjet = moduleG.ImportReference(typeof(RamjetProfilerPostProcessedAssemblyAttribute));
     if (assembly.HasCustomAttributes)
     {
         var attributes = assembly.CustomAttributes;
         foreach (var attr in attributes)
         {
             if (attr.AttributeType.FullName == ramjet.FullName)
             {
                 Debug.LogWarning("<color=yellow>Skipping already-patched assembly:</color>  " + assembly.Name);
                 return false;
             }
         }
     }
     assembly.CustomAttributes.Add(attribute);

     foreach (var module in assembly.Modules)
     {
         foreach (var type in module.Types)
         {
             // Skip any classes related to the RamjetProfiler
             if (type.Name.Contains("AssemblyPostProcessor") || type.Name.Contains("RamjetProfiler"))
             {
                 // Todo: use actual type equals, not string matching
                 Debug.Log("Skipping self class : " + type.Name);
                 continue;
             }

             if (type.BaseType != null && type.BaseType.FullName.Contains("UnityEngine.MonoBehaviour"))
             {
                 foreach (var method in type.Methods)
                 {
                     if ((method.Name == "Update" || method.Name == "LateUpdate" || method.Name == "FixedUpdate") &&
                         method.HasParameters == false)
                     {
                         var beginMethod =
                             module.ImportReference(typeof(Profiler).GetMethod("BeginSample",
                                                                                new[] { typeof(string) }));
                         var endMethod =
                             module.ImportReference(typeof(Profiler).GetMethod("EndSample",
                                                                                BindingFlags.Static |
                                                                                BindingFlags.Public));

                         Debug.Log(method.Name + " method found in class: " + type.Name);

                         var ilProcessor = method.Body.GetILProcessor();

                         var first = method.Body.Instructions[0];
                         ilProcessor.InsertBefore(first,
                                                  Instruction.Create(OpCodes.Ldstr,
                                                                     type.FullName + "." + method.Name));
                         ilProcessor.InsertBefore(first, Instruction.Create(OpCodes.Call, beginMethod));

                         var lastcall = Instruction.Create(OpCodes.Call, endMethod);

                         FixReturns(method, lastcall);

                         changed = true;
                     }
                 }
             }
         }
     }

     return changed;
 }

 static void FixReturns(MethodDefinition med, Instruction lastcall)
 {
     MethodBody body = med.Body;

     var instructions = body.Instructions;
     Instruction formallyLastInstruction = instructions[instructions.Count - 1];
     Instruction lastLeaveInstruction = null;

     var lastRet = Instruction.Create(OpCodes.Ret);
     instructions.Add(lastcall);
     instructions.Add(lastRet);

     for (var index = 0; index < instructions.Count - 1; index++)
     {
         var instruction = instructions[index];
         if (instruction.OpCode == OpCodes.Ret)
         {
             Instruction leaveInstruction = Instruction.Create(OpCodes.Leave, lastcall);
             if (instruction == formallyLastInstruction)
             {
                 lastLeaveInstruction = leaveInstruction;
             }

             instructions[index] = leaveInstruction;
         }
     }

     FixBranchTargets(lastLeaveInstruction, formallyLastInstruction, body);
 }

 private static void FixBranchTargets(
     Instruction lastLeaveInstruction,
     Instruction formallyLastRetInstruction,
     MethodBody body)
 {
     for (var index = 0; index < body.Instructions.Count - 2; index++)
     {
         var instruction = body.Instructions[index];
         if (instruction.Operand != null && instruction.Operand == formallyLastRetInstruction)
         {
             instruction.Operand = lastLeaveInstruction;
         }
     }
 }

基本上它的作用是添加一个Ret个实体,然后用Ret函数替换以前的所有Leave(通常是一个,为什么它应该多于一个?) (甚至不知道这意味着什么:)),以便所有以前的跳跃仍然有效。与原始代码不同,我在最后Leave

之前将EndSample指令指向Ret调用