Use native containers (#1910)

* Pack parameters

* Use native containers

Co-authored-by: LoveSy <shana@zju.edu.cn>
This commit is contained in:
南宫雪珊 2022-05-03 20:35:45 +08:00 committed by GitHub
parent 6fbffd23b7
commit c63fb7af37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 78 deletions

View File

@ -82,6 +82,20 @@ public final class XposedBridge {
public static volatile ClassLoader dummyClassLoader = null; public static volatile ClassLoader dummyClassLoader = null;
private static final ClassCastException castException = new ClassCastException("Return value's type from hook callback does not match the hooked method");
private static final Method getCause;
static {
Method tmp;
try {
tmp = InvocationTargetException.class.getMethod("getCause");
} catch (Throwable e) {
tmp = null;
}
getCause = tmp;
}
public static void initXResources() { public static void initXResources() {
if (dummyClassLoader != null) { if (dummyClassLoader != null) {
return; return;
@ -381,32 +395,21 @@ public final class XposedBridge {
} }
public static class AdditionalHookInfo { public static class AdditionalHookInfo {
private static final ClassCastException castException = new ClassCastException("Return value's type from hook callback does not match the hooked method"); private final Object params;
private static final Method getCause;
private final Executable method;
private final Object[][] callbacks;
private final Class<?> returnType;
private final boolean isStatic;
static { private AdditionalHookInfo(Executable method) {
Method tmp; var isStatic = Modifier.isStatic(method.getModifiers());
try { Object returnType;
tmp = InvocationTargetException.class.getMethod("getCause");
} catch (Throwable e) {
tmp = null;
}
getCause = tmp;
}
private AdditionalHookInfo(Executable method, Object[][] callbacks) {
this.method = method;
isStatic = Modifier.isStatic(method.getModifiers());
if (method instanceof Method) { if (method instanceof Method) {
returnType = ((Method) method).getReturnType(); returnType = ((Method) method).getReturnType();
} else { } else {
returnType = null; returnType = null;
} }
this.callbacks = callbacks; params = new Object[] {
method,
returnType,
isStatic,
};
} }
// This method is quite critical. We should try not to use system methods to avoid // This method is quite critical. We should try not to use system methods to avoid
@ -414,6 +417,12 @@ public final class XposedBridge {
public Object callback(Object[] args) throws Throwable { public Object callback(Object[] args) throws Throwable {
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam(); XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
var array = ((Object[]) params);
var method = (Executable) array[0];
var returnType = (Class<?>) array[1];
var isStatic = (Boolean) array[2];
param.method = method; param.method = method;
if (isStatic) { if (isStatic) {
@ -428,9 +437,8 @@ public final class XposedBridge {
} }
} }
Object[] callbacksSnapshot = callbacks[0]; Object[] callbacksSnapshot = HookBridge.callbackSnapshot(method);
final int callbacksLength = callbacksSnapshot.length; if (callbacksSnapshot == null || callbacksSnapshot.length == 0) {
if (callbacksLength == 0) {
try { try {
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args); return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
} catch (InvocationTargetException ite) { } catch (InvocationTargetException ite) {
@ -457,7 +465,7 @@ public final class XposedBridge {
beforeIdx++; beforeIdx++;
break; break;
} }
} while (++beforeIdx < callbacksLength); } while (++beforeIdx < callbacksSnapshot.length);
// call original method if not requested otherwise // call original method if not requested otherwise
if (!param.returnEarly) { if (!param.returnEarly) {

View File

@ -18,4 +18,6 @@ public class HookBridge {
@FastNative @FastNative
public static native boolean setTrusted(Object cookie); public static native boolean setTrusted(Object cookie);
public static native Object[] callbackSnapshot(Executable method);
} }

View File

@ -30,8 +30,7 @@ namespace {
struct HookItem { struct HookItem {
jobject backup {nullptr}; jobject backup {nullptr};
jobjectArray callbacks {nullptr}; std::multimap<jint, jobject, std::greater<>> callbacks {};
std::multiset<jint, std::greater<>> priorities {};
}; };
std::shared_mutex hooked_lock; std::shared_mutex hooked_lock;
@ -71,40 +70,19 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod,
if (!hook_item) { if (!hook_item) {
std::unique_lock lk(hooked_lock); std::unique_lock lk(hooked_lock);
hook_item = &hooked_methods[target]; hook_item = &hooked_methods[target];
if (!hook_item->callbacks) {
newHook = true; newHook = true;
hook_item->callbacks = (jobjectArray) env->NewGlobalRef(
env->NewObjectArray(1, env->FindClass("[Ljava/lang/Object;"),
env->NewObjectArray(0, JNI_FindClass(env, "java/lang/Object"), nullptr)));
}
} }
if (newHook) { if (newHook) {
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;[[Ljava/lang/Object;)V"); auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;)V");
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback", auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback",
"([Ljava/lang/Object;)Ljava/lang/Object;"), "([Ljava/lang/Object;)Ljava/lang/Object;"),
false); false);
auto hooker_object = env->NewObject(hooker, init, hookMethod, hook_item->callbacks); auto hooker_object = env->NewObject(hooker, init, hookMethod);
hook_item->backup = lsplant::Hook(env, hookMethod, hooker_object, callback_method); hook_item->backup = lsplant::Hook(env, hookMethod, hooker_object, callback_method);
env->DeleteLocalRef(hooker_object); env->DeleteLocalRef(hooker_object);
} }
env->MonitorEnter(hook_item->callbacks); JNIMonitor monitor(env, hook_item->backup);
auto insert_point = hook_item->priorities.emplace(priority); hook_item->callbacks.emplace(std::make_pair(priority, env->NewGlobalRef(callback)));
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0);
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size()), env->FindClass("java/lang/Object"), nullptr);
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) {
if (current == insert_point) {
env->SetObjectArrayElement(new_array, i, callback);
passed = true;
} else {
auto element = env->GetObjectArrayElement(old_array, i - passed);
env->SetObjectArrayElement(new_array, i, element);
env->DeleteLocalRef(element);
}
}
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
env->DeleteLocalRef(old_array);
env->DeleteLocalRef(new_array);
env->MonitorExit(hook_item->callbacks);
return hook_item->backup ? JNI_TRUE : JNI_FALSE; return hook_item->backup ? JNI_TRUE : JNI_FALSE;
} }
@ -118,34 +96,15 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jo
} }
} }
if (!hook_item) return JNI_FALSE; if (!hook_item) return JNI_FALSE;
JNIMonitor monitor(env, hook_item->callbacks); JNIMonitor monitor(env, hook_item->backup);
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0); for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) {
if (hook_item->priorities.empty()) return JNI_FALSE; if (env->IsSameObject(i->second, callback)) {
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size() - 1), env->FindClass("java/lang/Object"), nullptr); hook_item->callbacks.erase(i);
auto to_remove = hook_item->priorities.end(); return JNI_TRUE;
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) { }
auto element = env->GetObjectArrayElement(old_array, i); }
if (env->IsSameObject(element, callback)) {
to_remove = current;
passed = true;
} else {
if (i - passed >= hook_item->priorities.size() - 1) {
return JNI_FALSE; return JNI_FALSE;
} }
env->SetObjectArrayElement(new_array, i - passed, element);
}
env->DeleteLocalRef(element);
}
bool removed = false;
if (to_remove != hook_item->priorities.end()) {
hook_item->priorities.erase(to_remove);
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
removed = true;
}
env->DeleteLocalRef(old_array);
env->DeleteLocalRef(new_array);
return removed ? JNI_TRUE : JNI_FALSE;
}
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, deoptimizeMethod, jobject hookMethod, LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, deoptimizeMethod, jobject hookMethod,
jclass hooker, jint priority, jobject callback) { jclass hooker, jint priority, jobject callback) {
@ -177,6 +136,24 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, setTrusted, jobject cookie) {
return lsplant::MakeDexFileTrusted(env, cookie); return lsplant::MakeDexFileTrusted(env, cookie);
} }
LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jobject method) {
auto target = env->FromReflectedMethod(method);
HookItem *hook_item = nullptr;
{
std::shared_lock lk(hooked_lock);
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
hook_item = &found->second;
}
}
if (!hook_item) return nullptr;
JNIMonitor monitor(env, hook_item->backup);
auto res = env->NewObjectArray((jsize) hook_item->callbacks.size(), env->FindClass("java/lang/Object"), nullptr);
for (jsize i = 0; auto callback: hook_item->callbacks) {
env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback.second));
}
return res;
}
static JNINativeMethod gMethods[] = { static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
@ -184,10 +161,11 @@ static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"), LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"), LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"),
LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"),
}; };
void RegisterHookBridge(JNIEnv *env) { void RegisterHookBridge(JNIEnv *env) {
auto method = env->FindClass("java/lang/reflect/Method"); jclass method = env->FindClass("java/lang/reflect/Method");
invoke = env->GetMethodID( invoke = env->GetMethodID(
method, "invoke", method, "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"); "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");