前言

环境:

  • 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
    • il2cpp.dll
    • il2cpp.pdb

OK,万事以备,开始分析

(先贴一下整个流程图

231124124124

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后端:

1
2
// 000001E8 - 000001EB 00000000 = .NET.VirtualAddress
// 000001EC - 000001EF 00000000 = .NET.Size

正常来说C#编译的dll这两个都是有值的

1
2
// 00000168 - 0000016B 00002008 = .NET.VirtualAddress
// 0000016C - 0000016F 00000048 = .NET.Size

所以只能放进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; // [rsp+90h] [rbp-70h] BYREF

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");

===========================================================
// 透传il2cpp.exe的启动参数,正式调用il2cpp.dll的逻辑
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
{ // XREF: ?exe_start@@YAHHQEAPEB_W@Z/r
HINSTANCE__ *m_hostfxr_dll; // XREF: exe_start(int,wchar_t const * * const)+392/w
// exe_start(int,wchar_t const * * const)+463/r ...
std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> > m_host_path;
// XREF: exe_start(int,wchar_t const * * const)+397/w
// exe_start(int,wchar_t const * * const)+39B/w ...
std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> > m_dotnet_root;
// XREF: exe_start(int,wchar_t const * * const)+3A8/w
// exe_start(int,wchar_t const * * const)+3AC/w ...
std::basic_string<wchar_t,std::char_traits<wchar_t>,std::allocator<wchar_t> > m_fxr_path;
// XREF: exe_start(int,wchar_t const * * const)+3BD/w
// exe_start(int,wchar_t const * * const)+3C1/w ...
bool m_requires_startupinfo_iface; // XREF: exe_start(int,wchar_t const * * const)+3D2/w
// padding byte
// padding byte
// padding byte
StatusCode m_status_code; // XREF: exe_start(int,wchar_t const * * const)+406/w
// exe_start(int,wchar_t const * * const):loc_140012031/w
};

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
{
// Token: 0x02000065 RID: 101
public static class SourceWriter
{
// Token: 0x060003D1 RID: 977 RVA: 0x00022F1C File Offset: 0x0002111C
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);
}
}
}

// 这里就是处理Method的地方
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));
}
// 收集IL Blocks,将方法中的IL分块,例如1~3为一Chunk,4~8为一Chunk
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);
// FlowTree.Children为语法树中的内容,可以理解为上面的instructionBlocks
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());
// 处理IL,里面是一个1k行的switch case
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);

// 可以看到完全没有消除掉string构造的开销
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
// 提前分析方法中所有IL,以便在合适的IL处进行if-else的插入
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的起始行)

性能

图表 4

图表 1

后记

因为整个il2cpp过程业务代码居多,所以本文也只是提供一个流程概览,如果有修改功能的需要,则直接定位到相关pipeline的item进行研究和修改即可

在这里记录下一些比较重要的函数

获取所有元数据

1
this._runtimeMetadataAccess._typeResolver._typeContext._assemblyTable

其中包含了此次il2cpp过程所有经过依赖排序的dll以及其内部的类型,字段,方法的元数据信息

获取类型的元数据

1
2
3
4
5
6
// 传入一个TypeReference,返回其元数据类型
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