使用 Cecil 在函数周围插入 begin/end 块

Use Cecil to insert begin/end block around functions

这个简单的代码工作正常,并允许在每个 Update/LateUpdate/FixedUpdate 函数周围添加一个 BeginSample/EndSample 调用。但是它没有考虑早期的 return 指令,例如作为条件的结果。您是否知道如何编写一个类似的函数,在 return 之前就考虑这些因素,以便在任何情况下都执行 EndSample 调用?

请注意,我不是塞西尔专家,我现在只是在学习。在我看来,Cecil 在调用 InsertBefore 和类似函数后会自动更新 return 的操作。因此,如果 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 具有相同操作数的新创建指令之间的区别。 Append 是在后台执行 Emit 还是执行更多操作?

我可能已经找到了解决方案,至少看起来是可行的。我从这里按照用于解决类似问题的代码进行操作:

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指令,然后用一个[=13=替换之前的所有Ret(通常是一个,为什么要多于一个?) ] 函数(甚至不知道它是什么意思 :) ),这样所有之前的跳转仍然有效。与原始代码不同,我将 Leave 指令指向最后一个 Ret

之前的 EndSample 调用