.NET外挂系列:8. harmony 的IL编织 Transpiler
一:背景
1. 讲故事
前面文章所介绍的一些注入技术都是以方法
为原子单位,但在一些罕见的场合中,这种方法粒度又太大了,能不能以语句
为单位,那这个就是我们这篇介绍的 Transpiler
,它可以修改方法的 IL 代码,甚至重构,所以这就非常考验你的 IL 功底,个人建议在写的时候要多借助如下三个工具:
- ILSpy:观察原生代码
- 日志: 多看harmony日志,即方法上加盖 HarmonyDebug 特性。
- DeepSeek:大模型是一个非常好的助手,合理利用定会效率加倍。
否则遇到稍微复杂一点的,真的难搞。。。
二:有趣的IL编织案例
1. 如何将Sub中的加法改成减法
为了方便演示,我们先上一段代码,实现一个简单的 a+b
操作,代码如下:
internal class Program{static void Main(string[] args){var num = MyMath.Sub(40, 30);Console.WriteLine($"Result: {num}");Console.ReadLine();}}public class MyMath{public static int Sub(object a, object b){var num1 = Convert.ToInt32(a);var num2 = Convert.ToInt32(b);var num = num1 + num2;return num;}}
上面卦中的 Sub
方法的 IL 代码如下:
.method public hidebysig static int32 Sub (object a,object b) cil managed {.custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = (01 00 01 00 00)// Method begins at RVA 0x20b0// Header size: 12// Code size: 25 (0x19).maxstack 2.locals init ([0] int32 num1,[1] int32 num2,[2] int32 sum,[3] int32)IL_0000: nopIL_0001: ldarg.0IL_0002: call int32 [System.Runtime]System.Convert::ToInt32(object)IL_0007: stloc.0IL_0008: ldarg.1IL_0009: call int32 [System.Runtime]System.Convert::ToInt32(object)IL_000e: stloc.1IL_000f: ldloc.0IL_0010: ldloc.1IL_0011: addIL_0012: stloc.2IL_0013: ldloc.2IL_0014: stloc.3IL_0015: br.s IL_0017IL_0017: ldloc.3IL_0018: ret} // end of method MyMath::Sub
因为Sub怎么可能是a+b
,所以现在我的需求就是将 num1 + num2
改成 num1 - num2
,从 il 的角度就是将 IL_0011: add
改成 IL_0011: sub
即可,如何做到呢?用 harmony 的 CodeMatcher 类去替换IL代码即可,完整的代码如下:
namespace Example_20_1_1
{internal class Program{static void Main(string[] args){// 应用Harmony补丁 var harmony = new Harmony("com.example.patch");harmony.PatchAll();var num = MyMath.Sub(40, 30);Console.WriteLine($"Result: {num}"); // 原应输出70,补丁后输出10 Console.ReadLine();}}public class MyMath{public static int Sub(object a, object b){var num1 = Convert.ToInt32(a);var num2 = Convert.ToInt32(b);var num = num1 + num2; // 此行将被Transpiler修改为减法 return num;}}[HarmonyPatch(typeof(MyMath), "Sub")][HarmonyDebug]public static class MyMathPatch{static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions){var codeMatcher = new CodeMatcher(instructions);codeMatcher.MatchStartForward(new CodeMatch(OpCodes.Add)) // 匹配加法操作 (add 指令) .ThrowIfInvalid("Could not find add instruction").SetOpcodeAndAdvance(OpCodes.Sub); // 将 add 指令替换为 sub 指令 return codeMatcher.Instructions();}}}
从卦中的输出看,我们修改成功了,这里稍微说一下 CodeMatcher
的方法。
- MatchStartForward:这个就是游标,定位到
OpCodes.Add
行。 - ThrowIfInvalid: 如果没有定位到就抛出异常。
- SetOpcodeAndAdvance:替换 IL中的add为sub,并向下移动一行,可以理解成 i++。
由于在 MyMathPatch 上加了一个 [HarmonyDebug]
特性,打开 harmony.log.txt
的输出结果,成功看到了替换后的sub,参考如下:
### Patch: static System.Int32 Example_20_1_1.MyMath::Sub(System.Object a, System.Object b)
### Replacement: static System.Int32 Example_20_1_1.MyMath::Example_20_1_1.MyMath.Sub_Patch0(System.Object a, System.Object b)
IL_0000: Local var 0: System.Int32
IL_0000: Local var 1: System.Int32
IL_0000: Local var 2: System.Int32
IL_0000: Local var 3: System.Int32
IL_0000: // start original
IL_0000: nop
IL_0001: ldarg.0
IL_0002: call static System.Int32 System.Convert::ToInt32(System.Object value)
IL_0007: stloc.0
IL_0008: ldarg.1
IL_0009: call static System.Int32 System.Convert::ToInt32(System.Object value)
IL_000E: stloc.1
IL_000F: ldloc.0
IL_0010: ldloc.1
IL_0011: sub
IL_0012: stloc.2
IL_0013: ldloc.2
IL_0014: stloc.3
IL_0015: br => Label0
IL_001A: Label0
IL_001A: ldloc.3
IL_001B: // end original
IL_001B: ret
DONE
2. 如何给Sub加业务逻辑
上面的例子本质上是IL代码
的原地替换,接下来我们看下如何对IL代码进行删增
操作,我的业务需求是这样的,想将 num1 + num2
改成 num1 - num2 - num3
,我想要最终的 C# 代码变为这样:
public class MyMath{public static int Sub(object a, object b){var num1 = Convert.ToInt32(a);var num2 = Convert.ToInt32(b);var num3 = Convert.ToInt32("20"); // 新增的代码var num = num1 - num2 - num3;return num;}}
接下来用Transpiler
进行编织,代码如下:
[HarmonyPatch(typeof(MyMath), "Sub")][HarmonyDebug]public static class MyMathPatch{public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator){var codeMatcher = new CodeMatcher(instructions, generator).MatchStartForward( // 匹配模式:ldloc.0, ldloc.1, addnew CodeMatch(OpCodes.Ldloc_0),new CodeMatch(OpCodes.Ldloc_1),new CodeMatch(OpCodes.Add)).ThrowIfInvalid("Could not find add operation pattern")// 移除原来的三条指令.RemoveInstructions(3)// 插入新的指令序列.InsertAndAdvance(new CodeInstruction(OpCodes.Ldloc_0),new CodeInstruction(OpCodes.Ldloc_1),new CodeInstruction(OpCodes.Sub),new CodeInstruction(OpCodes.Ldstr, "20"),new CodeInstruction(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ToInt32),new[] { typeof(string) })),new CodeInstruction(OpCodes.Sub));return codeMatcher.InstructionEnumeration();}}
代码的逻辑非常简单,先在IL代码中定位到 num1 + num2
,然后删除再写入 num1 - num2 - num3
。
3. 如何添加try catch
最后我们来一个比较实用的修改,即在 Sub
中增加try catch
,理想的代码如下:
public class MyMath{public static int Sub(object a, object b){try{var num1 = Convert.ToInt32(a);var num2 = Convert.ToInt32(b);var num = num1 - num2;return num;}catch (Exception ex){Console.WriteLine(ex.Message);return 0;}}}
接下来就要开始编织了,这是从0开始的代码段,完整代码如下:
namespace Example_20_1_1
{internal class Program{static void Main(string[] args){// 应用Harmony补丁 var harmony = new Harmony("com.example.patch");harmony.PatchAll();// 测试原始方法 var num = MyMath.Sub("a", 30);Console.WriteLine($"异常: {num}");var num2 = MyMath.Sub(50, 30);Console.WriteLine($"正常: {num2}");Console.ReadLine();}}public class MyMath{public static int Sub(object a, object b){try{var num1 = Convert.ToInt32(a);var num2 = Convert.ToInt32(b);var num = num1 - num2;return num;}catch (Exception ex){Console.WriteLine(ex.Message);return 0;}}}[HarmonyPatch(typeof(MyMath), "Sub")][HarmonyDebug]public static class MyMathPatch{static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> originalInstructions, ILGenerator generator){// 定义标签Label tryStart = generator.DefineLabel();Label tryEnd = generator.DefineLabel();Label catchStart = generator.DefineLabel();Label endLabel = generator.DefineLabel();// 声明局部变量var exVar = generator.DeclareLocal(typeof(Exception)); // 用于存储异常的变量var resultVar = generator.DeclareLocal(typeof(int)); // 用于存储返回值的变量var newInstructions = new List<CodeInstruction>();// 1. try 块开始newInstructions.Add(new CodeInstruction(OpCodes.Nop).WithLabels(tryStart));// 2. 添加原始方法体(保持不变)newInstructions.AddRange(originalInstructions);// 3. 存储结果并离开 try 块newInstructions.Add(new CodeInstruction(OpCodes.Stloc, resultVar));newInstructions.Add(new CodeInstruction(OpCodes.Leave, endLabel).WithLabels(tryEnd));// 4. catch 块newInstructions.Add(new CodeInstruction(OpCodes.Stloc, exVar).WithLabels(catchStart));newInstructions.Add(new CodeInstruction(OpCodes.Nop));newInstructions.Add(new CodeInstruction(OpCodes.Ldloc, exVar));newInstructions.Add(new CodeInstruction(OpCodes.Callvirt,typeof(Exception).GetProperty("Message").GetGetMethod()));newInstructions.Add(new CodeInstruction(OpCodes.Call,typeof(Console).GetMethod("WriteLine", new[] { typeof(string) })));newInstructions.Add(new CodeInstruction(OpCodes.Ldc_I4_0)); // 返回0newInstructions.Add(new CodeInstruction(OpCodes.Stloc, resultVar));newInstructions.Add(new CodeInstruction(OpCodes.Leave, endLabel));// 5. 方法结束(加载结果并返回)newInstructions.Add(new CodeInstruction(OpCodes.Ldloc, resultVar).WithLabels(endLabel));newInstructions.Add(new CodeInstruction(OpCodes.Ret));// 添加异常处理generator.BeginExceptionBlock();generator.BeginCatchBlock(typeof(Exception));generator.EndExceptionBlock();return newInstructions;}}
}
哈哈,上面的代码正如我们所料。。。如果不借助 ILSpy 和 DeepSeek,不敢想象得要浪费多少时间。。。门槛太高了。。。
三:总结
这个系列总计8篇,已经全部写完啦!希望对同行们在解决.NET程序疑难杂症
相关问题时提供一些资料和灵感,同时也是对.NET调试训练营
的学员们功力提升添砖加瓦!
相关文章:

.NET外挂系列:8. harmony 的IL编织 Transpiler
一:背景 1. 讲故事 前面文章所介绍的一些注入技术都是以方法为原子单位,但在一些罕见的场合中,这种方法粒度又太大了,能不能以语句为单位,那这个就是我们这篇介绍的 Transpiler,它可以修改方法的 IL 代码…...

基于netty实现视频流式传输和多线程传输
文章目录 业务描述业务难点流式传输客户端(以tcp为例)服务端测试类测试步骤多线程传输客户端服务端测试类测试步骤多线程流式传输总结业务描述 多台终端设备持续给数据服务器(外)发送视频数据,数据服务器(外)通过HTTP协议将数据经过某安全平台转到数据服务器(内),数据…...

全面指南:使用Node.js和Python连接与操作MongoDB
在现代Web开发中,数据库是存储和管理数据的核心组件。MongoDB作为一款流行的NoSQL数据库,以其灵活的数据模型、高性能和易扩展性广受开发者欢迎。无论是使用Node.js还是Python,MongoDB都提供了强大的官方驱动和第三方库,使得数据库…...

游戏引擎学习第308天:调试循环检测
回顾并为今天的内容做准备 我们正在进行游戏开发中的精灵(sprite)排序工作,虽然目前的实现已经有了一些改进,情况也在逐步好转,我们已经实现了一个图结构的排序算法,用来处理精灵渲染顺序的问题。然而&…...
Java 海康录像机通过sdk下载的视频无法在线预览问题
下载的视频格式不对,需将视频转码为H.264/AAC的MP4格式 使用 ffmpeg 对视频进行转码 ffmpeg可以对视频进行转码、加水印等操作,还是挺强大的 代码如下 public static void transcodeToMP4(String inputPath, String outputPath) throws IOException, In…...

WPF性能优化之延迟加载(解决页面卡顿问题)
文章目录 前言一. 基础知识回顾二. 问题分析三. 解决方案1. 新建一个名为DeferredContentHost的控件。2. 在DeferredContentHost控件中定义一个名为Content的object类型的依赖属性,用于承载要加载的子控件。3. 在DeferredContentHost控件中定义一个名为Skeleton的ob…...

移植 FART 到 Android 10 实现自动化脱壳
版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/ FART 源码 FART 是 ART 环境下基于主动调用的自动化脱壳方案。 关于 FART 详细介绍参考: FART 自动化脱壳框架简介与脱壳点的选择 FART 主动调用…...
ES的Refresh、Flush、Merge操作对性能的影响? ES如何实现近实时(NRT)搜索? ES聚合查询的Terms和Cardinality区别?
一、Refresh/Flush/Merge机制与性能影响 Refresh(刷新) 作用:将内存缓冲区(In-memory buffer)数据写入文件系统缓存生成新段(Segment),使文档可被搜索性能影响: 默认每…...
WebXR 虚拟现实开发
WebXR(Web Extended Reality)是用于在浏览器中构建**虚拟现实(VR)和增强现实(AR)**应用的 Web 标准。它允许开发者通过 JavaScript 和 WebGL 创建沉浸式体验,无需安装原生应用。以下是 WebXR 开发的基本知识点和开发指南: 一、WebXR 的核心概念 1. XR 设备分类 VR(Vi…...

COMPUTEX 2025 | 广和通创新解决方案共筑AI交互新纪元
5月20日至23日,广和通携多领域创新解决方案亮相2025年台北国际电脑展(COMPUTEX 2025),台北南港展览馆#K0727a展位。此次展会,广和通围绕“Advancing Connectivity Intelligent Future”为主题,设置四大核心…...

了解Android studio 初学者零基础推荐(3)
kotlin中的数据类及对象 使用泛型创建可重复使用的类 我们将常在线答题考试,有的考试题型包括判断,或者填空,以及数学题,此外试题内容还包括难易程度:"easy”,"medium","hard",…...

Spring 定时器和异步线程池 实践指南
前言:Spring:异步线程池和定时器 原理篇 一、Spring Scheduler 1. 创建一个 SpringBoot项目,在启动类上添加 EnableScheduling 注解,表示开启定时任务。 2. 创建SchedulerService,在方法上面启用Scheduled 注解 在方…...

零基础设计模式——创建型模式 - 生成器模式
第二部分:创建型模式 - 生成器模式 (Builder Pattern) 前面我们学习了单例、工厂方法和抽象工厂模式,它们都关注如何创建对象。生成器模式(也常被称为建造者模式)是另一种创建型模式,它专注于将一个复杂对象的构建过程…...

MD编辑器推荐【Obsidian】含下载安装和实用教程
为什么推荐 Obsidian ? 免费 (Typora 开始收费了)Typora 实现的功能,它都有!代码块可一键复制 文件目录支持文件夹 大纲支持折叠、搜索 特色功能 – 白板 特色功能 – 关系图谱 下载 https://pan.baidu.com/s/1I1fSly…...
LLama-Factory 遇到的问题
目录 一、LLama-Factory安装 二、LLama-Factory 遇到的问题 (一)包不兼容问题 (二)使用文件路径,加载模型 一、LLama-Factory安装 参考官网介绍:https://github.com/hiyouga/LLaMA-Factory 二、LLama…...
I-CON: A UNIFYING FRAMEWORK FOR REPRESENTATION LEARNING
I-con:表示学习的统一框架 基本信息 ICLR 2025 博客贡献人 田心 作者 Shaden Alshammari, John Hershey, Axel Feldmann, William T. Freeman, Mark Hamilton 关键词 I-Con框架,表征学习,损失函数统一框架 摘要 随着表征学习领域的快速发展,各类…...

Missashe线代题型总结
Missashe线性代数考研题型总结 说明:这篇笔记用于博主对"线代"常考题型进行总结,99%为真题,大概可能应该会逐步更新解题思路。有目录可直接检索。 第一章 行列式 1 具体行列式计算 1)么字型 2015 数一 2016 数一三…...
蓝桥杯13届 卡牌
问题描述 这天, 小明在整理他的卡牌。 他一共有 n 种卡牌, 第 i 种卡牌上印有正整数数 i(i∈[1,n]), 且第 i 种卡牌 现有 ai 张。 而如果有 n 张卡牌, 其中每种卡牌各一张, 那么这 n 张卡牌可以被称为一 套牌。小明为了凑出尽可能多套牌, 拿出了 m 张空白牌, 他可以在上面…...

安卓开发用到的设计模式(1)创建型模式
安卓开发用到的设计模式(1)创建型模式 文章目录 安卓开发用到的设计模式(1)创建型模式1. 单例模式(Singleton Pattern)2. 工厂模式(Factory Pattern)3. 抽象工厂模式(Abs…...
【PalladiumZ2 使用专栏 3 -- 信号值的获取与设置 及 memory dump 与 memory load】
文章目录 Overviewforce 命令语法value 命令语法memory loadmemory dump Overview 在调试问题的时,有时需要将某些信号强制设置为某个值,或者某几个信号强制设置为某个值,这里就要用到 force 命令。 force 命令语法 force -h force <na…...
flutter dart 函数语法
以下是 Dart 语言中函数语法的 详细实例说明,涵盖了所有常用写法 基本语法参数类型(必选、可选、命名、默认值)匿名函数、箭头函数高阶函数(函数作为参数/返回值)异步函数(async / await) 1. …...
课外活动:大语言模型Claude的技术解析 与 自动化测试框架领域应用实践
大语言模型Claude的技术解析与测试领域应用实践 一、Claude模型的核心优势解析 1.1 关键技术特性对比 维度Claude 3 OpusGPT-4 Turbo核心优势上下文窗口200K tokens128K tokens长文档处理能力提升56%逻辑推理准确率92.3% (GSM8K数据集)89.7%复杂场景稳定性更强代码生成速度7…...

线程的一些基本知识
前言 最近在学习线程,线程与进程是面试中可能常考的问题,我总结了线程的一些知识。分享给大家,希望可以帮组到大家。 线程知识总结(包含与进程的区别) 结语 希望可以帮助到有需要的人,bye~~...

【Python打卡Day30】模块与包的导入@浙大疏锦行
#一、导入官方库 我们复盘下学习python的逻辑,所谓学习python就是学习python常见的基础语法学习你所处理任务需要用到的第三方库 所以你用到什么学什么库即可。学习python本身就是个伪命题,就像你说学习科目一样,你没说清晰你学习的具体科目…...

26考研|高等代数:λ-矩阵
前言 本章知识点较为简单,是作为工具性的一章,在学习过程中,要注意区分行列式因子、不变因子以及初等因子,同时还要对若尔当标准型的计算应该足够熟悉,尤其是复矩阵的若尔当标准型计算是十分重要的。 课本重点回顾 …...

我店模式系统开发打造本地生活生态商圈
在当今快节奏的商业环境中,商家们面临着越来越多的挑战,包括市场竞争加剧、消费者需求多样化以及运营效率的提高等。为了应对这些挑战,越来越多的商家开始寻求信息化解决方案,以提升运营效率和客户体验。我的店模式系统平台应运而…...

数据库练习(3)
简单选择题要点: 1.锁协议: 数据库原理及应用(高级篇)01——封锁协议(图文并解,超详细,一看就会)_数据库锁协议-CSDN博客https://blog.csdn.net/qq_44236958/article/details/105790970 2.tablespace和datafile 一个tablespace可以有一个或多…...

OpenGL ES 基本基本使用、绘制基本2D图形
OpenGL ES 绘制基础图形 OpenGL ES基本概念 OpenGL ES (Embedded-System) 是专为嵌入式设备(如手机、平板、VR 设备)设计的图形 API,是 OpenGL 的轻量级版本。 |下面是一个Android使用 OpenGL ES的基本框架 MainActivity 设置一…...
spark调度系统核心组件SparkContext、DAGSchedul、TaskScheduler、Taskset介绍
目录 1. SparkContext2.DAGScheduler3. TaskScheduler4. 协作关系5 TaskSet的定义6. 组件关系说明Spark调度系统的核心组件主要有SparkContext、DAGScheduler和TaskScheduler SparkContext介绍 1. SparkContext 1、资源申请: SparkContext是Spark应用程序与集群管理器(如St…...

BU9792驱动段式LCD
1、C文件,需要自己添加软件iic或硬件iic驱动,该驱动在我的别的文章内有。亲测bu9792是正常驱动的(只用到了前14个SEG),说实话有点懵了。后面的ICSET有个P2根据不同的SEG地址要置1或0,读的时候最高位也是0?读命令寄存器…...