From 607bbedec43d050ca76175070021e80f28bc9ee8 Mon Sep 17 00:00:00 2001 From: vvb2060 Date: Fri, 6 Aug 2021 21:40:41 +0800 Subject: [PATCH] [core] pre load module dexes (#853) * [core] pre load module dexes * 1 * 2 * 4 --- .../org/lsposed/lspd/models/ModuleConfig.aidl | 1 + .../de/robv/android/xposed/XposedInit.java | 8 ++- .../lsposed/lspd/service/ConfigManager.java | 62 +++++++++++++++++-- .../lspd/util/LspModuleClassLoader.java | 42 +++++-------- 4 files changed, 80 insertions(+), 33 deletions(-) diff --git a/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl b/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl index 0d663e09..cce31280 100644 --- a/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl +++ b/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl @@ -1,4 +1,5 @@ package org.lsposed.lspd.models; parcelable ModuleConfig { + SharedMemory[] preLoadedDexes; } 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 d32e7fba..ba5b1001 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java @@ -41,6 +41,7 @@ import android.content.res.XResources; import android.os.Build; import android.os.IBinder; import android.os.Process; +import android.os.SharedMemory; import android.util.ArraySet; import android.util.Log; @@ -224,11 +225,12 @@ public final class XposedInit { moduleList.forEach(module -> { var apk = module.apk; var name = module.name; + var dexes = module.config.preLoadedDexes; if (loadedModules.contains(apk)) { newLoadedApk.add(apk); } else { loadedModules.add(apk); // temporarily add it for XSharedPreference - boolean loadSuccess = loadModule(name, apk); + boolean loadSuccess = loadModule(name, apk, dexes); if (loadSuccess) { newLoadedApk.add(apk); } @@ -361,7 +363,7 @@ public final class XposedInit { * in assets/xposed_init. */ @SuppressLint("PrivateApi") - private static boolean loadModule(String name, String apk) { + private static boolean loadModule(String name, String apk, SharedMemory[] dexes) { Log.i(TAG, "Loading module " + name + " from " + apk); if (!new File(apk).exists()) { @@ -375,7 +377,7 @@ public final class XposedInit { librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator); } ClassLoader initLoader = XposedInit.class.getClassLoader(); - ClassLoader mcl = LspModuleClassLoader.loadApk(new File(apk), librarySearchPath.toString(), initLoader); + ClassLoader mcl = LspModuleClassLoader.loadApk(new File(apk), dexes, librarySearchPath.toString(), initLoader); try { if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) { diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java index aa3d13dc..a5f0a09b 100644 --- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java +++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java @@ -36,6 +36,7 @@ import android.os.SharedMemory; import android.os.SystemClock; import android.system.ErrnoException; import android.system.Os; +import android.system.OsConstants; import android.util.Log; import android.util.Pair; @@ -46,6 +47,7 @@ import org.apache.commons.lang3.SerializationUtils; import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.Module; +import org.lsposed.lspd.models.ModuleConfig; import java.io.File; import java.io.FileNotFoundException; @@ -53,6 +55,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; +import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Files; @@ -141,8 +144,6 @@ public class ConfigManager { private final Handler cacheHandler; - private final Map moduleDexes = new ConcurrentHashMap<>(); - private long lastModuleCacheTime = 0; private long requestModuleCacheTime = 0; @@ -199,8 +200,13 @@ public class ConfigManager { private final Map> cachedScope = new ConcurrentHashMap<>(); + // apkPath, dexes + private final Map cachedDexes = new ConcurrentHashMap<>(); + + // appId, packageName private final Map cachedModule = new ConcurrentHashMap<>(); + // packageName, userId, group, key, value private final Map, Map>> cachedConfig = new ConcurrentHashMap<>(); private void updateCaches(boolean sync) { @@ -251,9 +257,12 @@ public class ConfigManager { int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); while (cursor.moveToNext()) { var module = new Module(); + var config = new ModuleConfig(); + var path = cursor.getString(apkPathIdx); + config.preLoadedDexes = getModuleDexes(path); module.name = cursor.getString(pkgNameIdx); - module.apk = cursor.getString(apkPathIdx); - module.config = null; + module.apk = path; + module.config = config; modules.add(module); } } @@ -433,6 +442,7 @@ public class ConfigManager { } int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); int userIdIdx = cursor.getColumnIndex("user_id"); + // packageName, userId, packageInfo Map> modules = new HashMap<>(); Set obsoleteModules = new HashSet<>(); Set obsoleteScopes = new HashSet<>(); @@ -466,6 +476,7 @@ public class ConfigManager { for (var obsoleteScope : obsoleteScopes) { removeModuleScopeWithoutCache(obsoleteScope); } + cleanModuleDexes(); } Log.d(TAG, "cached modules"); for (int uid : cachedModule.keySet()) { @@ -506,9 +517,11 @@ public class ConfigManager { } for (ProcessScope processScope : processesScope) { var module = new Module(); + var config = new ModuleConfig(); + config.preLoadedDexes = getModuleDexes(apk_path); module.name = module_pkg; module.apk = apk_path; - module.config = null; + module.config = config; cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module); if (module_pkg.equals(app.packageName)) { var appId = processScope.uid % PER_USER_RANGE; @@ -534,6 +547,45 @@ public class ConfigManager { }); } + private SharedMemory[] loadModuleDexes(String path) { + var sharedMemories = new ArrayList(); + try (var apkFile = new ZipFile(path)) { + int secondary = 2; + for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null; + dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) { + try (var in = apkFile.getInputStream(dexFile)) { + var memory = SharedMemory.create(null, in.available()); + var byteBuffer = memory.mapReadWrite(); + Channels.newChannel(in).read(byteBuffer); + SharedMemory.unmap(byteBuffer); + memory.setProtect(OsConstants.PROT_READ); + sharedMemories.add(memory); + } catch (IOException | ErrnoException e) { + Log.w(TAG, "Can not load " + dexFile + " in " + path, e); + } + } + } catch (IOException e) { + Log.e(TAG, "Can not open " + path, e); + } + return sharedMemories.toArray(new SharedMemory[0]); + } + + private SharedMemory[] getModuleDexes(String path) { + return cachedDexes.computeIfAbsent(path, this::loadModuleDexes); + } + + private void cleanModuleDexes() { + cachedDexes.entrySet().removeIf(entry -> { + var path = entry.getKey(); + var dexes = entry.getValue(); + if (!new File(path).exists()) { + Arrays.stream(dexes).parallel().forEach(SharedMemory::close); + return true; + } + return false; + }); + } + // This is called when a new process created, use the cached result public List getModulesForProcess(String processName, int uid) { return isManager(uid) ? Collections.emptyList() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyList()); 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 c1550dc7..1a961b03 100644 --- a/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java +++ b/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java @@ -3,6 +3,7 @@ package org.lsposed.lspd.util; import static de.robv.android.xposed.XposedBridge.TAG; import android.os.Build; +import android.os.SharedMemory; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; @@ -15,15 +16,14 @@ import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; -import java.nio.channels.Channels; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.List; +import java.util.Objects; import java.util.jar.JarFile; import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; import hidden.ByteBufferDexClassLoader; @@ -177,35 +177,27 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader { } public static LspModuleClassLoader loadApk(File apk, + SharedMemory[] dexes, String librarySearchPath, ClassLoader parent) { - var byteBuffers = new ArrayList(); - try (var apkFile = new ZipFile(apk)) { - int secondary = 2; - for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null; - dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) { - try (var in = apkFile.getInputStream(dexFile)) { - var byteBuffer = ByteBuffer.allocate(in.available()); - byteBuffer.mark(); - Channels.newChannel(in).read(byteBuffer); - byteBuffer.reset(); - byteBuffers.add(byteBuffer); - } catch (IOException e) { - Log.w(TAG, "Can not read " + dexFile + " in " + apk, e); - } + var dexBuffers = Arrays.stream(dexes).parallel().map(dex -> { + try { + return dex.mapReadOnly(); + } catch (ErrnoException e) { + Log.w(TAG, "Can not map " + dex, e); + return null; } - } catch (IOException e) { - Log.e(TAG, "Can not open " + apk, e); - } - var dexBuffers = new ByteBuffer[byteBuffers.size()]; + }).filter(Objects::nonNull).toArray(ByteBuffer[]::new); + LspModuleClassLoader cl; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - return new LspModuleClassLoader(byteBuffers.toArray(dexBuffers), - librarySearchPath, parent, apk.getAbsolutePath()); - } else { - var cl = new LspModuleClassLoader(byteBuffers.toArray(dexBuffers), + cl = new LspModuleClassLoader(dexBuffers, librarySearchPath, parent, apk.getAbsolutePath()); + } else { + cl = new LspModuleClassLoader(dexBuffers, parent, apk.getAbsolutePath()); cl.initNativeLibraryDirs(librarySearchPath); - return cl; } + Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap); + Arrays.stream(dexes).parallel().forEach(SharedMemory::close); + return cl; } }