[core] pre load module dexes (#853)
* [core] pre load module dexes * 1 * 2 * 4
This commit is contained in:
parent
87bfb3f818
commit
607bbedec4
|
|
@ -1,4 +1,5 @@
|
|||
package org.lsposed.lspd.models;
|
||||
|
||||
parcelable ModuleConfig {
|
||||
SharedMemory[] preLoadedDexes;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue