From ba6c61fc1e508cd905d4aecbb23cb4c775d202ad Mon Sep 17 00:00:00 2001 From: LoveSy Date: Thu, 24 Dec 2020 03:05:59 +0800 Subject: [PATCH] Optimize dexmaker for yahfa --- .../riru/edxp/proxy/NormalProxy.java | 2 +- .../riru/edxp/util/FileUtils.java | 2 +- .../edxp/yahfa/dexmaker/DynamicBridge.java | 31 +- .../edxp/yahfa/dexmaker/HookerDexMaker.java | 325 ++---------------- .../java/de/robv/android/xposed/EdHooker.java | 113 ++++++ 5 files changed, 150 insertions(+), 323 deletions(-) create mode 100644 edxp-yahfa/src/main/java/de/robv/android/xposed/EdHooker.java 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 107778d9..ab1d3771 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 @@ -41,9 +41,9 @@ public class NormalProxy extends BaseProxy { mRouter.initResourcesHook(); mRouter.prepare(isSystem); PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote - mRouter.installBootstrapHooks(isSystem); ConfigManager.appDataDir = appDataDir; ConfigManager.niceName = niceName; + mRouter.installBootstrapHooks(isSystem); XposedInit.prefsBasePath = ConfigManager.getPrefsPath(""); mRouter.onEnterChildProcess(); Utils.logI("Loading modules for " + niceName); diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/FileUtils.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/FileUtils.java index 968f73bb..e1bd0d48 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/FileUtils.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/util/FileUtils.java @@ -72,6 +72,6 @@ public class FileUtils { } public static String getDataPathPrefix() { - return ConfigManager.getDataPathPrefix(); + return ConfigManager.getDataPathPrefix() + "/"; } } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DynamicBridge.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DynamicBridge.java index 3287ab4f..f82975c2 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DynamicBridge.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/DynamicBridge.java @@ -12,6 +12,7 @@ import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.concurrent.atomic.AtomicBoolean; +import de.robv.android.xposed.EdHooker; import de.robv.android.xposed.XposedBridge; import static com.elderdrivers.riru.edxp.util.FileUtils.getDataPathPrefix; @@ -21,7 +22,7 @@ import static com.elderdrivers.riru.edxp.yahfa.dexmaker.DexMakerUtils.shouldUseI public final class DynamicBridge { - private static final HashMap hookedInfo = new HashMap<>(); + private static final HashMap hookedInfo = new HashMap<>(); private static final HookerDexMaker dexMaker = new HookerDexMaker(); private static final AtomicBoolean dexPathInited = new AtomicBoolean(false); private static File dexDir; @@ -47,6 +48,7 @@ public final class DynamicBridge { } DexLog.d("start to generate class for: " + hookMethod); + long startTime = System.nanoTime(); try { // for Android Oreo and later use InMemoryClassLoader if (!shouldUseInMemoryHook()) { @@ -54,10 +56,12 @@ public final class DynamicBridge { } dexMaker.start(hookMethod, additionalHookInfo, hookMethod.getDeclaringClass().getClassLoader(), getDexDirPath()); - hookedInfo.put(hookMethod, dexMaker.getCallBackupMethod()); + hookedInfo.put(hookMethod, dexMaker.getHooker()); } catch (Exception e) { DexLog.e("error occur when generating dex. dexDir=" + dexDir, e); } + long endTime = System.nanoTime(); + DexLog.d("generated class for " + hookMethod + " in " + ((endTime-startTime) * 1.e-6) + "ms"); } private static String getDexDirPath() { @@ -106,28 +110,11 @@ public final class DynamicBridge { public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException { - Method callBackup = hookedInfo.get(method); - if (callBackup == null) { + EdHooker hooker = hookedInfo.get(method); + if (hooker == null) { throw new IllegalStateException("method not hooked, cannot call original method."); } - if (!Modifier.isStatic(callBackup.getModifiers())) { - throw new IllegalStateException("original method is not static, something must be wrong!"); - } - callBackup.setAccessible(true); - if (args == null) { - args = new Object[0]; - } - final int argsSize = args.length; - if (Modifier.isStatic(method.getModifiers())) { - return callBackup.invoke(null, args); - } else { - Object[] newArgs = new Object[argsSize + 1]; - newArgs[0] = thisObject; - for (int i = 1; i < newArgs.length; i++) { - newArgs[i] = args[i - 1]; - } - return callBackup.invoke(null, newArgs); - } + return hooker.callBackup(thisObject, args); } } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/HookerDexMaker.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/HookerDexMaker.java index 7ded6af1..20141746 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/HookerDexMaker.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/dexmaker/HookerDexMaker.java @@ -4,7 +4,6 @@ import android.annotation.TargetApi; import android.os.Build; import android.text.TextUtils; -import com.elderdrivers.riru.edxp.core.Yahfa; import com.elderdrivers.riru.edxp.core.yahfa.HookMain; import com.elderdrivers.riru.edxp.util.ProxyClassLoader; import com.elderdrivers.riru.edxp.yahfa.BuildConfig; @@ -19,14 +18,11 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import dalvik.system.InMemoryDexClassLoader; -import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.EdHooker; import de.robv.android.xposed.XposedBridge; -import external.com.android.dx.BinaryOp; import external.com.android.dx.Code; -import external.com.android.dx.Comparison; import external.com.android.dx.DexMaker; import external.com.android.dx.FieldId; -import external.com.android.dx.Label; import external.com.android.dx.Local; import external.com.android.dx.MethodId; import external.com.android.dx.TypeId; @@ -40,7 +36,6 @@ public class HookerDexMaker { public static final String METHOD_NAME_BACKUP = "backup"; public static final String METHOD_NAME_HOOK = "hook"; - public static final String METHOD_NAME_CALL_BACKUP = "callBackup"; public static final String METHOD_NAME_SETUP = "setup"; public static final TypeId objArrayTypeId = TypeId.get(Object[].class); private static final String CLASS_DESC_PREFIX = "L"; @@ -48,49 +43,14 @@ public class HookerDexMaker { * Note: this identifier is used in native codes to pass class access verification. */ private static final String CLASS_NAME_PREFIX = "EdHooker_"; - private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo"; - private static final String FIELD_NAME_METHOD = "method"; - private static final String PARAMS_FIELD_NAME_METHOD = "method"; - private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject"; - private static final String PARAMS_FIELD_NAME_ARGS = "args"; - private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod"; - private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod"; - private static final String PARAMS_METHOD_NAME_IS_EARLY_RETURN = "isEarlyReturn"; - private static final TypeId throwableTypeId = TypeId.get(Throwable.class); - private static final TypeId memberTypeId = TypeId.get(Member.class); - private static final TypeId callbackTypeId = TypeId.get(XC_MethodHook.class); - private static final TypeId hookInfoTypeId - = TypeId.get(XposedBridge.AdditionalHookInfo.class); - private static final TypeId callbacksTypeId - = TypeId.get(XposedBridge.CopyOnWriteSortedSet.class); - private static final TypeId paramTypeId - = TypeId.get(XC_MethodHook.MethodHookParam.class); - private static final MethodId setResultMethodId = - paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT); - private static final MethodId setThrowableMethodId = - paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId); - private static final MethodId getResultMethodId = - paramTypeId.getMethod(TypeId.OBJECT, "getResult"); - private static final MethodId getThrowableMethodId = - paramTypeId.getMethod(throwableTypeId, "getThrowable"); - private static final MethodId hasThrowableMethodId = - paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable"); - private static final MethodId callAfterCallbackMethodId = - callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId); - private static final MethodId callBeforeCallbackMethodId = - callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId); - private static final FieldId returnEarlyFieldId = - paramTypeId.getField(TypeId.BOOLEAN, "returnEarly"); - private static final TypeId xposedBridgeTypeId = TypeId.get(XposedBridge.class); - private static final MethodId logThrowableMethodId = - xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId); - private static final MethodId logStrMethodId = - xposedBridgeTypeId.getMethod(TypeId.VOID, "log", TypeId.STRING); + private static final String FIELD_NAME_HOOKER = "hooker"; + private static final TypeId hookerTypeId = TypeId.get(EdHooker.class); + private static final MethodId handleHookedMethodMethodId = + hookerTypeId.getMethod(TypeId.OBJECT, "handleHookedMethod", objArrayTypeId); private static AtomicLong sClassNameSuffix = new AtomicLong(1); - private FieldId mHookInfoFieldId; - private FieldId mMethodFieldId; + private FieldId mHookerFieldId; private MethodId mBackupMethodId; private MethodId mCallBackupMethodId; private MethodId mHookMethodId; @@ -101,8 +61,6 @@ public class HookerDexMaker { private Class mReturnType; private TypeId mReturnTypeId; private boolean mIsStatic; - // TODO use this to generate methods - private boolean mHasThrowable; private DexMaker mDexMaker; private Member mMember; @@ -111,8 +69,8 @@ public class HookerDexMaker { private Class mHookClass; private Method mHookMethod; private Method mBackupMethod; - private Method mCallBackupMethod; private String mDexDirPath; + private EdHooker mHooker; private static TypeId[] getParameterTypeIds(Class[] parameterTypes, boolean isStatic) { int parameterSize = parameterTypes.length; @@ -158,7 +116,6 @@ public class HookerDexMaker { } mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic); mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic); - mHasThrowable = method.getExceptionTypes().length > 0; } else if (member instanceof Constructor) { Constructor constructor = (Constructor) member; mIsStatic = false; @@ -166,7 +123,6 @@ public class HookerDexMaker { mReturnTypeId = TypeId.VOID; mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic); mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic); - mHasThrowable = constructor.getExceptionTypes().length > 0; } else if (member.getDeclaringClass().isInterface()) { throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString()); } else if (Modifier.isAbstract(member.getModifiers())) { @@ -219,12 +175,10 @@ public class HookerDexMaker { mHookClass = Class.forName(className.replace("/", "."), true, loader); // Execute our newly-generated code in-process. - mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, XposedBridge.AdditionalHookInfo.class) - .invoke(null, mMember, mHookInfo); - mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes); mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes); - mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes); - Yahfa.setMethodNonCompilable(mCallBackupMethod); + mHooker = new EdHooker(mHookInfo, mMember, mBackupMethod, mIsStatic); + mHookClass.getMethod(METHOD_NAME_SETUP, EdHooker.class).invoke(null, mHooker); + mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes); HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod); } @@ -236,7 +190,6 @@ public class HookerDexMaker { generateSetupMethod(); generateBackupMethod(); generateHookMethod(); - generateCallBackupMethod(); } public Method getHookMethod() { @@ -247,32 +200,26 @@ public class HookerDexMaker { return mBackupMethod; } - public Method getCallBackupMethod() { - return mCallBackupMethod; - } - public Class getHookClass() { return mHookClass; } + public EdHooker getHooker() {return mHooker;} + private void generateFields() { - mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO); - mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD); - mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null); - mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null); + mHookerFieldId = mHookerTypeId.getField(hookerTypeId, FIELD_NAME_HOOKER); + mDexMaker.declare(mHookerFieldId, Modifier.STATIC, null); } private void generateSetupMethod() { MethodId setupMethodId = mHookerTypeId.getMethod( - TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, hookInfoTypeId); + TypeId.VOID, METHOD_NAME_SETUP, hookerTypeId); Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC); // init logic // get parameters - Local method = code.getParameter(0, memberTypeId); - Local hookInfo = code.getParameter(1, hookInfoTypeId); + Local hooker = code.getParameter(0, hookerTypeId); // save params to static - code.sput(mMethodFieldId, method); - code.sput(mHookInfoFieldId, hookInfo); + code.sput(mHookerFieldId, hooker); code.returnVoid(); } @@ -289,253 +236,48 @@ public class HookerDexMaker { } } - private void generateCallBackupMethod() { - mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds); - Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC); - // just call backup and return its result - Local[] allArgsLocals = createParameterLocals(code); - Map resultLocals = createResultLocals(code); - if (mReturnTypeId.equals(TypeId.VOID)) { - code.invokeStatic(mBackupMethodId, null, allArgsLocals); - code.returnVoid(); - } else { - Local result = resultLocals.get(mReturnTypeId); - code.invokeStatic(mBackupMethodId, result, allArgsLocals); - code.returnValue(result); - } - } - private void generateHookMethod() { mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds); Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC); // code starts - - // prepare common labels - Label noHookReturn = new Label(); - Label incrementAndCheckBefore = new Label(); - Label tryBeforeCatch = new Label(); - Label noExceptionBefore = new Label(); - Label checkAndCallBackup = new Label(); - Label beginCallBefore = new Label(); - Label beginCallAfter = new Label(); - Label tryOrigCatch = new Label(); - Label noExceptionOrig = new Label(); - Label tryAfterCatch = new Label(); - Label decrementAndCheckAfter = new Label(); - Label noBackupThrowable = new Label(); - Label throwThrowable = new Label(); // prepare locals - Local disableHooks = code.newLocal(TypeId.BOOLEAN); - Local hookInfo = code.newLocal(hookInfoTypeId); - Local callbacks = code.newLocal(callbacksTypeId); - Local snapshot = code.newLocal(objArrayTypeId); - Local snapshotLen = code.newLocal(TypeId.INT); - Local callbackObj = code.newLocal(TypeId.OBJECT); - Local callback = code.newLocal(callbackTypeId); - + Local hooker = code.newLocal(hookerTypeId); Local resultObj = code.newLocal(TypeId.OBJECT); // as a temp Local - Local one = code.newLocal(TypeId.INT); - Local nullObj = code.newLocal(TypeId.OBJECT); - Local throwable = code.newLocal(throwableTypeId); - - Local param = code.newLocal(paramTypeId); - Local method = code.newLocal(memberTypeId); - Local thisObject = code.newLocal(TypeId.OBJECT); Local args = code.newLocal(objArrayTypeId); - Local returnEarly = code.newLocal(TypeId.BOOLEAN); Local actualParamSize = code.newLocal(TypeId.INT); Local argIndex = code.newLocal(TypeId.INT); - Local beforeIdx = code.newLocal(TypeId.INT); - Local lastResult = code.newLocal(TypeId.OBJECT); - Local lastThrowable = code.newLocal(throwableTypeId); - Local hasThrowable = code.newLocal(TypeId.BOOLEAN); - Local[] allArgsLocals = createParameterLocals(code); Map resultLocals = createResultLocals(code); code.loadConstant(args, null); code.loadConstant(argIndex, 0); - code.loadConstant(one, 1); - code.loadConstant(snapshotLen, 0); - code.loadConstant(nullObj, null); - // check XposedBridge.disableHooks flag - - FieldId disableHooksField = - xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks"); - code.sget(disableHooksField, disableHooks); - // disableHooks == true => no hooking - code.compareZ(Comparison.NE, noHookReturn, disableHooks); - - // check callbacks length - code.sget(mHookInfoFieldId, hookInfo); - code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo); - code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks); - code.arrayLength(snapshotLen, snapshot); - // snapshotLen == 0 => no hooking - code.compareZ(Comparison.EQ, noHookReturn, snapshotLen); + code.sget(mHookerFieldId, hooker); // start hooking - // prepare hooking locals int paramsSize = mParameterTypeIds.length; - int offset = 0; - // thisObject - if (mIsStatic) { - // thisObject = null - code.loadConstant(thisObject, null); - } else { - // thisObject = args[0] - offset = 1; - code.move(thisObject, allArgsLocals[0]); - } - // actual args (exclude thisObject if this is not a static method) - code.loadConstant(actualParamSize, paramsSize - offset); + code.loadConstant(actualParamSize, paramsSize); code.newArray(args, actualParamSize); - for (int i = offset; i < paramsSize; i++) { + for (int i = 0; i < paramsSize; i++) { Local parameter = allArgsLocals[i]; // save parameter to resultObj as Object autoBoxIfNecessary(code, resultObj, parameter); - code.loadConstant(argIndex, i - offset); + code.loadConstant(argIndex, i); // save Object to args code.aput(args, argIndex, resultObj); } - // create param - code.newInstance(param, paramTypeId.getConstructor()); - // set method, thisObject, args - code.sget(mMethodFieldId, method); - code.iput(paramTypeId.getField(memberTypeId, "method"), param, method); - code.iput(paramTypeId.getField(TypeId.OBJECT, "thisObject"), param, thisObject); - code.iput(paramTypeId.getField(objArrayTypeId, "args"), param, args); - - // call beforeCallbacks - code.loadConstant(beforeIdx, 0); - - code.mark(beginCallBefore); - // start of try - code.addCatchClause(throwableTypeId, tryBeforeCatch); - - code.aget(callbackObj, snapshot, beforeIdx); - code.cast(callback, callbackObj); - code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param); - code.jump(noExceptionBefore); - - // end of try - code.removeCatchClause(throwableTypeId); - - // start of catch - code.mark(tryBeforeCatch); - code.moveException(throwable); - code.invokeStatic(logThrowableMethodId, null, throwable); - code.invokeVirtual(setResultMethodId, null, param, nullObj); - code.loadConstant(returnEarly, false); - code.iput(returnEarlyFieldId, param, returnEarly); - code.jump(incrementAndCheckBefore); - - // no exception when calling beforeCallbacks - code.mark(noExceptionBefore); - code.iget(returnEarlyFieldId, returnEarly, param); - // if returnEarly == false, continue - code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly); - // returnEarly == true, break - code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one); - code.jump(checkAndCallBackup); - - // increment and check to continue - code.mark(incrementAndCheckBefore); - code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one); - code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen); - - // check and call backup - code.mark(checkAndCallBackup); - code.iget(returnEarlyFieldId, returnEarly, param); - // if returnEarly == true, go to call afterCallbacks directly - code.compareZ(Comparison.NE, noExceptionOrig, returnEarly); - // try to call backup - // try start - code.addCatchClause(throwableTypeId, tryOrigCatch); - // we have to load args[] to paramLocals - // because args[] may be changed in beforeHookedMethod - // should consider first param is thisObj if hooked method is not static - offset = mIsStatic ? 0 : 1; - for (int i = offset; i < allArgsLocals.length; i++) { - code.loadConstant(argIndex, i - offset); - code.aget(resultObj, args, argIndex); - autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true); - } - // get pre-created Local with a matching typeId - if (mReturnTypeId.equals(TypeId.VOID)) { - code.invokeStatic(mBackupMethodId, null, allArgsLocals); - // TODO maybe keep preset result to do some magic? - code.invokeVirtual(setResultMethodId, null, param, nullObj); - } else { - Local returnedResult = resultLocals.get(mReturnTypeId); - code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals); - // save returnedResult to resultObj as a Object - autoBoxIfNecessary(code, resultObj, returnedResult); - // save resultObj to param - code.invokeVirtual(setResultMethodId, null, param, resultObj); - } - // go to call afterCallbacks - code.jump(noExceptionOrig); - // try end - code.removeCatchClause(throwableTypeId); - // catch - code.mark(tryOrigCatch); - code.moveException(throwable); - // exception occurred when calling backup, save throwable to param - code.invokeVirtual(setThrowableMethodId, null, param, throwable); - - code.mark(noExceptionOrig); - code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one); - - // call afterCallbacks - code.mark(beginCallAfter); - // save results of backup calling - code.invokeVirtual(getResultMethodId, lastResult, param); - code.invokeVirtual(getThrowableMethodId, lastThrowable, param); - // try start - code.addCatchClause(throwableTypeId, tryAfterCatch); - code.aget(callbackObj, snapshot, beforeIdx); - code.cast(callback, callbackObj); - code.invokeVirtual(callAfterCallbackMethodId, null, callback, param); - // all good, just continue - code.jump(decrementAndCheckAfter); - // try end - code.removeCatchClause(throwableTypeId); - // catch - code.mark(tryAfterCatch); - code.moveException(throwable); - code.invokeStatic(logThrowableMethodId, null, throwable); - // if lastThrowable == null, go to recover lastResult - code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable); - // lastThrowable != null, recover lastThrowable - code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable); - // continue - code.jump(decrementAndCheckAfter); - code.mark(noBackupThrowable); - // recover lastResult and continue - code.invokeVirtual(setResultMethodId, null, param, lastResult); - // decrement and check continue - code.mark(decrementAndCheckAfter); - code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one); - code.compareZ(Comparison.GE, beginCallAfter, beforeIdx); - - // callbacks end - // return - code.invokeVirtual(hasThrowableMethodId, hasThrowable, param); - // if hasThrowable, throw the throwable and return - code.compareZ(Comparison.NE, throwThrowable, hasThrowable); - // return getResult + // handleHookedMethod if (mReturnTypeId.equals(TypeId.VOID)) { + code.invokeVirtual(handleHookedMethodMethodId, null, hooker, args); code.returnVoid(); } else { - // getResult always return an Object, so save to resultObj - code.invokeVirtual(getResultMethodId, resultObj, param); + // hooker always return an Object, so save to resultObj + code.invokeVirtual(handleHookedMethodMethodId, resultObj, hooker, args); // have to unbox it if returnType is primitive // casting Object TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId); @@ -547,21 +289,6 @@ public class HookerDexMaker { // return code.returnValue(toReturn); } - // throw throwable - code.mark(throwThrowable); - code.invokeVirtual(getThrowableMethodId, throwable, param); - code.throwValue(throwable); - - // call backup and return - code.mark(noHookReturn); - if (mReturnTypeId.equals(TypeId.VOID)) { - code.invokeStatic(mBackupMethodId, null, allArgsLocals); - code.returnVoid(); - } else { - Local result = resultLocals.get(mReturnTypeId); - code.invokeStatic(mBackupMethodId, result, allArgsLocals); - code.returnValue(result); - } } private Local[] createParameterLocals(Code code) { diff --git a/edxp-yahfa/src/main/java/de/robv/android/xposed/EdHooker.java b/edxp-yahfa/src/main/java/de/robv/android/xposed/EdHooker.java new file mode 100644 index 00000000..1090e657 --- /dev/null +++ b/edxp-yahfa/src/main/java/de/robv/android/xposed/EdHooker.java @@ -0,0 +1,113 @@ +package de.robv.android.xposed; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; + +import static de.robv.android.xposed.XposedBridge.disableHooks; + +public class EdHooker { + private final XposedBridge.AdditionalHookInfo additionalInfo; + private final Member method; + private final Method backup; + private final boolean isStatic; + + public EdHooker(XposedBridge.AdditionalHookInfo info, Member origin, Method backup, boolean isStatic) { + this.additionalInfo = info; + this.method = origin; + this.backup = backup; + this.isStatic = isStatic; + } + + public Object callBackup(Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException { + if (args == null) { + args = new Object[0]; + } + if (isStatic) { + return backup.invoke(null, args); + } else { + Object[] newArgs = new Object[args.length + 1]; + newArgs[0] = thisObject; + System.arraycopy(args, 0, newArgs, 1, args.length); + return backup.invoke(null, newArgs); + } + } + + @SuppressWarnings({"unused", "RedundantSuppression"}) + public Object handleHookedMethod(Object[] args) throws Throwable { + if (disableHooks) { + return backup.invoke(null, args); + } + + Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot(); + final int callbacksLength = callbacksSnapshot.length; + if (callbacksLength == 0) { + return backup.invoke(null, args); + } + + XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam(); + + param.method = method; + + if (isStatic) { + param.thisObject = null; + param.args = args; + } else { + param.thisObject = args[0]; + param.args = new Object[args.length - 1]; + System.arraycopy(args, 1, param.args, 0, args.length - 1); + } + + // call "before method" callbacks + int beforeIdx = 0; + do { + try { + ((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param); + } catch (Throwable t) { + XposedBridge.log(t); + + // reset result (ignoring what the unexpectedly exiting callback did) + param.setResult(null); + param.returnEarly = false; + continue; + } + + if (param.returnEarly) { + // skip remaining "before" callbacks and corresponding "after" callbacks + beforeIdx++; + break; + } + } while (++beforeIdx < callbacksLength); + + // call original method if not requested otherwise + if (!param.returnEarly) { + param.setResult(callBackup(param.thisObject, param.args)); + } + + // call "after method" callbacks + int afterIdx = beforeIdx - 1; + do { + Object lastResult = param.getResult(); + Throwable lastThrowable = param.getThrowable(); + + try { + ((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param); + } catch (Throwable t) { + XposedBridge.log(t); + + // reset to last result (ignoring what the unexpectedly exiting callback did) + if (lastThrowable == null) + param.setResult(lastResult); + else + param.setThrowable(lastThrowable); + } + } while (--afterIdx >= 0); + + // return + if (param.hasThrowable()) + throw param.getThrowable(); + else + return param.getResult(); + } + +}