BepInEx / Il2CppInterop

A tool interoperate between CoreCLR and Il2Cpp at runtime
GNU Lesser General Public License v3.0
185 stars 59 forks source link

New function metadata init cause runtime crash with XrefScan #107

Open HookedBehemoth opened 11 months ago

HookedBehemoth commented 11 months ago

In the past, il2cpp would emit a single call to il2cpp_codegen_initialize_method with a method token. Now there are multiple calls to il2cpp_codegen_initialize_runtime_metadata with potentially multiple tokens. When using XrefScan on a method before the runtime, values can be left uninitialized, resulting in a segfault/AccessViolation.

Didn't bisect when exactly codegen was altered but I can observe this behavior in 2022.3.5

I couldn't find any conversation on this issue so far. I'll work on a patch myself if you don't have something waiting for it already. This would probably change the Cache-Attribute parameter "public long MetadataInitTokenRva;" to public long[] MetadataInitTokenRvas;. I don't expect this to cause any major breakage on any prior version so I don't think I'll actually have to bisect the version and nail this logic down.

Steps for reproduction

- Project on ~2022.3.5 - Have class like ```cs using UnityEngine; public class Test : MonoBehaviour { void Update() { Debug.Log("test"); Debug.Log("This is a test with a long string"); } } ``` - Compile with il2cpp - Install mod framework using il2cppinterop - Invoke xref on Test::Update before the game does ```cs var method = typeof(Il2Cpp.Test) .GetMethod("Update", Public | Instance); foreach (var str in XrefScanner .XrefScan(method) .Where(scan => scan.Type == XrefType.Global) .Select(scan => scan.ReadAsObject().ToString())) { LoggerInstance.Msg(str); }; ``` - Observe crash - Observe that it doesn't crash if you add this snippet before the xref ```cs var test = new Il2Cpp.Test(); test.Update(); ```
HookedBehemoth commented 11 months ago

Ok I missed that those aren't tokens but object pointers.

HookedBehemoth commented 11 months ago

Ok this is even more annoying than I had anticipated.

  1. I couldn't get Cecil to store the arrays. I'll look into that but for now I'm just testing without caching.
  2. I found a function that has multiple initializer blocks sprinkled in. I couldn't find such code in the generated C++ files but it might be inlined by the compiler... Not entirely sure how I'd want to deal with that just yet. I could just call the metadata init function before dereferencing any of the found globals but that's messy, potentially slow and I'm not sure if those are always immutable or if a game could alter those.