From f6d2e3b62f5631597714fc477fbc7350570bf563 Mon Sep 17 00:00:00 2001 From: solohsu Date: Wed, 19 Jun 2019 15:34:51 +0800 Subject: [PATCH] Optimize module loading under dynamic-modules mode by only loading newly added or updated modules --- appveyor.yml | 2 +- .../riru/edxp/proxy/BaseRouter.java | 4 +- .../riru/edxp/proxy/BlackWhiteListProxy.java | 18 +-- .../riru/edxp/proxy/NormalProxy.java | 10 +- .../elderdrivers/riru/edxp/proxy/Router.java | 2 +- edxp-core/build.gradle | 2 +- .../src/main/cpp/main/src/edxp_context.cpp | 1 + .../edxp/sandhook/core/SandHookRouter.java | 1 - .../riru/edxp/whale/core/WhaleRouter.java | 12 -- .../riru/edxp/yahfa/core/YahfaRouter.java | 3 - hiddenapi-stubs/libs/framework-stub.jar | Bin 15372 -> 15372 bytes .../robv/android/xposed/IModuleContext.java | 6 + .../IXposedHookInitPackageResources.java | 11 +- .../xposed/IXposedHookLoadPackage.java | 10 +- .../android/xposed/IXposedHookZygoteInit.java | 5 + .../de/robv/android/xposed/XposedBridge.java | 6 + .../de/robv/android/xposed/XposedInit.java | 150 +++++++++++++----- .../android/xposed/callbacks/XCallback.java | 8 +- 18 files changed, 173 insertions(+), 78 deletions(-) create mode 100644 xposed-bridge/src/main/java/de/robv/android/xposed/IModuleContext.java diff --git a/appveyor.yml b/appveyor.yml index c149e1ef..1d2c9d20 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BaseRouter.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BaseRouter.java index a40bb8fe..98ba85cc 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BaseRouter.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BaseRouter.java @@ -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); } diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BlackWhiteListProxy.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BlackWhiteListProxy.java index 217a48d1..bca49ce9 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BlackWhiteListProxy.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/BlackWhiteListProxy.java @@ -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(); } } diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java index c28ff453..1777fb98 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/NormalProxy.java @@ -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(); } diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/Router.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/Router.java index 223524dc..1467bcf3 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/Router.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/proxy/Router.java @@ -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); diff --git a/edxp-core/build.gradle b/edxp-core/build.gradle index 72a04dcc..8ad002e6 100644 --- a/edxp-core/build.gradle +++ b/edxp-core/build.gradle @@ -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) { diff --git a/edxp-core/src/main/cpp/main/src/edxp_context.cpp b/edxp-core/src/main/cpp/main/src/edxp_context.cpp index 9b4b1823..6b49baf6 100644 --- a/edxp-core/src/main/cpp/main/src/edxp_context.cpp +++ b/edxp-core/src/main/cpp/main/src/edxp_context.cpp @@ -120,6 +120,7 @@ namespace edxp { variant_ = static_cast(variant); } } + LOGI("EdxpVariant: %d", variant_); initialized_ = true; diff --git a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/core/SandHookRouter.java b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/core/SandHookRouter.java index 3710353c..39ba4d76 100644 --- a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/core/SandHookRouter.java +++ b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/core/SandHookRouter.java @@ -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()); } } diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/core/WhaleRouter.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/core/WhaleRouter.java index 3d7cc11d..37940496 100644 --- a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/core/WhaleRouter.java +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/core/WhaleRouter.java @@ -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"); diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/YahfaRouter.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/YahfaRouter.java index 08c88fe4..03a6b811 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/YahfaRouter.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/core/YahfaRouter.java @@ -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()); } } diff --git a/hiddenapi-stubs/libs/framework-stub.jar b/hiddenapi-stubs/libs/framework-stub.jar index e461d96f401c1f282c62314e301e1f557d51ec4e..4d48c364f229f721022a892821c1fe7a4f87251b 100644 GIT binary patch delta 784 zcmeCF=&9fh@MdP=VqoClU~rARJdsz8`S`|*6V1H9RI>`0TBQx5Hs09J1fnO;Vg?IO zKFF*E;%|P(T+Rq)WUw9tGbZb>tAbUAuxo-P8rX%wS~pMQ+{q{i)Z@nh1WX_T2m~f8 z@(Rl`Gay`ou0j~7LX00x#pbQt(TuzxrTc_WH5_DQU^q9KPg;Dkfr;2=FW%D-CrR)Z zLY&kiUkGL2gZWAp`d~W5!UjxFwr~a0w=I0Yw3cNwnC`I50n^_tA^tG5ascy7 XtRU+5SV8@^G<=a6V1H9RI>`0TBQx5Hs09J1fnO;Vg?IO zKFF*E;%|P(T+Rq)WUw9tGbZb>tAbUAuxo-P8rX%wS~pMQ+{q{i)Z@nh1WX_T2m~f8 z@(Rl`Gay`ou0j~7LX00x#pbQt(TuzxrTc_WH5_DQU^q9KPg;Dkfr;2=FW%D-CrR)Z zLY&kiUkGL2gZWAp`d~W5!UjxFwr~a0w=I0Yw3cNwnC`I50n^_tA^tG5ascy7 XtRU+5SV8INSTALLER_DATA_BASE_DIR/conf/modules.list */ - 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 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 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 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 assets/xposed_init. */ - 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); diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/callbacks/XCallback.java b/xposed-bridge/src/main/java/de/robv/android/xposed/callbacks/XCallback.java index b8976e57..6e6663d2 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/callbacks/XCallback.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/callbacks/XCallback.java @@ -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 { +public abstract class XCallback implements Comparable, IModuleContext { /** * Callback priority, higher number means earlier execution. * @@ -121,6 +122,11 @@ public abstract class XCallback implements Comparable { /** @hide */ protected void call(Param param) throws Throwable {} + @Override + public String getApkPath() { + return ""; + } + /** @hide */ @Override public int compareTo(XCallback other) {