InjectFix实现bug修复主要靠这两部分:虚拟机负责新逻辑的解析执行;注入代码负责把调用重定向到虚拟机;下面我们结合最简单的例子介绍下这两部分。
虚拟机
关键部分用几行伪码就可以描述清楚:
导读
-
pc指向的是函数的第一条指令;
-
argumentBase指向的是第一个参数;
-
while+switch一条条指令往下执行,具体指令的操作在case那;
argumentBase指向的是求值栈该函数的栈帧,栈帧是这么安排的:
先放参数(如果有的话),再放本地变量(如果有的话),接着是临时区域,当函数返回时弹掉所有东西,如果有返回值就放到栈顶(函数执行前参数0的位置)。
用如下一个静态方法来演示下虚拟机怎么运行:
public static float Add(float a, float b)
{ return a - b;}
这函数编译后是这四条指令
Add函数的执行过程
指令1把参数0 Push到栈顶;
指令2把参数1 Push到栈顶;
指令3把两个栈顶元素弹出(Pop)并相加,结果Push到栈顶;
指令4把栈顶拷贝到参数0的位置,清理栈,退出循环,Execute函数执行结束。
代码注入
上面的Add函数注入后是这样的
public static float Add(float a, float b)
{
if (WrappersManagerImpl.IsPatched(92))
{
return WrappersManagerImpl.GetPatch(92).__Gen_Wrap_25(a, b);
}
return a - b;
}
比较简单,发现这函数有patch的话,就重定向到虚拟机。
而__Gen_Wrap_25是个适配器函数,赋值把参数压栈,调用虚拟机的Execute函数,并把结果返回。__Gen_Wrap_25的实现如下:
public float __Gen_Wrap_25(float P0, float P1)
{
Call call = Call.Begin();
call.PushSingle(P0);
call.PushSingle(P1);
this.virtualMachine.Execute(this.methodId, ref call, 2, 0);
return call.GetSingle(0);
}
PS:我们的例子仅有三种指令,和这几条指令无关的代码全部简化了,真正复杂得多,有兴趣可以看源码了解。
总结
InjectFix使用简单,小巧,合规且安全。即使你不打算用它来更新线上版本,只要你程序有原生部分,接入也能一定程度上提高开发效率,没什么拒绝它的理由,是吧?