From 03ec9fa13fb45cbb9ea2a7f99f7c231c748ba543 Mon Sep 17 00:00:00 2001 From: solohsu Date: Sat, 2 Mar 2019 01:07:42 +0800 Subject: [PATCH] Reduce unnecessary IO when using compat mode --- .../riru/xposed/config/ConfigManager.java | 19 ++++-- .../riru/xposed/dexmaker/DexMakerUtils.java | 5 +- .../riru/xposed/dexmaker/DynamicBridge.java | 60 ++++++++++++------- .../riru/xposed/dexmaker/HookerDexMaker.java | 6 +- .../riru/xposed/util/FileUtils.java | 10 +++- .../riru/xposed/util/ProcessUtils.java | 4 ++ 6 files changed, 68 insertions(+), 36 deletions(-) diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java index 13446f75..784cdfb1 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/config/ConfigManager.java @@ -1,6 +1,7 @@ package com.elderdrivers.riru.xposed.config; import java.util.Collections; +import java.util.HashMap; import java.util.Set; import de.robv.android.xposed.SELinuxHelper; @@ -16,24 +17,32 @@ public class ConfigManager { private static final String USE_WHITE_LIST = INSTALLER_DATA_BASE_DIR + "conf/usewhitelist"; private static final String DYNAMIC_MODULES = INSTALLER_DATA_BASE_DIR + "conf/dynamicmodules"; private static final Set WHITE_LIST = Collections.singleton(INSTALLER_PACKAGE_NAME); + private static final HashMap compatModeCache = new HashMap<>(); private static volatile boolean IS_DYNAMIC_MODULES = false; + public static boolean isDynamicModulesMode() { + return IS_DYNAMIC_MODULES; + } + public static synchronized void setDynamicModulesMode(boolean isDynamicModulesMode) { if (isDynamicModulesMode != IS_DYNAMIC_MODULES) { IS_DYNAMIC_MODULES = isDynamicModulesMode; } } - public static boolean isDynamicModulesMode() { - return IS_DYNAMIC_MODULES; - } - public static boolean shouldUseWhitelist() { return isFileExists(USE_WHITE_LIST); } public static boolean shouldUseCompatMode(String packageName) { - return isFileExists(COMPAT_LIST_PATH + packageName); + Boolean result; + if (compatModeCache.containsKey(packageName) + && (result = compatModeCache.get(packageName)) != null) { + return result; + } + result = isFileExists(COMPAT_LIST_PATH + packageName); + compatModeCache.put(packageName, result); + return result; } public static boolean shouldHook(String packageName) { diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java index 671561f0..47eebdcd 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DexMakerUtils.java @@ -10,7 +10,6 @@ import com.elderdrivers.riru.xposed.config.ConfigManager; import java.util.HashMap; import java.util.Map; -import de.robv.android.xposed.SELinuxHelper; import external.com.android.dx.Code; import external.com.android.dx.Local; import external.com.android.dx.TypeId; @@ -156,8 +155,8 @@ public class DexMakerUtils { code.loadConstant(booleanLocal, false); code.loadConstant(byteLocal, (byte) 0); code.loadConstant(charLocal, '\0'); - code.loadConstant(doubleLocal,0.0); - code.loadConstant(floatLocal,0.0f); + code.loadConstant(doubleLocal, 0.0); + code.loadConstant(floatLocal, 0.0f); code.loadConstant(intLocal, 0); code.loadConstant(longLocal, 0L); code.loadConstant(shortLocal, (short) 0); diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java index 4face33d..f9aca719 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/DynamicBridge.java @@ -27,6 +27,10 @@ public final class DynamicBridge { private static File dexDir; private static File dexOptDir; + /** + * Reset dexPathInited flag once we enter child process + * since it might have been set to true in zygote process + */ public static void onForkPost() { dexPathInited.set(false); } @@ -46,36 +50,48 @@ public final class DynamicBridge { try { // for Android Oreo and later use InMemoryClassLoader if (!shouldUseInMemoryHook()) { - // using file based DexClassLoader - if (dexPathInited.compareAndSet(false, true)) { - // delete previous compiled dex to prevent potential crashing - // TODO find a way to reuse them in consideration of performance - try { - // we always choose to use device encrypted storage data on android N and later - // in case some app is installing hooks before phone is unlocked - String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/"; - dexDir = new File(fixedAppDataDir, "/cache/edhookers/" - + getCurrentProcessName().replace(":", "_") + "/"); - dexOptDir = new File(dexDir, "oat"); - dexDir.mkdirs(); - DexLog.d(Main.appProcessName + " deleting dir: " + dexOptDir.getAbsolutePath()); - try { - FileUtils.delete(dexOptDir); - } catch (Throwable throwable) { - } - } catch (Throwable throwable) { - DexLog.e("error when init dex path", throwable); - } - } + setupDexCachePath(); } dexMaker.start(hookMethod, additionalHookInfo, - hookMethod.getDeclaringClass().getClassLoader(), dexDir == null ? null : dexDir.getAbsolutePath()); + hookMethod.getDeclaringClass().getClassLoader(), getDexDirPath()); hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod()); } catch (Exception e) { DexLog.e("error occur when generating dex. dexDir=" + dexDir, e); } } + private static String getDexDirPath() { + if (dexDir == null) { + return null; + } + return dexDir.getAbsolutePath(); + } + + private static void setupDexCachePath() { + // using file based DexClassLoader + if (!dexPathInited.compareAndSet(false, true)) { + return; + } + // delete previous compiled dex to prevent potential crashing + // TODO find a way to reuse them in consideration of performance + try { + // we always choose to use device encrypted storage data on android N and later + // in case some app is installing hooks before phone is unlocked + String fixedAppDataDir = getDataPathPrefix() + getPackageName(Main.appDataDir) + "/"; + dexDir = new File(fixedAppDataDir, "/cache/edhookers/" + + getCurrentProcessName().replace(":", "_") + "/"); + dexOptDir = new File(dexDir, "oat"); + dexDir.mkdirs(); + DexLog.d(Main.appProcessName + " deleting dir: " + dexOptDir.getAbsolutePath()); + try { + FileUtils.delete(dexOptDir); + } catch (Throwable throwable) { + } + } catch (Throwable throwable) { + DexLog.e("error when init dex path", throwable); + } + } + private static boolean checkMember(Member member) { if (member instanceof Method) { diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java index fdaf8146..163ca26a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/dexmaker/HookerDexMaker.java @@ -30,7 +30,6 @@ import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoBoxIfNeces import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.autoUnboxIfNecessary; import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.createResultLocals; import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.getObjTypeIdIfPrimitive; -import static com.elderdrivers.riru.xposed.dexmaker.DexMakerUtils.shouldUseInMemoryHook; public class HookerDexMaker { @@ -193,14 +192,11 @@ public class HookerDexMaker { generateCallBackupMethod(); ClassLoader loader; - if (shouldUseInMemoryHook()) { + if (TextUtils.isEmpty(mDexDirPath)) { // in memory dex classloader byte[] dexBytes = mDexMaker.generate(); loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader); } else { - if (TextUtils.isEmpty(mDexDirPath)) { - throw new IllegalArgumentException("dexDirPath should not be empty!!!"); - } // Create the dex file and load it. loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath)); } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java index dc34d07d..4443380a 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/FileUtils.java @@ -1,6 +1,8 @@ package com.elderdrivers.riru.xposed.util; +import android.annotation.SuppressLint; import android.os.Build; +import android.os.Process; import android.text.TextUtils; import java.io.BufferedReader; @@ -10,6 +12,8 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import static com.elderdrivers.riru.xposed.util.ProcessUtils.PER_USER_RANGE; + public class FileUtils { public static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N; @@ -71,7 +75,11 @@ public class FileUtils { return dataDir.substring(lastIndex + 1); } + // FIXME: Although multi-users is considered here, but compat mode doesn't support other users' apps on Oreo and later yet. + @SuppressLint("SdCardPath") public static String getDataPathPrefix() { - return IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data/"; + int userId = Process.myUid() / PER_USER_RANGE; + String format = IS_USING_PROTECTED_STORAGE ? "/data/user_de/%d/" : "/data/user/%d/"; + return String.format(format, userId); } } diff --git a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java index f5442ec8..3021b38f 100644 --- a/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java +++ b/Bridge/src/main/java/com/elderdrivers/riru/xposed/util/ProcessUtils.java @@ -12,6 +12,10 @@ import java.io.InputStreamReader; public class ProcessUtils { + // Copied from UserHandle, indicates range of uids allocated for a user. + public static final int PER_USER_RANGE = 100000; + public static final int USER_SYSTEM = 0; + public static String getCurrentProcessName() { String prettyName = Main.appProcessName; if (!TextUtils.isEmpty(prettyName)) {