IOS平台Unity3D AOT全局模块结构分析

发表于2016-07-18
评论0 5.2k浏览

分析背景
  由于IOS平台中不允许执行动态代码,Unity 4.6之前的版本在IOS平台中采用了AOT的处理方式,提前将C#代码静态编译为机器识别的二进制机器码。Unity引擎4.6之前的版本中IOS框架采用了Mono的AOT机制实现静态编译和处理,本文针对全局AOT模块结构及神奇助手外挂获取函数指针处理方式进行分析。

实现原理
  IOS平台中,Unity引擎采用了Mono的AOT(Ahead-Of-Time)机制,静态编译过程中将C#实现的函数直接编译为CPU可执行的原生码(例如:Arm、X86等)。用户开发的C#代码编译之后会生成命名为mono_aot_module__info全局符号模块,由于每个C#代码模块在IOS平台中采用静态编译机制,如果需要针对IOS平台Unity AOT全局模块进行分析则需要查看Mono源码、并通过游戏实际的验证。
  本文以IOS某U3D游戏为切入点进行分析,游戏使用的Unity引擎版本为3.5.7f6,Unity引擎版本查看只需要搜索“engine version”字符串便可定位到,例如游戏引擎版本搜索字符串之后截图如下:

                   

Unity引擎3.5.7f6使用的Mono版本为:mono-2-6,对应源码截图如下:

                  

在确定了游戏使用的Unity引擎版本及Mono版本之后,便可开始针对IOS平台中Unity采用AOT编译的全局模块(编译之后命名为:mono_aot_module__info)结构进行分析。

一、IOS平台Unity 全局模块结构分析
1、编译阶段AOT全局模块生成过程
  IOS平台游戏游戏主逻辑模块对应的全局AOT模块结构信息如下图所示:

   

   

   

其中截取了重要部分的相关信息,通过以上截图信息发现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全局模块解析过程
  Unity引擎采用静态方式将每个C#代码模块生成对应AOT全局模块,运行阶段进行解析和执行。Mono源码中aot_runtime.c文件的load_aot_module函数负责解析每个C#的全局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中的函数指针方式便获得了执行时机。

 

总结
  以上对IOS平台的Unity的全局AOT模块编译和解析处理方式,通过结构的分析能够更好的掌握IOS平台的Unity AOT机制,同时能够弄清楚神奇助手作者前期是如何针对IOS游戏通过替换全局符号的函数指针制作外挂。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引