Skip to content

Commit

Permalink
[core] Don’t proactively trigger JIT by default
Browse files Browse the repository at this point in the history
Fix #51
  • Loading branch information
canyie committed Dec 2, 2023
1 parent 5358344 commit f772373
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 31 deletions.
14 changes: 8 additions & 6 deletions core/src/main/cpp/pine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}

Expand Down Expand Up @@ -264,8 +265,9 @@ jboolean Pine_disableJitInline0(JNIEnv*, jclass) {
return static_cast<jboolean>(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) {
Expand Down Expand Up @@ -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"},
Expand Down Expand Up @@ -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},
Expand Down
1 change: 1 addition & 0 deletions core/src/main/cpp/pine_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
73 changes: 48 additions & 25 deletions core/src/main/java/top/canyie/pine/Pine.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public final class Pine {
private static final Map<Long, HookRecord> 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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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;
}

/**
Expand Down

0 comments on commit f772373

Please sign in to comment.