diff --git a/lsplant/src/main/jni/art/instrumentation.hpp b/lsplant/src/main/jni/art/instrumentation.hpp deleted file mode 100644 index 2b0cb82..0000000 --- a/lsplant/src/main/jni/art/instrumentation.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include "common.hpp" -#include "runtime/art_method.hpp" - -namespace lsplant::art { - -class Instrumentation { - inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) { - std::shared_lock lk(hooked_methods_lock_); - if (auto found = hooked_methods_.find(art_method); - found != hooked_methods_.end() && art_method->GetEntryPoint() != quick_code) - [[unlikely]] { - LOGD("Propagate update method code %p for hooked method %s to its backup", quick_code, - art_method->PrettyMethod().c_str()); - return found->second.second; - } - return art_method; - } - - CREATE_MEM_HOOK_STUB_ENTRY( - "_ZN3art15instrumentation15Instrumentation21UpdateMethodsCodeImplEPNS_9ArtMethodEPKv", void, - UpdateMethodsCodeImpl, - (Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), - { backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code); }); - - CREATE_MEM_HOOK_STUB_ENTRY( - "_ZN3art15instrumentation15Instrumentation17UpdateMethodsCodeEPNS_9ArtMethodEPKv", void, - UpdateMethodsCode, (Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), - { - if (UpdateMethodsCodeImpl.backup) { - UpdateMethodsCodeImpl.backup(thiz, MaybeUseBackupMethod(art_method, quick_code), - quick_code); - } else { - backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code); - } - }); - CREATE_MEM_HOOK_STUB_ENTRY( - "_ZN3art15instrumentation15Instrumentation17UpdateMethodsCodeEPNS_6mirror9ArtMethodEPKvS6_b", - void, UpdateMethodsCodeWithProtableCode, - (Instrumentation * thiz, ArtMethod *art_method, const void *quick_code, - const void *portable_code, bool have_portable_code), - { - backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code, portable_code, - have_portable_code); - }); - -public: - static bool Init(const HookHandler &handler) { - int sdk_int = GetAndroidApiLevel(); - if (sdk_int < __ANDROID_API_M__) [[unlikely]] { - if (!HookSyms(handler, UpdateMethodsCodeWithProtableCode)) { - return false; - } - return true; - } - if (!HookSyms(handler, UpdateMethodsCode)) { - return false; - } - if (sdk_int >= __ANDROID_API_N__) [[likely]] { - if (!HookSyms(handler, UpdateMethodsCodeImpl)) { - return false; - } - } - return true; - } -}; - -} // namespace lsplant::art diff --git a/lsplant/src/main/jni/art/mirror/class.hpp b/lsplant/src/main/jni/art/mirror/class.hpp new file mode 100644 index 0000000..710988c --- /dev/null +++ b/lsplant/src/main/jni/art/mirror/class.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "common.hpp" + +namespace lsplant::art { + +namespace dex { +class ClassDef {}; +} // namespace dex + +namespace mirror { + +class Class { +private: + CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) { + if (GetDescriptorSym) [[likely]] + return GetDescriptorSym(thiz, storage); + else + return ""; + } + + CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) { + if (GetClassDefSym) [[likely]] + return GetClassDefSym(thiz); + return nullptr; + } + +public: + static bool Init(const HookHandler &handler) { + if (!RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor, + "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112" + "basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE")) { + return false; + } + if (!RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv")) { + return false; + } + return true; + } + + const char *GetDescriptor(std::string *storage) { + if (GetDescriptorSym) { + return GetDescriptor(this, storage); + } + return ""; + } + + std::string GetDescriptor() { + std::string storage; + return GetDescriptor(&storage); + } + + const dex::ClassDef *GetClassDef() { + if (GetClassDefSym) return GetClassDef(this); + return nullptr; + } +}; + +} // namespace mirror +} // namespace lsplant::art diff --git a/lsplant/src/main/jni/art/runtime/art_method.hpp b/lsplant/src/main/jni/art/runtime/art_method.hpp index 31b1e0f..d6a0fc2 100644 --- a/lsplant/src/main/jni/art/runtime/art_method.hpp +++ b/lsplant/src/main/jni/art/runtime/art_method.hpp @@ -1,5 +1,6 @@ #pragma once +#include "art/mirror/class.hpp" #include "common.hpp" namespace lsplant::art { @@ -125,6 +126,11 @@ public: return PrettyMethod(this, with_signature); } + mirror::Class *GetDeclaringClass() { + return reinterpret_cast(*reinterpret_cast( + reinterpret_cast(this) + declaring_class_offset)); + } + static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { if (art_method_field) [[likely]] { return reinterpret_cast( @@ -221,6 +227,8 @@ public: field_offset); }; access_flags_offset = get_offset_from_art_method("accessFlags", "I"); + declaring_class_offset = + get_offset_from_art_method("declaringClass", "Ljava/lang/Class;"); if (sdk_int == __ANDROID_API_L__) { entry_point_offset = get_offset_from_art_method("entryPointFromQuickCompiledCode", "J"); @@ -229,6 +237,7 @@ public: data_offset = get_offset_from_art_method("entryPointFromJni", "J"); } } + LOGD("ArtMethod::declaring_class offset: %zu", declaring_class_offset); LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); LOGD("ArtMethod::data offset: %zu", data_offset); LOGD("ArtMethod::access_flags offset: %zu", access_flags_offset); @@ -279,7 +288,7 @@ public: kAccCompileDontBother = kAccDefaultConflict; } } - if (sdk_int <= __ANDROID_API_N__) { + if (sdk_int < __ANDROID_API_N__) { kAccCompileDontBother = 0; } if (sdk_int <= __ANDROID_API_M__) [[unlikely]] { @@ -312,6 +321,7 @@ private: inline static size_t interpreter_entry_point_offset = 0; inline static size_t data_offset = 0; inline static size_t access_flags_offset = 0; + inline static size_t declaring_class_offset = 0; inline static uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000; inline static uint32_t kAccPreCompiled = 0x00200000; inline static uint32_t kAccCompileDontBother = 0x02000000; diff --git a/lsplant/src/main/jni/art/runtime/class_linker.hpp b/lsplant/src/main/jni/art/runtime/class_linker.hpp index 8bea0d9..a4242cc 100644 --- a/lsplant/src/main/jni/art/runtime/class_linker.hpp +++ b/lsplant/src/main/jni/art/runtime/class_linker.hpp @@ -1,6 +1,7 @@ #pragma once #include "art/runtime/art_method.hpp" +#include "art/runtime/obj_ptr.h" #include "art/thread.hpp" #include "common.hpp" @@ -35,6 +36,59 @@ private: return backup(art_method); }); + static auto GetBackupMethods(mirror::Class *mirror_class) { + std::list> out; + auto class_def = mirror_class->GetClassDef(); + if (!class_def) return out; + std::shared_lock lk(hooked_classes_lock_); + if (auto found = hooked_classes_.find(class_def); found != hooked_classes_.end()) { + LOGD("Before fixup %s, backup hooked methods' trampoline", + mirror_class->GetDescriptor().c_str()); + for (auto method : found->second) { + out.emplace_back(method, method->GetEntryPoint()); + } + } + return out; + } + + static void FixTrampoline(const std::list> &methods) { + std::shared_lock lk(hooked_methods_lock_); + for (const auto &[art_method, old_trampoline] : methods) { + if (auto found = hooked_methods_.find(art_method); found != hooked_methods_.end()) { + if (auto new_trampoline = art_method->GetEntryPoint(); + new_trampoline != old_trampoline) { + found->second.second->SetEntryPoint(new_trampoline); + art_method->SetEntryPoint(old_trampoline); + } + } + } + } + + CREATE_MEM_HOOK_STUB_ENTRY( + "_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void, + FixupStaticTrampolines, (ClassLinker * thiz, ObjPtr mirror_class), { + auto backup_methods = GetBackupMethods(mirror_class); + backup(thiz, mirror_class); + FixTrampoline(backup_methods); + }); + + CREATE_MEM_HOOK_STUB_ENTRY( + "_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE", + void, FixupStaticTrampolinesWithThread, + (ClassLinker * thiz, art::Thread *self, ObjPtr mirror_class), { + auto backup_methods = GetBackupMethods(mirror_class); + backup(thiz, self, mirror_class); + FixTrampoline(backup_methods); + }); + + CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE", + void, FixupStaticTrampolinesRaw, + (ClassLinker * thiz, mirror::Class *mirror_class), { + auto backup_methods = GetBackupMethods(mirror_class); + backup(thiz, mirror_class); + FixTrampoline(backup_methods); + }); + public: static bool Init(const HookHandler &handler) { int sdk_int = GetAndroidApiLevel(); @@ -46,6 +100,11 @@ public: } } + if (!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines, + FixupStaticTrampolinesRaw)) { + return false; + } + if (!RETRIEVE_MEM_FUNC_SYMBOL( SetEntryPointsToInterpreter, "_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) diff --git a/lsplant/src/main/jni/art/runtime/obj_ptr.h b/lsplant/src/main/jni/art/runtime/obj_ptr.h new file mode 100644 index 0000000..b8be2e0 --- /dev/null +++ b/lsplant/src/main/jni/art/runtime/obj_ptr.h @@ -0,0 +1,12 @@ +#pragma once + +template +class ObjPtr { +public: + inline MirrorType* operator->() const { return Ptr(); } + inline MirrorType* Ptr() const { return reference_; } + inline operator MirrorType*() const { return Ptr(); } + +private: + MirrorType* reference_; +}; diff --git a/lsplant/src/main/jni/art/thread.hpp b/lsplant/src/main/jni/art/thread.hpp index d1285c5..56a12de 100644 --- a/lsplant/src/main/jni/art/thread.hpp +++ b/lsplant/src/main/jni/art/thread.hpp @@ -5,17 +5,6 @@ namespace lsplant::art { class Thread { - struct ObjPtr { - void *data; - }; - - CREATE_MEM_FUNC_SYMBOL_ENTRY(ObjPtr, DecodeJObject, Thread *thiz, jobject obj) { - if (DecodeJObjectSym) [[likely]] - return DecodeJObjectSym(thiz, obj); - else - return {.data = nullptr}; - } - CREATE_FUNC_SYMBOL_ENTRY(Thread *, CurrentFromGdb) { if (CurrentFromGdbSym) [[likely]] return CurrentFromGdbSym(); @@ -27,22 +16,11 @@ public: static Thread *Current() { return CurrentFromGdb(); } static bool Init(const HookHandler &handler) { - if (!RETRIEVE_MEM_FUNC_SYMBOL(DecodeJObject, "_ZNK3art6Thread13DecodeJObjectEP8_jobject")) - [[unlikely]] { - return false; - } if (!RETRIEVE_FUNC_SYMBOL(CurrentFromGdb, "_ZN3art6Thread14CurrentFromGdbEv")) [[unlikely]] { return false; } return true; } - - void *DecodeJObject(jobject obj) { - if (DecodeJObjectSym) [[likely]] { - return DecodeJObject(this, obj).data; - } - return nullptr; - } }; } // namespace lsplant::art diff --git a/lsplant/src/main/jni/common.hpp b/lsplant/src/main/jni/common.hpp index c6a27dd..576ce29 100644 --- a/lsplant/src/main/jni/common.hpp +++ b/lsplant/src/main/jni/common.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "logging.hpp" #include "lsplant.hpp" @@ -56,6 +57,12 @@ inline static constexpr auto kPointerSize = sizeof(void *); namespace art { class ArtMethod; +namespace dex { +class ClassDef; +} +namespace mirror { +class Class; +} } // namespace art namespace { @@ -65,6 +72,10 @@ inline std::shared_mutex hooked_methods_lock_; inline std::list> jit_movements_; inline std::shared_mutex jit_movements_lock_; + +inline std::unordered_map> + hooked_classes_; +inline std::shared_mutex hooked_classes_lock_; } // namespace inline bool IsHooked(art::ArtMethod *art_method) { @@ -77,9 +88,16 @@ inline std::list> GetJitMovements( return std::move(jit_movements_); } -inline void RecordHooked(art::ArtMethod *target, jobject reflected_backup, art::ArtMethod *backup) { - std::unique_lock lk(hooked_methods_lock_); - hooked_methods_.emplace(target, std::make_pair(reflected_backup, backup)); +inline void RecordHooked(art::ArtMethod *target, const art::dex::ClassDef *class_def, + jobject reflected_backup, art::ArtMethod *backup) { + { + std::unique_lock lk(hooked_methods_lock_); + hooked_methods_[target] = {reflected_backup, backup}; + } + { + std::unique_lock lk(hooked_classes_lock_); + hooked_classes_[class_def].emplace(target); + } } inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) { diff --git a/lsplant/src/main/jni/lsplant.cc b/lsplant/src/main/jni/lsplant.cc index c039f7c..e14c1c6 100644 --- a/lsplant/src/main/jni/lsplant.cc +++ b/lsplant/src/main/jni/lsplant.cc @@ -7,7 +7,7 @@ #include #include -#include "art/instrumentation.hpp" +#include "art/mirror/class.hpp" #include "art/runtime/art_method.hpp" #include "art/runtime/class_linker.hpp" #include "art/runtime/dex_file.hpp" @@ -29,10 +29,10 @@ namespace lsplant { using art::ArtMethod; using art::ClassLinker; using art::DexFile; -using art::Instrumentation; using art::Thread; using art::gc::ScopedGCCriticalSection; using art::jit::JitCodeCache; +using art::mirror::Class; using art::thread_list::ScopedSuspendAll; namespace { @@ -226,8 +226,8 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) { LOGE("Failed to init class linker"); return false; } - if (!Instrumentation::Init(handler)) { - LOGE("Failed to init instrumentation"); + if (!Class::Init(handler)) { + LOGE("Failed to init mirror class"); return false; } if (!ScopedSuspendAll::Init(handler)) { @@ -580,7 +580,7 @@ using ::lsplant::IsHooked; if (DoHook(target, hook, backup)) { jobject global_backup = JNI_NewGlobalRef(env, reflected_backup); - RecordHooked(target, global_backup, backup); + RecordHooked(target, target->GetDeclaringClass()->GetClassDef(), global_backup, backup); if (!is_proxy) [[likely]] { RecordJitMovement(target, backup); } @@ -605,6 +605,16 @@ using ::lsplant::IsHooked; hooked_methods_.erase(it); } } + { + std::unique_lock lk(hooked_classes_lock_); + if (auto it = hooked_classes_.find(target->GetDeclaringClass()->GetClassDef()); + it != hooked_classes_.end()) { + it->second.erase(target); + if (it->second.empty()) { + hooked_classes_.erase(it); + } + } + } if (reflected_backup == nullptr) { LOGE("Unable to unhook a method that is not hooked"); return false; @@ -619,11 +629,8 @@ using ::lsplant::IsHooked; return false; } auto *art_method = ArtMethod::FromReflectedMethod(env, method); - - if (std::shared_lock lk(hooked_methods_lock_); hooked_methods_.contains(art_method)) { - return true; - } - return false; + std::shared_lock lk(hooked_methods_lock_); + return hooked_methods_.contains(art_method); } [[maybe_unused]] bool Deoptimize(JNIEnv *env, jobject method) { diff --git a/test/src/androidTest/java/org/lsposed/lsplant/UnitTest.java b/test/src/androidTest/java/org/lsposed/lsplant/UnitTest.java index ed62711..70cbc9d 100644 --- a/test/src/androidTest/java/org/lsposed/lsplant/UnitTest.java +++ b/test/src/androidTest/java/org/lsposed/lsplant/UnitTest.java @@ -107,7 +107,7 @@ public class UnitTest { Hooker hooker = Hooker.hook(staticMethod, staticMethodReplacement, null); Assert.assertNotNull(hooker); - for (int i = 0; i < 1000; ++i) { + for (int i = 0; i < 5000; ++i) { Assert.assertTrue("Iter " + i, (Boolean) callStaticMethod.invoke(null)); Assert.assertFalse("Iter " + i, (boolean) hooker.backup.invoke(null)); }