2017-07-25 89 views
0

這個簡單的代碼工作正常,並允許在每個Update/LateUpdate/FixedUpdate函數週圍添加一個BeginSample/EndSample調用。但是,它不考慮提前退貨指示,例如由於某種情況。你知道如何編寫一個類似的函數來考慮早期返回,以便在任何情況下執行EndSample調用?使用塞西爾插入功能的開始/結束塊

請注意,我不是塞西爾專家,我現在只是在學習。在我看來,塞西爾在呼叫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或做更多的事情?

+0

快速評論之前,而一無所知約塞西爾:如果要保證的東西,無論總是會發生的內部控制流程,在它包裹了'試着......終於'塊將是明顯的事情要做,而不是追捕所有出口點。另一個顯而易見的方法是將原始函數重命名爲完全替換爲'begin()/ function()/ end()'的包裝器。最後但並非最不重要的一點是,您可以考慮使用分析器而不是顯然重新發明輪子。當然,不是說你真的不需要自己的車輪。 –

回答

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 instuction,但後來更換什麼都以前RetLeave功能(?通常一個,爲什麼它應該是不止一個)(唐甚至不知道它是什麼意思:)),以便所有以前的跳轉保持有效。不同於原來的代碼,我做了Leave指令指向EndSample呼叫最後Ret

+1

離開操作碼只是可以用來跳出受保護區域的分支(br *)(try,catch,... - blocks) – thehennyy