From f772373ba0558c9f5de7e0c6a7c378b39005d7e0 Mon Sep 17 00:00:00 2001 From: canyie Date: Sat, 2 Dec 2023 18:01:29 +0800 Subject: [PATCH] =?UTF-8?q?[core]=20Don=E2=80=99t=20proactively=20trigger?= =?UTF-8?q?=20JIT=20by=20default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #51 --- core/src/main/cpp/pine.cpp | 14 ++-- core/src/main/cpp/pine_config.h | 1 + core/src/main/java/top/canyie/pine/Pine.java | 73 +++++++++++++------- 3 files changed, 57 insertions(+), 31 deletions(-) diff --git a/core/src/main/cpp/pine.cpp b/core/src/main/cpp/pine.cpp index 96fb6f0..a86fa68 100644 --- a/core/src/main/cpp/pine.cpp +++ b/core/src/main/cpp/pine.cpp @@ -35,6 +35,7 @@ bool PineConfig::debug = false; bool PineConfig::debuggable = false; bool PineConfig::anti_checks = false; bool PineConfig::jit_compilation_allowed = true; +bool PineConfig::auto_compile_bridge = false; EXPORT_C void PineSetAndroidVersion(int version) { Android::version = version; @@ -143,7 +144,7 @@ jobject Pine_hook0(JNIEnv* env, jclass, jlong threadAddress, jclass declaring, j auto target = art::ArtMethod::FromReflectedMethod(env, javaTarget); auto bridge = art::ArtMethod::FromReflectedMethod(env, javaBridge); - if (PineConfig::jit_compilation_allowed) { + if (PineConfig::jit_compilation_allowed && PineConfig::auto_compile_bridge) { // The bridge method entry will be hardcoded in the trampoline, subsequent optimization // operations that require modification of the bridge method entry will not take effect. // Try to do JIT compilation first to get the best performance. @@ -157,7 +158,7 @@ jobject Pine_hook0(JNIEnv* env, jclass, jlong threadAddress, jclass declaring, j TrampolineInstaller* trampoline_installer = TrampolineInstaller::GetDefault(); - if (UNLIKELY(is_inline_hook && trampoline_installer->IsReplacementOnly())) { + if (is_inline_hook && (trampoline_installer->IsReplacementOnly() || !target->IsCompiled())) { is_inline_hook = false; } @@ -264,8 +265,9 @@ jboolean Pine_disableJitInline0(JNIEnv*, jclass) { return static_cast(art::Jit::DisableInline()); } -void Pine_setJitCompilationAllowed(JNIEnv*, jclass, jboolean allowed) { - PineConfig::jit_compilation_allowed = allowed; +void Pine_setJitCompilationAllowed(JNIEnv*, jclass, jboolean allowed, jboolean autoCompileBridge) { + PineConfig::jit_compilation_allowed = JBOOL_TRUE(allowed); + PineConfig::auto_compile_bridge = JBOOL_TRUE(autoCompileBridge); } jboolean Pine_disableProfileSaver0(JNIEnv*, jclass) { @@ -454,7 +456,7 @@ static const struct { {"syncMethodInfo", "(Ljava/lang/reflect/Member;Ljava/lang/reflect/Method;)V"}, {"decompile0", "(Ljava/lang/reflect/Member;Z)Z"}, {"disableJitInline0", "()Z"}, - {"setJitCompilationAllowed0", "(Z)V"}, + {"setJitCompilationAllowed0", "(ZZ)V"}, {"disableProfileSaver0", "()Z"}, {"getObject0", "(JJ)Ljava/lang/Object;"}, {"getAddress0", "(JLjava/lang/Object;)J"}, @@ -488,7 +490,7 @@ static const JNINativeMethod gMethods[] = { {"compile0", "(JLjava/lang/reflect/Member;)Z", (void*) Pine_compile0}, {"decompile0", "(Ljava/lang/reflect/Member;Z)Z", (void*) Pine_decompile0}, {"disableJitInline0", "()Z", (void*) Pine_disableJitInline0}, - {"setJitCompilationAllowed0", "(Z)V", (void*) Pine_setJitCompilationAllowed}, + {"setJitCompilationAllowed0", "(ZZ)V", (void*) Pine_setJitCompilationAllowed}, {"disableProfileSaver0", "()Z", (void*) Pine_disableProfileSaver0}, {"syncMethodInfo", "(Ljava/lang/reflect/Member;Ljava/lang/reflect/Method;)V", (void*) Pine_syncMethodInfo}, {"getObject0", "(JJ)Ljava/lang/Object;", (void*) Pine_getObject0}, diff --git a/core/src/main/cpp/pine_config.h b/core/src/main/cpp/pine_config.h index 1c2838e..3e726d1 100644 --- a/core/src/main/cpp/pine_config.h +++ b/core/src/main/cpp/pine_config.h @@ -14,6 +14,7 @@ namespace pine { static bool debuggable; static bool anti_checks; static bool jit_compilation_allowed; + static bool auto_compile_bridge; private: DISALLOW_IMPLICIT_CONSTRUCTORS(PineConfig); }; diff --git a/core/src/main/java/top/canyie/pine/Pine.java b/core/src/main/java/top/canyie/pine/Pine.java index 2360bf5..fa7b6b3 100644 --- a/core/src/main/java/top/canyie/pine/Pine.java +++ b/core/src/main/java/top/canyie/pine/Pine.java @@ -36,7 +36,7 @@ public final class Pine { private static final Map sHookRecords = new ConcurrentHashMap<>(); private static final Object sHookLock = new Object(); private static int arch; - private static volatile int hookMode = HookMode.AUTO; + private static volatile int hookMode; private static HookHandler sHookHandler = new HookHandler() { @Override public MethodHook.Unhook handleHook(HookRecord hookRecord, MethodHook hook, int modifiers, @@ -119,6 +119,8 @@ else if (sdkLevel == Build.VERSION_CODES.TIRAMISU) { if (vmVersion == null || !vmVersion.startsWith("2")) throw new RuntimeException("Only supports ART runtime"); + hookMode = sdkLevel < Build.VERSION_CODES.O ? HookMode.INLINE_WITHOUT_JIT : HookMode.REPLACEMENT; + try { LibLoader libLoader = PineConfig.libLoader; if (libLoader != null) libLoader.loadLib(); @@ -168,17 +170,26 @@ private static void initBridgeMethods() { } /** - * Set how Pine to hook method. - * @param newHookMode One of {@code Pine.HookMode.AUTO}, {@code Pine.HookMode.INLINE} or - * {@code Pine.HookMode.REPLACEMENT}. + * Set the way how Pine hook method. + * @param newHookMode One of {@code Pine.HookMode.AUTO}, {@code Pine.HookMode.INLINE}, + * {@code Pine.HookMode.REPLACEMENT} or {@code Pine.HookMode.INLINE_WITHOUT_JIT}. * @throws IllegalArgumentException If the {@code newHookMode} is not one of - * {@code Pine.HookMode.AUTO}, {@code Pine.HookMode.INLINE} or - * {@code Pine.HookMode.REPLACEMENT}. + * {@code Pine.HookMode.AUTO}, {@code Pine.HookMode.INLINE}, + * {@code Pine.HookMode.REPLACEMENT}, or {@code Pine.HookMode.INLINE_WITHOUT_JIT}. * @see Pine.HookMode */ public static void setHookMode(int newHookMode) { - if (newHookMode < HookMode.AUTO || newHookMode > HookMode.REPLACEMENT) + if (newHookMode < HookMode.AUTO || newHookMode > HookMode.INLINE_WITHOUT_JIT) throw new IllegalArgumentException("Illegal hookMode " + newHookMode); + if (newHookMode == HookMode.AUTO) { + // On Android N or lower, entry_point_from_compiled_code_ may be hard-coded in the machine code + // (sharpening optimization), entry replacement will most likely not take effect, + // so we prefer to use inline hook; And on Android O+, this optimization is not performed, + // so we prefer a more stable entry replacement mode. + + newHookMode = PineConfig.sdkLevel < Build.VERSION_CODES.O + ? HookMode.INLINE_WITHOUT_JIT : HookMode.REPLACEMENT; + } hookMode = newHookMode; } @@ -309,17 +320,8 @@ public static MethodHook.Unhook hook(Member method, MethodHook callback, boolean static void hookNewMethod(HookRecord hookRecord, int modifiers, boolean canInitDeclaringClass) { Member method = hookRecord.target; - boolean isInlineHook; - if (hookMode == HookMode.AUTO) { - // On Android N or lower, entry_point_from_compiled_code_ may be hard-coded in the machine code - // (sharpening optimization), entry replacement will most likely not take effect, - // so we prefer to use inline hook; And on Android O+, this optimization is not performed, - // so we prefer a more stable entry replacement mode. - - isInlineHook = PineConfig.sdkLevel < Build.VERSION_CODES.O; - } else { - isInlineHook = hookMode == HookMode.INLINE; - } + final int mode = hookMode; + boolean isInlineHook = mode == HookMode.INLINE || mode == HookMode.INLINE_WITHOUT_JIT; long thread = currentArtThread0(); if ((hookRecord.isStatic = Modifier.isStatic(modifiers)) && canInitDeclaringClass) { @@ -344,10 +346,12 @@ static void hookNewMethod(HookRecord hookRecord, int modifiers, boolean canInitD if (isInlineHook) { // Cannot compile native or proxy methods. if (!(jni || proxy)) { - boolean compiled = compile0(thread, method); - if (!compiled) { - Log.w(TAG, "Cannot compile the target method, force replacement mode."); - isInlineHook = false; + if (mode == HookMode.INLINE) { + boolean compiled = compile0(thread, method); + if (!compiled) { + Log.w(TAG, "Cannot compile the target method, force replacement mode."); + isInlineHook = false; + } } } else { isInlineHook = false; @@ -580,12 +584,21 @@ public static boolean disableJitInline() { * @param allowed {@code true} if allowed, {@code false} otherwise. */ public static void setJitCompilationAllowed(boolean allowed) { + setJitCompilationAllowed(allowed, false); + } + + /** + * Set whether we can manually JIT compile a method. + * @param allowed {@code true} if allowed, {@code false} otherwise. + * @param autoCompileBridge {@code true} if try to automatically compile bridge methods for better performance + */ + public static void setJitCompilationAllowed(boolean allowed, boolean autoCompileBridge) { if (PineConfig.sdkLevel < Build.VERSION_CODES.N) { // No JIT. return; } ensureInitialized(); - setJitCompilationAllowed0(allowed); + setJitCompilationAllowed0(allowed, autoCompileBridge); } /** @@ -750,7 +763,7 @@ private static native Method hook0(long thread, Class declaring, Member targe private static native boolean disableJitInline0(); - private static native void setJitCompilationAllowed0(boolean allowed); + private static native void setJitCompilationAllowed0(boolean allowed, boolean autoCompileBridge); private static native boolean disableProfileSaver0(); @@ -816,15 +829,25 @@ public interface HookMode { int AUTO = 0; /** - * INLINE: Use inline hook (overwrite the first few instructions to hook) first. + * INLINE: Prefer inline hook (overwrite the first few instructions to hook), + * try to manually compile the method if not compiled. * If the method cannot be hooked in this mode, fallback to {@code REPLACEMENT}. + * @deprecated Since manually do a JIT compilation causes crashes on some devices, + * we prefer {@code INLINE_WITHOUT_JIT} instead. */ + @Deprecated int INLINE = 1; /** * REPLACEMENT: Always change entry point of the method to hook it. */ int REPLACEMENT = 2; + + /** + * Similar to {@code INLINE}, but when the target method isn't compiled yet, + * automatically fallback to {@code REPLACEMENT} mode instead of manually compile it + */ + int INLINE_WITHOUT_JIT = 3; } /**