IOS平台Unity3D AOT全局模块结构分析
分析背景 实现原理 Unity引擎3.5.7f6使用的Mono版本为:mono-2-6,对应源码截图如下: 在确定了游戏使用的Unity引擎版本及Mono版本之后,便可开始针对IOS平台中Unity采用AOT编译的全局模块(编译之后命名为:mono_aot_module__info)结构进行分析。 一、IOS平台Unity 全局模块结构分析 其中截取了重要部分的相关信息,通过以上截图信息发现AOT模块结构中以两个Dword数据为一组数据,多组数据组成了AOT模块结构。Mono源码中aot-compiler.c文件负责在编译阶段静态编译全局AOT模块结构,其中函数mono_compile_assembly负责将C#代码静态编译为可执行的机器指令、并将编译阶段每个C#模块信息转换为对应静态的全局AOT模块,mono_compile_assembly静态编译C#代码为可执行的机器指令过程通过Compile_method函数实现,Compile_method内部最终调用mini_method_compile实现静态编译C#函数,其中Compile_method函数定义和解释如下: mono_compile_assembly函数将C#静态编译为可执行的机器码之后便通过下图代码将每个相关信息打包AOT全局模块中: 上图第一行代码的emit_code函数负责将已静态编译的不同C#函数代码存储于Method_Offset中,emit_code函数关键代码如下图所示: 通过上图红框部分可看出emit_code函数处理了methods、 method_offsets、methods_end三个重要结构,对应已编译的IOS版游戏游戏主逻辑AOT模块如下所示: 由emit_code处理方式可得到:IOS平台中Unity 游戏AOT全局模块结构由多组单位数据组成,每组数据单位为两个Dword数据类型,两个数据类型依次为:pszStructName、pstStruct(解析为:第一个数据为对应结构体命名Char内容的指针、第二个数据为对应结构体指针)。全局的mono_aot_module__info符号由多组数据组成,最终通过mono_compile_assembly函数实现。 2、执行阶段AOT全局模块解析过程 通过上图代码可知Mono中通过find_symbol函数查看全局模块对应项信息,通过传入对应结构体名称便可获取到结构体的指针,例如:method_offsets结构可通过find_symbol查找”method_offsets”获取到。以Method_offsets为例分析该项在游戏中含义。 Method_offsets中存储了已静态编译的每个C#函数相对偏移指针,load_method函数会在C#函数第一次调用过程中获取该函数对应的代码其实位置,对应获取C#函数起始地址的关键代码如下图所示: 通过load_aot_module函数可知amodule->code_offsets由全局AOT模块的method_offsets指针赋值(amodule->code_offsets定义为:uint32_t *类型),amodule->code由全局AOT模块的methods结构指针赋值(amodule->code定义为:uint8_t *类型),以上代码通过C#函数索引(method_index)进行计算。对应计算原理如下: pFuncCodeBegin = amodule->code + *(uint32_t*)(amodule->code_offsets + 4 * method_index) 转换为全局AOT模块的计算方式为(神奇助手利用该种方法定位到关键函数并替换指针): pFuncCodeBegin = methods + *(uint32_t*)(*(uint32_t*)method_offsets + 4 * method_index) 通过以上的计算方式可知amodule->code属于编译之后的可执行代码的基址偏移,amodule->code_offsets为相对于amodule->code位置的相对偏移。对应AOT模块为:每个C#函数通过method_index计算之后的数值为相对于methods的相对偏移。 以上只针对method相关结构进行详细分析和介绍,AOT全局模块其他结构解析可通过阅读关键代码获取到。 二、游戏神奇助手实现方式回顾 神奇助手外挂针对游戏游戏定制化无敌功能,外挂将游戏逻辑的mono_aot_module_Assembly_CSharp_info文件符号结构进行私有拷贝,之后将mono_aot_module_Assembly_CSharp_info符号中的MethodTable进行拷贝,对应代码如下图所示: 外挂将拷贝的目的内存以全局方式分配在Alice.dylib功能模块中,之后外挂对Method_offsset存储的C#代码指针表中两个重要函数进行替换,替换的函数如下图所示: 替换的两个函数指针分别为:EntityCollider_LateUpdate和DamgeTrigger_Damage_UnityEngine_Collide,替换指针之后,调用mono的mono_aot_register_modules函数重新注册逻辑模块的全局符号,其中两个函数Index如下图所示: 通过第一节的分析可知外挂作者通过全局模块符号”method_offfsets”、”methods”获取到编译之后所有C#函数指针表,外挂分析清楚需要接管的两个C#函数的Method_index,通过Method_Index便可查找到C#函数的指针,通过替换method_offsets中的函数指针方式便获得了执行时机。
总结 |