├── lib_thread_shield ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── cpp │ │ ├── art │ │ │ ├── macro.h │ │ │ ├── tls.h │ │ │ └── art_thread.h │ │ ├── jni_init.h │ │ ├── base │ │ │ ├── stringprintf.h │ │ │ └── stringprintf.cpp │ │ ├── suspend_thread_timeout_v15.h │ │ ├── suspend_thread_timeout_v12_14.h │ │ ├── suspend_thread_timeout_v5_11.h │ │ ├── suspend_thread_timeout_apex_upgrade_logic_checker.h │ │ ├── cn_huolala_threadshield_suspend_thread_safe_helper.h │ │ ├── suspend_thread_timeout_apex_upgrade_logic_checker.cpp │ │ ├── jni_init.cpp │ │ ├── cn_huolala_threadshield_suspend_thread_safe_helper.cpp │ │ ├── CMakeLists.txt │ │ ├── suspend_thread_timeout_v5_11.cpp │ │ ├── suspend_thread_timeout_v12_14.cpp │ │ └── suspend_thread_timeout_v15.cpp │ │ └── java │ │ └── cn │ │ └── huolala │ │ └── threadshield │ │ ├── SuspendThreadSafeHelper.kt │ │ └── BaseInlineHook.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle.kts ├── app ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── xml │ │ │ ├── backup_rules.xml │ │ │ └── data_extraction_rules.xml │ │ ├── values-night │ │ │ └── themes.xml │ │ ├── layout │ │ │ └── activity_main.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── cn │ │ │ └── huolala │ │ │ └── syshooklib │ │ │ ├── ThreadSuspendHookHelper.kt │ │ │ └── MainActivity.kt │ │ ├── AndroidManifest.xml │ │ └── cpp │ │ └── CMakeLists.txt ├── proguard-rules.pro ├── build.gradle.kts └── .gitignore ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── img └── suspend_thread_timeout_trend.png ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── README.md ├── README_CN.md ├── gradlew.bat ├── gradlew └── LICENSE.txt /lib_thread_shield/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | hll-unify-hook-lib 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /img/suspend_thread_timeout_trend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/img/suspend_thread_timeout_trend.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuolalaTech/hll-sys-hook-android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /lib_thread_shield/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/art/macro.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by knight-zxw on 2023/1/2. 3 | // Email: nimdanoob@163.com 4 | // 5 | #pragma once 6 | 7 | #define PACKED(x) __attribute__((__aligned__(x), __packed__)) 8 | #define ALWAYS_INLINE __attribute__((always_inline)) -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 21 13:53:30 GMT+08:00 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | /.idea 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | local.properties 17 | -------------------------------------------------------------------------------- /lib_thread_shield/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | /.gradle/ -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/jni_init.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 2024/5/20. 3 | // 4 | 5 | #ifndef LLM_MOBILE_UNIFY_HOOK_LIB_JNI_INIT_H 6 | #define LLM_MOBILE_UNIFY_HOOK_LIB_JNI_INIT_H 7 | 8 | #include 9 | 10 | JavaVM* getGlobalJVM(); 11 | 12 | JNIEnv *getJNIEnv(); 13 | 14 | void cleanup(); 15 | 16 | #endif //LLM_MOBILE_UNIFY_HOOK_LIB_JNI_INIT_H 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/base/stringprintf.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 2024/7/16. 3 | // 4 | #include 5 | 6 | #ifndef LLM_MOBILE_UNIFY_HOOK_LIB_STRINGPRINTF_H 7 | #define LLM_MOBILE_UNIFY_HOOK_LIB_STRINGPRINTF_H 8 | 9 | 10 | namespace base { 11 | void StringAppendV(std::string* dst, const char* format, va_list ap); 12 | } 13 | 14 | #endif //LLM_MOBILE_UNIFY_HOOK_LIB_STRINGPRINTF_H 15 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v15.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | #ifndef HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V15_H 6 | #define HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V15_H 7 | 8 | namespace suspend_thread_safe_v15 { 9 | void suspendThreadSafe(JNIEnv *env, jobject jCallback); 10 | } 11 | 12 | #endif //HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V15_H 13 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v12_14.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | #ifndef HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V12_14_H 6 | #define HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V12_14_H 7 | 8 | namespace suspend_thread_safe_v12_14 { 9 | void suspendThreadSafe(JNIEnv *env, jobject jCallback); 10 | } 11 | 12 | #endif //HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V12_14_H 13 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v5_11.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/26/24. 3 | // 4 | 5 | #ifndef HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V5_11_H 6 | #define HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V5_11_H 7 | 8 | #include 9 | 10 | namespace suspend_thread_safe_v5_11 { 11 | void suspendThreadSafe(JNIEnv *env, jobject jCallback); 12 | } 13 | 14 | #endif //HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_V5_11_H 15 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_apex_upgrade_logic_checker.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 9/27/24. 3 | // 4 | 5 | #ifndef HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_APEX_UPGRADE_LOGIC_CHECKER_H 6 | #define HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_APEX_UPGRADE_LOGIC_CHECKER_H 7 | 8 | 9 | namespace suspend_thread_timeout_apex_upgrade_logic_checker { 10 | bool checkApexUpgradeToAndroid15Logic(); 11 | } 12 | 13 | 14 | #endif //HLL_UNIFY_HOOK_LIB_SUSPEND_THREAD_TIMEOUT_APEX_UPGRADE_LOGIC_CHECKER_H 15 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google { 4 | content { 5 | includeGroupByRegex("com\\.android.*") 6 | includeGroupByRegex("com\\.google.*") 7 | includeGroupByRegex("androidx.*") 8 | } 9 | } 10 | mavenCentral() 11 | gradlePluginPortal() 12 | } 13 | } 14 | dependencyResolutionManagement { 15 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 16 | repositories { 17 | google() 18 | mavenCentral() 19 | maven { url = uri("https://jitpack.io") } 20 | } 21 | } 22 | 23 | rootProject.name = "hll-unify-hook-lib" 24 | include(":app") 25 | include(":lib_thread_shield") 26 | -------------------------------------------------------------------------------- /app/src/main/java/cn/huolala/syshooklib/ThreadSuspendHookHelper.kt: -------------------------------------------------------------------------------- 1 | package cn.huolala.syshooklib 2 | 3 | import java.lang.reflect.Field 4 | 5 | object ThreadSuspendHookHelper { 6 | fun getNativePeer(thread: Thread): Long? { 7 | try { 8 | val threadClass = Class.forName("java.lang.Thread") 9 | val nativePeerField: Field = threadClass.getDeclaredField("nativePeer") 10 | nativePeerField.isAccessible = true 11 | return nativePeerField.getLong(thread) 12 | } catch (e: ClassNotFoundException) { 13 | e.printStackTrace() 14 | } catch (e: NoSuchFieldException) { 15 | e.printStackTrace() 16 | } catch (e: IllegalAccessException) { 17 | e.printStackTrace() 18 | } 19 | return null 20 | } 21 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /lib_thread_shield/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/cn_huolala_threadshield_suspend_thread_safe_helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | #ifndef HLL_UNIFY_HOOK_LIB_CN_HUOLALA_THREADSHIELD_SUSPEND_THREAD_SAFE_HELPER_H 6 | #define HLL_UNIFY_HOOK_LIB_CN_HUOLALA_THREADSHIELD_SUSPEND_THREAD_SAFE_HELPER_H 7 | 8 | 9 | #include 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | extern "C" JNIEXPORT void JNICALL 16 | Java_cn_huolala_threadshield_SuspendThreadSafeHelper_nativeSuspendThreadSafe(JNIEnv *env, 17 | jobject /*this*/, 18 | jobject callback); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | #endif //HLL_UNIFY_HOOK_LIB_CN_HUOLALA_THREADSHIELD_SUSPEND_THREAD_SAFE_HELPER_H 25 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/art/tls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #if defined(__aarch64__) 5 | # define __get_tls() ({ void** __val; __asm__("mrs %0, tpidr_el0" : "=r"(__val)); __val; }) 6 | #elif defined(__arm__) 7 | # define __get_tls() ({ void** __val; __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(__val)); __val; }) 8 | #elif defined(__i386__) 9 | # define __get_tls() ({ void** __val; __asm__("movl %%gs:0, %0" : "=r"(__val)); __val; }) 10 | #elif defined(__riscv) 11 | # define __get_tls() ({ void** __val; __asm__("mv %0, tp" : "=r"(__val)); __val; }) 12 | #elif defined(__x86_64__) 13 | # define __get_tls() ({ void** __val; __asm__("mov %%fs:0, %0" : "=r"(__val)); __val; }) 14 | #else 15 | #error unsupported architecture 16 | #endif 17 | 18 | #if defined(__arm__) || defined(__aarch64__) 19 | #define TLS_SLOT_ART_THREAD_SELF 7 20 | #elif defined(__i386__) || defined(__x86_64__) 21 | #define TLS_SLOT_ART_THREAD_SELF 7 22 | #elif defined(__riscv) 23 | #define TLS_SLOT_ART_THREAD_SELF (-1) 24 | #endif 25 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_apex_upgrade_logic_checker.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 9/27/24. 3 | // 4 | 5 | #include 6 | #include "suspend_thread_timeout_apex_upgrade_logic_checker.h" 7 | 8 | #define TARGET_ART_LIB "libart.so" 9 | #define SYMBOL_SUSPEND_THREAD "_ZN3art10ThreadList19SuspendThreadByPeerEP8_jobjectNS_13SuspendReasonE" 10 | #define SYMBOL_ABORT_IN_THIS "_ZN3art6Thread11AbortInThisERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE" 11 | 12 | namespace suspend_thread_timeout_apex_upgrade_logic_checker { 13 | 14 | // According to Android 15 new suspend thread logic. 15 | bool checkApexUpgradeToAndroid15Logic() { 16 | void *handle = shadowhook_dlopen(TARGET_ART_LIB); 17 | void *threadSuspendHandle = shadowhook_dlsym(handle, SYMBOL_SUSPEND_THREAD); 18 | void *abortInThisHandle = shadowhook_dlsym(handle, SYMBOL_ABORT_IN_THIS); 19 | bool result = threadSuspendHandle != nullptr && abortInThisHandle != nullptr; 20 | shadowhook_dlclose(threadSuspendHandle); 21 | shadowhook_dlclose(abortInThisHandle); 22 | return result; 23 | } 24 | } -------------------------------------------------------------------------------- /lib_thread_shield/src/main/java/cn/huolala/threadshield/SuspendThreadSafeHelper.kt: -------------------------------------------------------------------------------- 1 | package cn.huolala.threadshield 2 | 3 | class SuspendThreadSafeHelper private constructor() { 4 | companion object { 5 | @Volatile 6 | private var instance: SuspendThreadSafeHelper? = null 7 | 8 | fun getInstance(): SuspendThreadSafeHelper = 9 | instance ?: synchronized(this) { 10 | instance ?: SuspendThreadSafeHelper().also { instance = it } 11 | } 12 | 13 | private const val UNKNOWN_ERROR = "Unknown error" 14 | } 15 | 16 | 17 | @Synchronized 18 | fun suspendThreadSafe(callback: SuspendThreadCallback) { 19 | runCatching { 20 | BaseInlineHook.getInstance().initNativeLib() 21 | }.onSuccess { 22 | nativeSuspendThreadSafe(callback) 23 | }.onFailure { 24 | callback.onError(it.message ?: UNKNOWN_ERROR) 25 | } 26 | } 27 | 28 | private external fun nativeSuspendThreadSafe(callback: SuspendThreadCallback) 29 | 30 | interface SuspendThreadCallback { 31 | fun suspendThreadTimeout(waitTime: Double) 32 | fun onError(errorMsg: String) 33 | } 34 | } -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/jni_init.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | 6 | #include "jni_init.h" 7 | 8 | JavaVM *g_jvm; 9 | 10 | JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { 11 | g_jvm = vm; 12 | return JNI_VERSION_1_6; 13 | } 14 | 15 | JNIEXPORT void JNI_OnUnload(JavaVM *vm, void *reserved) { 16 | g_jvm = nullptr; 17 | } 18 | 19 | JavaVM *getGlobalJVM() { 20 | return g_jvm; 21 | } 22 | 23 | thread_local bool tlsAttached = false; 24 | 25 | JNIEnv *getJNIEnv() { 26 | JNIEnv *env = nullptr; 27 | if (getGlobalJVM() == nullptr) { 28 | return nullptr; 29 | } 30 | jint result = getGlobalJVM()->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); 31 | if (result == JNI_EDETACHED) { 32 | if (getGlobalJVM()->AttachCurrentThread(&env, nullptr) != 0) { 33 | return nullptr; 34 | } 35 | tlsAttached = true; 36 | } else if (result != JNI_OK) { 37 | return nullptr; 38 | } 39 | return env; 40 | } 41 | 42 | void cleanup() { 43 | if (tlsAttached) { 44 | if (getGlobalJVM()->DetachCurrentThread() == JNI_OK) { 45 | tlsAttached = false; 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/java/cn/huolala/threadshield/BaseInlineHook.kt: -------------------------------------------------------------------------------- 1 | package cn.huolala.threadshield 2 | 3 | import com.bytedance.shadowhook.ShadowHook 4 | import java.util.concurrent.atomic.AtomicBoolean 5 | import kotlin.jvm.Throws 6 | 7 | class BaseInlineHook { 8 | companion object { 9 | @Volatile 10 | private var instance: BaseInlineHook? = null 11 | fun getInstance(): BaseInlineHook = instance ?: synchronized(this) { 12 | instance ?: BaseInlineHook().also { instance = it } 13 | } 14 | } 15 | 16 | private val loadHookLibDone = AtomicBoolean(false) 17 | 18 | @Throws(Exception::class) 19 | @Synchronized 20 | fun initNativeLib() { 21 | if (loadHookLibDone.get()) { 22 | return 23 | } 24 | ShadowHook 25 | .ConfigBuilder() 26 | .setMode(ShadowHook.Mode.UNIQUE) 27 | .setDebuggable(true) 28 | .setRecordable(true) 29 | .build() 30 | .run { ShadowHook.init(this) } 31 | .takeIf { it != 0 }?.let { 32 | throw Exception("ShadowHook init failed with code: $this") 33 | } 34 | System.loadLibrary("threadshield") 35 | loadHookLibDone.set(true) 36 | } 37 | } -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "com.huolala.syshooklib" 8 | compileSdk = 34 9 | 10 | defaultConfig { 11 | applicationId = "com.huolala.syshooklib" 12 | minSdk = 21 13 | targetSdk = 34 14 | versionCode = 1 15 | versionName = "1.0" 16 | 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | isMinifyEnabled = false 23 | proguardFiles( 24 | getDefaultProguardFile("proguard-android-optimize.txt"), 25 | "proguard-rules.pro" 26 | ) 27 | } 28 | } 29 | compileOptions { 30 | sourceCompatibility = JavaVersion.VERSION_17 31 | targetCompatibility = JavaVersion.VERSION_17 32 | } 33 | kotlinOptions { 34 | jvmTarget = "17" 35 | } 36 | buildFeatures { 37 | viewBinding = true 38 | } 39 | } 40 | 41 | dependencies { 42 | 43 | implementation(libs.androidx.core.ktx) 44 | implementation(libs.androidx.appcompat) 45 | implementation(libs.material) 46 | implementation(libs.androidx.constraintlayout) 47 | implementation(libs.syshook) 48 | // implementation(project(":lib_thread_shield")) 49 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. For more details, visit 12 | # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.8.0-alpha02" 3 | kotlin = "1.9.24" 4 | coreKtx = "1.10.1" 5 | junit = "4.14-SNAPSHOT" 6 | junitVersion = "1.1.5" 7 | espressoCore = "3.5.1" 8 | appcompat = "1.6.1" 9 | material = "1.10.0" 10 | constraintlayout = "2.1.4" 11 | shadowhook = "1.0.10" 12 | syshook = "1.4-SNAPSHOT" 13 | 14 | [libraries] 15 | androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } 16 | junit = { group = "junit", name = "junit", version.ref = "junit" } 17 | androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } 18 | androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } 19 | androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } 20 | material = { group = "com.google.android.material", name = "material", version.ref = "material" } 21 | androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } 22 | shadowhook = { group = "com.bytedance.android", name = "shadowhook", version.ref = "shadowhook" } 23 | syshook = { group = "com.github.HuolalaTech", name = "hll-sys-hook-android", version.ref = "syshook" } 24 | 25 | [plugins] 26 | android-application = { id = "com.android.application", version.ref = "agp" } 27 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 28 | android-library = { id = "com.android.library", version.ref = "agp" } 29 | 30 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/cn_huolala_threadshield_suspend_thread_safe_helper.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "cn_huolala_threadshield_suspend_thread_safe_helper.h" 9 | #include "suspend_thread_timeout_apex_upgrade_logic_checker.h" 10 | #include "suspend_thread_timeout_v15.h" 11 | #include "suspend_thread_timeout_v12_14.h" 12 | #include "suspend_thread_timeout_v5_11.h" 13 | 14 | extern "C" JNIEXPORT void JNICALL 15 | Java_cn_huolala_threadshield_SuspendThreadSafeHelper_nativeSuspendThreadSafe(JNIEnv *env, 16 | jobject /*this*/, 17 | jobject callback) { 18 | int apiLevel = android_get_device_api_level(); 19 | if (apiLevel >= __ANDROID_API_V__) { 20 | // android [15 , +∞) 21 | using namespace suspend_thread_safe_v15; 22 | suspendThreadSafe(env, callback); 23 | } else if (apiLevel > __ANDROID_API_R__) { 24 | using namespace suspend_thread_timeout_apex_upgrade_logic_checker; 25 | if (checkApexUpgradeToAndroid15Logic()) { 26 | using namespace suspend_thread_safe_v15; 27 | suspendThreadSafe(env, callback); 28 | } else { 29 | // android [12 , 14] 30 | using namespace suspend_thread_safe_v12_14; 31 | suspendThreadSafe(env, callback); 32 | } 33 | } else { 34 | // android [5 , 11] 35 | using namespace suspend_thread_safe_v5_11; 36 | suspendThreadSafe(env, callback); 37 | } 38 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, 9 | # Since this is the top level CMakeLists.txt, the project name is also accessible 10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level 11 | # build script scope). 12 | project("syshooklib") 13 | 14 | # Creates and names a library, sets it as either STATIC 15 | # or SHARED, and provides the relative paths to its source code. 16 | # You can define multiple libraries, and CMake builds them for you. 17 | # Gradle automatically packages shared libraries with your APK. 18 | # 19 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define 20 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} 21 | # is preferred for the same purpose. 22 | # 23 | # In order to load a library into your app from Java/Kotlin, you must call 24 | # System.loadLibrary() and pass the name of the library defined here; 25 | # for GameActivity/NativeActivity derived applications, the same library name must be 26 | # used in the AndroidManifest.xml file. 27 | add_library(${CMAKE_PROJECT_NAME} SHARED 28 | # List C/C++ source files with relative paths to this CMakeLists.txt. 29 | 30 | ) 31 | 32 | # Specifies libraries CMake should link to your target library. You 33 | # can link libraries from various origins, such as libraries defined in this 34 | # build script, prebuilt third-party libraries, or Android system libraries. 35 | target_link_libraries(${CMAKE_PROJECT_NAME} 36 | # List libraries link to the target library 37 | android 38 | log) -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | .idea/jarRepositories.xml 48 | # Android Studio 3 in .gitignore file. 49 | .idea/caches 50 | .idea/modules.xml 51 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 52 | .idea/navEditor.xml 53 | .idea/* 54 | .idea/compiler.xml 55 | .idea/misc.xml 56 | 57 | # Keystore files 58 | # Uncomment the following lines if you do not want to check your keystore files in. 59 | #*.jks 60 | #*.keystore 61 | 62 | # External native build folder generated in Android Studio 2.2 and later 63 | .externalNativeBuild 64 | .cxx/ 65 | 66 | # Google Services (e.g. APIs or Firebase) 67 | # google-services.json 68 | 69 | # Freeline 70 | freeline.py 71 | freeline/ 72 | freeline_project_description.json 73 | 74 | # fastlane 75 | fastlane/report.xml 76 | fastlane/Preview.html 77 | fastlane/screenshots 78 | fastlane/test_output 79 | fastlane/readme.md 80 | 81 | # Version control 82 | vcs.xml 83 | 84 | # lint 85 | lint/intermediates/ 86 | lint/generated/ 87 | lint/outputs/ 88 | lint/tmp/ 89 | # lint/reports/ 90 | 91 | # Android Profiling 92 | *.hprof 93 | 94 | sdk/src/main/cpp/cmake-build-debug 95 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/base/stringprintf.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 2024/7/16. 3 | // 4 | 5 | #include "stringprintf.h" 6 | #include 7 | 8 | namespace base { 9 | // copy from android base. 10 | void StringAppendV(std::string* dst, const char* format, va_list ap) { 11 | // First try with a small fixed size buffer 12 | char space[1024] __attribute__((__uninitialized__)); 13 | 14 | // It's possible for methods that use a va_list to invalidate 15 | // the data in it upon use. The fix is to make a copy 16 | // of the structure before using it and use that copy instead. 17 | va_list backup_ap; 18 | va_copy(backup_ap, ap); 19 | int result = vsnprintf(space, sizeof(space), format, backup_ap); 20 | va_end(backup_ap); 21 | 22 | if (result < static_cast(sizeof(space))) { 23 | if (result >= 0) { 24 | // Normal case -- everything fit. 25 | dst->append(space, result); 26 | return; 27 | } 28 | 29 | if (result < 0) { 30 | // Just an error. 31 | return; 32 | } 33 | } 34 | 35 | // Increase the buffer size to the size requested by vsnprintf, 36 | // plus one for the closing \0. 37 | int length = result + 1; 38 | char* buf = new char[length]; 39 | 40 | // Restore the va_list before we use it again 41 | va_copy(backup_ap, ap); 42 | result = vsnprintf(buf, length, format, backup_ap); 43 | va_end(backup_ap); 44 | 45 | if (result >= 0 && result < length) { 46 | // It fit 47 | dst->append(buf, result); 48 | } 49 | delete[] buf; 50 | } 51 | 52 | std::string StringPrintf(const char* fmt, ...) { 53 | va_list ap; 54 | va_start(ap, fmt); 55 | std::string result; 56 | StringAppendV(&result, fmt, ap); 57 | va_end(ap); 58 | return result; 59 | } 60 | } -------------------------------------------------------------------------------- /lib_thread_shield/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.library) 3 | alias(libs.plugins.kotlin.android) 4 | `maven-publish` 5 | } 6 | 7 | android { 8 | namespace = "cn.huolala.threadshield" 9 | compileSdk = 34 10 | 11 | packaging { 12 | jniLibs.excludes.add("**/libshadowhook.so") 13 | } 14 | 15 | defaultConfig { 16 | minSdk = 21 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | consumerProguardFiles("consumer-rules.pro") 19 | externalNativeBuild { 20 | cmake { 21 | cppFlags("-std=c++11") 22 | } 23 | } 24 | ndk { 25 | abiFilters.apply { 26 | add("armeabi-v7a") 27 | add("arm64-v8a") 28 | } 29 | } 30 | } 31 | buildFeatures { 32 | prefab = true 33 | } 34 | buildTypes { 35 | release { 36 | isMinifyEnabled = false 37 | proguardFiles( 38 | getDefaultProguardFile("proguard-android-optimize.txt"), 39 | "proguard-rules.pro" 40 | ) 41 | } 42 | } 43 | externalNativeBuild { 44 | cmake { 45 | path("src/main/cpp/CMakeLists.txt") 46 | version = "3.22.1" 47 | } 48 | } 49 | compileOptions { 50 | sourceCompatibility = JavaVersion.VERSION_17 51 | targetCompatibility = JavaVersion.VERSION_17 52 | } 53 | kotlinOptions { 54 | jvmTarget = "17" 55 | } 56 | } 57 | 58 | dependencies { 59 | implementation(libs.shadowhook) 60 | } 61 | 62 | publishing { 63 | publications { 64 | create("release") { 65 | groupId = "com.github.HuolalaTech" 66 | artifactId = "hll-sys-hook-android" 67 | version = "1.4-SNAPSHOT" 68 | 69 | afterEvaluate { 70 | val pubComponent = components.findByName("release") 71 | if (pubComponent != null) { 72 | from(pubComponent) 73 | } 74 | } 75 | } 76 | } 77 | 78 | repositories { 79 | maven { 80 | name = "jitpack" 81 | url = uri("https://jitpack.io") 82 | } 83 | } 84 | } 85 | 86 | tasks.named("publishToMavenLocal") { 87 | dependsOn("assembleRelease") 88 | } -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about using CMake with Android Studio, read the 2 | # documentation: https://d.android.com/studio/projects/add-native-code.html. 3 | # For more examples on how to use CMake, see https://github.com/android/ndk-samples. 4 | 5 | # Sets the minimum CMake version required for this project. 6 | cmake_minimum_required(VERSION 3.22.1) 7 | 8 | # Declares the project name. The project name can be accessed via ${ PROJECT_NAME}, 9 | # Since this is the top level CMakeLists.txt, the project name is also accessible 10 | # with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level 11 | # build script scope). 12 | project("threadshield") 13 | 14 | # Creates and names a library, sets it as either STATIC 15 | # or SHARED, and provides the relative paths to its source code. 16 | # You can define multiple libraries, and CMake builds them for you. 17 | # Gradle automatically packages shared libraries with your APK. 18 | # 19 | # In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define 20 | # the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME} 21 | # is preferred for the same purpose. 22 | # 23 | # In order to load a library into your app from Java/Kotlin, you must call 24 | # System.loadLibrary() and pass the name of the library defined here; 25 | # for GameActivity/NativeActivity derived applications, the same library name must be 26 | # used in the AndroidManifest.xml file. 27 | add_library(${CMAKE_PROJECT_NAME} SHARED 28 | # List C/C++ source files with relative paths to this CMakeLists.txt. 29 | base/stringprintf.cpp 30 | jni_init.cpp 31 | cn_huolala_threadshield_suspend_thread_safe_helper.cpp 32 | suspend_thread_timeout_v5_11.cpp 33 | suspend_thread_timeout_v12_14.cpp 34 | suspend_thread_timeout_v15.cpp 35 | suspend_thread_timeout_apex_upgrade_logic_checker.cpp 36 | ) 37 | 38 | find_package(shadowhook REQUIRED CONFIG) 39 | 40 | # Specifies libraries CMake should link to your target library. You 41 | # can link libraries from various origins, such as libraries defined in this 42 | # build script, prebuilt third-party libraries, or Android system libraries. 43 | target_link_libraries(${CMAKE_PROJECT_NAME} 44 | # List libraries link to the target library 45 | android 46 | log 47 | shadowhook::shadowhook 48 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HuoLaLa unify hook lib 2 | 3 | [![license](https://img.shields.io/hexpm/l/plug.svg)](https://www.apache.org/licenses/LICENSE-2.0) ![release](https://img.shields.io/badge/release-v1.0-green.svg) 4 | 5 | [简体中文](./README_CN.md) 6 | 7 | The HuoLaLa and Lalamove Android project employs `inline-hook` techniques to address system-level issues and enhance app's overall stability or performance. 8 | 9 | ## Thread Shield 10 | 11 | ### Safe to suspend thread. 12 | 13 | Avoid suspending thread timeout to make process kill by `Abort` signal. 14 | 15 | - Android [5,11] : Hook [ThreadSuspendByPeerWarning()](https://cs.android.com/android/platform/superproject/+/android-9.0.0_r61:art/runtime/thread_list.cc) to modified log level, if hit thread suspend timeout, will call `suspendThreadTimeout()` to notified java side. 16 | 17 | - Android [12,14] : Hook [SuspendThreadByPeer()](https://cs.android.com/android/platform/superproject/+/android-12.0.0_r34:art/runtime/thread_list.cc) function and replace it to call SuspendThreadByPeerId() function, will not need to call `suspendThreadTimeout()` to notified java side, because if thread suspend timeout can not send abort signal. 18 | 19 | - Android 15 : Hook [StringPrintf()](https://cs.android.com/android/platform/superproject/main/+/main:external/cronet/base/strings/stringprintf.cc;bpv=1;bpt=1) function and block thread to wait thread suspends finish, will call `suspendThreadTimeout()` to notified java side. 20 | 21 | #### Earnings 22 | 23 | In Lalamove Side, the number of thread suspend timeout crash times is so huge. 24 | 25 | ![suspend_thread_timeout_trend.png](img/suspend_thread_timeout_trend.png) 26 | 27 | 28 | #### How to use 29 | 30 | ##### 1. Add dependencies 31 | 32 | ```setting.gradle.kts 33 | 34 | dependencyResolutionManagement { 35 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 36 | repositories { 37 | google() 38 | mavenCentral() 39 | maven { url = uri("https://jitpack.io") } 40 | } 41 | } 42 | 43 | ``` 44 | 45 | ```build.gradle.kts 46 | 47 | dependencies { 48 | implementation 'com.github.HuolalaTech:hll-sys-hook-android:1.4-SNAPSHOT' 49 | } 50 | 51 | ``` 52 | 53 | ##### 2. Add code 54 | 55 | ```kotlin 56 | 57 | SuspendThreadSafeHelper.getInstance() 58 | .suspendThreadSafe(object : SuspendThreadSafeHelper.SuspendThreadCallback { 59 | override fun suspendThreadTimeout(waitTime: Double) { 60 | Log.i("TAG", "Waitting for thread suspend done, spend : $waitTime s") 61 | } 62 | 63 | override fun onError(errorMsg: String) { 64 | Log.e("TAG", "onError: $errorMsg") 65 | } 66 | }) 67 | 68 | ``` -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # HuoLaLa unify hook lib 2 | 3 | [![license](https://img.shields.io/hexpm/l/plug.svg)](https://www.apache.org/licenses/LICENSE-2.0) ![release](https://img.shields.io/badge/release-v1.0-green.svg) 4 | 5 | 货拉拉 与 Lalamove Android 项目采用`inline-hook`技术来解决系统级问题以提高系统稳定性与性能问题。 6 | 7 | ## Thread Shield 8 | 9 | ### 安全的挂起线程(屏蔽超时事件) 10 | 11 | 避免当挂起超时时,进程被 `Abort` 信号杀死。 12 | 13 | ``` 14 | SuspendThreadSafeHelper.getInstance() 15 | .suspendThreadSafe(object : SuspendThreadSafeHelper.SuspendThreadCallback { 16 | override fun suspendThreadTimeout(waitTime: Double) { 17 | Log.i("TAG", "Waitting for thread suspend done, spend : $waitTime s") 18 | } 19 | 20 | override fun onError(errorMsg: String) { 21 | Log.e("TAG", "onError: $errorMsg") 22 | } 23 | }) 24 | ``` 25 | 26 | - Android [5,11] : Hook [ThreadSuspendByPeerWarning()](https://cs.android.com/android/platform/superproject/+/android-9.0.0_r61:art/runtime/thread_list.cc)函数, 将日志致命级别修改为警告级别,如果线程挂起超时,将调用 `suspendThreadTimeout()` 会通知 Java 层。 27 | 28 | - Android [12,14] : Hook [SuspendThreadByPeer()](https://cs.android.com/android/platform/superproject/+/android-12.0.0_r34:art/runtime/thread_list.cc) 函数转而调用 [SuspendThreadByThreadIdSuspendThreadByThreadId](https://cs.android.com/android/platform/superproject/+/android-12.0.0_r34:art/runtime/thread_list.cc), 此时并不会回调给 Java 调用方, 因为替换方法不会触发 `Abort` 信号发送,进而不会造成崩溃。 29 | 30 | - Android 15 : Hook [StringPrintf()](https://cs.android.com/android/platform/superproject/main/+/main:external/cronet/base/strings/stringprintf.cc;bpv=1;bpt=1) 函数,并且在代理`StringPrintf`中等待线程真正挂起结束后再执行后续流程, 将调用 `suspendThreadTimeout()` 会通知 Java 层,并回调等待时间。 31 | 32 | #### 收益 33 | 34 | 在货拉拉国际版业务中, 由于存在一部分低端设备,线程挂起超时导致的崩溃次数较多,修复后收益明显。 35 | 36 | ![suspend_thread_timeout_trend.png](img/suspend_thread_timeout_trend.png) 37 | 38 | 39 | #### 使用方式 40 | 41 | ##### 1. 添加依赖 42 | 43 | ```setting.gradle.kts 44 | 45 | dependencyResolutionManagement { 46 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 47 | repositories { 48 | google() 49 | mavenCentral() 50 | maven { url = uri("https://jitpack.io") } 51 | } 52 | } 53 | 54 | ``` 55 | 56 | ```build.gradle.kts 57 | 58 | dependencies { 59 | implementation 'com.github.HuolalaTech:hll-sys-hook-android:1.4-SNAPSHOT' 60 | } 61 | 62 | ``` 63 | ##### 2. 初始化代码 64 | 65 | ```kotlin 66 | 67 | SuspendThreadSafeHelper.getInstance() 68 | .suspendThreadSafe(object : SuspendThreadSafeHelper.SuspendThreadCallback { 69 | override fun suspendThreadTimeout(waitTime: Double) { 70 | Log.i("TAG", "Waitting for thread suspend done, spend : $waitTime s") 71 | } 72 | 73 | override fun onError(errorMsg: String) { 74 | Log.e("TAG", "onError: $errorMsg") 75 | } 76 | }) 77 | 78 | ``` 79 | -------------------------------------------------------------------------------- /app/src/main/java/cn/huolala/syshooklib/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.huolala.syshooklib 2 | 3 | import android.os.Bundle 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.util.Log 7 | import android.widget.Toast 8 | import androidx.appcompat.app.AppCompatActivity 9 | import cn.huolala.threadshield.SuspendThreadSafeHelper 10 | import com.huolala.syshooklib.databinding.ActivityMainBinding 11 | import kotlin.concurrent.thread 12 | import kotlin.random.Random 13 | 14 | class MainActivity : AppCompatActivity() { 15 | 16 | private lateinit var binding: ActivityMainBinding 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | binding = ActivityMainBinding.inflate(layoutInflater) 22 | setContentView(binding.root) 23 | 24 | SuspendThreadSafeHelper.getInstance() 25 | .suspendThreadSafe(object : SuspendThreadSafeHelper.SuspendThreadCallback { 26 | override fun suspendThreadTimeout(waitTime: Double) { 27 | // Handle timeout 28 | Log.i("MainActivity", "suspendThreadTimeout: $waitTime") 29 | } 30 | 31 | override fun onError(errorMsg: String) { 32 | // Handle error 33 | Log.e("MainActivity", "onError: $errorMsg") 34 | } 35 | }) 36 | 37 | Handler(Looper.getMainLooper()).postDelayed({ 38 | Toast.makeText(this, "Start Mock Thread Suspend Timeout", Toast.LENGTH_SHORT).show() 39 | testThreadHook() 40 | }, 5000) 41 | } 42 | 43 | var myThread: Thread? = null 44 | 45 | private fun testThreadHook() { 46 | thread { 47 | for (i in 0..1000) { 48 | thread { 49 | while (true) { 50 | 51 | } 52 | } 53 | } 54 | } 55 | 56 | thread { 57 | myThread = thread(name = "EdisonLi-init-name") { 58 | // callThreadSuspendTimeout(myThread!!) 59 | while (true) { 60 | // Log.d("EdisonLi", this@SecondActivity.myThread?.name.toString()) 61 | } 62 | } 63 | while (true) { 64 | //Log.d("EdisonLi", myThread?.name.toString()) 65 | myThread?.name = "Thread-${Random.nextLong(1, 1000)}" 66 | } 67 | } 68 | 69 | thread { 70 | while (true) { 71 | Thread.sleep(5L) 72 | Thread.getAllStackTraces() 73 | } 74 | } 75 | thread { 76 | while (true) { 77 | Thread.sleep(5L) 78 | thread { 79 | Thread.sleep(3L) 80 | } 81 | } 82 | } 83 | } 84 | 85 | companion object { 86 | // Used to load the 'syshooklib' library on application startup. 87 | init { 88 | 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/art/art_thread.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by knight-zxw on 2023/1/3. 3 | // Email: nimdanoob@163.com 4 | // 5 | 6 | #ifndef KB_ART_THREAD_H_ 7 | #define KB_ART_THREAD_H_ 8 | #include 9 | #include "macro.h" 10 | #include "tls.h" 11 | 12 | #define UN_SUPPORT (-1) 13 | #define NOT_FOUND (0) 14 | 15 | namespace kbArt { 16 | class Thread { 17 | struct PACKED(4) tls_32bit_sized_values { 18 | // We have no control over the size of 'bool', but want our boolean fields 19 | // to be 4-byte quantities. 20 | using bool32_t = uint32_t; 21 | 22 | // The state and flags field must be changed atomically so that flag values 23 | // aren't lost. See `StateAndFlags` for bit assignments of `ThreadFlag` and 24 | // `ThreadState` values. Keeping the state and flags together allows an 25 | // atomic CAS to change from being Suspended to Runnable without a suspend 26 | // request occurring. 27 | std::atomic state_and_flags; 28 | // A non-zero value is used to tell the current thread to enter a safe point 29 | 30 | // at the next poll. 31 | int suspend_count; 32 | 33 | // Thin lock thread id. This is a small integer used by the thin lock 34 | // implementation. This is not to be confused with the native thread's tid, 35 | // nor is it the value returned by java.lang.Thread.getId --- this is a 36 | // distinct value, used only for locking. One important difference between 37 | // this id and the ids visible to managed code is that these ones get reused 38 | // (to ensure that they fit in the number of bits available). 39 | uint32_t thin_lock_thread_id; 40 | 41 | // System thread id. 42 | uint32_t tid; 43 | 44 | } tls32_; 45 | 46 | public: 47 | // Guaranteed to be non-zero. 48 | inline int32_t GetThreadId(int api_level) const { 49 | if (api_level < __ANDROID_API_S__ || 50 | api_level > __ANDROID_API_U__) { // < Android 12 || > android 14 51 | // now only support Android 12 13 14. 52 | return UN_SUPPORT; 53 | } 54 | uint32_t offset = 0; 55 | // calculate the offset of the field `thin_lock_thread_id` in the struct. 56 | offset = offsetof(tls_32bit_sized_values, thin_lock_thread_id); 57 | return *(int32_t *)((char *)this + offset); 58 | } 59 | 60 | inline uint32_t GetTid() { return tls32_.tid; } 61 | 62 | inline static Thread *current() { 63 | void *thread = __get_tls()[TLS_SLOT_ART_THREAD_SELF]; 64 | return reinterpret_cast(thread); 65 | } 66 | }; 67 | 68 | 69 | // only for android 15+ 70 | // See Thread.tlsPtr_.active_suspend1_barriers below for explanation. 71 | struct WrappedSuspend1Barrier { 72 | // TODO(b/23668816): At least weaken CHECKs to DCHECKs once the bug is fixed. 73 | static constexpr int kMagic = 0xba8; 74 | 75 | WrappedSuspend1Barrier() : magic_(kMagic), barrier_(1), next_(nullptr) {} 76 | 77 | int magic_; 78 | std::atomic barrier_; 79 | struct WrappedSuspend1Barrier *next_; 80 | }; 81 | } // namespace kbArt 82 | 83 | #endif // KB_ART_THREAD_H_ 84 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v5_11.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/26/24. 3 | // 4 | 5 | #include "suspend_thread_timeout_v5_11.h" 6 | #include "jni_init.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define TARGET_ART_LIB "libart.so" 13 | #define SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_14 "_ZN3artL26ThreadSuspendByPeerWarningERNS_18ScopedObjectAccessEN7android4base11LogSeverityEPKcP8_jobject" 14 | #define SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_8_11 "_ZN3artL26ThreadSuspendByPeerWarningEPNS_6ThreadEN7android4base11LogSeverityEPKcP8_jobject" 15 | // #define SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_8_13 16 | // "_ZN3artL26ThreadSuspendByPeerWarningERNS_18ScopedObjectAccessEN7android4base11LogSeverityEPKcP8_jobject" 17 | #define SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_6_7 "_ZN3artL26ThreadSuspendByPeerWarningEPNS_6ThreadENS_11LogSeverityEPKcP8_jobject" 18 | #define SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_5 "_ZN3artL26ThreadSuspendByPeerWarningEPNS_6ThreadEiPKcP8_jobject" 19 | #define LOG_TAG "suspend_thread_safe_v5_11" 20 | #define SUSPEND_LOG_MSG "Thread suspension timed out" 21 | 22 | enum LogSeverity { 23 | VERBOSE, 24 | DEBUG, 25 | INFO, 26 | WARNING, 27 | ERROR, 28 | FATAL_WITHOUT_ABORT, // For loggability tests, this is considered identical to FATAL. 29 | FATAL, 30 | }; 31 | 32 | namespace suspend_thread_safe_v5_11 { 33 | 34 | namespace { 35 | jobject callbackObj = nullptr; 36 | 37 | void *stubFunction = nullptr; 38 | void *originalFunction = nullptr; 39 | 40 | typedef void (*ThreadSuspendByPeerWarning)(void *self, LogSeverity severity, 41 | const char *message, jobject peer); 42 | 43 | void triggerSuspendTimeout(double withConsumedTime) { 44 | JNIEnv *pEnv = getJNIEnv(); 45 | if (pEnv == nullptr) { 46 | return; 47 | } 48 | jclass clsThread = pEnv->FindClass("java/lang/Thread"); 49 | if (clsThread == nullptr) { 50 | return; 51 | } 52 | // java.lang.NoClassDefFoundError: Class not found using the boot class 53 | // loader; no stack trace available 54 | jmethodID midCurrentThread = pEnv->GetStaticMethodID(clsThread, 55 | "currentThread", 56 | "()Ljava/lang/Thread;"); 57 | jobject currentThread = pEnv->CallStaticObjectMethod(clsThread, midCurrentThread); 58 | jmethodID midGetClassLoader = pEnv->GetMethodID(clsThread, 59 | "getContextClassLoader", 60 | "()Ljava/lang/ClassLoader;"); 61 | if (midGetClassLoader == nullptr) { 62 | return; 63 | } 64 | jobject classLoader = pEnv->CallObjectMethod(currentThread, midGetClassLoader); 65 | if (classLoader == nullptr) { 66 | return; 67 | } 68 | jclass clsClassLoader = pEnv->FindClass("java/lang/ClassLoader"); 69 | if (clsClassLoader == nullptr) { 70 | return; 71 | } 72 | jmethodID midLoadClass = pEnv->GetMethodID(clsClassLoader, 73 | "loadClass", 74 | "(Ljava/lang/String;)Ljava/lang/Class;"); 75 | if (midLoadClass == nullptr) { 76 | return; 77 | } 78 | jstring className = pEnv->NewStringUTF( 79 | "cn/huolala/threadshield/SuspendThreadSafeHelper$SuspendThreadCallback"); 80 | auto jThreadHookClass = (jclass) pEnv->CallObjectMethod(classLoader, 81 | midLoadClass, 82 | className); 83 | if (jThreadHookClass == nullptr) { 84 | return; 85 | } 86 | jmethodID jMethodId = pEnv->GetMethodID(jThreadHookClass, 87 | "suspendThreadTimeout", 88 | "(D)V"); 89 | if (jMethodId == nullptr) { 90 | return; 91 | } 92 | pEnv->CallVoidMethod(callbackObj, jMethodId, withConsumedTime); 93 | cleanup(); 94 | } 95 | 96 | void threadSuspendByPeerWarning(void *self, 97 | LogSeverity severity, 98 | const char *message, 99 | jobject peer) { 100 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hooked point success : %s", message); 101 | if (severity == FATAL && strcmp(message, SUSPEND_LOG_MSG) == 0) { 102 | // set low level log that can not send abort signal. 103 | severity = INFO; 104 | __android_log_print(ANDROID_LOG_WARN, LOG_TAG, "hit target: %s", message); 105 | ((ThreadSuspendByPeerWarning) originalFunction)(self, severity, message, peer); 106 | triggerSuspendTimeout(0); 107 | } 108 | } 109 | 110 | const char *getThreadSuspendByPeerWarningFunctionName() { 111 | int apiLevel = android_get_device_api_level(); 112 | // Simplified logic based on Android API levels 113 | if (apiLevel < __ANDROID_API_M__) { 114 | return SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_5; 115 | } else if (apiLevel < __ANDROID_API_O__) { 116 | // below android 8 117 | return SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_6_7; 118 | } else if (apiLevel <= __ANDROID_API_S__) { 119 | // android 12.0 120 | // here can not distinguish 12.0 and 12.1. 121 | // 12.1 need to use SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING as symbol name. 122 | // but now android 12 13 14 not use this logic. 123 | // hook `SuspendThreadByPeer` to call `SuspendThreadByThreadId` as substitute. 124 | // 12.0/12.1 can not call by java side control. 125 | return SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_8_11; 126 | } else { 127 | // in fact, unreachable here. 128 | // android 14+ 129 | // now [12 - 14] use SuspendThreadByThreadId to suspend thread. 130 | return SYMBOL_THREAD_SUSPEND_BY_PEER_WARNING_14; 131 | } 132 | } 133 | 134 | void hookPointFailed(const char *errMsg) { 135 | JNIEnv *pEnv = getJNIEnv(); 136 | if (pEnv == nullptr || callbackObj == nullptr) { 137 | return; 138 | } 139 | jclass jThreadHookClass = pEnv->FindClass( 140 | "cn/huolala/threadshield/SuspendThreadSafeHelper$SuspendThreadCallback"); 141 | if (jThreadHookClass != nullptr) { 142 | jmethodID jMethodId = pEnv->GetMethodID(jThreadHookClass, 143 | "onError", 144 | "(Ljava/lang/String;)V"); 145 | if (jMethodId != nullptr) { 146 | jstring jErrMsg = pEnv->NewStringUTF(errMsg); 147 | pEnv->CallVoidMethod(callbackObj, jMethodId, jErrMsg); 148 | pEnv->DeleteLocalRef(jErrMsg); 149 | } 150 | pEnv->DeleteLocalRef(jThreadHookClass); 151 | } 152 | cleanup(); 153 | } 154 | 155 | void initHookPoint() { 156 | if (stubFunction != nullptr) { 157 | shadowhook_unhook(stubFunction); 158 | stubFunction = nullptr; 159 | } 160 | stubFunction = shadowhook_hook_sym_name( 161 | TARGET_ART_LIB, 162 | getThreadSuspendByPeerWarningFunctionName(), 163 | (void *) threadSuspendByPeerWarning, 164 | (void **) &originalFunction 165 | ); 166 | if (stubFunction == nullptr) { 167 | const int err_num = shadowhook_get_errno(); 168 | const char *errMsg = shadowhook_to_errmsg(err_num); 169 | if (errMsg == nullptr) { 170 | return; 171 | } 172 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup failed: %s", errMsg); 173 | hookPointFailed(errMsg); 174 | } else { 175 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup success"); 176 | } 177 | } 178 | } 179 | 180 | void suspendThreadSafe(JNIEnv *env, jobject jCallback) { 181 | if (callbackObj != nullptr) { 182 | env->DeleteGlobalRef(callbackObj); 183 | } 184 | callbackObj = env->NewGlobalRef(jCallback); 185 | initHookPoint(); 186 | } 187 | } -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v12_14.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/25/24. 3 | // 4 | #include 5 | #include 6 | #include 7 | #include "jni_init.h" 8 | #include "art/art_thread.h" 9 | 10 | #define TARGET_ART_LIB "libart.so" 11 | #define LOG_TAG "suspend_thread_safe_v12_14" 12 | 13 | #define SYMBOL_SUSPEND_THREAD_BY_ID "_ZN3art10ThreadList23SuspendThreadByThreadIdEjNS_13SuspendReasonEPb" 14 | // [12.1, 15) 15 | #define SYMBOL_SUSPEND_THREAD_BY_PEER_NEW "_ZN3art10ThreadList19SuspendThreadByPeerEP8_jobjectNS_13SuspendReasonEPb" 16 | // [9, 12.0] 17 | #define SYMBOL_SUSPEND_THREAD_BY_PEER_OLD "_ZN3art10ThreadList19SuspendThreadByPeerEP8_jobjectbNS_13SuspendReasonEPb" 18 | 19 | namespace suspend_thread_safe_v12_14 { 20 | 21 | enum class SuspendReason : char { 22 | // Suspending for internal reasons (e.g. GC, stack trace, etc.). 23 | kInternal, 24 | // Suspending due to non-runtime, user controlled, code. (For example Thread#Suspend()). 25 | kForUserCode, 26 | }; 27 | 28 | namespace { 29 | jobject callbackObj = nullptr; 30 | jfieldID nativePeerFieldId = nullptr; 31 | 32 | void *suspendThreadByThreadId = nullptr; 33 | void *originalFunctionReplace = nullptr; 34 | 35 | void *stubFunction = nullptr; 36 | 37 | typedef void *(*SuspendThreadByThreadId_t)(void *thread_list, 38 | uint32_t threadId, 39 | SuspendReason suspendReason, 40 | bool *timed_out); 41 | 42 | typedef void *(*SuspendThreadByPeer_t)(void *thread_list, 43 | jobject peer, 44 | SuspendReason suspendReason, 45 | bool *timed_out); 46 | 47 | typedef void *(*SuspendThreadByPeer_compatible_t)(void *thread_list, 48 | jobject peer, 49 | bool request_suspension, 50 | SuspendReason suspendReason, 51 | bool *timed_out); 52 | 53 | jlong getThreadIdByPeer(jobject peer) { 54 | JNIEnv *pEnv = getJNIEnv(); 55 | jlong threadIdByPeer = (pEnv != nullptr && nativePeerFieldId != nullptr && 56 | peer != nullptr) 57 | ? pEnv->GetLongField(peer, nativePeerFieldId) 58 | : 0; 59 | cleanup(); 60 | return threadIdByPeer; 61 | } 62 | 63 | // Function to mask thread suspend operation by peer, replacing it with thread suspend by ID. 64 | void *replaceThreadSuspendFunc(void *thread_list, jobject peer, 65 | SuspendReason suspendReason, 66 | bool *timed_out) { 67 | jlong thread_id = getThreadIdByPeer(peer); 68 | auto *thread = reinterpret_cast(thread_id); 69 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Replace function %p success", thread); 70 | if (thread == nullptr) { 71 | return ((SuspendThreadByPeer_t) originalFunctionReplace)( 72 | thread_list, 73 | peer, 74 | suspendReason, 75 | timed_out); 76 | } 77 | const int32_t threadId = thread->GetThreadId(android_get_device_api_level()); 78 | if (threadId == UN_SUPPORT || threadId == NOT_FOUND) { 79 | return ((SuspendThreadByPeer_t) originalFunctionReplace)( 80 | thread_list, 81 | peer, 82 | suspendReason, 83 | timed_out); 84 | } 85 | return ((SuspendThreadByThreadId_t) suspendThreadByThreadId)( 86 | thread_list, 87 | threadId, 88 | suspendReason, 89 | timed_out); 90 | } 91 | 92 | void *replaceThreadSuspendCompatibleFunc( 93 | void *thread_list, 94 | jobject peer, 95 | bool request_suspension, 96 | SuspendReason suspendReason, 97 | bool *timed_out) { 98 | jlong thread_id = getThreadIdByPeer(peer); 99 | auto *thread = reinterpret_cast(thread_id); 100 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Replace function %p success", thread); 101 | if (thread == nullptr) { 102 | return ((SuspendThreadByPeer_compatible_t) originalFunctionReplace)( 103 | thread_list, 104 | peer, 105 | request_suspension, 106 | suspendReason, 107 | timed_out); 108 | } 109 | const int32_t threadId = thread->GetThreadId(android_get_device_api_level()); 110 | if (threadId == UN_SUPPORT || threadId == NOT_FOUND) { 111 | return ((SuspendThreadByPeer_compatible_t) originalFunctionReplace)( 112 | thread_list, 113 | peer, 114 | request_suspension, 115 | suspendReason, 116 | timed_out); 117 | } 118 | return ((SuspendThreadByThreadId_t) suspendThreadByThreadId)( 119 | thread_list, 120 | threadId, 121 | suspendReason, 122 | timed_out); 123 | } 124 | 125 | void hookPointFailed(const char *errMsg) { 126 | JNIEnv *pEnv = getJNIEnv(); 127 | if (pEnv == nullptr) { 128 | return; 129 | } 130 | jclass jThreadHookClass = pEnv->FindClass( 131 | "cn/huolala/threadshield/SuspendThreadSafeHelper$SuspendThreadCallback"); 132 | if (jThreadHookClass != nullptr) { 133 | jmethodID jMethodId = 134 | pEnv->GetMethodID(jThreadHookClass, "onError", "(Ljava/lang/String;)V"); 135 | if (jMethodId != nullptr) { 136 | jstring jErrMsg = pEnv->NewStringUTF(errMsg); 137 | pEnv->CallVoidMethod(callbackObj, jMethodId, jErrMsg); 138 | pEnv->DeleteLocalRef(jErrMsg); 139 | } 140 | pEnv->DeleteLocalRef(jThreadHookClass); 141 | } 142 | cleanup(); 143 | } 144 | 145 | void handleHookSetupFailure(const void *stubFunc) { 146 | if (stubFunc == nullptr) { 147 | const int err_num = shadowhook_get_errno(); 148 | const char *errMsg = shadowhook_to_errmsg(err_num); 149 | if (errMsg == nullptr || callbackObj == nullptr) { 150 | return; 151 | } 152 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup failed: %s", errMsg); 153 | hookPointFailed(errMsg); 154 | } else { 155 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup success"); 156 | } 157 | } 158 | 159 | void initHookPoint(JNIEnv *env, jobject jCallback) { 160 | if (callbackObj != nullptr) { 161 | env->DeleteGlobalRef(callbackObj); 162 | } 163 | callbackObj = env->NewGlobalRef(jCallback); 164 | jclass jThreadHookClass = env->FindClass("java/lang/Thread"); 165 | 166 | void *handle = shadowhook_dlopen(TARGET_ART_LIB); 167 | suspendThreadByThreadId = shadowhook_dlsym(handle, SYMBOL_SUSPEND_THREAD_BY_ID); 168 | 169 | if (jThreadHookClass == nullptr || suspendThreadByThreadId == nullptr) { 170 | return; 171 | } 172 | 173 | // 'nativePeer' is a field in the Thread.class 174 | nativePeerFieldId = env->GetFieldID(jThreadHookClass, "nativePeer", "J"); 175 | 176 | if (stubFunction != nullptr) { 177 | shadowhook_unhook(stubFunction); 178 | stubFunction = nullptr; 179 | } 180 | 181 | stubFunction = shadowhook_hook_sym_name(TARGET_ART_LIB, 182 | SYMBOL_SUSPEND_THREAD_BY_PEER_NEW, 183 | (void *) replaceThreadSuspendFunc, 184 | (void **) &originalFunctionReplace); 185 | 186 | if (android_get_device_api_level() != __ANDROID_API_S__) { 187 | handleHookSetupFailure(stubFunction); 188 | return; 189 | } 190 | 191 | if (stubFunction == nullptr) { 192 | // because can not distinguish 12.0 with 12.1 by any native api. 193 | // retry with another symbol name. 194 | stubFunction = shadowhook_hook_sym_name(TARGET_ART_LIB, 195 | SYMBOL_SUSPEND_THREAD_BY_PEER_OLD, 196 | (void *) replaceThreadSuspendCompatibleFunc, 197 | (void **) &originalFunctionReplace); 198 | handleHookSetupFailure(stubFunction); 199 | } 200 | } 201 | } 202 | 203 | void suspendThreadSafe(JNIEnv *env, jobject jCallback) { 204 | initHookPoint(env, jCallback); 205 | } 206 | } -------------------------------------------------------------------------------- /lib_thread_shield/src/main/cpp/suspend_thread_timeout_v15.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by EdisonLi on 8/24/24. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "art/art_thread.h" 10 | #include "suspend_thread_timeout_v15.h" 11 | #include "base/stringprintf.h" 12 | #include "jni_init.h" 13 | 14 | #define TARGET_BASE_LIB_64 "/apex/com.android.art/lib64/libbase.so" 15 | #define TARGET_BASE_LIB_32 "/apex/com.android.art/lib/libbase.so" 16 | #define SYMBOL_HOOK_POINT_STRING_PRINTF "_ZN7android4base12StringPrintfEPKcz" 17 | #define LOG_TAG "suspend_thread_safe_v15" 18 | 19 | #if defined(__aarch64__) || defined(__x86_64__) || defined(__mips64) 20 | const char *libName = TARGET_BASE_LIB_64; 21 | #else 22 | const char* libName = TARGET_BASE_LIB_32; 23 | #endif 24 | 25 | namespace suspend_thread_safe_v15 { 26 | namespace { 27 | jobject callbackObj = nullptr; 28 | void *stubFunction = nullptr; 29 | void *originalFunction = nullptr; 30 | 31 | typedef std::string (*StringPrintf_t)(const char *format, ...); 32 | 33 | bool checkFormat(const char *format) { 34 | return 35 | strstr(format, "timed out") != nullptr && 36 | strstr(format, "state&flags") != nullptr && 37 | strstr(format, "priority") != nullptr && 38 | strstr(format, "barriers") != nullptr && 39 | strstr(format, "ours") != nullptr && 40 | strstr(format, "barrier value") != nullptr && 41 | strstr(format, "nsusps") != nullptr && 42 | strstr(format, "ncheckpts") != nullptr && 43 | strstr(format, "thread_info") != nullptr; 44 | } 45 | 46 | void triggerSuspendTimeout(double withConsumedTime) { 47 | JNIEnv *pEnv = getJNIEnv(); 48 | if (pEnv == nullptr) { 49 | return; 50 | } 51 | jclass clsThread = pEnv->FindClass("java/lang/Thread"); 52 | if (clsThread == nullptr) { 53 | return; 54 | } 55 | // java.lang.NoClassDefFoundError: Class not found using the boot class 56 | // loader; no stack trace available 57 | jmethodID midCurrentThread = pEnv->GetStaticMethodID(clsThread, 58 | "currentThread", 59 | "()Ljava/lang/Thread;"); 60 | jobject currentThread =pEnv->CallStaticObjectMethod(clsThread, midCurrentThread); 61 | jmethodID midGetClassLoader = pEnv->GetMethodID(clsThread, 62 | "getContextClassLoader", 63 | "()Ljava/lang/ClassLoader;"); 64 | if (midGetClassLoader == nullptr) { 65 | return; 66 | } 67 | jobject classLoader =pEnv->CallObjectMethod(currentThread, midGetClassLoader); 68 | if (classLoader == nullptr) { 69 | return; 70 | } 71 | jclass clsClassLoader = pEnv->FindClass("java/lang/ClassLoader"); 72 | if (clsClassLoader == nullptr) { 73 | return; 74 | } 75 | jmethodID midLoadClass = pEnv->GetMethodID(clsClassLoader, 76 | "loadClass", 77 | "(Ljava/lang/String;)Ljava/lang/Class;"); 78 | if (midLoadClass == nullptr) { 79 | return; 80 | } 81 | jstring className = pEnv->NewStringUTF("cn/huolala/threadshield/SuspendThreadSafeHelper$SuspendThreadCallback"); 82 | auto jThreadHookClass =(jclass) pEnv->CallObjectMethod(classLoader, 83 | midLoadClass, 84 | className); 85 | if (jThreadHookClass == nullptr) { 86 | return; 87 | } 88 | jmethodID jMethodId = pEnv->GetMethodID(jThreadHookClass, 89 | "suspendThreadTimeout", 90 | "(D)V"); 91 | if (jMethodId == nullptr) { 92 | return; 93 | } 94 | pEnv->CallVoidMethod(callbackObj, jMethodId, withConsumedTime); 95 | cleanup(); 96 | } 97 | 98 | std::string proxyStringPrintfFunc(const char *format, ...) { 99 | if (checkFormat(format)) { 100 | va_list args; 101 | va_start(args, format); 102 | const char *func_name = va_arg(args, const char*); // func_name 103 | va_arg(args, int); // tid 104 | va_arg(args, const char*); // name.c_str() 105 | va_arg(args, int); // state_and_flags 106 | va_arg(args, int); // native_priority 107 | va_arg(args, void*); // first_barrier 108 | using namespace kbArt; 109 | WrappedSuspend1Barrier *wrappedBarrier = va_arg(args, WrappedSuspend1Barrier*); 110 | if (wrappedBarrier != nullptr) { 111 | if (wrappedBarrier->barrier_.load(std::memory_order_acquire) == 0) { 112 | return ((StringPrintf_t) originalFunction)("thread has been suspend : %s", 113 | func_name); 114 | } 115 | // record start time. 116 | struct timespec startTime{}; 117 | clock_gettime(CLOCK_MONOTONIC, &startTime); 118 | 119 | struct timespec ts{}; 120 | ts.tv_sec = 0; 121 | ts.tv_nsec = 10000000; // 10ms (1ms = 1,000,000ns) 122 | 123 | while (true) { 124 | if (wrappedBarrier->barrier_.load(std::memory_order_acquire) == 0) { 125 | struct timespec endTime{}; 126 | clock_gettime(CLOCK_MONOTONIC, &endTime); 127 | long secDuration = endTime.tv_sec - startTime.tv_sec; 128 | long nsecDuration = endTime.tv_nsec - startTime.tv_nsec; 129 | if (nsecDuration < 0) { 130 | --secDuration; 131 | nsecDuration += 1e9; // 1s = 1,000,000,000 ns 132 | } 133 | const double waitDuration = 134 | static_cast(secDuration + nsecDuration) / 1e9; 135 | triggerSuspendTimeout(std::round(waitDuration * 1000) / 1000); 136 | return ((StringPrintf_t) originalFunction)( 137 | "thread has been suspend : %s, cost time %f", func_name, 138 | waitDuration); 139 | } 140 | // release cpu resource. 141 | nanosleep(&ts, nullptr); 142 | } 143 | } 144 | va_end(args); 145 | } 146 | 147 | va_list ap; 148 | va_start(ap, format); 149 | std::string result; 150 | base::StringAppendV(&result, format, ap); 151 | va_end(ap); 152 | return result; 153 | } 154 | 155 | void hookPointFailed(const char *errMsg) { 156 | JNIEnv *pEnv = getJNIEnv(); 157 | if (pEnv == nullptr || callbackObj == nullptr) { 158 | return; 159 | } 160 | jclass jThreadHookClass = pEnv->FindClass( 161 | "cn/huolala/threadshield/SuspendThreadSafeHelper$SuspendThreadCallback"); 162 | if (jThreadHookClass != nullptr) { 163 | jmethodID jMethodId = pEnv->GetMethodID(jThreadHookClass, 164 | "onError", 165 | "(Ljava/lang/String;)V"); 166 | if (jMethodId != nullptr) { 167 | jstring jErrMsg = pEnv->NewStringUTF(errMsg); 168 | pEnv->CallVoidMethod(callbackObj, jMethodId, jErrMsg); 169 | pEnv->DeleteLocalRef(jErrMsg); 170 | } 171 | pEnv->DeleteLocalRef(jThreadHookClass); 172 | } 173 | cleanup(); 174 | } 175 | 176 | void initHookPoint() { 177 | if (stubFunction != nullptr) { 178 | shadowhook_unhook(stubFunction); 179 | stubFunction = nullptr; 180 | } 181 | stubFunction = shadowhook_hook_sym_name(libName, 182 | SYMBOL_HOOK_POINT_STRING_PRINTF, 183 | (void *) proxyStringPrintfFunc, 184 | (void **) &originalFunction); 185 | if (stubFunction == nullptr) { 186 | const int err_num = shadowhook_get_errno(); 187 | const char *errMsg = shadowhook_to_errmsg(err_num); 188 | if (errMsg == nullptr) { 189 | return; 190 | } 191 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup failed: %s", errMsg); 192 | hookPointFailed(errMsg); 193 | } else { 194 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Hook setup success"); 195 | } 196 | } 197 | } 198 | 199 | void suspendThreadSafe(JNIEnv *env, jobject jCallback) { 200 | if (callbackObj != nullptr) { 201 | env->DeleteGlobalRef(callbackObj); 202 | } 203 | callbackObj = env->NewGlobalRef(jCallback); 204 | initHookPoint(); 205 | } 206 | } 207 | 208 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------