前言
环境:
Unity 2022.3.4f
PC Win10
Script BackEnd : IL2CPP
近期在研究IL2CPP相关内容,对其如何将C#生成C++非常感兴趣,在IL2CPPDumper 中看到了下面一段
The IL2CPP AOT compiler is named il2cpp.exe. On Windows you can find it in the Editor\Data\il2cpp directory. On OSX it is in the Contents/Frameworks/il2cpp/build directory in the Unity installation. The il2cpp.exe utility is a managed executable, written entirely in C#. We compile it with both .NET and Mono compilers during our development of IL2CPP.
IL2CPP AOT编译器的名称为il2cpp.exe。在Windows上,您可以在Editor\Data\il2cpp目录中找到它。在OSX上,它位于Unity安装的Contents/Frameworks/il2cpp/build目录中。il2cpp.exe实用程序是一个托管可执行文件,完全由C#编写。在我们开发IL2CPP期间,我们使用.NET和Mono编译器编译它。
去往上面所提及的目录,发现不仅仅有il2cpp.exe,还有一些其他文件,其中另一个比较可疑的文件就是UnityLinker,猜测应该是前置在il2cpp的代码裁剪功能,所以我们分析目标就很明确了:
UnityLinker.exe:实现代码裁剪
UnityLinker.dll
UnityLinker.pdb
il2cpp.exe:实现IL2CPP
OK,万事以备,开始分析
(先贴一下整个流程图
UnityLinker
流程比较简单,直接列出来
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 32 33 UnityPipeline unityPipeline = new UnityPipeline(); unityPipeline.AppendStep(new SetupAndRegisterUnityEngineSteps()); unityPipeline.AppendStep(new UnityLoadReferencesStep()); unityPipeline.AppendStep(new SetupAndRegisterUnityRootsSteps2()); unityPipeline.AppendStep(new SetupI18N()); unityPipeline.AppendStep(new ResolveFromDescriptorsStep()); 根据项目配置的xml进行裁剪 unityPipeline.AppendStep(new UnityBlacklistStep()); unityPipeline.AppendStep(new DynamicDependencyLookupStep()); unityPipeline.AppendStep(new EarlyReportGenerationStep(this ._originalArguments)); unityPipeline.AppendStep(new CaptureInputSnapshot(linkerOptions)); unityPipeline.AppendStep(new BuildTestBundle(linkerOptions, this ._originalArguments)); unityPipeline.AppendStep(new ResolveTestsStep()); unityPipeline.AppendStep(new ResolveFromPreserveAttribute()); unityPipeline.AppendStep(new UnityTypeMapStep()); unityPipeline.AppendStep(new BeforeMarkReportGenerationStep()); unityPipeline.AppendStep(new BeforeMarkAnalyticsStep()); unityPipeline.AppendStep(new UnityMarkStep()); unityPipeline.AppendStep(new ValidateVirtualMethodAnnotationsStep()); unityPipeline.AppendStep(new ProcessWarningsStep()); unityPipeline.AppendStep(new UnitySweepStep()); unityPipeline.AppendStep(new UnityCodeRewriterStep()); unityPipeline.AppendStep(new CleanStep()); unityPipeline.AppendStep(new StubifyStep()); unityPipeline.AppendStep(new AddUnresolvedStubsStep()); unityPipeline.AppendStep(new RegenerateGuidStep()); unityPipeline.AppendStep(new BeforeOutputAnalyticsStep()); unityPipeline.AppendStep(new BeforeOutputReportGenerationStep()); unityPipeline.AppendStep(new UnityOutputStep()); unityPipeline.AppendStep(new LinkerToEditorDataGenerationStep()); unityPipeline.AppendStep(new ReportGenerationStep()); unityPipeline.AppendStep(new CaptureOutputSnapshot()); unityPipeline.AppendStep(new ProcessSnapshots()); return unityPipeline;
其中对于dll中il指令的操作是基于Mono.Ceil 库实现的
最后产出Library\Bee\artifacts\WinPlayerBuildProgram\ManagedStripped
中的dll
IL2CPP
既然有exe文件,那我们直接从exe文件开始冻手
但是将exe文件拖入c#反汇编软件发现无法反汇编,查看pe头发现根本不是.net后端:
正常来说C#编译的dll这两个都是有值的
所以只能放进IDA进行反汇编了
IDA反汇编查看逻辑
通过反汇编可以跟踪到这个函数
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 __int64 __fastcall exe_start (unsigned int argc, const wchar_t **argv) { hostfxr_resolver_t fxr; if ( GetModuleFileNameWrapper(0LL , &host_path) && pal::realpath(&host_path, 0 ) ) { *(_OWORD *)&fxr.m_hostfxr_dll = 0LL ; if ( fxr_resolver::try_get_path(&app_root, &fxr.m_dotnet_root, &fxr.m_fxr_path) ) { if ( pal::load_library(&fxr.m_fxr_path, &fxr.m_hostfxr_dll) ) { v4 = Success; fxr.m_status_code = Success; goto LABEL_63; } } fxr.m_status_code = v4; LABEL_63: if ( v4 == Success ) { if ( qword_140024068 ) { ProcAddress = GetProcAddress(fxr.m_hostfxr_dll, "hostfxr_main_bundle_startupinfo" ); if ( ProcAddress ) { p_host_path = &host_path; if ( host_path._Mypair._Myval2._Myres >= 8 ) p_host_path = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)host_path._Mypair._Myval2._Bx._Ptr; if ( fxr.m_dotnet_root._Mypair._Myval2._Mysize ) { p_m_dotnet_root = &fxr.m_dotnet_root; if ( fxr.m_dotnet_root._Mypair._Myval2._Myres >= 8 ) p_m_dotnet_root = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)fxr.m_dotnet_root._Mypair._Myval2._Bx._Ptr; } else { p_m_dotnet_root = 0LL ; } v33 = qword_140024068; v34 = &fxr.m_fxr_path; if ( fxr.m_fxr_path._Mypair._Myval2._Myres >= 8 ) v34 = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)fxr.m_fxr_path._Mypair._Myval2._Bx._Ptr; trace::info(L"Invoking fx resolver [%s] hostfxr_main_bundle_startupinfo" , v34); v35 = &host_path; if ( host_path._Mypair._Myval2._Myres >= 8 ) v35 = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)host_path._Mypair._Myval2._Bx._Ptr; trace::info(L"Host path: [%s]" , v35); v36 = &fxr.m_dotnet_root; if ( fxr.m_dotnet_root._Mypair._Myval2._Myres >= 8 ) v36 = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)fxr.m_dotnet_root._Mypair._Myval2._Bx._Ptr; trace::info(L"Dotnet path: [%s]" , v36); v37 = &app_path; if ( app_path._Mypair._Myval2._Myres >= 8 ) v37 = (std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *)app_path._Mypair._Myval2._Bx._Ptr; trace::info(L"App path: [%s]" , v37); trace::info(L"Bundle Header Offset: [%lx]" , v33); v38 = (void (__fastcall *(__fastcall *)(void (__fastcall *)(const wchar_t *)))(const wchar_t *))GetProcAddress(fxr.m_hostfxr_dll, "hostfxr_set_error_writer" ); if ( !v38 ) trace::info(L"Probed for and did not resolve library symbol %S" , "hostfxr_set_error_writer" ); =========================================================== v4 = ((unsigned int (__fastcall *)(_QWORD, const wchar_t **, std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *, std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *, std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > *, __int64))ProcAddress)( argc, argv, p_host_path, p_m_dotnet_root, v32, v33); =========================================================== if ( v41 && v38 ) v38(0LL ); } } operator delete (v74, v73) ; } return (unsigned int )v4; }
其中hostfxr_resolver_t为:
可以看到其中记录了调用il2cpp.dll所需要的所有信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 struct __cppobj hostfxr_resolver_t // sizeof =0x70 { HINSTANCE__ *m_hostfxr_dll; std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > m_host_path; std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > m_dotnet_root; std ::basic_string<wchar_t ,std ::char_traits<wchar_t >,std ::allocator<wchar_t > > m_fxr_path; bool m_requires_startupinfo_iface; StatusCode m_status_code; };
dnSpy反汇编查看逻辑&&Debug
随后我们使用dnspy 反编译il2cpp.dll,即可找到程序入口
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public static ExitCode Run (string [] args, bool setInvariantCulture, bool throwExceptions = true ) { ExitCode result2; using (TinyProfiler2 tinyProfiler = new TinyProfiler2()) { Il2CppCommandLineArguments il2CppCommandLineArguments = null ; try { using (tinyProfiler.AnalyticsSection("il2cpp.exe" )) { bool continueToRun; ExitCode exitCode; RuntimePlatform platform; BuildingOptions buildingOptions; using (tinyProfiler.Section("ParseArguments" , null )) { Il2CppOptionParser.ParseArguments(args, out continueToRun, out exitCode, out platform, out il2CppCommandLineArguments, out buildingOptions); } using (tinyProfiler.Section("RegisterRuntimeEventListeners" , null )) { tinyProfiler.RegisterRuntimeEventListeners(); } if (il2CppCommandLineArguments.SettingsForConversionAndCompilation.PrintCommandLine) { ConsoleOutput.Info.WriteLine(Process.GetCurrentProcess().MainModule.FileName + " " + args.AggregateWith(" " )); } if (setInvariantCulture) { Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; } if (il2CppCommandLineArguments.ConversionRequest.DebugEnableAttach) { DebugAttacher.AttachToCurrentProcess(il2CppCommandLineArguments.ConversionRequest.DebugRiderInstallPath, il2CppCommandLineArguments.ConversionRequest.DebugSolutionPath); } if (il2CppCommandLineArguments.ConversionRequest.DotMemoryCollectAllocations) { MemoryProfiler.CollectAllocations(true ); } ExitCode result; if (!continueToRun) { result2 = exitCode; } else if (Program.RunDotTraceProfilingIfEnabled(il2CppCommandLineArguments, out result)) { result2 = result; } else if (Program.RunDotMemoryProfilingIfEnabled(il2CppCommandLineArguments, out result)) { result2 = result; } else { result2 = Program.DoRun(tinyProfiler, args, platform, il2CppCommandLineArguments, buildingOptions, throwExceptions); } } } } return result2; }
干看很容易迷失方向,这还学个集贸啊
不用急,dnSpy支持直接针对dll进行Debug,我们只需要将启动参数正确填入即可,那么如何获取启动参数呢?
Process Explorer获取il2cpp.exe启动参数
下载process-explorer
要获取启动参数,以及一些其他必须的文件,需要先build一次工程,点击unity build按钮后,一直盯着process-explorer
在il2cpp.exe出现在process-explorer时,按下空格,即可snap住当前所有process信息
我们双击il2cpp.exe条目,即可查看进程详情,启动参数为Command line条目中的内容
例如
1 --convert-to-cpp --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/Assembly-CSharp.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/Mono.Security.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/mscorlib.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/System.Configuration.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/System.Core.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/System.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/System.Xml.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.AIModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.AndroidJNIModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.AnimationModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.AssetBundleModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.AudioModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.CoreModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.DirectorModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.GridModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.IMGUIModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.InputLegacyModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.InputModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.ParticleSystemModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.Physics2DModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.PhysicsModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.PropertiesModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.SharedInternalsModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.SubsystemsModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.TerrainModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.TextRenderingModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.TilemapModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.UIElementsModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.UIModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.UnityAnalyticsModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.UnityWebRequestModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.VFXModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.VideoModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.VRModule.dll" --assembly="Library/Bee/artifacts/WinPlayerBuildProgram/ManagedStripped/UnityEngine.XRModule.dll" --generatedcppdir="H:/UnityProjects/il2cpp-mod/Library/Bee/artifacts/WinPlayerBuildProgram/il2cppOutput/cpp" --symbols-folder="H:/UnityProjects/il2cpp-mod/Library/Bee/artifacts/WinPlayerBuildProgram/il2cppOutput/cpp/Symbols" --enable-analytics --emit-null-checks --enable-array-bounds-check --dotnetprofile=unityaot-win32 --profiler-report --profiler-output-file="H:/UnityProjects/il2cpp-mod/Library/Bee/artifacts/il2cpp_conv_b94y.traceevents" --print-command-line --data-folder="H:/UnityProjects/il2cpp-mod/Library/Bee/artifacts/WinPlayerBuildProgram/il2cppOutput/data"
需要注意的是,在dnSpy对il2pp.dll进行Debug时,我们需要指定Debug工作目录为Unity工程目录,否则当我们对il2cpp.dll进行debug时,我们的主进程环境已经不是Unity工程了,是找不到这些dll的
Debug IL2CPP流程
流程图如下:
先为各个流程创建了调度器,默认是多线程的
推荐直接修改Unity.IL2CPP.Api.ConversionRequest.Jobs为1,方便Debug代码,速度会很慢就是了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static PhaseWorkScheduler <TContext > CreateScheduler <TContext >( TContext context, GlobalSchedulingContext schedulingContext, Func<TContext, OverrideObjects, int , ForkedContextScope<int , TContext>> forker, Func<TContext, Exception, Exception> workerItemExceptionHandler, bool allowContextForMainThread ) { using (schedulingContext.Services.TinyProfiler.Section("Create Scheduler" )) { RealSchedulerComponent workers = new RealSchedulerComponent(); OverrideObjects overrideObjects = new OverrideObjects((ImmediateSchedulerComponent) workers); int workerCount = schedulingContext.Parameters.EnableSerialConversion ? 1 : schedulingContext.InputData.JobCount; PhaseWorkScheduler<TContext> scheduler = new PhaseWorkScheduler<TContext>(schedulingContext, (Func<int , ForkedContextScope<int , TContext>>) (count => forker(context, overrideObjects, count)), workerCount, workerItemExceptionHandler, allowContextForMainThread); workers.Initialize((IWorkScheduler) scheduler); return scheduler; } }
前面的流程我们就略过了,可以直接定位到写入cpp文件的函数
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 namespace Unity.IL2CPP { public static class SourceWriter { internal static void WriteTypesMethods (SourceWritingContext context, IGeneratedMethodCodeWriter writer, in TypeWritingInformation writingInformation, NPath filePath, bool writeMarshalingDefinitions ) { writer.AddStdInclude("limits" ); TypeDefinition systemArray = context.Global.Services.TypeProvider.SystemArray; if (systemArray != null ) { writer.AddIncludeForTypeDefinition(context, systemArray); } writer.WriteClangWarningDisables(); TypeReference type = writingInformation.DeclaringType; writer.AddIncludeForTypeDefinition(context, type); if (writingInformation.WriteTypeLevelInformation && context.Global.Parameters.UsingTinyBackend) { TypeDefinitionWriter.WriteStaticFieldDefinitionsForTinyProfile(writer, type); TypeDefinitionWriter.WriteStaticFieldRVAExternsForTinyProfile(writer, type); } try { if (writingInformation.WriteTypeLevelInformation) { if (type.IsDelegate) { new DelegateMethodsWriter(writer).WriteInvokeStubs(type); } ReadOnlyCollection<MethodDefinition> methods = writingInformation.DeclaringType.Resolve().Methods; if (writeMarshalingDefinitions) { MarshalingDefinitions.Write(context, writer, type); } else if (context.Global.Parameters.FullGenericSharingOnly) { foreach (MethodDefinition methodDefinition2 in methods) { MethodDefinition methodDefinition = methodDefinition2.Resolve(); context.Global.Services.ErrorInformation.CurrentMethod = methodDefinition; SourceWriter.WriteReversePInvokeWrappersForMethodIfNecessary(writer, methodDefinition); } } } foreach (MethodReference methodToWrite in writingInformation.MethodsToWrite) { MethodDefinition method = methodToWrite.Resolve(); context.Global.Services.ErrorInformation.CurrentMethod = method; if (context.Global.Parameters.EnableErrorMessageTest) { ErrorTypeAndMethod.ThrowIfIsErrorMethod(context, method); } if (!string .IsNullOrEmpty(context.Global.InputData.AssemblyMethod) && filePath != null && method.FullName.Contains(context.Global.InputData.AssemblyMethod)) { context.Global.Collectors.MatchedAssemblyMethodSourceFiles.Add(filePath); } MethodWriter.WriteMethodDefinition(context.CreateAssemblyWritingContext(method), writer, methodToWrite); } } catch (Exception) { writer.ErrorOccurred = true ; throw ; } writer.WriteClangWarningEnables(); } } }
我们断到业务Mono的Start函数
- methodToWrite
“Start”
AggressiveInlining
false
Attributes
MemberAccessMask
+ Body
{Unity.IL2CPP.DataModel.MethodBody}
CallingConvention
Default
CodeSize
0x0000000D
ContainsFullySharedGenericTypes
false
ContainsGenericParameter
false
CppName
“HelloWorld_Start_m7D4140BFC9DCC632B4B2907C1BFFAC4FF2FE9247”
+ CustomAttributes
Count = 0x00000000
DebuggerDisplayName
“Start”
+ DebugInformation
{Unity.IL2CPP.DataModel.MethodDebugInfo}
+ DeclaringType (Unity.IL2CPP.DataModel.MemberReference)
“HelloWorld”
+ DeclaringType
“HelloWorld”
ExplicitThis
false
FullName
“System.Void HelloWorld::Start()”
其中CppName就是最后生成cpp的函数名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void HelloWorld_Start_m7D4140BFC9DCC632B4B2907C1BFFAC4FF2FE9247 (HelloWorld_tAA2C066E3E94F880902D1D61ADB3172B1AD6D1DB* __this, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&_stringLiteralF475C40B66CDDEC9B385F4B9DDBBD87798470EA5); s_Il2CppMethodInitialized = true ; } { il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteralF475C40B66CDDEC9B385F4B9DDBBD87798470EA5, NULL ); return ; } }
格式为TypeName_MethodName_Hash,其中Hash来自于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 internal unsafe static string GenerateForString (string str ) { return string .Create<string >(CppNamePopulator.HashSize * 2 , str2, delegate (Span<char > span, string str) { byte * hashBuffer = stackalloc byte [(UIntPtr)CppNamePopulator.HashSize]; Span<byte > hash = new Span<byte >((void *)hashBuffer, CppNamePopulator.HashSize); SHA1.HashData(MemoryMarshal.AsBytes<char >(str.AsSpan()), hash); for (int i = 0 ; i < CppNamePopulator.HashSize; i++) { byte b = *hash[i]; *span[i * 2 ] = CppNamePopulator.HashLookup[(int )(b / 16 )]; *span[i * 2 + 1 ] = CppNamePopulator.HashLookup[(int )(b % 16 )]; } }); }
方法体的代码生成部分如下:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public void Generate ( ) { if (!this ._methodDefinition.HasBody) return ; if (GenericsUtilities.CheckForMaximumRecursion((ReadOnlyContext) this ._context, this ._methodReference) && this ._methodReference != this ._methodDefinition.FullySharedMethod) { if (this ._context.Global.Parameters.DisableFullGenericSharing) { this ._writer.WriteStatement(Emit.RaiseManagedException("il2cpp_codegen_get_maximum_nested_generics_exception()" )); } else { List<string > argsFor = new List<string >(this ._methodDefinition.Parameters.Count + 2 ); if (!this ._methodDefinition.IsStatic) argsFor.Add("__this" ); foreach (ParameterDefinition parameter in this ._methodReference.Parameters) argsFor.Add(parameter.CppName); this .WriteCallExpressionFor(this ._methodReference, (MethodReference) this ._methodDefinition, MethodCallType.Normal, argsFor, this ._runtimeMetadataAccess.MethodMetadataFor((MethodReference) this ._methodDefinition).OverrideHiddenMethodInfo("method" ), false ); if (this ._valueStack.Count != 1 ) return ; this ._writer.WriteReturnStatement(this ._valueStack.Pop().Expression); } } else { this .WriteLocalVariables(); if (this ._context.Global.Parameters.EnableDebugger) { this .WriteDebuggerSupport(); SequencePointInfo info; if (this ._sequencePointProvider.TryGetSequencePointAt(this ._methodDefinition, -1 , SequencePointKind.Normal, out info)) this .WriteCheckSequencePoint(info); if (this ._sequencePointProvider.TryGetSequencePointAt(this ._methodDefinition, 16777215 , SequencePointKind.Normal, out info)) this .WriteCheckMethodExitSequencePoint(info); this .WriteCheckPausePoint(-1 ); } this ._exceptionSupport = new ExceptionSupport((ReadOnlyContext) this ._context, this ._methodDefinition, this ._cfg.FlowTree, this ._writer); this ._exceptionSupport.Prepare(); foreach (ExceptionHandler exceptionHandler in this ._methodDefinition.Body.ExceptionHandlers) { if (exceptionHandler.CatchType != null ) this ._writer.AddIncludeForTypeDefinition((ReadOnlyContext) this ._context, this ._typeResolver.Resolve(exceptionHandler.CatchType)); } ReadOnlyDictionary<InstructionBlock, ResolvedInstructionBlock> instructionBlocks = this ._resolvedMethodContext.Blocks.ToDictionary<ResolvedInstructionBlock, InstructionBlock, ResolvedInstructionBlock>((Func<ResolvedInstructionBlock, InstructionBlock>) (b => b.Block), (Func<ResolvedInstructionBlock, ResolvedInstructionBlock>) (b => b)).AsReadOnly<InstructionBlock, ResolvedInstructionBlock>(); foreach (GlobalVariable global in this ._stackAnalysis.Globals) this .WriteVariable(global .Type, global .VariableName); foreach (Node child in this ._exceptionSupport.FlowTree.Children) { if (child.Type != NodeType.Finally && child.Type != NodeType.Fault) this .GenerateCodeRecursive(child, instructionBlocks); } if (this ._methodReference.ReturnType.IsNotVoid) { Instruction instruction = this ._methodDefinition.Body.Instructions.LastOrDefault<Instruction>(); if (instruction != null && instruction.OpCode != OpCodes.Ret && instruction.OpCode != OpCodes.Throw && instruction.OpCode != OpCodes.Rethrow && !(instruction.Operand is Instruction)) ((ICodeWriter) this ._writer).WriteLine("il2cpp_codegen_no_return();" ); } this ._variableSizedTypeSupport.GenerateInitializerStatements((ReadOnlyContext) this ._context); }
而GenerateCodeRecursive就是最核心的IL分析并生成代码过程
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 private void GenerateCodeRecursive ( Node node, ReadOnlyDictionary<InstructionBlock, ResolvedInstructionBlock> instructionBlocks ) { using (RuntimeMetadataAccessContext.Create(this ._runtimeMetadataAccess, node)) { InstructionBlock block = node.Block; if (block != null ) { if (node.Children.Count > 0 ) throw new NotSupportedException("Node with explicit Block should have no children!" ); if (block.IsDead) { if (this ._writer.Context.Global.Parameters.EmitComments) this .WriteComment("Dead block : " + block.First.ToString()); if (!this .IsReturnNeededForDeadBlock(block)) return ; ((ICodeWriter) this ._writer).WriteLine("IL2CPP_UNREACHABLE;" ); this ._writer.WriteDefaultReturn((ReadOnlyContext) this ._context, this ._methodReference.GetResolvedReturnType((ITypeFactoryProvider) this ._context)); } else { this ._valueStack.Clear(); this ._variableSizedTypeSupport.EnterBlock(); if (this ._options.EmitBlockInfo && this ._writer.Context.Global.Parameters.EmitComments) { DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(7 , 1 ); interpolatedStringHandler.AppendLiteral("BLOCK: " ); interpolatedStringHandler.AppendFormatted<int >(block.Index); this .WriteComment(interpolatedStringHandler.ToStringAndClear()); } if (this ._options.EmitInputAndOutputs) this .DumpInsFor(block); ResolvedInstruction ins = instructionBlocks[block].Instructions.First<ResolvedInstruction>(); this .EnterNode(node, instructionBlocks); GlobalVariable[] globalVariableArray = this ._stackAnalysis.InputVariablesFor(block); for (int index = globalVariableArray.Length - 1 ; index >= 0 ; --index) { GlobalVariable globalVariable = globalVariableArray[index]; this ._valueStack.Push(new StackInfo(globalVariable.VariableName, globalVariable.Type)); } this ._exceptionSupport.PushExceptionOnStackIfNeeded(node, this ._valueStack, this ._typeResolver, this ._context.Global.Services.TypeProvider.SystemException); this ._classesAlreadyInitializedInBlock.Clear(); DefaultInterpolatedStringHandler interpolatedStringHandler1; while (true ) { SequencePoint sequencePoint = this .GetSequencePoint(ins.Instruction); if (sequencePoint != null ) { if (sequencePoint.StartLine != 16707566 && this ._options.EmitLineNumbers) { IGeneratedMethodCodeWriter writer = this ._writer; interpolatedStringHandler1 = new DefaultInterpolatedStringHandler(9 , 2 ); interpolatedStringHandler1.AppendLiteral("#line " ); interpolatedStringHandler1.AppendFormatted<int >(sequencePoint.StartLine); interpolatedStringHandler1.AppendLiteral(" \"" ); interpolatedStringHandler1.AppendFormatted(sequencePoint.Document.Url.ToString().Replace("\\" , "\\\\" )); interpolatedStringHandler1.AppendLiteral("\"" ); string stringAndClear = interpolatedStringHandler1.ToStringAndClear(); writer.WriteUnindented(stringAndClear); } if (ins.OpCode != OpCodes.Nop) this ._sourceAnnotationWriter.EmitAnnotation((ICodeWriter) this ._writer, sequencePoint); this .WriteCheckSequencePoint(sequencePoint); } this .WriteCheckPausePoint(ins.Offset); if (this ._options.EmitIlCode && this ._writer.Context.Global.Parameters.EmitComments) this ._writer.WriteComment(ins.ToString()); this .ProcessInstruction(node, block, ins); this .ProcessInstructionOperand(ins); if (ins.Next != null && ins.Instruction != block.Last) ins = ins.Next; else break ; } if ((ins.OpCode.Code < Code.Br_S || ins.OpCode.Code > Code.Blt_Un) && block.Successors.Any<InstructionBlock>() && ins.OpCode.Code != Code.Switch) this .SetupFallthroughVariables(block); if (this ._options.EmitInputAndOutputs) this .DumpOutsFor(block); if (this ._options.EmitBlockInfo && this ._writer.Context.Global.Parameters.EmitComments) { if (block.Successors.Any<InstructionBlock>()) { interpolatedStringHandler1 = new DefaultInterpolatedStringHandler(19 , 2 ); interpolatedStringHandler1.AppendLiteral("END BLOCK " ); interpolatedStringHandler1.AppendFormatted<int >(block.Index); interpolatedStringHandler1.AppendLiteral(" (succ: " ); interpolatedStringHandler1.AppendFormatted(block.Successors.Select<InstructionBlock, string >((Func<InstructionBlock, string >) (b => b.Index.ToString())).AggregateWithComma((ReadOnlyContext) this ._context)); interpolatedStringHandler1.AppendLiteral(")" ); this .WriteComment(interpolatedStringHandler1.ToStringAndClear()); } else { interpolatedStringHandler1 = new DefaultInterpolatedStringHandler(23 , 1 ); interpolatedStringHandler1.AppendLiteral("END BLOCK " ); interpolatedStringHandler1.AppendFormatted<int >(block.Index); interpolatedStringHandler1.AppendLiteral(" (succ: none)" ); this .WriteComment(interpolatedStringHandler1.ToStringAndClear()); } ((ICodeWriter) this ._writer).WriteLine(); ((ICodeWriter) this ._writer).WriteLine(); } this .ExitNode(node); this ._variableSizedTypeSupport.LeaveBlock(); } } else { if (node.Children.Count == 0 ) throw new NotSupportedException("Unexpected empty node!" ); this .EnterNode(node, instructionBlocks); foreach (Node child in node.Children) { if (child.Type != NodeType.Finally && child.Type != NodeType.Fault) this .GenerateCodeRecursive(child, instructionBlocks); } this .ExitNode(node); } } }
整个代码生成过程没有什么奇淫技巧,相当规范,工整的代码生成过程
实战
ok,流程已经跟完了,接下来可以考虑做下优化了
游戏开发中需要打印很多log来定位问题,即使最后出包关闭了log,但调用log的那行代码依旧会因为字符串拼接或者装箱导致GC,那么我们可以从IL2CPP入手,实现一个无侵入式的log优化功能
思路也很简单,直接一行if-else将log函数包裹进去,就不会有性能消耗了
对比分析
其实没有特别好的切入点,我们只能大概知道是去修改2cpp部分的代码,但是具体怎么做,毫无头绪,所以先来找下区别,以不带if-else和带if-else的代码段进行对比
源代码
1
using UnityEngine;
=
1
using UnityEngine;
2
2
3
namespace IL2CPP_MOD
3
namespace IL2CPP_MOD
4
{
4
{
5
public class HelloWorld : MonoBehaviour
5
public class HelloWorld : MonoBehaviour
6
{
6
{
7
void Start()
7
void Start()
8
{
8
{
9
int a = 1;
9
int a = 1;
10
10
11
//if (Define.HELLO_WORLD_ENABLE_LOG)
<>
11
if (Define.HELLO_WORLD_ENABLE_LOG)
12
{
=
12
{
13
Debug.Log(“Hello World!!!”);
13
Debug.Log(“Hello World!!!”);
14
}
14
}
15
15
16
int b = 2;
16
int b = 2;
17
}
17
}
18
}
18
}
19
}
19
}
IL
1
.method private hidebysig instance void
=
1
.method private hidebysig instance void
2
Start() cil managed
2
Start() cil managed
3
{
3
{
4
.maxstack 1
4
.maxstack 1
5
.locals init (
5
.locals init (
6
[0] int32 a,
6
[0] int32 a,
7
[1] int32 b
<>
7
[1] int32 b,
8
[2] bool V_2
8
)
=
9
)
9
10
10
// [8 9 - 8 10]
11
// [8 9 - 8 10]
11
IL_0000: nop
12
IL_0000: nop
12
13
13
// [9 13 - 9 23]
14
// [9 13 - 9 23]
14
IL_0001: ldc.i4.1
15
IL_0001: ldc.i4.1
15
IL_0002: stloc.0 // a
16
IL_0002: stloc.0 // a
16
17
-+
18
// [11 13 - 11 47]
19
IL_0003: ldsfld bool Define::HELLO_WORLD_ENABLE_LOG
20
IL_0008: stloc.2 // V_2
=
21
-+
22
IL_0009: ldloc.2 // V_2
23
IL_000a: brfalse.s IL_0019
=
24
17
// [12 13 - 12 14]
25
// [12 13 - 12 14]
18
IL_0003: nop
<>
26
IL_000c: nop
19
=
27
20
// [13 17 - 13 57]
28
// [13 17 - 13 57]
21
IL_0004: ldstr “Hello World!!!”
<>
29
IL_000d: ldstr “Hello World!!!”
22
IL_0009: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
30
IL_0012: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
23
IL_000e: nop
31
IL_0017: nop
24
=
32
25
// [14 13 - 14 14]
33
// [14 13 - 14 14]
26
IL_000f: nop
<>
34
IL_0018: nop
27
=
35
28
// [16 13 - 16 23]
36
// [16 13 - 16 23]
29
IL_0010: ldc.i4.2
<>
37
IL_0019: ldc.i4.2
30
IL_0011: stloc.1 // b
38
IL_001a: stloc.1 // b
31
=
39
32
// [17 9 - 17 10]
40
// [17 9 - 17 10]
33
IL_0012: ret
<>
41
IL_001b: ret
34
=
42
35
} // end of method HelloWorld::Start
43
} // end of method HelloWorld::Start
CPP
1
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void HelloWorld_Start_m658AC3EF4765CF19EB7A856416F15806B72D874F (HelloWorld_t79303A76B5694FEE3875C4DE3B3C6840161F0D0E* __this, const RuntimeMethod* method)
=
1
IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void HelloWorld_Start_m658AC3EF4765CF19EB7A856416F15806B72D874F (HelloWorld_t79303A76B5694FEE3875C4DE3B3C6840161F0D0E* __this, const RuntimeMethod* method)
2
{
2
{
3
static bool s_Il2CppMethodInitialized;
3
static bool s_Il2CppMethodInitialized;
4
if (!s_Il2CppMethodInitialized)
4
if (!s_Il2CppMethodInitialized)
5
{
5
{
6
il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var);
6
il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var);
-+
7
il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var);
7
il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250);
=
8
il2cpp_codegen_initialize_runtime_metadata((uintptr_t*)&_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250);
8
s_Il2CppMethodInitialized = true;
9
s_Il2CppMethodInitialized = true;
9
}
10
}
10
int32_t V_0 = 0;
11
int32_t V_0 = 0;
11
int32_t V_1 = 0;
12
int32_t V_1 = 0;
-+
13
bool V_2 = false;
12
{
=
14
{
13
V_0 = 1;
15
V_0 = 1;
-+
16
bool L_0 = ((Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_StaticFields*)il2cpp_codegen_static_fields_for(Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var))->___HELLO_WORLD_ENABLE_LOG;
17
V_2 = L_0;
18
bool L_1 = V_2;
19
if (!L_1)
20
{
21
goto IL_0019;
22
}
23
}
24
{
14
il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var);
=
25
il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var);
15
Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250, NULL);
26
Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250, NULL);
-+
27
}
28
29
IL_0019:
30
{
16
V_1 = 2;
=
31
V_1 = 2;
17
return;
32
return;
18
}
33
}
19
}
34
}
区别
可以看到相比不带if-else的代码,多生成了:
第7行,静态变量元数据初始化
第13行,全局变量的临时存储
第16~22行,执行跳转过程
第29行,定义一个goto block,通过if-else跳转
当然这是在规范化下的2cpp代码,我们既然要自定义,可以简单行事,例如可以直接改成:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void HelloWorld_Start_m658AC3EF4765CF19EB7A856416F15806B72D874F (HelloWorld_t79303A76B5694FEE3875C4DE3B3C6840161F0D0E* __this, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250); s_Il2CppMethodInitialized = true ; } int32_t V_0 = 0 ; int32_t V_1 = 0 ; { if (!(((Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_StaticFields*)il2cpp_codegen_static_fields_for(Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var))->___HELLO_WORLD_ENABLE_LOG)) goto IL_0019; il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250, NULL ); IL_0019: V_1 = 2 ; return ; } }
相当于直接goto到debug后一句,这样相当于改动最少的代码来实现功能,对生成代码的破坏性也最小,so fvcking easily?
锁定目标
直接修改
1 2 3 4 this .PreProcessMethodILBlock(block, resolvedInstruction, resolvedInstruction.Next);this .ProcessInstruction(node, block, resolvedInstruction);this .ProcessInstructionOperand(resolvedInstruction);this .PostProcessMethodILBlock(block, resolvedInstruction, resolvedInstruction.Next);
进行代码处理
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 32 33 34 35 36 37 38 39 40 41 42 43 44 private void PreProcessMethodILBlock (InstructionBlock block, ResolvedInstruction currentInstruction, ResolvedInstruction nextInstruction ) { if (this ._context.Assembly.AssemblyDefinition.FullName == "Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" ) { if (currentInstruction.OpCode == OpCodes.Call && currentInstruction.MethodInfo.FullName == "System.Void UnityEngine.Debug::Log(System.Object)" ) { foreach (var typeInfo in this ._context.Assembly.AssemblyDefinition.GetAllTypes()) { if (typeInfo.FullName == "Define" ) { string jumpTarget = nextInstruction != null ? $"{nextInstruction.Instruction.ToString().Split(':' )[0 ]} ;" : $"{block.Last.ToString().Split(':' )[0 ]} ;" ; string finalString = $"\n if(!(({typeInfo.CppName} _StaticFields*)il2cpp_codegen_static_fields_for({typeInfo.CppName} _il2cpp_TypeInfo_var))->___HELLO_WORLD_ENABLE_LOG)\n\tgoto {jumpTarget} ;\n" ; ((Unity.IL2CPP.CodeWriters.InMemoryGeneratedMethodCodeWriter)this ._writer).Writer.WriteLine(finalString); break ; } } } } } private void PostProcessMethodILBlock (InstructionBlock block, ResolvedInstruction currentInstruction, ResolvedInstruction nextInstruction ) { if (this ._context.Assembly.AssemblyDefinition.FullName == "Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" && currentInstruction.OpCode == OpCodes.Call && currentInstruction.MethodInfo.FullName == "System.Void UnityEngine.Debug::Log(System.Object)" ) { foreach (var typeInfo in this ._context.Assembly.AssemblyDefinition.GetAllTypes()) { if (typeInfo.FullName == "Define" ) { string jumpTarget = nextInstruction != null ? $"{nextInstruction.Instruction.ToString().Split(':' )[0 ]} " : $"{block.Last.ToString().Split(':' )[0 ]} " ; ((Unity.IL2CPP.CodeWriters.InMemoryGeneratedMethodCodeWriter)this ._writer).Writer.WriteLine($"\n{jumpTarget} :\n" ); } } } }
即可实现最简单情况下的debuglog屏蔽
但工程中log使用情形复杂的多
例如:
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 using UnityEngine;namespace IL2CPP_MOD { public class HelloWorld : MonoBehaviour { void Start ( ) { int f = 4 ; string test = $"Hello World!!!!!!!!!!!!!!!" ; string test1 = $"11111111111" ; int s = f + 1 ; Debug.Log(GetSring(test, test1, f)); int d = 4 ; } protected virtual string GetSring (string pa1, string pa2, int test ) { return string .Format("{0}+{1}+{2}" , pa1, pa2, test); } } }
IL代码已经完全变样了:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 .method private hidebysig instance void Start() cil managed { .maxstack 4 .locals init ( [0] int32 f, [1] string test, [2] string test1, [3] int32 s, [4] int32 d ) // [8 9 - 8 10] IL_0000: nop // [9 13 - 9 23] IL_0001: ldc.i4.4 IL_0002: stloc.0 // f // [11 13 - 11 57] IL_0003: ldstr "Hello World!!!!!!!!!!!!!!!" IL_0008: stloc.1 // test // [12 13 - 12 43] IL_0009: ldstr "11111111111" IL_000e: stloc.2 // test1 // [14 13 - 14 27] IL_000f: ldloc.0 // f IL_0010: ldc.i4.1 IL_0011: add IL_0012: stloc.3 // s // [15 13 - 15 68] IL_0013: ldstr "{0}+{1}+{2}" IL_0018: ldloc.2 // test1 IL_0019: ldloc.1 // test IL_001a: ldloc.1 // test IL_001b: call string [mscorlib]System.String::Format(string, object, object, object) IL_0020: stloc.1 // test // [17 13 - 17 49] IL_0021: ldarg.0 // this IL_0022: ldloc.1 // test IL_0023: ldloc.2 // test1 IL_0024: ldloc.0 // f IL_0025: callvirt instance string IL2CPP_MOD.HelloWorld::GetSring(string, string, int32) IL_002a: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object) IL_002f: nop // [19 13 - 19 23] IL_0030: ldc.i4.4 IL_0031: stloc.s d // [20 9 - 20 10] IL_0033: ret } // end of method HelloWorld::Start
至少我们上面的代码已经适配不了了,结果会是这样:
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 IL2CPP_EXTERN_C IL2CPP_METHOD_ATTR void HelloWorld_Start_m658AC3EF4765CF19EB7A856416F15806B72D874F (HelloWorld_t79303A76B5694FEE3875C4DE3B3C6840161F0D0E* __this, const RuntimeMethod* method) { static bool s_Il2CppMethodInitialized; if (!s_Il2CppMethodInitialized) { il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&_stringLiteral3376120BEC0195FC1FC00A86D98A84447C2F7BFA); il2cpp_codegen_initialize_runtime_metadata((uintptr_t *)&_stringLiteral831D44388900A57E6C18D677DBBC4F24E33BB90D); s_Il2CppMethodInitialized = true ; } int32_t V_0 = 0 ; String_t* V_1 = NULL ; String_t* V_2 = NULL ; int32_t V_3 = 0 ; int32_t V_4 = 0 ; { V_0 = 4 ; V_1 = _stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250; V_2 = _stringLiteral831D44388900A57E6C18D677DBBC4F24E33BB90D; int32_t L_0 = V_0; V_3 = ((int32_t )il2cpp_codegen_add(L_0, 1 )); String_t* L_1 = V_2; String_t* L_2 = V_1; String_t* L_3 = V_1; String_t* L_4; L_4 = String_Format_mA0534D6E2AE4D67A6BD8D45B3321323930EB930C(_stringLiteral3376120BEC0195FC1FC00A86D98A84447C2F7BFA, L_1, L_2, L_3, NULL ); V_1 = L_4; String_t* L_5 = V_1; String_t* L_6 = V_2; int32_t L_7 = V_0; String_t* L_8; L_8 = VirtualFuncInvoker3< String_t*, String_t*, String_t*, int32_t >::Invoke(4 , __this, L_5, L_6, L_7); if (!((Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_StaticFields*)il2cpp_codegen_static_fields_for(Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var))->___HELLO_WORLD_ENABLE_LOG) goto IL_002f;; il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(L_8, NULL ); IL_002f: V_4 = 4 ; return ; } }
所以要完美处理所有情况,应该还要加一个对整个方法的分析,分析log有多少个参数,然后提前几行来进行if判断,即可满足绝大部分情况
1 2 3 4 5 6 7 8 9 AnalysisMethodIL ........... this .PreProcessMethodILBlock(block, resolvedInstruction, resolvedInstruction.Next);this .ProcessInstruction(node, block, resolvedInstruction);this .ProcessInstructionOperand(resolvedInstruction);this .PostProcessMethodILBlock(block, resolvedInstruction, resolvedInstruction.Next);
需要注意的是,对于静态类引用,il2cpp会给每个类型生成一份struct,所以需要处理include
1 AddIncludeForTypeDefinition
当然了,如果在log中的参数存在三目运算符的情况,依旧没办法完全消除构造的开销,但考虑到这种情况在实际项目中占比较少,而且要处理的话非常麻烦(需要自己模拟整个函数的变量入栈,出栈情况,才能准确判断出哪一行是if的起始行)
性能
后记
因为整个il2cpp过程业务代码居多,所以本文也只是提供一个流程概览,如果有修改功能的需要,则直接定位到相关pipeline的item进行研究和修改即可
在这里记录下一些比较重要的函数
获取所有元数据
1 this ._runtimeMetadataAccess._typeResolver._typeContext._assemblyTable
其中包含了此次il2cpp过程所有经过依赖排序的dll以及其内部的类型,字段,方法的元数据信息
获取类型的元数据
1 2 3 4 5 6 this ._runtimeMetadataAccess.StaticData(unresolvedType) eg: UnityEngine.Debug => "Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var"
获取生成的 MethodBody 代码
生成代码string可以从MethodSourceWriter.InMemoryGeneratedMethodCodeWrite.Writer._charBuffer获取
1 2 3 4 5 6 7 8 9 10 string .Join("" ,((Unity.IL2CPP.CodeWriters.InMemoryGeneratedMethodCodeWriter) this ._writer).Writer._charBuffer) => int32_t V_0 = 0 ; int32_t V_1 = 0 ; { V_0 = 1 ; il2cpp_codegen_runtime_class_init_inline(Debug_t8394C7EEAECA3689C2C9B9DE9C7166D73596276F_il2cpp_TypeInfo_var); Debug_Log_m87A9A3C761FF5C43ED8A53B16190A53D08F818BB(_stringLiteral1FBCB67F68388201F99265374006ADAFF5A10250, NULL);
静态变量的代码写入
1 private void StaticFieldAccess (ResolvedInstruction ins )
获取字段命名
1 2 3 ResolvedTypeExtensions.ForField(this INamingService namingService, ResolvedFieldInfo fieldInfo) => ___HELLO_WORLD_ENABLE_LOG
静态变量表达式
1 2 3 TypeStaticsExpressionFor => ((Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_StaticFields*)il2cpp_codegen_static_fields_for(Define_t91FF450AE0B20185FEED361C7DF1CD32B3C12793_il2cpp_TypeInfo_var))
TypeDefinition和TypeReference
一般来说TypeDefinition可以显式强转成TypeReference
参考
IL2CPPDumper
dnspy
process-explorer