From 96664fb72f748480180a239df69d177db0f14ba7 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 13 Feb 2026 18:27:48 +0100 Subject: [PATCH 1/2] Implement hookClassInitializer API for static initializers Since standard Java Reflection cannot access the `` method (class's static initializer), we add a new native function, `HookBridge.getStaticInitializer`, which uses JNI `GetStaticMethodID` to retrieve a `Method` handle for it. The `LSPosedContext` API then passes this handle to the existing `doHook` machinery. There are two possible exceptions thrown by the API: - `IllegalArgumentException`: Thrown for user-level errors, such as attempting to hook a class that has no static initializer block (`static { ... }`). This is a predictable failure based on the provided class. - `HookFailedError`: Thrown when the underlying native hooking engine (`lsplant`) fails to install the hook. This indicates a framework or environment-level issue (e.g., ART incompatibility, method inlining by the JIT/AOT compiler) and is not a fault in the module's logic. --- .../org/lsposed/lspd/impl/LSPosedContext.java | 21 +++++++++++++++++++ .../lsposed/lspd/nativebridge/HookBridge.java | 8 +++++++ core/src/main/jni/src/jni/hook_bridge.cpp | 16 ++++++++++++++ xposed/libxposed | 2 +- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 9551bbac7..a4f64d142 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -189,6 +189,27 @@ public MethodUnhooker> hook(@NonNull Constructor origin, i return LSPosedBridge.doHook(origin, priority, hooker); } + @Override + @NonNull + public MethodUnhooker> hookClassInitializer(@NonNull Class origin, @NonNull Class hooker) { + return hookClassInitializer(origin, PRIORITY_DEFAULT, hooker); + } + + @Override + @NonNull + @SuppressWarnings({"unchecked", "rawtypes"}) + public MethodUnhooker> hookClassInitializer(@NonNull Class origin, int priority, @NonNull Class hooker) { + Method staticInitializer = HookBridge.getStaticInitializer(origin); + + // The class might not have a static initializer block + if (staticInitializer == null) { + throw new IllegalArgumentException("Class " + origin.getName() + " has no static initializer"); + } + + // Use the existing doHook logic. It will return a MethodUnhooker. + return (MethodUnhooker) LSPosedBridge.doHook(staticInitializer, priority, hooker); + } + private static boolean doDeoptimize(@NonNull Executable method) { if (Modifier.isAbstract(method.getModifiers())) { throw new IllegalArgumentException("Cannot deoptimize abstract methods: " + method); diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java index e07caa0ee..3f8fd8667 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java @@ -25,4 +25,12 @@ public class HookBridge { public static native boolean setTrusted(Object cookie); public static native Object[][] callbackSnapshot(Class hooker_callback, Executable method); + + /** + * Retrieves the static initializer () of a class as a Method object. + * Standard Java reflection cannot access this. + * @param clazz The class to inspect. + * @return A Method object for the static initializer, or null if it doesn't exist. + */ + public static native Method getStaticInitializer(Class clazz); } diff --git a/core/src/main/jni/src/jni/hook_bridge.cpp b/core/src/main/jni/src/jni/hook_bridge.cpp index 104ed2840..1639c84b9 100644 --- a/core/src/main/jni/src/jni/hook_bridge.cpp +++ b/core/src/main/jni/src/jni/hook_bridge.cpp @@ -322,6 +322,21 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac return res; } +LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass clazz) { + // is the internal name for a static initializer. + // Its signature is always ()V (no arguments, void return). + jmethodID mid = env->GetStaticMethodID(clazz, "", "()V"); + if (!mid) { + // If GetStaticMethodID fails, it throws an exception. + // We clear it and return null to let the Java side handle it gracefully. + env->ExceptionClear(); + return nullptr; + } + // Convert the method ID to a java.lang.reflect.Method object. + // The last parameter must be JNI_TRUE because it's a static method. + return env->ToReflectedMethod(clazz, mid, JNI_TRUE); +} + static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(HookBridge, hookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"), LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(ZLjava/lang/reflect/Executable;Ljava/lang/Object;)Z"), @@ -332,6 +347,7 @@ static JNINativeMethod gMethods[] = { 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, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"), + LSP_NATIVE_METHOD(HookBridge, getStaticInitializer, "(Ljava/lang/Class;)Ljava/lang/reflect/Method;"), }; void RegisterHookBridge(JNIEnv *env) { diff --git a/xposed/libxposed b/xposed/libxposed index 64e29bd65..b896dbcda 160000 --- a/xposed/libxposed +++ b/xposed/libxposed @@ -1 +1 @@ -Subproject commit 64e29bd657ef4d2540b34402f5a988778f29e676 +Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8 From 6652ce101beab6b8d3f63209ad657ccc555a74c2 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 13 Feb 2026 18:51:33 +0100 Subject: [PATCH 2/2] Fix compilation error Name `clazz` was used in marco --- .../main/java/org/lsposed/lspd/nativebridge/HookBridge.java | 1 + core/src/main/jni/src/jni/hook_bridge.cpp | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java index 3f8fd8667..1c1e8da87 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java @@ -1,6 +1,7 @@ package org.lsposed.lspd.nativebridge; import java.lang.reflect.Executable; +import java.lang.reflect.Method; import java.lang.reflect.InvocationTargetException; import dalvik.annotation.optimization.FastNative; diff --git a/core/src/main/jni/src/jni/hook_bridge.cpp b/core/src/main/jni/src/jni/hook_bridge.cpp index 1639c84b9..1d78f3c1d 100644 --- a/core/src/main/jni/src/jni/hook_bridge.cpp +++ b/core/src/main/jni/src/jni/hook_bridge.cpp @@ -322,10 +322,10 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac return res; } -LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass clazz) { +LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass target_class) { // is the internal name for a static initializer. // Its signature is always ()V (no arguments, void return). - jmethodID mid = env->GetStaticMethodID(clazz, "", "()V"); + jmethodID mid = env->GetStaticMethodID(target_class, "", "()V"); if (!mid) { // If GetStaticMethodID fails, it throws an exception. // We clear it and return null to let the Java side handle it gracefully. @@ -334,7 +334,7 @@ LSP_DEF_NATIVE_METHOD(jobject, HookBridge, getStaticInitializer, jclass clazz) { } // Convert the method ID to a java.lang.reflect.Method object. // The last parameter must be JNI_TRUE because it's a static method. - return env->ToReflectedMethod(clazz, mid, JNI_TRUE); + return env->ToReflectedMethod(target_class, mid, JNI_TRUE); } static JNINativeMethod gMethods[] = {