From 61b087e2312fcf3fbc5ab450082d6a4c9f8d768c Mon Sep 17 00:00:00 2001 From: Mingluan Mu Date: Tue, 25 Aug 2026 21:19:50 +0800 Subject: [PATCH] Fix early LoadedApk class loader initialization --- .../lspatch/loader/LSPApplication.java | 24 ++++++++++++++- .../src/main/jni/src/patch_loader.cpp | 30 ++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index 06d0b13d6..2498ddff2 100644 --- a/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/patch-loader/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.ref.Reference; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; @@ -283,7 +284,28 @@ private static Context createLoadedApkWithContext() { stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info"); var appInfo = (ApplicationInfo) XposedHelpers.getObjectField(mBoundApplication, "appInfo"); var compatInfo = (CompatibilityInfo) XposedHelpers.getObjectField(mBoundApplication, "compatInfo"); - var baseClassLoader = stubLoadedApk.getClassLoader(); + var stubFactory = Class.forName( + "org.lsposed.lspatch.metaloader.LSPAppComponentFactoryStub", + false, + LSPApplication.class.getClassLoader()); + var baseClassLoader = stubFactory.getClassLoader(); + + // Some Android variants invoke LoadedApk.getClassLoader() from a hot-patch hook before + // handleBindApplication publishes AppBindData.info. The LoadedApk being initialized has + // already been registered in ActivityThread.mPackages, though, and the factory class was + // defined by its loader. Recover that exact object rather than dereferencing a transient + // null or recursively calling getClassLoader() while it is still creating the factory. + if (stubLoadedApk == null) { + var mPackages = (Map) XposedHelpers.getObjectField(activityThread, "mPackages"); + var holder = mPackages.get(appInfo.packageName); + var candidate = holder instanceof Reference reference ? reference.get() : holder; + if (!(candidate instanceof LoadedApk)) { + throw new IllegalStateException( + "Stub LoadedApk is not available for " + appInfo.packageName); + } + stubLoadedApk = (LoadedApk) candidate; + Log.i(TAG, "Recovered stub LoadedApk during early class-loader initialization"); + } try (var is = baseClassLoader.getResourceAsStream(CONFIG_ASSET_PATH)) { BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); diff --git a/patch-loader/src/main/jni/src/patch_loader.cpp b/patch-loader/src/main/jni/src/patch_loader.cpp index 39964120e..e1abc7079 100644 --- a/patch-loader/src/main/jni/src/patch_loader.cpp +++ b/patch-loader/src/main/jni/src/patch_loader.cpp @@ -38,25 +38,19 @@ using namespace vector::native; namespace lspd { void PatchLoader::LoadDex(JNIEnv* env, Context::PreloadedDex&& dex) { - auto class_activity_thread = JNI_FindClass(env, "android/app/ActivityThread"); - auto class_activity_thread_app_bind_data = - JNI_FindClass(env, "android/app/ActivityThread$AppBindData"); - auto class_loaded_apk = JNI_FindClass(env, "android/app/LoadedApk"); - - auto mid_current_activity_thread = JNI_GetStaticMethodID( - env, class_activity_thread, "currentActivityThread", "()Landroid/app/ActivityThread;"); + auto class_class = JNI_FindClass(env, "java/lang/Class"); + auto stub = + JNI_FindClass(env, "org/lsposed/lspatch/metaloader/LSPAppComponentFactoryStub"); auto mid_get_classloader = - JNI_GetMethodID(env, class_loaded_apk, "getClassLoader", "()Ljava/lang/ClassLoader;"); - auto fid_m_bound_application = JNI_GetFieldID(env, class_activity_thread, "mBoundApplication", - "Landroid/app/ActivityThread$AppBindData;"); - auto fid_info = - JNI_GetFieldID(env, class_activity_thread_app_bind_data, "info", "Landroid/app/LoadedApk;"); - - auto activity_thread = - JNI_CallStaticObjectMethod(env, class_activity_thread, mid_current_activity_thread); - auto m_bound_application = JNI_GetObjectField(env, activity_thread, fid_m_bound_application); - auto info = JNI_GetObjectField(env, m_bound_application, fid_info); - auto stub_classloader = JNI_CallObjectMethod(env, info, mid_get_classloader); + JNI_GetMethodID(env, class_class, "getClassLoader", "()Ljava/lang/ClassLoader;"); + + // Loading the AppComponentFactory can be re-entrant. In particular, some Android variants call + // LoadedApk.getClassLoader() from an early hot-patch hook before ActivityThread has published + // mBoundApplication.info. Reading the stub LoadedApk through ActivityThread at that point used a + // null receiver in CallObjectMethod and CheckJNI aborted the process. JNI_OnLoad is invoked from + // the already-defined factory class, so its defining loader is both available here and the exact + // parent the in-memory framework loader needs. + auto stub_classloader = JNI_CallObjectMethod(env, stub, mid_get_classloader); if (!stub_classloader) [[unlikely]] { LOGE("getStubClassLoader failed!!!");