[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;
parcelable ModuleConfig {
SharedMemory[] preLoadedDexes;
}

View File

@ -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 <code>assets/xposed_init</code>.
*/
@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) {

View File

@ -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<String, SharedMemory> moduleDexes = new ConcurrentHashMap<>();
private long lastModuleCacheTime = 0;
private long requestModuleCacheTime = 0;
@ -199,8 +200,13 @@ public class ConfigManager {
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<>();
// packageName, userId, group, key, value
private final Map<Pair<String, Integer>, Map<String, ConcurrentHashMap<String, Object>>> 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<String, Map<Integer, PackageInfo>> modules = new HashMap<>();
Set<String> obsoleteModules = new HashSet<>();
Set<Application> 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<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
public List<Module> getModulesForProcess(String processName, int uid) {
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 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<ByteBuffer>();
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;
}
}