diff --git a/build.gradle.kts b/build.gradle.kts index 20d6672..c3952d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,5 +2,5 @@ val androidTargetSdkVersion by extra(33) val androidMinSdkVersion by extra(21) val androidBuildToolsVersion by extra("33.0.0") val androidCompileSdkVersion by extra(33) -val androidNdkVersion by extra("24.0.8215888") +val androidNdkVersion by extra("25.1.8937393") val androidCmakeVersion by extra("3.22.1+") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fc..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/lsplant/build.gradle.kts b/lsplant/build.gradle.kts index 2b59b07..c5096f8 100644 --- a/lsplant/build.gradle.kts +++ b/lsplant/build.gradle.kts @@ -114,7 +114,7 @@ publishing { register("lsplant") { group = "org.lsposed.lsplant" artifactId = "lsplant" - version = "4.1" + version = "4.2" afterEvaluate { from(components.getByName("release")) artifact(symbolsTask) diff --git a/lsplant/src/main/jni/art/runtime/runtime.hpp b/lsplant/src/main/jni/art/runtime/runtime.hpp index 5d45eab..f4e247f 100644 --- a/lsplant/src/main/jni/art/runtime/runtime.hpp +++ b/lsplant/src/main/jni/art/runtime/runtime.hpp @@ -24,19 +24,46 @@ namespace lsplant::art { class Runtime { +public: + enum class RuntimeDebugState { + // This doesn't support any debug features / method tracing. This is the expected state + // usually. + kNonJavaDebuggable, + // This supports method tracing and a restricted set of debug features (for ex: redefinition + // isn't supported). We transition to this state when method tracing has started or when the + // debugger was attached and transition back to NonDebuggable once the tracing has stopped / + // the debugger agent has detached.. + kJavaDebuggable, + // The runtime was started as a debuggable runtime. This allows us to support the extended + // set + // of debug features (for ex: redefinition). We never transition out of this state. + kJavaDebuggableAtInit + }; + private: inline static Runtime *instance_; CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) { - if (SetJavaDebuggableSym) [[likely]] { - SetJavaDebuggableSym(thiz, value); - } + SetJavaDebuggableSym(thiz, value); } + CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetRuntimeDebugState, void *thiz, RuntimeDebugState value) { + SetRuntimeDebugStateSym(thiz, value); + } + + inline static size_t debug_state_offset = 0U; + public: inline static Runtime *Current() { return instance_; } - void SetJavaDebuggable(bool value) { SetJavaDebuggable(this, value); } + void SetJavaDebuggable(RuntimeDebugState value) { + if (SetJavaDebuggableSym) { + SetJavaDebuggable(this, value != RuntimeDebugState::kNonJavaDebuggable); + } else if (debug_state_offset > 0) { + *reinterpret_cast(reinterpret_cast(instance_) + + debug_state_offset) = value; + } + } static bool Init(const HookHandler &handler) { int sdk_int = GetAndroidApiLevel(); @@ -48,7 +75,32 @@ public: LOGD("runtime instance = %p", instance_); if (sdk_int >= __ANDROID_API_O__) { if (!RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, - "_ZN3art7Runtime17SetJavaDebuggableEb")) { + "_ZN3art7Runtime17SetJavaDebuggableEb") && + !RETRIEVE_MEM_FUNC_SYMBOL( + SetRuntimeDebugState, + "_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE")) { + return false; + } + } + if (SetRuntimeDebugStateSym) { + static constexpr size_t kLargeEnoughSizeForRuntime = 4096; + std::array code; + static_assert(static_cast(RuntimeDebugState::kJavaDebuggable) != 0); + static_assert(static_cast(RuntimeDebugState::kJavaDebuggableAtInit) != 0); + code.fill(uint8_t{0}); + auto *const fake_runtime = reinterpret_cast(code.data()); + SetRuntimeDebugState(fake_runtime, RuntimeDebugState::kJavaDebuggable); + for (size_t i = 0; i < kLargeEnoughSizeForRuntime; ++i) { + if (*reinterpret_cast( + reinterpret_cast(fake_runtime) + i) == + RuntimeDebugState::kJavaDebuggable) { + LOGD("found debug_state at offset %zu", i); + debug_state_offset = i; + break; + } + } + if (debug_state_offset == 0) { + LOGE("failed to find debug_state"); return false; } } diff --git a/lsplant/src/main/jni/lsplant.cc b/lsplant/src/main/jni/lsplant.cc index d63a59d..e65fe7f 100644 --- a/lsplant/src/main/jni/lsplant.cc +++ b/lsplant/src/main/jni/lsplant.cc @@ -274,7 +274,7 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) { if (IsJavaDebuggable(env)) { // Make the runtime non-debuggable as a workaround // when ShouldUseInterpreterEntrypoint inlined - Runtime::Current()->SetJavaDebuggable(false); + Runtime::Current()->SetJavaDebuggable(Runtime::RuntimeDebugState::kNonJavaDebuggable); } return true; } @@ -735,8 +735,13 @@ using ::lsplant::IsHooked; [[maybe_unused]] bool MakeDexFileTrusted(JNIEnv *env, jobject cookie) { struct Guard { - Guard() { Runtime::Current()->SetJavaDebuggable(true); } - ~Guard() { Runtime::Current()->SetJavaDebuggable(false); } + Guard() { + Runtime::Current()->SetJavaDebuggable( + Runtime::RuntimeDebugState::kJavaDebuggableAtInit); + } + ~Guard() { + Runtime::Current()->SetJavaDebuggable(Runtime::RuntimeDebugState::kNonJavaDebuggable); + } } guard; if (!cookie) return false; return DexFile::SetTrusted(env, cookie); diff --git a/settings.gradle.kts b/settings.gradle.kts index dd2d641..9204d51 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,8 +5,8 @@ pluginManagement { mavenCentral() } plugins { - id("com.android.application") version "7.2.1" - id("com.android.library") version "7.2.1" + id("com.android.application") version "7.2.2" + id("com.android.library") version "7.2.2" } } dependencyResolutionManagement {