IL2CPP IL2CPP是什么 首先我们来了解一下它的前缀,IL (Intermediate Language-中间语言)—基于堆栈的伪汇编语言;很多时候还会看到CIL(Common Intermediate Language,特指在.Net平台下的IL标准)。这两者是属于通用语言架构和.NET框架的低阶的人类可读的编程语言。目标位.NET框架的语言被编译成(C)IL,然后汇编为字节码。(C)IL类似于一个面向对象的汇编语言,并且他是完全基于堆栈的,并且是在虚拟机上(.Net Framework, Mono VM)运行的语言。
IL2CPP(把IL中间语言转换成C++源代码,然后利用标准c++平台编译器产生二进制文件。简单的来说就是将IL转化为CPP文件。)
Fundamentally, it differs from the current implementation in that the IL2CPP compiler converts assemblies into C++ source code. It then leverages the standard platform C++ compilers to produce native binaries.(这是下面的文章对IL2CPP的部分描述)
这里有一篇Unity3D官方博客的文章,引出了IL2CPP的概念,以及一篇对IL2CPP的介绍。(值得一看)
The future of scripting in Unity
An introduction to IL2CPP internals
文中指出IL2CPP由AOT预编译器和虚拟机两部分组成。并且明确指出IL2CPP不会重新构建整个.NET框架和Mono工具链;MonoC#编译器和Mono的类、库,以及现在支持的功能和MonoAOT使用的第三方库都将会和IL2CPP继续使用
下图为IL2CPP的工具链概念图
文中还提到了AOT预编译(Ahead of Time ),可以做个了解。主要是对程序在运行前进行优化。
IL2CPP是怎样工作的
整体流程可以用如上图表示,例子详见上面的第二篇文章
How IL2CPP works
Unityj scripting API 代码被编译为常规的.NETDLL程序集
所有的不是脚本的一部分的托管程序集由Unused Bytecode Stripper处理(从动态链接库的DLL中去除一些无用的类和方法),这个过程会显著减小创建游戏的大小。(其上在上面那片文章也有提到)
所有的托管程序集被翻译为标准的C++代码
生成的C++代码和IL2CPP运行时生成的部分代码被本地平台的编译器编译
最后,代码会被连接到可执行文件(取决于你的目标平台)或DLL文件。
至于为什么会产生许多无用代码
我们易一段简单的C++代码和IL汇编代码作为示例
1 2 3 4 5 6 7 #include <stdio.h> int main (int argc, char **argv) { int a = 1 ; int b = 2 ; printf ("Hello world: %d\r\n" , a + b); }
1 2 3 4 5 6 7 8 9 10 11 12 13 ldc.i4.1 #将常量1压入栈 stloc.0 #将1从栈中取出,并赋给第一个变量a ldc.i4.2 stloc.1 ldstr "Hello World: {0}" #将字符串压入栈 ldloc.0 #将变量a压入栈 ldloc.1 add #a+b box System.Int32 #将类型装箱,其实这一步是在一定程度上优化程序 call System.Void System.Console::WriteLine(System.String,System.Object) #输出函数调用 ret #这块程序集对应上面大括号里的内容
接下来插入一段对应IL2CPP工作的第一部后出现的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // System.Void HelloWorld.Program::Main(System.String[]) IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void Program_Main_m7A2CC8035362C204637A882EDBDD0999B3D31776 (StringU5BU5D_t933FB07893230EA91C40FF900D5400665E87B14E* ___args0, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_method (Program_Main_m7A2CC8035362C204637A882EDBDD0999B3D31776_MetadataUsageId); s_Il2CppMethodInitialized = true; } int32_t V_0 = 0; int32_t V_1 = 0; { V_0 = 2; int32_t L_0 = V_0; V_1 = ((int32_t)il2cpp_codegen_add((int32_t)1, (int32_t)L_0)); int32_t L_1 = V_1; int32_t L_2 = L_1; RuntimeObject * L_3 = Box(Int32_t585191389E07734F19F3156FF88FB3EF4800D102_il2cpp_TypeInfo_var, &L_2); IL2CPP_RUNTIME_CLASS_INIT(Console_t5C8E87BA271B0DECA837A3BF9093AC3560DB3D5D_il2cpp_TypeInfo_var); Console_WriteLine_m22F0C6199F705AB340B551EA46D3DB63EE4C6C56(_stringLiteral331919585E3D6FC59F6389F88AE91D15E4D22DD4, L_3, /*hidden argument*/NULL); return; } }
可以看到代码非常复杂。
如此复杂的原因在于IL2CPP对IL字节码线性扫描,并转换为与C++代码等效的非基于堆栈的代码。所以才会产生如此多的多余的变量和赋值操作。
再接下来,是IL2CPP版本的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 ; void __fastcall Program_Main_m2325437134(Il2CppObject *__this, StringU5BU5D_t1642385972 *___args0, MethodInfo *method) Program_Main_m2325437134 proc near push rbx sub rsp, 20h cmp cs:s_Il2CppMethodInitialized_8016, 0 jnz short loc_14038BFF1 mov ecx, cs:?Program_Main_m2325437134_MetadataUsageId@@3IB call ?InitializeMethodMetadata@MetadataCache@vm@il2cpp@@SAXI@Z mov cs:s_Il2CppMethodInitialized_8016, 1 loc_14038BFF1: mov rcx, cs:?Int32_t2071877448_il2cpp_TypeInfo_var@@3PEAUIl2CppClass@@EA lea rdx, [rsp+48h] mov dword ptr [rsp+48h], 3 call ?Box@Object@vm@il2cpp@@SAPEAUIl2CppObject@@PEAUIl2CppClass@@PEAX@Z mov rcx, cs:?Console_t2311202731_il2cpp_TypeInfo_var@@3PEAUIl2CppClass@@EA mov rbx, rax test byte ptr [rcx+10Ah], 1 jz short loc_14038C02B cmp dword ptr [rcx+0BCh], 0 jnz short loc_14038C02B call ?ClassInit@Runtime@vm@il2cpp@@SAXPEAUIl2CppClass@@@Z loc_14038C02B: mov rdx, cs:?_stringLiteral3443654334@@3PEAUString_t2029220233@@EA xor r9d, r9d mov r8, rbx xor ecx, ecx call Console_WriteLine_m3776981455 add rsp, 20h pop rbx retn Program_Main_m2325437134 endp
可以看到有很多刚开始没有的符号,这时候就需要用一些工具去除这些多余的符号了,完全去除,就可以开始分析代码了。
写在最后 (本文是在做一个Unitylab的时候当作前置知识学习的一篇简短的笔记,原本想着和Unitylab实验的思路一起发出来,还是提前发了。)