You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Mono.Cecil用C#为DLL所有方法注入秒表(含多返回方法)

解决方法:注入秒表计时到DLL的所有方法(含多返回场景)

看起来你需要给target.dll里的每个方法(不管返回类型、有没有多个return语句)注入秒表的启停逻辑,来统计方法执行耗时。原注入器的问题在于只处理了方法的首尾指令,没法覆盖多返回分支的场景,还带了无用的字符串加载操作。下面是修正后的完整实现,以及关键细节的解释:

修正后的注入器代码

using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Linq;

class Trial_injector {
    static void Main(string[] args) {
        // 加载目标DLL和秒表DLL,开启读写模式
        var targetReadParams = new ReaderParameters { ReadWrite = true };
        AssemblyDefinition targetAssembly = AssemblyDefinition.ReadAssembly("targetDll.dll", targetReadParams);
        var targetModule = targetAssembly.MainModule;

        var sourceReadParams = new ReaderParameters { ReadWrite = true };
        AssemblyDefinition sourceAssembly = AssemblyDefinition.ReadAssembly("stopwatch.dll", sourceReadParams);
        var sourceModule = sourceAssembly.MainModule;

        // 定位秒表类的启停方法
        MethodDefinition startMethod = null;
        MethodDefinition stopMethod = null;
        foreach (var type in sourceModule.Types) {
            if (type.Name == "stopwatch_class") {
                startMethod = type.Methods.First(m => m.Name == "stopwatch_start");
                stopMethod = type.Methods.First(m => m.Name == "stopwatch_stop");
                break;
            }
        }

        // 将秒表方法导入到目标模块,确保跨模块调用合法
        var importedStart = targetModule.ImportReference(startMethod);
        var importedStop = targetModule.ImportReference(stopMethod);

        // 遍历目标DLL的所有用户定义类型和方法
        foreach (var type in targetModule.Types) {
            // 跳过编译器自动生成的特殊类型(比如模块类)
            if (type.IsSpecialName || type.IsRuntimeSpecialName) continue;
            
            foreach (var method in type.Methods) {
                // 跳过无方法体、抽象或原生方法,避免注入错误
                if (!method.HasBody || method.IsAbstract || method.IsNative) continue;

                var ilProcessor = method.Body.GetILProcessor();

                // 1. 在方法最开始注入start方法
                var firstInstruction = method.Body.Instructions.First();
                var callStart = ilProcessor.Create(OpCodes.Call, importedStart);
                ilProcessor.InsertBefore(firstInstruction, callStart);

                // 2. 在所有return指令前注入stop方法
                // 先收集所有ret指令,避免遍历过程中修改指令集合导致异常
                var retInstructions = method.Body.Instructions.Where(inst => inst.OpCode == OpCodes.Ret).ToList();
                foreach (var retInst in retInstructions) {
                    var callStop = ilProcessor.Create(OpCodes.Call, importedStop);
                    ilProcessor.InsertBefore(retInst, callStop);
                }
            }
        }

        // 保存修改后的目标DLL
        targetAssembly.Write();
    }
}

核心改进点说明

  • 移除冗余代码:删掉了原代码中无用的ldstr指令,因为秒表的启停方法不需要字符串参数。
  • 覆盖多返回场景:通过查找方法体内所有OpCodes.Ret指令,在每个return前都插入stop调用,确保不管走哪个分支,都会正确停止计时并输出耗时。
  • 跨模块调用优化:用ImportReference正确导入秒表方法到目标模块,避免跨DLL调用失败。
  • 过滤无效方法:跳过抽象、原生或无方法体的方法,防止注入过程中出现错误。
  • 类型过滤:只处理用户定义的类型(比如targetDll),跳过编译器生成的特殊类型。

效果验证

以你的func2为例,注入后IL代码会在两个return指令前都插入stopwatch_stop(),同时在方法开头插入stopwatch_start()——不管代码走哪个分支,都会完整统计方法的执行耗时。

注意事项

  1. 注入器项目需要安装Mono.Cecil NuGet包才能编译运行。
  2. 操作前记得备份target.dll和stopwatch.dll,避免文件损坏。
  3. 如果目标方法包含try-catch-finally块,建议在finally块末尾也插入stop方法,确保异常场景下也能停止计时。

内容的提问来源于stack exchange,提问作者sugan_2416

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 17:49:10