Use native containers (#1910)
* Pack parameters * Use native containers Co-authored-by: LoveSy <shana@zju.edu.cn>
This commit is contained in:
parent
6fbffd23b7
commit
c63fb7af37
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;");
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue