commit 71a67908bd9437b05701c556bddf802d6265594e Author: particle-box Date: Thu Jun 18 00:22:45 2026 +0530 Improve LSPatch embedded module loading diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro index 844704e3..8495f569 100644 --- a/core/proguard-rules.pro +++ b/core/proguard-rules.pro @@ -7,6 +7,8 @@ -keep class org.lsposed.lspd.impl.LSPosedBridge$NativeHooker {*;} -keep class org.lsposed.lspd.impl.LSPosedBridge$HookerCallback {*;} -keep class org.lsposed.lspd.util.Hookers {*;} +-keep class org.lsposed.lspd.util.LspModuleClassLoader {*;} +-keep class hidden.ByteBufferDexClassLoader {*;} -keepnames class org.lsposed.lspd.impl.LSPosedHelper { public ; diff --git a/core/src/main/java/de/robv/android/xposed/XposedInit.java b/core/src/main/java/de/robv/android/xposed/XposedInit.java index 97436936..773b418f 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java @@ -258,11 +258,18 @@ public final class XposedInit { private static boolean initModule(ClassLoader mcl, String apk, List moduleClassNames) { var count = 0; + Log.i(TAG, "XposedInit: initModule start apk=" + apk + + " moduleClassNames=" + moduleClassNames + + " moduleClassLoader=" + mcl + + " initLoader=" + XposedInit.class.getClassLoader()); for (var moduleClassName : moduleClassNames) { try { Log.i(TAG, " Loading class " + moduleClassName); Class moduleClass = mcl.loadClass(moduleClassName); + Log.i(TAG, "XposedInit: loaded module class " + moduleClassName + + " classLoader=" + moduleClass.getClassLoader() + + " interfaces=" + java.util.Arrays.toString(moduleClass.getInterfaces())); if (!IXposedMod.class.isAssignableFrom(moduleClass)) { Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it"); @@ -270,29 +277,45 @@ public final class XposedInit { } final Object moduleInstance = moduleClass.newInstance(); + Log.i(TAG, "XposedInit: created module instance " + moduleClassName + + " instanceClass=" + moduleInstance.getClass() + + " zygote=" + (moduleInstance instanceof IXposedHookZygoteInit) + + " loadPackage=" + (moduleInstance instanceof IXposedHookLoadPackage) + + " initResources=" + (moduleInstance instanceof IXposedHookInitPackageResources)); if (moduleInstance instanceof IXposedHookZygoteInit) { IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); param.modulePath = apk; param.startsSystemServer = startsSystemServer; + Log.i(TAG, "XposedInit: calling initZygote for " + moduleClassName + + " modulePath=" + param.modulePath + + " startsSystemServer=" + param.startsSystemServer); ((IXposedHookZygoteInit) moduleInstance).initZygote(param); count++; + Log.i(TAG, "XposedInit: initZygote finished for " + moduleClassName); } if (moduleInstance instanceof IXposedHookLoadPackage) { + Log.i(TAG, "XposedInit: registering loadPackage hook for " + moduleClassName + + " callbacksBefore=" + XposedBridge.sLoadedPackageCallbacks.size()); XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); count++; + Log.i(TAG, "XposedInit: registered loadPackage hook for " + moduleClassName + + " callbacksAfter=" + XposedBridge.sLoadedPackageCallbacks.size()); } if (moduleInstance instanceof IXposedHookInitPackageResources) { + Log.i(TAG, "XposedInit: registering initPackageResources hook for " + moduleClassName); hookResources(); XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); count++; + Log.i(TAG, "XposedInit: registered initPackageResources hook for " + moduleClassName); } } catch (Throwable t) { Log.e(TAG, " Failed to load class " + moduleClassName, t); } } + Log.i(TAG, "XposedInit: initModule done apk=" + apk + " registeredCount=" + count); return count > 0; } @@ -302,6 +325,12 @@ public final class XposedInit { */ private static boolean loadModule(String name, String apk, PreLoadedApk file) { Log.i(TAG, "Loading legacy module " + name + " from " + apk); + Log.i(TAG, "XposedInit: loadModule details name=" + name + + " apk=" + apk + + " preLoadedDexes=" + (file == null ? null : file.preLoadedDexes) + + " dexCount=" + (file == null || file.preLoadedDexes == null ? -1 : file.preLoadedDexes.size()) + + " moduleClassNames=" + (file == null ? null : file.moduleClassNames) + + " moduleLibraryNames=" + (file == null ? null : file.moduleLibraryNames)); var sb = new StringBuilder(); var abis = Process.is64Bit() ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS; @@ -309,22 +338,33 @@ public final class XposedInit { sb.append(apk).append("!/lib/").append(abi).append(File.pathSeparator); } var librarySearchPath = sb.toString(); + Log.i(TAG, "XposedInit: loadModule abi64=" + Process.is64Bit() + + " abis=" + java.util.Arrays.toString(abis) + + " librarySearchPath=" + librarySearchPath); var initLoader = XposedInit.class.getClassLoader(); + Log.i(TAG, "XposedInit: creating LspModuleClassLoader parent=" + initLoader); var mcl = LspModuleClassLoader.loadApk(apk, file.preLoadedDexes, librarySearchPath, initLoader); + Log.i(TAG, "XposedInit: created module classloader=" + mcl); try { - if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) { + var bridgeClass = mcl.loadClass(XposedBridge.class.getName()); + Log.i(TAG, "XposedInit: XposedBridge resolved through module loader classLoader=" + + bridgeClass.getClassLoader() + " expected=" + initLoader); + if (bridgeClass.getClassLoader() != initLoader) { Log.e(TAG, " Cannot load module: " + name); Log.e(TAG, " The Xposed API classes are compiled into the module's APK."); Log.e(TAG, " This may cause strange issues and must be fixed by the module developer."); Log.e(TAG, " For details, see: https://api.xposed.info/using.html"); return false; } - } catch (ClassNotFoundException ignored) { + } catch (ClassNotFoundException e) { + Log.e(TAG, "XposedInit: XposedBridge class check failed for module " + name, e); return false; } + Log.i(TAG, "XposedInit: initNativeModule start libraries=" + file.moduleLibraryNames); initNativeModule(file.moduleLibraryNames); + Log.i(TAG, "XposedInit: initNativeModule done"); return initModule(mcl, apk, file.moduleClassNames); } diff --git a/core/src/main/java/org/lsposed/lspd/core/Startup.java b/core/src/main/java/org/lsposed/lspd/core/Startup.java index e4d08b96..b5e19c56 100644 --- a/core/src/main/java/org/lsposed/lspd/core/Startup.java +++ b/core/src/main/java/org/lsposed/lspd/core/Startup.java @@ -66,8 +66,13 @@ public class Startup { public static void bootstrapXposed() { // Initialize the Xposed framework try { + Utils.logD("Startup.bootstrapXposed starts: startsSystemServer = " + XposedInit.startsSystemServer + + ", processName = " + LSPosedContext.processName + + ", appDir = " + LSPosedContext.appDir); startBootstrapHook(XposedInit.startsSystemServer); + Utils.logD("Startup.bootstrapXposed: bootstrap hooks installed"); XposedInit.loadLegacyModules(); + Utils.logD("Startup.bootstrapXposed: legacy modules loaded"); } catch (Throwable t) { Utils.logE("error during Xposed initialization", t); } @@ -75,12 +80,25 @@ public class Startup { public static void initXposed(boolean isSystem, String processName, String appDir, ILSPApplicationService service) { // init logger + Utils.logD("Startup.initXposed starts: isSystem = " + isSystem + + ", processName = " + processName + + ", appDir = " + appDir + + ", service = " + service); + Utils.logD("Startup.initXposed: ApplicationServiceClient.Init start"); ApplicationServiceClient.Init(service, processName); + Utils.logD("Startup.initXposed: ApplicationServiceClient.Init done"); + Utils.logD("Startup.initXposed: XposedBridge.initXResources start"); XposedBridge.initXResources(); + Utils.logD("Startup.initXposed: XposedBridge.initXResources done"); XposedInit.startsSystemServer = isSystem; LSPosedContext.isSystemServer = isSystem; LSPosedContext.appDir = appDir; LSPosedContext.processName = processName; + Utils.logD("Startup.initXposed: context fields set isSystemServer = " + LSPosedContext.isSystemServer + + ", processName = " + LSPosedContext.processName + + ", appDir = " + LSPosedContext.appDir); + Utils.logD("Startup.initXposed: PrebuiltMethodsDeopter.deoptBootMethods start"); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote + Utils.logD("Startup.initXposed: PrebuiltMethodsDeopter.deoptBootMethods done"); } } diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java b/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java index 1b6b0875..5b1f4eb9 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/ResourcesHook.java @@ -21,7 +21,6 @@ package org.lsposed.lspd.nativebridge; import android.content.res.Resources; -import android.content.res.XResources; import dalvik.annotation.optimization.FastNative; @@ -34,5 +33,5 @@ public class ResourcesHook { public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass); @FastNative - public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); + public static native void rewriteXmlReferencesNative(long parserPtr, Resources origRes, Resources repRes); } diff --git a/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java b/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java index 56f60b7e..d75bf8fd 100644 --- a/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java +++ b/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java @@ -68,25 +68,71 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { nativeLibraryDirs.addAll(systemNativeLibraryDirs); } + private static boolean shouldTraceClass(String name) { + return name.startsWith("de.robv.android.xposed.") + || name.startsWith("org.lsposed.") + || name.startsWith("me.eternal.") + || name.startsWith("com.snapchat.") + || name.equals("android.content.res.XResources") + || name.equals("xposed.dummy.XResourcesSuperClass") + || name.contains("XResources") + || name.contains("Purrfect"); + } + @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + var trace = shouldTraceClass(name); + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass start name=" + name + + " resolve=" + resolve + + " loader=" + this + + " parent=" + getParent()); + } var cl = findLoadedClass(name); if (cl != null) { + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass hit loaded name=" + name + + " classLoader=" + cl.getClassLoader()); + } return cl; } try { - return Object.class.getClassLoader().loadClass(name); - } catch (ClassNotFoundException ignored) { + cl = Object.class.getClassLoader().loadClass(name); + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass boot success name=" + name + + " classLoader=" + cl.getClassLoader()); + } + return cl; + } catch (ClassNotFoundException | NoClassDefFoundError e) { + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass boot miss name=" + name + " error=" + e); + } } ClassNotFoundException fromSuper; try { - return findClass(name); + cl = findClass(name); + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass findClass success name=" + name + + " classLoader=" + cl.getClassLoader()); + } + return cl; } catch (ClassNotFoundException ex) { fromSuper = ex; + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass findClass miss name=" + name + " error=" + ex); + } } try { - return getParent().loadClass(name); + cl = getParent().loadClass(name); + if (trace) { + Log.i(TAG, "LspModuleClassLoader: loadClass parent success name=" + name + + " classLoader=" + cl.getClassLoader()); + } + return cl; } catch (ClassNotFoundException cnfe) { + if (trace) { + Log.e(TAG, "LspModuleClassLoader: loadClass failed name=" + name, fromSuper); + } throw fromSuper; } } @@ -94,6 +140,9 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { @Override public String findLibrary(String libraryName) { var fileName = System.mapLibraryName(libraryName); + Log.i(TAG, "LspModuleClassLoader: findLibrary start libraryName=" + libraryName + + " fileName=" + fileName + + " dirs=" + nativeLibraryDirs); for (var file : nativeLibraryDirs) { var path = file.getPath(); if (path.contains(zipSeparator)) { @@ -102,8 +151,13 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { var entryName = split[1] + '/' + fileName; var entry = jarFile.getEntry(entryName); if (entry != null && entry.getMethod() == ZipEntry.STORED) { + Log.i(TAG, "LspModuleClassLoader: findLibrary found stored zip entry " + entryName + + " in " + split[0]); return split[0] + zipSeparator + entryName; } + Log.i(TAG, "LspModuleClassLoader: findLibrary zip miss entry=" + entryName + + " jar=" + split[0] + + " entry=" + entry); } catch (IOException e) { Log.e(TAG, "Can not open " + split[0], e); } @@ -112,11 +166,13 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { try { var fd = Os.open(entryPath, OsConstants.O_RDONLY, 0); Os.close(fd); + Log.i(TAG, "LspModuleClassLoader: findLibrary found file " + entryPath); return entryPath; } catch (ErrnoException ignored) { } } } + Log.i(TAG, "LspModuleClassLoader: findLibrary miss libraryName=" + libraryName); return null; } @@ -134,6 +190,9 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { @Override protected URL findResource(String name) { + if (name.startsWith("assets/") || name.contains("xposed") || name.contains("lspatch")) { + Log.i(TAG, "LspModuleClassLoader: findResource start name=" + name + " apk=" + apk); + } try { var urlHandler = new ClassPathURLStreamHandler(apk); var url = urlHandler.getEntryUrlOrNull(name); @@ -141,8 +200,14 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { // noinspection FinalizeCalledExplicitly urlHandler.finalize(); } + if (name.startsWith("assets/") || name.contains("xposed") || name.contains("lspatch")) { + Log.i(TAG, "LspModuleClassLoader: findResource result name=" + name + " url=" + url); + } return url; } catch (IOException e) { + if (name.startsWith("assets/") || name.contains("xposed") || name.contains("lspatch")) { + Log.e(TAG, "LspModuleClassLoader: findResource failed name=" + name, e); + } return null; } } @@ -185,14 +250,24 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { List dexes, String librarySearchPath, ClassLoader parent) { + Log.i(TAG, "LspModuleClassLoader: loadApk start apk=" + apk + + " dexCount=" + (dexes == null ? -1 : dexes.size()) + + " librarySearchPath=" + librarySearchPath + + " parent=" + parent + + " sdk=" + Build.VERSION.SDK_INT); var dexBuffers = dexes.stream().parallel().map(dex -> { try { - return dex.mapReadOnly(); + var buffer = dex.mapReadOnly(); + Log.i(TAG, "LspModuleClassLoader: mapped dex sharedMemory=" + dex + + " bufferCapacity=" + buffer.capacity() + + " remaining=" + buffer.remaining()); + return buffer; } catch (ErrnoException e) { Log.w(TAG, "Can not map " + dex, e); return null; } }).filter(Objects::nonNull).toArray(ByteBuffer[]::new); + Log.i(TAG, "LspModuleClassLoader: loadApk mapped dexBuffers=" + dexBuffers.length); LspModuleClassLoader cl; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { cl = new LspModuleClassLoader(dexBuffers, librarySearchPath, parent, apk); @@ -200,8 +275,11 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { cl = new LspModuleClassLoader(dexBuffers, parent, apk); cl.initNativeLibraryDirs(librarySearchPath); } + Log.i(TAG, "LspModuleClassLoader: loadApk created classLoader=" + cl + + " nativeLibraryDirs=" + cl.nativeLibraryDirs); Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap); dexes.stream().parallel().forEach(SharedMemory::close); + Log.i(TAG, "LspModuleClassLoader: loadApk unmapped buffers and closed shared memory"); return cl; } } diff --git a/core/src/main/jni/src/jni/resources_hook.cpp b/core/src/main/jni/src/jni/resources_hook.cpp index 18d8246d..baf789c1 100644 --- a/core/src/main/jni/src/jni/resources_hook.cpp +++ b/core/src/main/jni/src/jni/resources_hook.cpp @@ -138,7 +138,7 @@ namespace lspd { auto dex_buffer = env->NewDirectByteBuffer(const_cast(image.ptr()), image.size()); return JNI_NewObject(env, in_memory_classloader, initMid, - dex_buffer, parent).release(); + dex_buffer, parent).release(); } LSP_DEF_NATIVE_METHOD(void, ResourcesHook, rewriteXmlReferencesNative, @@ -215,16 +215,13 @@ namespace lspd { static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(ResourcesHook, initXResourcesNative, "()Z"), LSP_NATIVE_METHOD(ResourcesHook, makeInheritable,"(Ljava/lang/Class;)Z"), - LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader, - "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/ClassLoader;"), - LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative, - "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V") + LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader, + "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/ClassLoader;"), + LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative, + "(JLandroid/content/res/Resources;Landroid/content/res/Resources;)V") }; void RegisterResourcesHook(JNIEnv *env) { - auto sign = fmt::format("(JL{};Landroid/content/res/Resources;)V", GetXResourcesClassName()); - gMethods[3].signature = sign.c_str(); - REGISTER_LSP_NATIVE_METHODS(ResourcesHook); } } diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java index 1f4d08e0..ae8e07fe 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/ConfigFileManager.java @@ -344,7 +344,13 @@ public class ConfigFileManager { private static SharedMemory readDex(InputStream in, boolean obfuscate) throws IOException, ErrnoException { var memory = SharedMemory.create(null, in.available()); var byteBuffer = memory.mapReadWrite(); - Channels.newChannel(in).read(byteBuffer); + try (var channel = Channels.newChannel(in)) { + while (byteBuffer.hasRemaining()) { + if (channel.read(byteBuffer) < 0) { + throw new IOException("Unexpected end of dex stream"); + } + } + } SharedMemory.unmap(byteBuffer); if (obfuscate) { var newMemory = ObfuscationManager.obfuscateDex(memory);