前期准备
- 根据读上面的文章,可以了解到,InjectFix步骤分为以下几步
– 插桩: 就是通过标签指定需要修复的函数,通过Mono.Cecil插件,在函数最前方插入IL指令,通过唯一key值判断是否有修复代码,如果有将执行修复函数内 – 修复: 修改错误的代码,通过Mono.Cecil插件,检查修复函数的代码,生产修复函数的IL指令并存储到文件内,并和上面插桩代码的唯一key值对应 – 执行修复代码的IL指令: 这部分就是解析生成的IL指令,也就是虚拟机,这部分有大量的unsafe代码
- 首先看一下最终效果图,就是把加法写错了,然后通过修复将原来写错的减法修改成加法
- 首先下面这张图,函数名为Sum是进行加法的,因为写错了,打印出来的结果是-1
- 下面是通过修复,原代码依然的错误的,但是打印出来的是正确是数值3
插桩
- 这块就是插桩的代码,执行后会在Sum函数IL指令最上方,插入PatchLoad.HasPatch(0),检查函数,0是因为我这只有一个函数,要修复直接用0这个硬编码就行,如果有修复需要,将会执行genMethod这个生成的代码
- genMethod的IL指令生成,部分直接采用了硬编码,具体就是实例化一个Call对象,将参数Push进去,并使用PatchLoad的virtualMachine执行Execute函数
static void InjectMethod(ModuleDefinition module, MethodDefinition method)
{
var genMethod = GenPatchMethod(module, method);
var ilp = method.Body.GetILProcessor();
var startNopIns = method.Body.Instructions[0];
var brFalseJumpIns = startNopIns.Next;
var ins_ldstr = ilp.Create(OpCodes.Ldstr, "0");
ilp.InsertBefore(startNopIns, ins_ldstr);
var incrIndex = 0;
var method_HasPatch = module.Types.Single(t => t.Name == "PatchLoad").Methods.Single(m => m.Name == "HasPatch");
var ins_call = ilp.Create(OpCodes.Call, method_HasPatch);
IncrAddIns(ilp, ins_call, ref incrIndex);
var ins_brfalse = ilp.Create(OpCodes.Brfalse, brFalseJumpIns);
IncrAddIns(ilp, ins_brfalse, ref incrIndex);
if(method.Parameters.Count > 0) {
IncrAddIns(ilp, Instruction.Create(OpCodes.Ldarg_1), ref incrIndex);
IncrAddIns(ilp, Instruction.Create(OpCodes.Ldarg_2), ref incrIndex);
}
IncrAddIns(ilp, Instruction.Create(OpCodes.Call, genMethod), ref incrIndex);
var ins_ret = ilp.Create(OpCodes.Ret);
IncrAddIns(ilp, ins_ret, ref incrIndex);
}
static MethodDefinition GenPatchMethod(ModuleDefinition module, MethodDefinition method) {
var patchLoadType = module.Types.Single(t => t.Name == "PatchLoad");
var vmField = patchLoadType.Fields.Single(f => f.Name == "virtualMachine");
var callType = module.Types.Single(t => t.Name == "Call");
var callBegin = callType.Methods.Single(m => m.Name == "Begin");
var callPushInt32 = callType.Methods.Single(m => m.Name == "PushInt32");
var callGetInt32 = callType.Methods.Single(m => m.Name == "GetInt32");
var vmExecute = module.Types.Single(t => t.Name == "VirtualMachine")
.Methods.Single(m => m.Name == "Execute" && m.Parameters.Count == 4);
var returnType = method.ReturnType;
Mono.Cecil.MethodAttributes methodAttributes = Mono.Cecil.MethodAttributes.Public | Mono.Cecil.MethodAttributes.Static;
MethodDefinition patchMethod = new MethodDefinition(GenPatchMethodPrefix, methodAttributes, returnType);
foreach (var parameter in method.Parameters) {
patchMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
}
VariableDefinition callVar = new VariableDefinition(callType);
patchMethod.Body.Variables.Add(callVar);
var ilp = patchMethod.Body.GetILProcessor();
ilp.Append(Instruction.Create(OpCodes.Nop));
ilp.Append(Instruction.Create(OpCodes.Call, callBegin));
ilp.Append(Instruction.Create(OpCodes.Stloc_0));
ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
ilp.Append(Instruction.Create(OpCodes.Ldarg_0));
ilp.Append(Instruction.Create(OpCodes.Call, callPushInt32));
ilp.Append(Instruction.Create(OpCodes.Nop));
ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
ilp.Append(Instruction.Create(OpCodes.Ldarg_1));
ilp.Append(Instruction.Create(OpCodes.Call, callPushInt32));
ilp.Append(Instruction.Create(OpCodes.Nop));
ilp.Append(Instruction.Create(OpCodes.Ldsfld, vmField));
ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
ilp.Append(Instruction.Create(OpCodes.Ldc_I4_2));
ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
ilp.Append(Instruction.Create(OpCodes.Call, vmExecute));
ilp.Append(Instruction.Create(OpCodes.Nop));
ilp.Append(Instruction.Create(OpCodes.Ldloca_S, callVar));
ilp.Append(Instruction.Create(OpCodes.Ldc_I4_0));
ilp.Append(Instruction.Create(OpCodes.Call, callGetInt32));
ilp.Append(Instruction.Create(OpCodes.Ret));
patchLoadType.Methods.Add(patchMethod);
return patchMethod;
}
- 执行完这部分代码,可以使用ildasm看看,Sum函数的IL指令的变化,蓝圈是插入的IL指令,红圈是原本Sum的指令
修复
- 修复就是通过遍历Body.Instructions中所有IL指令,解析并转成VMInstruction格式的指令,我这边图方便,直接序列化Json通过EditorPrefs.SetString存储在Unity编辑器下了,InjectFix就很复杂了最后存储在一个文件里面,感兴趣可以去读InjectFix源码
static void Fix(ModuleDefinition module, MethodDefinition method) {
var ils = method.Body.Instructions;
var parseIls = new List<VMInstruction>();
for (int i = 0; i < ils.Count; i++) {
var il = ils[i];
var ilStr = ils[i].OpCode.Code.ToString();
switch (il.OpCode.Code) {
case Mono.Cecil.Cil.Code.Nop:
parseIls.Add(new VMInstruction {
Code = Code.StackSpace,
Operand = (method.Body.Variables.Count << 16) | method.Body.MaxStackSize
});
break;
case Mono.Cecil.Cil.Code.Ldarg_0:
case Mono.Cecil.Cil.Code.Ldarg_1:
case Mono.Cecil.Cil.Code.Ldarg_2:
case Mono.Cecil.Cil.Code.Ldarg_3:
parseIls.Add(new VMInstruction() {
Code = Code.Ldarg,
Operand = int.Parse(ilStr.Substring(ilStr.Length - 1)) - 1
});
break;
case Mono.Cecil.Cil.Code.Add:
parseIls.Add(new VMInstruction {
Code = (Code)Enum.Parse(typeof(Code), ilStr),
Operand = 0
});
break;
case Mono.Cecil.Cil.Code.Stloc_0:
case Mono.Cecil.Cil.Code.Stloc_1:
case Mono.Cecil.Cil.Code.Stloc_2:
case Mono.Cecil.Cil.Code.Stloc_3:
parseIls.Add(new VMInstruction {
Code = Code.Stloc,
Operand = int.Parse(ilStr.Substring(ilStr.Length - 1)),
});
break;
case Mono.Cecil.Cil.Code.Ret:
parseIls.Add(new VMInstruction {
Code = Code.Ret,
Operand = method.ReturnType.ToString() == "System.Void" ? 0 : 1,
});
break;
}
}
foreach (var vmil in parseIls) {
Debug.LogError(vmil);
}
var json = JsonMapper.ToJson(parseIls);
EditorPrefs.SetString(FixJson, json);
}
虚拟机
- 这个基本就是InjectFix虚拟机实现,今天有点困,改天再写吧
|