Optimize module loading under dynamic-modules mode

by only loading newly added or updated modules
This commit is contained in:
solohsu 2019-06-19 15:34:51 +08:00
parent be9105317f
commit f6d2e3b62f
18 changed files with 173 additions and 78 deletions

View File

@ -1,4 +1,4 @@
version: '0.4.4.6_alpha({build})'
version: '0.4.4.7_alpha({build})'
environment:
ANDROID_HOME: C:\android-sdk-windows

View File

@ -72,10 +72,10 @@ public abstract class BaseRouter implements Router {
}
}
public void loadModulesSafely(boolean isInZygote) {
public void loadModulesSafely(boolean isInZygote, boolean callInitZygote) {
try {
// FIXME some coredomain app can't reading modules.list
XposedInit.loadModules(isInZygote);
XposedInit.loadModules(isInZygote, callInitZygote);
} catch (Exception exception) {
Utils.logE("error loading module list", exception);
}

View File

@ -84,8 +84,7 @@ public class BlackWhiteListProxy extends BaseProxy {
// because installed hooks would be propagated to all child processes of zygote
mRouter.startWorkAroundHook();
// loadModules once for all child processes of zygote
// TODO maybe just save initZygote callbacks and call them when whitelisted process forked?
mRouter.loadModulesSafely(true);
mRouter.loadModulesSafely(true, false);
}
private void onForkPostCommon(boolean isSystemServer, String appDataDir, String niceName) {
@ -104,12 +103,15 @@ public class BlackWhiteListProxy extends BaseProxy {
mRouter.prepare(isSystemServer);
PrebuiltMethodsDeopter.deoptBootMethods();
mRouter.installBootstrapHooks(isSystemServer);
// under dynamic modules mode, don't call initZygote when loadModule
// cuz loaded module won't has that chance to do it
if (isDynamicModulesMode) {
mRouter.loadModulesSafely(false);
} else {
XposedBridge.callInitZygotes();
XposedBridge.clearInitZygotes(); // one-time use
mRouter.loadModulesSafely(false, false);
}
// call all initZygote callbacks
XposedBridge.callInitZygotes();
mRouter.onForkFinish();
}
@ -131,8 +133,6 @@ public class BlackWhiteListProxy extends BaseProxy {
}
private static void onBlackListed() {
XposedBridge.clearLoadedPackages();
XposedBridge.clearInitPackageResources();
XposedBridge.clearInitZygotes();
XposedBridge.clearAllCallbacks();
}
}

View File

@ -25,7 +25,7 @@ public class NormalProxy extends BaseProxy {
// install bootstrap hooks for secondary zygote
mRouter.installBootstrapHooks(false);
// only load modules for secondary zygote
mRouter.loadModulesSafely(true);
mRouter.loadModulesSafely(true, true);
}
public void forkAndSpecializePost(int pid, String appDataDir, String niceName) {
@ -35,7 +35,7 @@ public class NormalProxy extends BaseProxy {
mRouter.prepare(false);
mRouter.onEnterChildProcess();
// load modules for each app process on its forked if dynamic modules mode is on
mRouter.loadModulesSafely(false);
mRouter.loadModulesSafely(false, true);
mRouter.onForkFinish();
}
@ -53,7 +53,7 @@ public class NormalProxy extends BaseProxy {
// loadModules have to be executed in zygote even isDynamicModules is false
// because if not global hooks installed in initZygote might not be
// propagated to processes not forked via forkAndSpecialize
mRouter.loadModulesSafely(true);
mRouter.loadModulesSafely(true, true);
}
public void forkSystemServerPost(int pid) {
@ -63,7 +63,9 @@ public class NormalProxy extends BaseProxy {
mRouter.prepare(true);
mRouter.onEnterChildProcess();
// reload module list if dynamic mode is on
mRouter.loadModulesSafely(false);
if (ConfigManager.isDynamicModulesEnabled()) {
mRouter.loadModulesSafely(false, true);
}
mRouter.onForkFinish();
}

View File

@ -10,7 +10,7 @@ public interface Router {
void installBootstrapHooks(boolean isSystem);
void loadModulesSafely(boolean isInZygote);
void loadModulesSafely(boolean isInZygote, boolean callInitZygote);
void startBootstrapHook(boolean isSystem);

View File

@ -4,7 +4,7 @@ import org.gradle.internal.os.OperatingSystem
apply plugin: 'com.android.library'
// Values set here will be overriden by AppVeyor, feel free to modify during development.
def buildVersionName = 'v0.4.4.6_alpha'
def buildVersionName = 'v0.4.4.7_alpha'
def buildVersionCode = 10000
if (System.env.APPVEYOR_BUILD_VERSION != null) {

View File

@ -120,6 +120,7 @@ namespace edxp {
variant_ = static_cast<Variant>(variant);
}
}
LOGI("EdxpVariant: %d", variant_);
initialized_ = true;

View File

@ -62,7 +62,6 @@ public class SandHookRouter extends BaseRouter {
public void injectConfig() {
EdXpConfigGlobal.sConfig = new SandHookEdxpConfig();
EdXpConfigGlobal.sHookProvider = new SandHookProvider();
XposedBridge.log("using HookProvider: " + EdXpConfigGlobal.sHookProvider.getClass().getName());
}
}

View File

@ -1,22 +1,11 @@
package com.elderdrivers.riru.edxp.whale.core;
import android.app.ActivityThread;
import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import com.elderdrivers.riru.edxp._hooker.impl.HandleBindApp;
import com.elderdrivers.riru.edxp._hooker.impl.LoadedApkCstr;
import com.elderdrivers.riru.edxp._hooker.yahfa.HandleBindAppHooker;
import com.elderdrivers.riru.edxp._hooker.yahfa.LoadedApkConstructorHooker;
import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
import com.elderdrivers.riru.edxp.framework.Zygote;
import com.elderdrivers.riru.edxp.proxy.BaseRouter;
import com.elderdrivers.riru.edxp.whale.config.WhaleEdxpConfig;
import com.elderdrivers.riru.edxp.whale.config.WhaleHookProvider;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
public class WhaleRouter extends BaseRouter {
public void onEnterChildProcess() {
@ -27,7 +16,6 @@ public class WhaleRouter extends BaseRouter {
BaseRouter.useXposedApi = true;
EdXpConfigGlobal.sConfig = new WhaleEdxpConfig();
EdXpConfigGlobal.sHookProvider = new WhaleHookProvider();
XposedBridge.log("using HookProvider: " + EdXpConfigGlobal.sHookProvider.getClass().getName());
Zygote.allowFileAcrossFork("/system/lib/libwhale.edxp.so");
Zygote.allowFileAcrossFork("/system/lib64/libwhale.edxp.so");
Zygote.allowFileAcrossFork("/system/lib/libart.so");

View File

@ -6,8 +6,6 @@ import com.elderdrivers.riru.edxp.yahfa.config.YahfaEdxpConfig;
import com.elderdrivers.riru.edxp.yahfa.config.YahfaHookProvider;
import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge;
import de.robv.android.xposed.XposedBridge;
public class YahfaRouter extends BaseRouter {
public void onEnterChildProcess() {
@ -17,7 +15,6 @@ public class YahfaRouter extends BaseRouter {
public void injectConfig() {
EdXpConfigGlobal.sConfig = new YahfaEdxpConfig();
EdXpConfigGlobal.sHookProvider = new YahfaHookProvider();
XposedBridge.log("using HookProvider: " + EdXpConfigGlobal.sHookProvider.getClass().getName());
}
}

View File

@ -0,0 +1,6 @@
package de.robv.android.xposed;
public interface IModuleContext {
String getApkPath();
}

View File

@ -25,12 +25,21 @@ public interface IXposedHookInitPackageResources extends IXposedMod {
/** @hide */
final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance;
public Wrapper(IXposedHookInitPackageResources instance) {
private final String apkPath;
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
this.instance = instance;
this.apkPath = apkPath;
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam);
}
@Override
public String getApkPath() {
return apkPath;
}
}
}

View File

@ -26,12 +26,20 @@ public interface IXposedHookLoadPackage extends IXposedMod {
/** @hide */
final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance;
public Wrapper(IXposedHookLoadPackage instance) {
private final String apkPath;
public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
this.instance = instance;
this.apkPath = apkPath;
}
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
@Override
public String getApkPath() {
return apkPath;
}
}
}

View File

@ -62,5 +62,10 @@ public interface IXposedHookZygoteInit extends IXposedMod {
// cause startupParam info is generated and saved along with instance here
instance.initZygote(this.startupParam);
}
@Override
public String getApkPath() {
return startupParam.modulePath;
}
}
}

View File

@ -446,6 +446,12 @@ public final class XposedBridge {
XCallback.callAll(new IXposedHookZygoteInit.StartupParam(sInitZygoteCallbacks));
}
public static void clearAllCallbacks() {
clearLoadedPackages();
clearInitPackageResources();
clearInitZygotes();
}
/**
* Intercept every call to the specified method and call a handler function instead.
* @param method The method to intercept

View File

@ -11,6 +11,7 @@ import android.os.Build;
import android.os.IBinder;
import android.os.Process;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.os.ZygoteInit;
@ -27,6 +28,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
@ -34,11 +36,17 @@ import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback;
import de.robv.android.xposed.services.BaseService;
import static de.robv.android.xposed.XposedBridge.clearAllCallbacks;
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.closeSilently;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
@ -292,44 +300,104 @@ public final class XposedInit {
/**
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
*/
private static volatile AtomicBoolean modulesLoaded = new AtomicBoolean(false);
private static final AtomicBoolean modulesLoaded = new AtomicBoolean(false);
private static final Object moduleLoadLock = new Object();
// @GuardedBy("moduleLoadLock")
private static final ArraySet<String> loadedModules = new ArraySet<>();
// @GuardedBy("moduleLoadLock")
private static long lastModuleListModifiedTime = -1;
public static void loadModules(boolean isInZygote) throws IOException {
public static boolean loadModules(boolean isInZygote, boolean callInitZygote) throws IOException {
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
// dynamic module list mode doesn't apply to loading in zygote
if (hasLoaded && (isInZygote || !EdXpConfigGlobal.getConfig().isDynamicModulesMode())) {
return;
}
// FIXME module list is cleared but never could be reload again when using dynamic-module-list under multi-user environment
XposedBridge.clearLoadedPackages();
final String filename = EdXpConfigGlobal.getConfig().getInstallerBaseDir() + "conf/modules.list";
BaseService service = SELinuxHelper.getAppDataFileService();
if (!service.checkFileExists(filename)) {
Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
return;
return false;
}
synchronized (moduleLoadLock) {
final String filename = EdXpConfigGlobal.getConfig().getInstallerBaseDir() + "conf/modules.list";
BaseService service = SELinuxHelper.getAppDataFileService();
if (!service.checkFileExists(filename)) {
Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
// FIXME module list is cleared but never could be reload again
// when using dynamic-module-list under multi-user environment
clearAllCallbacks();
return false;
}
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
ClassLoader parent;
while ((parent = topClassLoader.getParent()) != null) {
topClassLoader = parent;
}
long moduleListModifiedTime = service.getFileModificationTime(filename);
if (lastModuleListModifiedTime == moduleListModifiedTime) {
// module list has not changed
return false;
}
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
ClassLoader parent;
while ((parent = topClassLoader.getParent()) != null) {
topClassLoader = parent;
}
InputStream stream = service.getFileInputStream(filename);
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
ArraySet<String> newLoadedApk = new ArraySet<>();
String apk;
while ((apk = apks.readLine()) != null) {
if (loadedModules.contains(apk)) {
newLoadedApk.add(apk);
} else {
boolean loadSuccess = loadModule(apk, topClassLoader, callInitZygote);
if (loadSuccess) {
newLoadedApk.add(apk);
}
}
}
loadedModules.clear();
loadedModules.addAll(newLoadedApk);
apks.close();
// refresh callback according to current loaded module list
pruneCallbacks(loadedModules);
lastModuleListModifiedTime = moduleListModifiedTime;
InputStream stream = service.getFileInputStream(filename);
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
String apk;
while ((apk = apks.readLine()) != null) {
loadModule(apk, topClassLoader);
}
apks.close();
return true;
}
// remove deactivated or outdated module callbacks
private static void pruneCallbacks(Set<String> loadedModules) {
synchronized (moduleLoadLock) {
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
Object[] initZygoteSnapshot = sInitZygoteCallbacks.getSnapshot();
for (Object loadedPkg : loadedPkgSnapshot) {
if (loadedPkg instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) loadedPkg).getApkPath())) {
sLoadedPackageCallbacks.remove((XC_LoadPackage) loadedPkg);
}
}
}
for (Object initPkgRes : initPkgResSnapshot) {
if (initPkgRes instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) initPkgRes).getApkPath())) {
sInitPackageResourcesCallbacks.remove((XC_InitPackageResources) initPkgRes);
}
}
}
for (Object initZygote : initZygoteSnapshot) {
if (initZygote instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) initZygote).getApkPath())) {
sInitZygoteCallbacks.remove((XC_InitZygote) initZygote);
}
}
}
}
}
/**
* Load a module from an APK by calling the init(String) method for all classes defined
* in <code>assets/xposed_init</code>.
*/
private static void loadModule(String apk, ClassLoader topClassLoader) {
private static boolean loadModule(String apk, ClassLoader topClassLoader, boolean callInitZygote) {
Log.i(TAG, "Loading modules from " + apk);
// todo remove this legacy logic
@ -337,12 +405,12 @@ public final class XposedInit {
if (!TextUtils.isEmpty(apk) && !TextUtils.isEmpty(blackListModulePackageName)
&& apk.contains(blackListModulePackageName)) {
Log.i(TAG, "We are going to take over black list's job...");
return;
return false;
}
if (!new File(apk).exists()) {
Log.e(TAG, " File does not exist");
return;
return false;
}
DexFile dexFile;
@ -350,13 +418,13 @@ public final class XposedInit {
dexFile = new DexFile(apk);
} catch (IOException e) {
Log.e(TAG, " Cannot load module", e);
return;
return false;
}
if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
closeSilently(dexFile);
return;
return false;
}
if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
@ -365,7 +433,7 @@ public final class XposedInit {
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");
closeSilently(dexFile);
return;
return false;
}
closeSilently(dexFile);
@ -378,13 +446,13 @@ public final class XposedInit {
if (zipEntry == null) {
Log.e(TAG, " assets/xposed_init not found in the APK");
closeSilently(zipFile);
return;
return false;
}
is = zipFile.getInputStream(zipEntry);
} catch (IOException e) {
Log.e(TAG, " Cannot read assets/xposed_init in the APK", e);
closeSilently(zipFile);
return;
return false;
}
ClassLoader mcl = new PathClassLoader(apk, XposedInit.class.getClassLoader());
@ -414,24 +482,21 @@ public final class XposedInit {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
if (EdXpConfigGlobal.getConfig().isBlackWhiteListMode()
&& !EdXpConfigGlobal.getConfig().isDynamicModulesMode()) {
// postpone initZygote callbacks under black/white list mode
// if dynamic modules mode is on, callback directly cause we
// are already in app process here
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
} else {
// FIXME under dynamic modules mode, initZygote is called twice
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
if (callInitZygote) {
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
(IXposedHookLoadPackage) moduleInstance, apk));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
(IXposedHookInitPackageResources) moduleInstance, apk));
} else {
if (moduleInstance instanceof IXposedHookCmdInit) {
IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
@ -442,10 +507,13 @@ public final class XposedInit {
}
} catch (Throwable t) {
Log.e(TAG, " Failed to load class " + moduleClassName, t);
return false;
}
}
return true;
} catch (IOException e) {
Log.e(TAG, " Failed to load module from " + apk, e);
return false;
} finally {
closeSilently(is);
closeSilently(zipFile);

View File

@ -6,6 +6,7 @@ import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal;
import java.io.Serializable;
import de.robv.android.xposed.IModuleContext;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
@ -15,7 +16,7 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses.
*/
public abstract class XCallback implements Comparable<XCallback> {
public abstract class XCallback implements Comparable<XCallback>, IModuleContext {
/**
* Callback priority, higher number means earlier execution.
*
@ -121,6 +122,11 @@ public abstract class XCallback implements Comparable<XCallback> {
/** @hide */
protected void call(Param param) throws Throwable {}
@Override
public String getApkPath() {
return "";
}
/** @hide */
@Override
public int compareTo(XCallback other) {