[core] pre load module dexes (#853)

* [core] pre load module dexes

* 1

* 2

* 4
This commit is contained in:
vvb2060 2021-08-06 21:40:41 +08:00 committed by GitHub
parent 87bfb3f818
commit 607bbedec4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 33 deletions

View File

@ -1,4 +1,5 @@
package org.lsposed.lspd.models; package org.lsposed.lspd.models;
parcelable ModuleConfig { parcelable ModuleConfig {
SharedMemory[] preLoadedDexes;
} }

View File

@ -41,6 +41,7 @@ import android.content.res.XResources;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.Process; import android.os.Process;
import android.os.SharedMemory;
import android.util.ArraySet; import android.util.ArraySet;
import android.util.Log; import android.util.Log;
@ -224,11 +225,12 @@ public final class XposedInit {
moduleList.forEach(module -> { moduleList.forEach(module -> {
var apk = module.apk; var apk = module.apk;
var name = module.name; var name = module.name;
var dexes = module.config.preLoadedDexes;
if (loadedModules.contains(apk)) { if (loadedModules.contains(apk)) {
newLoadedApk.add(apk); newLoadedApk.add(apk);
} else { } else {
loadedModules.add(apk); // temporarily add it for XSharedPreference loadedModules.add(apk); // temporarily add it for XSharedPreference
boolean loadSuccess = loadModule(name, apk); boolean loadSuccess = loadModule(name, apk, dexes);
if (loadSuccess) { if (loadSuccess) {
newLoadedApk.add(apk); newLoadedApk.add(apk);
} }
@ -361,7 +363,7 @@ public final class XposedInit {
* in <code>assets/xposed_init</code>. * in <code>assets/xposed_init</code>.
*/ */
@SuppressLint("PrivateApi") @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); Log.i(TAG, "Loading module " + name + " from " + apk);
if (!new File(apk).exists()) { if (!new File(apk).exists()) {
@ -375,7 +377,7 @@ public final class XposedInit {
librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator); librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator);
} }
ClassLoader initLoader = XposedInit.class.getClassLoader(); 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 { try {
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) { if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {

View File

@ -36,6 +36,7 @@ import android.os.SharedMemory;
import android.os.SystemClock; import android.os.SystemClock;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.system.OsConstants;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -46,6 +47,7 @@ import org.apache.commons.lang3.SerializationUtils;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.models.Application; import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.models.Module; import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.models.ModuleConfig;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@ -53,6 +55,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Serializable; import java.io.Serializable;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
import java.nio.channels.FileLock; import java.nio.channels.FileLock;
import java.nio.file.Files; import java.nio.file.Files;
@ -141,8 +144,6 @@ public class ConfigManager {
private final Handler cacheHandler; private final Handler cacheHandler;
private final Map<String, SharedMemory> moduleDexes = new ConcurrentHashMap<>();
private long lastModuleCacheTime = 0; private long lastModuleCacheTime = 0;
private long requestModuleCacheTime = 0; private long requestModuleCacheTime = 0;
@ -199,8 +200,13 @@ public class ConfigManager {
private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>(); private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
// apkPath, dexes
private final Map<String, SharedMemory[]> cachedDexes = new ConcurrentHashMap<>();
// appId, packageName
private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>(); private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>();
// packageName, userId, group, key, value
private final Map<Pair<String, Integer>, Map<String, ConcurrentHashMap<String, Object>>> cachedConfig = new ConcurrentHashMap<>(); private final Map<Pair<String, Integer>, Map<String, ConcurrentHashMap<String, Object>>> cachedConfig = new ConcurrentHashMap<>();
private void updateCaches(boolean sync) { private void updateCaches(boolean sync) {
@ -251,9 +257,12 @@ public class ConfigManager {
int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); int pkgNameIdx = cursor.getColumnIndex("module_pkg_name");
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
var module = new Module(); var module = new Module();
var config = new ModuleConfig();
var path = cursor.getString(apkPathIdx);
config.preLoadedDexes = getModuleDexes(path);
module.name = cursor.getString(pkgNameIdx); module.name = cursor.getString(pkgNameIdx);
module.apk = cursor.getString(apkPathIdx); module.apk = path;
module.config = null; module.config = config;
modules.add(module); modules.add(module);
} }
} }
@ -433,6 +442,7 @@ public class ConfigManager {
} }
int pkgNameIdx = cursor.getColumnIndex("module_pkg_name"); int pkgNameIdx = cursor.getColumnIndex("module_pkg_name");
int userIdIdx = cursor.getColumnIndex("user_id"); int userIdIdx = cursor.getColumnIndex("user_id");
// packageName, userId, packageInfo
Map<String, Map<Integer, PackageInfo>> modules = new HashMap<>(); Map<String, Map<Integer, PackageInfo>> modules = new HashMap<>();
Set<String> obsoleteModules = new HashSet<>(); Set<String> obsoleteModules = new HashSet<>();
Set<Application> obsoleteScopes = new HashSet<>(); Set<Application> obsoleteScopes = new HashSet<>();
@ -466,6 +476,7 @@ public class ConfigManager {
for (var obsoleteScope : obsoleteScopes) { for (var obsoleteScope : obsoleteScopes) {
removeModuleScopeWithoutCache(obsoleteScope); removeModuleScopeWithoutCache(obsoleteScope);
} }
cleanModuleDexes();
} }
Log.d(TAG, "cached modules"); Log.d(TAG, "cached modules");
for (int uid : cachedModule.keySet()) { for (int uid : cachedModule.keySet()) {
@ -506,9 +517,11 @@ public class ConfigManager {
} }
for (ProcessScope processScope : processesScope) { for (ProcessScope processScope : processesScope) {
var module = new Module(); var module = new Module();
var config = new ModuleConfig();
config.preLoadedDexes = getModuleDexes(apk_path);
module.name = module_pkg; module.name = module_pkg;
module.apk = apk_path; module.apk = apk_path;
module.config = null; module.config = config;
cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module); cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module);
if (module_pkg.equals(app.packageName)) { if (module_pkg.equals(app.packageName)) {
var appId = processScope.uid % PER_USER_RANGE; var appId = processScope.uid % PER_USER_RANGE;
@ -534,6 +547,45 @@ public class ConfigManager {
}); });
} }
private SharedMemory[] loadModuleDexes(String path) {
var sharedMemories = new ArrayList<SharedMemory>();
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 // This is called when a new process created, use the cached result
public List<Module> getModulesForProcess(String processName, int uid) { public List<Module> getModulesForProcess(String processName, int uid) {
return isManager(uid) ? Collections.emptyList() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyList()); return isManager(uid) ? Collections.emptyList() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyList());

View File

@ -3,6 +3,7 @@ package org.lsposed.lspd.util;
import static de.robv.android.xposed.XposedBridge.TAG; import static de.robv.android.xposed.XposedBridge.TAG;
import android.os.Build; import android.os.Build;
import android.os.SharedMemory;
import android.system.ErrnoException; import android.system.ErrnoException;
import android.system.Os; import android.system.Os;
import android.system.OsConstants; import android.system.OsConstants;
@ -15,15 +16,14 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.jar.JarFile; import java.util.jar.JarFile;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import hidden.ByteBufferDexClassLoader; import hidden.ByteBufferDexClassLoader;
@ -177,35 +177,27 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader {
} }
public static LspModuleClassLoader loadApk(File apk, public static LspModuleClassLoader loadApk(File apk,
SharedMemory[] dexes,
String librarySearchPath, String librarySearchPath,
ClassLoader parent) { ClassLoader parent) {
var byteBuffers = new ArrayList<ByteBuffer>(); var dexBuffers = Arrays.stream(dexes).parallel().map(dex -> {
try (var apkFile = new ZipFile(apk)) { try {
int secondary = 2; return dex.mapReadOnly();
for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null; } catch (ErrnoException e) {
dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) { Log.w(TAG, "Can not map " + dex, e);
try (var in = apkFile.getInputStream(dexFile)) { return null;
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);
} }
} }).filter(Objects::nonNull).toArray(ByteBuffer[]::new);
} catch (IOException e) { LspModuleClassLoader cl;
Log.e(TAG, "Can not open " + apk, e);
}
var dexBuffers = new ByteBuffer[byteBuffers.size()];
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return new LspModuleClassLoader(byteBuffers.toArray(dexBuffers), cl = new LspModuleClassLoader(dexBuffers, librarySearchPath,
librarySearchPath, parent, apk.getAbsolutePath());
} else {
var cl = new LspModuleClassLoader(byteBuffers.toArray(dexBuffers),
parent, apk.getAbsolutePath()); parent, apk.getAbsolutePath());
} else {
cl = new LspModuleClassLoader(dexBuffers, parent, apk.getAbsolutePath());
cl.initNativeLibraryDirs(librarySearchPath); cl.initNativeLibraryDirs(librarySearchPath);
}
Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap);
Arrays.stream(dexes).parallel().forEach(SharedMemory::close);
return cl; return cl;
} }
}
} }