[core] Preload module apk (#877)
This commit is contained in:
parent
4f3a615ba9
commit
3cd9fd1735
|
|
@ -1,8 +1,8 @@
|
||||||
package org.lsposed.lspd.models;
|
package org.lsposed.lspd.models;
|
||||||
import org.lsposed.lspd.models.ModuleConfig;
|
import org.lsposed.lspd.models.PreLoadedApk;
|
||||||
|
|
||||||
parcelable Module {
|
parcelable Module {
|
||||||
String name;
|
String packageName;
|
||||||
String apk;
|
String apkPath;
|
||||||
ModuleConfig config;
|
PreLoadedApk file;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.lsposed.lspd.models;
|
package org.lsposed.lspd.models;
|
||||||
|
|
||||||
parcelable ModuleConfig {
|
parcelable PreLoadedApk {
|
||||||
List<SharedMemory> preLoadedDexes;
|
List<SharedMemory> preLoadedDexes;
|
||||||
List<String> moduleClassNames;
|
List<String> moduleClassNames;
|
||||||
|
List<String> moduleLibraryNames;
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,6 @@ import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||||
import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
|
import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType;
|
||||||
import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
|
import static de.robv.android.xposed.XposedHelpers.setStaticObjectField;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.content.res.ResourcesImpl;
|
import android.content.res.ResourcesImpl;
|
||||||
|
|
@ -40,25 +39,20 @@ 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;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.models.PreLoadedApk;
|
||||||
import org.lsposed.lspd.nativebridge.NativeAPI;
|
import org.lsposed.lspd.nativebridge.NativeAPI;
|
||||||
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
||||||
import org.lsposed.lspd.util.LspModuleClassLoader;
|
import org.lsposed.lspd.util.LspModuleClassLoader;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
|
|
@ -87,7 +81,7 @@ public final class XposedInit {
|
||||||
findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication",
|
findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication",
|
||||||
ApplicationInfo.class, new XC_MethodHook() {
|
ApplicationInfo.class, new XC_MethodHook() {
|
||||||
@Override
|
@Override
|
||||||
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
ApplicationInfo app = (ApplicationInfo) param.args[0];
|
ApplicationInfo app = (ApplicationInfo) param.args[0];
|
||||||
XResources.setPackageNameForResDir(app.packageName,
|
XResources.setPackageNameForResDir(app.packageName,
|
||||||
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir);
|
app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir);
|
||||||
|
|
@ -119,7 +113,7 @@ public final class XposedInit {
|
||||||
|
|
||||||
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
|
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
|
||||||
@Override
|
@Override
|
||||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
protected void afterHookedMethod(MethodHookParam param) {
|
||||||
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
|
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
|
||||||
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
|
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
|
||||||
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
|
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
|
||||||
|
|
@ -131,15 +125,18 @@ public final class XposedInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object activityToken = param.args[activityTokenIdx];
|
Object activityToken = param.args[activityTokenIdx];
|
||||||
|
//noinspection SynchronizeOnNonFinalField
|
||||||
synchronized (param.thisObject) {
|
synchronized (param.thisObject) {
|
||||||
ArrayList<WeakReference<Resources>> resourceReferences;
|
ArrayList<WeakReference<Resources>> resourceReferences;
|
||||||
if (activityToken != null) {
|
if (activityToken != null) {
|
||||||
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
|
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
|
||||||
|
//noinspection unchecked
|
||||||
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
|
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
|
||||||
} else {
|
} else {
|
||||||
|
//noinspection unchecked
|
||||||
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
|
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
|
||||||
}
|
}
|
||||||
resourceReferences.add(new WeakReference(newRes));
|
resourceReferences.add(new WeakReference<>(newRes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -214,23 +211,23 @@ public final class XposedInit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean loadModules() throws IOException {
|
public static void loadModules() {
|
||||||
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
|
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
|
||||||
if (hasLoaded) {
|
if (hasLoaded) {
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
synchronized (moduleLoadLock) {
|
synchronized (moduleLoadLock) {
|
||||||
var moduleList = serviceClient.getModulesList();
|
var moduleList = serviceClient.getModulesList();
|
||||||
ArraySet<String> newLoadedApk = new ArraySet<>();
|
var newLoadedApk = new ArraySet<String>();
|
||||||
moduleList.forEach(module -> {
|
moduleList.forEach(module -> {
|
||||||
var apk = module.apk;
|
var apk = module.apkPath;
|
||||||
var name = module.name;
|
var name = module.packageName;
|
||||||
var dexes = module.config.preLoadedDexes;
|
var file = module.file;
|
||||||
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, dexes);
|
boolean loadSuccess = loadModule(name, apk, file);
|
||||||
if (loadSuccess) {
|
if (loadSuccess) {
|
||||||
newLoadedApk.add(apk);
|
newLoadedApk.add(apk);
|
||||||
}
|
}
|
||||||
|
|
@ -240,14 +237,13 @@ public final class XposedInit {
|
||||||
loadedModules.addAll(newLoadedApk);
|
loadedModules.addAll(newLoadedApk);
|
||||||
|
|
||||||
// refresh callback according to current loaded module list
|
// refresh callback according to current loaded module list
|
||||||
pruneCallbacks(loadedModules);
|
pruneCallbacks();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove deactivated or outdated module callbacks
|
// remove deactivated or outdated module callbacks
|
||||||
private static void pruneCallbacks(Set<String> loadedModules) {
|
private static void pruneCallbacks() {
|
||||||
synchronized (moduleLoadLock) {
|
synchronized (moduleLoadLock) {
|
||||||
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
|
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
|
||||||
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
|
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
|
||||||
|
|
@ -280,110 +276,87 @@ public final class XposedInit {
|
||||||
* Load all so from an APK by reading <code>assets/native_init</code>.
|
* Load all so from an APK by reading <code>assets/native_init</code>.
|
||||||
* It will only store the so names but not doing anything.
|
* It will only store the so names but not doing anything.
|
||||||
*/
|
*/
|
||||||
private static boolean initNativeModule(ClassLoader mcl, String name) {
|
private static void initNativeModule(List<String> moduleLibraryNames) {
|
||||||
try (InputStream is = mcl.getResourceAsStream("assets/native_init")) {
|
moduleLibraryNames.forEach(NativeAPI::recordNativeEntrypoint);
|
||||||
if (is == null) return true;
|
|
||||||
BufferedReader moduleLibraryReader = new BufferedReader(new InputStreamReader(is));
|
|
||||||
String moduleLibraryName;
|
|
||||||
while ((moduleLibraryName = moduleLibraryReader.readLine()) != null) {
|
|
||||||
if (!moduleLibraryName.startsWith("#")) {
|
|
||||||
NativeAPI.recordNativeEntrypoint(moduleLibraryName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, " Failed to load native library list from " + name, e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean initModule(ClassLoader mcl, String name, String apk) {
|
private static boolean initModule(ClassLoader mcl, String apk, List<String> moduleClassNames) {
|
||||||
InputStream is = mcl.getResourceAsStream("assets/xposed_init");
|
var count = 0;
|
||||||
if (is == null) {
|
for (var moduleClassName : moduleClassNames) {
|
||||||
return true;
|
try {
|
||||||
}
|
Log.i(TAG, " Loading class " + moduleClassName);
|
||||||
try (BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is))) {
|
|
||||||
String moduleClassName;
|
Class<?> moduleClass = mcl.loadClass(moduleClassName);
|
||||||
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
|
|
||||||
moduleClassName = moduleClassName.trim();
|
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
|
||||||
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
|
Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
|
||||||
|
continue;
|
||||||
|
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
|
||||||
|
Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
try {
|
|
||||||
Log.i(TAG, " Loading class " + moduleClassName);
|
|
||||||
Class<?> moduleClass = mcl.loadClass(moduleClassName);
|
|
||||||
|
|
||||||
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
|
|
||||||
Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
|
|
||||||
continue;
|
|
||||||
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
|
|
||||||
Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Object moduleInstance = moduleClass.newInstance();
|
|
||||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
|
||||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
|
||||||
param.modulePath = apk;
|
|
||||||
param.startsSystemServer = startsSystemServer;
|
|
||||||
|
|
||||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
|
||||||
(IXposedHookZygoteInit) moduleInstance, param));
|
|
||||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (moduleInstance instanceof IXposedHookLoadPackage)
|
|
||||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
|
||||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
|
||||||
|
|
||||||
if (moduleInstance instanceof IXposedHookInitPackageResources)
|
|
||||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
|
||||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Object moduleInstance = moduleClass.newInstance();
|
||||||
|
|
||||||
|
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||||
|
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||||
|
param.modulePath = apk;
|
||||||
|
param.startsSystemServer = startsSystemServer;
|
||||||
|
|
||||||
|
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||||
|
(IXposedHookZygoteInit) moduleInstance, param));
|
||||||
|
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
||||||
|
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
||||||
|
(IXposedHookLoadPackage) moduleInstance, apk));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
||||||
|
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
||||||
|
(IXposedHookInitPackageResources) moduleInstance, apk));
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, " Failed to load module " + name + " from " + apk, e);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
return count > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load a module from an APK by calling the init(String) method for all classes defined
|
* Load a module from an APK by calling the init(String) method for all classes defined
|
||||||
* in <code>assets/xposed_init</code>.
|
* in <code>assets/xposed_init</code>.
|
||||||
*/
|
*/
|
||||||
@SuppressLint("PrivateApi")
|
private static boolean loadModule(String name, String apk, PreLoadedApk file) {
|
||||||
private static boolean loadModule(String name, String apk, List<SharedMemory> dexes) {
|
|
||||||
Log.i(TAG, "Loading module " + name + " from " + apk);
|
Log.i(TAG, "Loading module " + name + " from " + apk);
|
||||||
|
|
||||||
if (!new File(apk).exists()) {
|
var sb = new StringBuilder();
|
||||||
Log.e(TAG, " File does not exist");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var librarySearchPath = new StringBuilder();
|
|
||||||
var abis = Process.is64Bit() ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS;
|
var abis = Process.is64Bit() ? Build.SUPPORTED_64_BIT_ABIS : Build.SUPPORTED_32_BIT_ABIS;
|
||||||
for (String abi : abis) {
|
for (String abi : abis) {
|
||||||
librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator);
|
sb.append(apk).append("!/lib/").append(abi).append(File.pathSeparator);
|
||||||
}
|
}
|
||||||
ClassLoader initLoader = XposedInit.class.getClassLoader();
|
var librarySearchPath = sb.toString();
|
||||||
ClassLoader mcl = LspModuleClassLoader.loadApk(new File(apk), dexes, librarySearchPath.toString(), initLoader);
|
|
||||||
|
var initLoader = XposedInit.class.getClassLoader();
|
||||||
|
var mcl = LspModuleClassLoader.loadApk(apk, file.preLoadedDexes, librarySearchPath, initLoader);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {
|
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {
|
||||||
Log.e(TAG, " Cannot load module: " + name);
|
Log.e(TAG, " Cannot load module: " + name);
|
||||||
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
|
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, " This may cause strange issues and must be fixed by the module developer.");
|
||||||
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
|
Log.e(TAG, " For details, see: https://api.xposed.info/using.html");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (ClassNotFoundException ignored) {
|
} catch (ClassNotFoundException ignored) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
initNativeModule(file.moduleLibraryNames);
|
||||||
return initNativeModule(mcl, name) && initModule(mcl, name, apk);
|
return initModule(mcl, apk, file.moduleClassNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
|
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
|
||||||
|
|
|
||||||
|
|
@ -78,14 +78,6 @@ public class Main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void loadModulesSafely() {
|
|
||||||
try {
|
|
||||||
XposedInit.loadModules();
|
|
||||||
} catch (Exception exception) {
|
|
||||||
Utils.logE("error loading module list", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
||||||
// init logger
|
// init logger
|
||||||
YahfaHooker.init();
|
YahfaHooker.init();
|
||||||
|
|
@ -95,7 +87,7 @@ public class Main {
|
||||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||||
installBootstrapHooks(isSystem, appDataDir);
|
installBootstrapHooks(isSystem, appDataDir);
|
||||||
Utils.logI("Loading modules for " + niceName + "/" + Process.myUid());
|
Utils.logI("Loading modules for " + niceName + "/" + Process.myUid());
|
||||||
loadModulesSafely();
|
XposedInit.loadModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void forkAndSpecializePost(String appDataDir, String niceName, IBinder binder) {
|
public static void forkAndSpecializePost(String appDataDir, String niceName, IBinder binder) {
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,14 @@ 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 org.lsposed.lspd.models.PreLoadedApk;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
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.Channels;
|
||||||
|
|
@ -200,8 +202,8 @@ public class ConfigManager {
|
||||||
|
|
||||||
private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
|
private final Map<ProcessScope, List<Module>> cachedScope = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// apkPath, dexes
|
// apkPath, PreLoadedApk
|
||||||
private final Map<String, List<SharedMemory>> cachedDexes = new ConcurrentHashMap<>();
|
private final Map<String, PreLoadedApk> cachedApkFile = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
// appId, packageName
|
// appId, packageName
|
||||||
private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>();
|
private final Map<Integer, String> cachedModule = new ConcurrentHashMap<>();
|
||||||
|
|
@ -256,14 +258,17 @@ public class ConfigManager {
|
||||||
int apkPathIdx = cursor.getColumnIndex("apk_path");
|
int apkPathIdx = cursor.getColumnIndex("apk_path");
|
||||||
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 config = new ModuleConfig();
|
|
||||||
var path = cursor.getString(apkPathIdx);
|
var path = cursor.getString(apkPathIdx);
|
||||||
config.preLoadedDexes = getModuleDexes(path);
|
var file = getCachedApkFile(path);
|
||||||
module.name = cursor.getString(pkgNameIdx);
|
if (file != null) {
|
||||||
module.apk = path;
|
var module = new Module();
|
||||||
module.config = config;
|
module.packageName = cursor.getString(pkgNameIdx);
|
||||||
modules.add(module);
|
module.apkPath = path;
|
||||||
|
module.file = file;
|
||||||
|
modules.add(module);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Can not load " + path + ", skip!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return modules;
|
return modules;
|
||||||
|
|
@ -476,7 +481,7 @@ public class ConfigManager {
|
||||||
for (var obsoleteScope : obsoleteScopes) {
|
for (var obsoleteScope : obsoleteScopes) {
|
||||||
removeModuleScopeWithoutCache(obsoleteScope);
|
removeModuleScopeWithoutCache(obsoleteScope);
|
||||||
}
|
}
|
||||||
cleanModuleDexes();
|
checkCachedApkFile();
|
||||||
}
|
}
|
||||||
Log.d(TAG, "cached modules");
|
Log.d(TAG, "cached modules");
|
||||||
for (int uid : cachedModule.keySet()) {
|
for (int uid : cachedModule.keySet()) {
|
||||||
|
|
@ -507,26 +512,35 @@ public class ConfigManager {
|
||||||
app.userId = cursor.getInt(userIdIdx);
|
app.userId = cursor.getInt(userIdIdx);
|
||||||
// system server always loads database
|
// system server always loads database
|
||||||
if (app.packageName.equals("android")) continue;
|
if (app.packageName.equals("android")) continue;
|
||||||
String apk_path = cursor.getString(apkPathIdx);
|
|
||||||
String module_pkg = cursor.getString(modulePkgNameIdx);
|
|
||||||
try {
|
try {
|
||||||
List<ProcessScope> processesScope = getAssociatedProcesses(app);
|
List<ProcessScope> processesScope = getAssociatedProcesses(app);
|
||||||
if (processesScope.isEmpty()) {
|
if (processesScope.isEmpty()) {
|
||||||
obsoletePackages.add(app);
|
obsoletePackages.add(app);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
var apkPath = cursor.getString(apkPathIdx);
|
||||||
|
var modulePackageName = cursor.getString(modulePkgNameIdx);
|
||||||
|
var file = getCachedApkFile(apkPath);
|
||||||
|
Module module;
|
||||||
|
if (file != null) {
|
||||||
|
module = new Module();
|
||||||
|
module.packageName = modulePackageName;
|
||||||
|
module.apkPath = apkPath;
|
||||||
|
module.file = file;
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Can not load " + apkPath + ", skip!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
for (ProcessScope processScope : processesScope) {
|
for (ProcessScope processScope : processesScope) {
|
||||||
var module = new Module();
|
cachedScope.computeIfAbsent(processScope,
|
||||||
var config = new ModuleConfig();
|
ignored -> new LinkedList<>()).add(module);
|
||||||
config.preLoadedDexes = getModuleDexes(apk_path);
|
// Always allow the module to inject itself
|
||||||
module.name = module_pkg;
|
if (modulePackageName.equals(app.packageName)) {
|
||||||
module.apk = apk_path;
|
|
||||||
module.config = config;
|
|
||||||
cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module);
|
|
||||||
if (module_pkg.equals(app.packageName)) {
|
|
||||||
var appId = processScope.uid % PER_USER_RANGE;
|
var appId = processScope.uid % PER_USER_RANGE;
|
||||||
for (var user : UserService.getUsers()) {
|
for (var user : UserService.getUsers()) {
|
||||||
cachedScope.computeIfAbsent(new ProcessScope(processScope.processName, user.id * PER_USER_RANGE + appId),
|
var moduleUid = user.id * PER_USER_RANGE + appId;
|
||||||
|
var moduleSelf = new ProcessScope(processScope.processName, moduleUid);
|
||||||
|
cachedScope.computeIfAbsent(moduleSelf,
|
||||||
ignored -> new LinkedList<>()).add(module);
|
ignored -> new LinkedList<>()).add(module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -543,43 +557,76 @@ public class ConfigManager {
|
||||||
Log.d(TAG, "cached Scope");
|
Log.d(TAG, "cached Scope");
|
||||||
cachedScope.forEach((ps, modules) -> {
|
cachedScope.forEach((ps, modules) -> {
|
||||||
Log.d(TAG, ps.processName + "/" + ps.uid);
|
Log.d(TAG, ps.processName + "/" + ps.uid);
|
||||||
modules.forEach(module -> Log.d(TAG, "\t" + module.name));
|
modules.forEach(module -> Log.d(TAG, "\t" + module.packageName));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SharedMemory> loadModuleDexes(String path) {
|
private void readDexes(ZipFile apkFile, List<SharedMemory> preLoadedDexes) {
|
||||||
var sharedMemories = new ArrayList<SharedMemory>();
|
int secondary = 2;
|
||||||
try (var apkFile = new ZipFile(path)) {
|
for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null;
|
||||||
int secondary = 2;
|
dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) {
|
||||||
for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null;
|
try (var in = apkFile.getInputStream(dexFile)) {
|
||||||
dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) {
|
var memory = SharedMemory.create(null, in.available());
|
||||||
try (var in = apkFile.getInputStream(dexFile)) {
|
var byteBuffer = memory.mapReadWrite();
|
||||||
var memory = SharedMemory.create(null, in.available());
|
Channels.newChannel(in).read(byteBuffer);
|
||||||
var byteBuffer = memory.mapReadWrite();
|
SharedMemory.unmap(byteBuffer);
|
||||||
Channels.newChannel(in).read(byteBuffer);
|
memory.setProtect(OsConstants.PROT_READ);
|
||||||
SharedMemory.unmap(byteBuffer);
|
preLoadedDexes.add(memory);
|
||||||
memory.setProtect(OsConstants.PROT_READ);
|
} catch (IOException | ErrnoException e) {
|
||||||
sharedMemories.add(memory);
|
Log.w(TAG, "Can not load " + dexFile + " in " + apkFile, e);
|
||||||
} catch (IOException | ErrnoException e) {
|
}
|
||||||
Log.w(TAG, "Can not load " + dexFile + " in " + path, e);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void readName(ZipFile apkFile, String initName, List<String> names) {
|
||||||
|
var initEntry = apkFile.getEntry(initName);
|
||||||
|
if (initEntry == null) return;
|
||||||
|
try (var in = apkFile.getInputStream(initEntry)) {
|
||||||
|
var reader = new BufferedReader(new InputStreamReader(in));
|
||||||
|
String name;
|
||||||
|
while ((name = reader.readLine()) != null) {
|
||||||
|
name = name.trim();
|
||||||
|
if (name.isEmpty() || name.startsWith("#")) continue;
|
||||||
|
names.add(name);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Can not open " + path, e);
|
Log.e(TAG, "Can not open " + initEntry, e);
|
||||||
}
|
}
|
||||||
return sharedMemories;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<SharedMemory> getModuleDexes(String path) {
|
@Nullable
|
||||||
return cachedDexes.computeIfAbsent(path, this::loadModuleDexes);
|
private PreLoadedApk loadModule(String path) {
|
||||||
|
var file = new PreLoadedApk();
|
||||||
|
var preLoadedDexes = new ArrayList<SharedMemory>();
|
||||||
|
var moduleClassNames = new ArrayList<String>(1);
|
||||||
|
var moduleLibraryNames = new ArrayList<String>(1);
|
||||||
|
try (var apkFile = new ZipFile(path)) {
|
||||||
|
readDexes(apkFile, preLoadedDexes);
|
||||||
|
readName(apkFile, "assets/xposed_init", moduleClassNames);
|
||||||
|
readName(apkFile, "assets/native_init", moduleLibraryNames);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Log.e(TAG, "Can not open " + path, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (preLoadedDexes.isEmpty()) return null;
|
||||||
|
if (moduleClassNames.isEmpty()) return null;
|
||||||
|
file.preLoadedDexes = preLoadedDexes;
|
||||||
|
file.moduleClassNames = moduleClassNames;
|
||||||
|
file.moduleLibraryNames = moduleLibraryNames;
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cleanModuleDexes() {
|
@Nullable
|
||||||
cachedDexes.entrySet().removeIf(entry -> {
|
private PreLoadedApk getCachedApkFile(String path) {
|
||||||
|
return cachedApkFile.computeIfAbsent(path, this::loadModule);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCachedApkFile() {
|
||||||
|
cachedApkFile.entrySet().removeIf(entry -> {
|
||||||
var path = entry.getKey();
|
var path = entry.getKey();
|
||||||
var dexes = entry.getValue();
|
var file = entry.getValue();
|
||||||
if (!new File(path).exists()) {
|
if (!new File(path).exists()) {
|
||||||
dexes.stream().parallel().forEach(SharedMemory::close);
|
file.preLoadedDexes.stream().parallel().forEach(SharedMemory::close);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader {
|
||||||
super.toString() + "]";
|
super.toString() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LspModuleClassLoader loadApk(File apk,
|
public static LspModuleClassLoader loadApk(String apk,
|
||||||
List<SharedMemory> dexes,
|
List<SharedMemory> dexes,
|
||||||
String librarySearchPath,
|
String librarySearchPath,
|
||||||
ClassLoader parent) {
|
ClassLoader parent) {
|
||||||
|
|
@ -191,9 +191,9 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader {
|
||||||
LspModuleClassLoader cl;
|
LspModuleClassLoader cl;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
cl = new LspModuleClassLoader(dexBuffers, librarySearchPath,
|
cl = new LspModuleClassLoader(dexBuffers, librarySearchPath,
|
||||||
parent, apk.getAbsolutePath());
|
parent, apk);
|
||||||
} else {
|
} else {
|
||||||
cl = new LspModuleClassLoader(dexBuffers, parent, apk.getAbsolutePath());
|
cl = new LspModuleClassLoader(dexBuffers, parent, apk);
|
||||||
cl.initNativeLibraryDirs(librarySearchPath);
|
cl.initNativeLibraryDirs(librarySearchPath);
|
||||||
}
|
}
|
||||||
Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap);
|
Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue