diff --git a/lsplant/src/main/jni/art/mirror/class.hpp b/lsplant/src/main/jni/art/mirror/class.hpp deleted file mode 100644 index f82ec8e..0000000 --- a/lsplant/src/main/jni/art/mirror/class.hpp +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -#include "art/thread.hpp" -#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 GetDescriptor(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(JNIEnv *env, 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; - } - - auto clazz = JNI_FindClass(env, "java/lang/Class"); - if (!clazz) { - LOGE("Failed to find Class"); - return false; - } - - if (class_status = JNI_GetFieldID(env, clazz, "status", "I"); !class_status) { - LOGE("Failed to find status"); - return false; - } - - int sdk_int = GetAndroidApiLevel(); - - if (sdk_int >= __ANDROID_API_P__) { - initialized_status = -14; - } else if (sdk_int == __ANDROID_API_O_MR1__) { - initialized_status = 11; - } else { - initialized_status = 10; - } - - return true; - } - - const char *GetDescriptor(std::string *storage) { - if (GetDescriptorSym) { - return GetDescriptor(storage); - } - return ""; - } - - std::string GetDescriptor() { - std::string storage; - return GetDescriptor(&storage); - } - - const dex::ClassDef *GetClassDef() { - if (GetClassDefSym) return GetClassDef(this); - return nullptr; - } - - static int GetStatus(JNIEnv *env, jclass clazz) { - int status = JNI_GetIntField(env, clazz, class_status); - return initialized_status < 0 ? static_cast(status) >> (32 - 4) : status; - } - - static bool IsInitialized(JNIEnv *env, jclass clazz) { - return initialized_status < 0 ? GetStatus(env, clazz) >= -initialized_status - : GetStatus(env, clazz) == initialized_status; - } - - static Class *FromReflectedClass(JNIEnv *, jclass clazz) { - return reinterpret_cast(art::Thread::Current()->DecodeJObject(clazz)); - } - -private: - inline static jfieldID class_status = nullptr; - inline static int initialized_status = 0; -}; - -} // 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 1000236..31b1e0f 100644 --- a/lsplant/src/main/jni/art/runtime/art_method.hpp +++ b/lsplant/src/main/jni/art/runtime/art_method.hpp @@ -1,6 +1,5 @@ #pragma once -#include "art/mirror/class.hpp" #include "common.hpp" namespace lsplant::art { diff --git a/lsplant/src/main/jni/art/runtime/class_linker.hpp b/lsplant/src/main/jni/art/runtime/class_linker.hpp index 2ad139b..8bea0d9 100644 --- a/lsplant/src/main/jni/art/runtime/class_linker.hpp +++ b/lsplant/src/main/jni/art/runtime/class_linker.hpp @@ -1,6 +1,5 @@ #pragma once -#include "art/mirror/class.hpp" #include "art/runtime/art_method.hpp" #include "art/thread.hpp" #include "common.hpp" @@ -40,7 +39,7 @@ public: static bool Init(const HookHandler &handler) { int sdk_int = GetAndroidApiLevel(); - if (sdk_int >= __ANDROID_API_N__) { + if (sdk_int >= __ANDROID_API_N__) [[likely]] { if (!HookSyms(handler, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter)) [[unlikely]] { return false; @@ -49,13 +48,14 @@ public: if (!RETRIEVE_MEM_FUNC_SYMBOL( SetEntryPointsToInterpreter, - "_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) { + "_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) + [[unlikely]] { if (!RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge, - "art_quick_to_interpreter_bridge")) { + "art_quick_to_interpreter_bridge")) [[unlikely]] { return false; } if (!RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline, - "art_quick_generic_jni_trampoline")) { + "art_quick_generic_jni_trampoline")) [[unlikely]] { return false; } LOGD("art_quick_to_interpreter_bridge = %p", art_quick_to_interpreter_bridgeSym); diff --git a/lsplant/src/main/jni/art/runtime/dex_file.hpp b/lsplant/src/main/jni/art/runtime/dex_file.hpp index e2ddd23..9272d07 100644 --- a/lsplant/src/main/jni/art/runtime/dex_file.hpp +++ b/lsplant/src/main/jni/art/runtime/dex_file.hpp @@ -45,11 +45,6 @@ class DexFile { return nullptr; } - CREATE_MEM_FUNC_SYMBOL_ENTRY(void, push_back, std::vector* thiz, - const DexFile** dex_file) { - push_backSym(thiz, dex_file); - } - public: static const DexFile* OpenMemory(const void* dex_file, size_t size, std::string location, std::string* error_msg) { @@ -58,11 +53,11 @@ public: reinterpret_cast(dex_file)->checksum_, nullptr, nullptr, error_msg) .release(); - } else if (OpenMemoryRawSym) { + } else if (OpenMemoryRawSym) [[likely]] { return OpenMemoryRaw(reinterpret_cast(dex_file), size, location, reinterpret_cast(dex_file)->checksum_, nullptr, nullptr, error_msg); - } else if (OpenMemoryWithoutOdexSym) { + } else if (OpenMemoryWithoutOdexSym) [[likely]] { return OpenMemoryWithoutOdex(reinterpret_cast(dex_file), size, location, reinterpret_cast(dex_file)->checksum_, nullptr, error_msg); @@ -119,26 +114,26 @@ public: return false; } dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile")); - if (!dex_file_class) { + if (!dex_file_class) [[unlikely]] { return false; } - if (sdk_int >= __ANDROID_API_M__) { + if (sdk_int >= __ANDROID_API_M__) [[unlikely]] { cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;"); } else { cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J"); dex_file_start_index = -1; } - if (!cookie_field) { + if (!cookie_field) [[unlikely]] { return false; } file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;"); - if (!file_name_field) { + if (!file_name_field) [[unlikely]] { return false; } - if (sdk_int >= __ANDROID_API_N__) { + if (sdk_int >= __ANDROID_API_N__) [[likely]] { internal_cookie_field = JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;"); - if (!internal_cookie_field) { + if (!internal_cookie_field) [[unlikely]] { return false; } dex_file_start_index = 1u; diff --git a/lsplant/src/main/jni/art/runtime/gc/scoped_gc_critical_section.hpp b/lsplant/src/main/jni/art/runtime/gc/scoped_gc_critical_section.hpp index 6244e90..828950e 100644 --- a/lsplant/src/main/jni/art/runtime/gc/scoped_gc_critical_section.hpp +++ b/lsplant/src/main/jni/art/runtime/gc/scoped_gc_critical_section.hpp @@ -16,17 +16,13 @@ private: class ScopedGCCriticalSection { CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedGCCriticalSection *thiz, Thread *self, GcCause cause, CollectorType collector_type) { - if (thiz == nullptr) [[unlikely]] - return; - if (constructorSym) [[likely]] + if (thiz && constructorSym) [[likely]] return constructorSym(thiz, self, cause, collector_type); } CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedGCCriticalSection *thiz) { - if (thiz == nullptr) [[unlikely]] - return; - if (destructorSym) [[likely]] - return destructorSym(thiz); + if (thiz && destructorSym) [[likely]] + destructorSym(thiz); } public: @@ -39,7 +35,7 @@ public: static bool Init(const HookHandler &handler) { // for Android M, it's safe to not found since we have suspendVM & resumeVM auto sdk_int = GetAndroidApiLevel(); - if (sdk_int >= __ANDROID_API_N__) { + if (sdk_int >= __ANDROID_API_N__) [[likely]] { if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_" "7GcCauseENS0_13CollectorTypeE")) [[unlikely]] { diff --git a/lsplant/src/main/jni/art/runtime/jit/jit_code_cache.hpp b/lsplant/src/main/jni/art/runtime/jit/jit_code_cache.hpp index 159395f..a289d33 100644 --- a/lsplant/src/main/jni/art/runtime/jit/jit_code_cache.hpp +++ b/lsplant/src/main/jni/art/runtime/jit/jit_code_cache.hpp @@ -36,7 +36,7 @@ public: } } if (sdk_int >= __ANDROID_API_N__) [[likely]] { - if (!HookSyms(handler, GarbageCollectCache)) { + if (!HookSyms(handler, GarbageCollectCache)) [[unlikely]] { return false; } } diff --git a/lsplant/src/main/jni/art/thread.hpp b/lsplant/src/main/jni/art/thread.hpp index 0d569b3..d1285c5 100644 --- a/lsplant/src/main/jni/art/thread.hpp +++ b/lsplant/src/main/jni/art/thread.hpp @@ -10,7 +10,7 @@ class Thread { }; CREATE_MEM_FUNC_SYMBOL_ENTRY(ObjPtr, DecodeJObject, Thread *thiz, jobject obj) { - if (DecodeJObjectSym) + if (DecodeJObjectSym) [[likely]] return DecodeJObjectSym(thiz, obj); else return {.data = nullptr}; @@ -27,10 +27,12 @@ public: static Thread *Current() { return CurrentFromGdb(); } static bool Init(const HookHandler &handler) { - if (!RETRIEVE_MEM_FUNC_SYMBOL(DecodeJObject, "_ZNK3art6Thread13DecodeJObjectEP8_jobject")) { + if (!RETRIEVE_MEM_FUNC_SYMBOL(DecodeJObject, "_ZNK3art6Thread13DecodeJObjectEP8_jobject")) + [[unlikely]] { return false; } - if (!RETRIEVE_FUNC_SYMBOL(CurrentFromGdb, "_ZN3art6Thread14CurrentFromGdbEv")) { + if (!RETRIEVE_FUNC_SYMBOL(CurrentFromGdb, "_ZN3art6Thread14CurrentFromGdbEv")) + [[unlikely]] { return false; } return true; diff --git a/lsplant/src/main/jni/art/thread_list.hpp b/lsplant/src/main/jni/art/thread_list.hpp index 077ff4f..e3b4796 100644 --- a/lsplant/src/main/jni/art/thread_list.hpp +++ b/lsplant/src/main/jni/art/thread_list.hpp @@ -7,9 +7,7 @@ namespace lsplant::art::thread_list { class ScopedSuspendAll { CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedSuspendAll *thiz, const char *cause, bool long_suspend) { - if (thiz == nullptr) [[unlikely]] - return; - if (constructorSym) [[likely]] { + if (thiz && constructorSym) [[likely]] { return constructorSym(thiz, cause, long_suspend); } else { SuspendVM(); @@ -17,10 +15,7 @@ class ScopedSuspendAll { } CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedSuspendAll *thiz) { - if (thiz == nullptr) [[unlikely]] { - return; - } - if (destructorSym) [[likely]] { + if (thiz && destructorSym) [[likely]] { return destructorSym(thiz); } else { ResumeVM(); @@ -47,15 +42,13 @@ public: ~ScopedSuspendAll() { destructor(this); } static bool Init(const HookHandler &handler) { - if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb")) { - if (!RETRIEVE_FUNC_SYMBOL(SuspendVM, "_ZN3art3Dbg9SuspendVMEv")) { - return false; - } + if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb") && + !RETRIEVE_FUNC_SYMBOL(SuspendVM, "_ZN3art3Dbg9SuspendVMEv")) [[unlikely]] { + return false; } - if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev")) { - if (!RETRIEVE_FUNC_SYMBOL(ResumeVM, "_ZN3art3Dbg8ResumeVMEv")) { - return false; - } + if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev") && + !RETRIEVE_FUNC_SYMBOL(ResumeVM, "_ZN3art3Dbg8ResumeVMEv")) [[unlikely]] { + return false; } return true; } diff --git a/lsplant/src/main/jni/common.hpp b/lsplant/src/main/jni/common.hpp index 89a78aa..c6a27dd 100644 --- a/lsplant/src/main/jni/common.hpp +++ b/lsplant/src/main/jni/common.hpp @@ -6,7 +6,6 @@ #include #include #include -#include #include "logging.hpp" #include "lsplant.hpp" @@ -55,22 +54,8 @@ inline auto GetAndroidApiLevel() { inline static constexpr auto kPointerSize = sizeof(void *); -template -inline T GetArtSymbol(const std::function &resolver, - std::string_view symbol) requires(std::is_pointer_v) { - if (auto *result = resolver(symbol); result) { - return reinterpret_cast(result); - } else { - LOGW("Failed to find symbol %*s", static_cast(symbol.length()), symbol.data()); - return nullptr; - } -} - namespace art { - class ArtMethod; - namespace dex { - class ClassDef; - } +class ArtMethod; } // namespace art namespace { @@ -92,7 +77,7 @@ inline std::list> GetJitMovements( return std::move(jit_movements_); } -inline void RecordHooked(art::ArtMethod *target, jobject reflected_backup, art::ArtMethod* backup) { +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)); } diff --git a/lsplant/src/main/jni/include/lsplant.hpp b/lsplant/src/main/jni/include/lsplant.hpp index 4026959..0af3a87 100644 --- a/lsplant/src/main/jni/include/lsplant.hpp +++ b/lsplant/src/main/jni/include/lsplant.hpp @@ -59,8 +59,8 @@ struct InitInfo { /// \return Indicate whether initialization succeed. Behavior is undefined if calling other /// LSPlant interfaces before initialization or after a fail initialization. /// \see InitInfo. -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] bool Init(JNIEnv *env, - const InitInfo &info); +[[nodiscard, maybe_unused, gnu::visibility("default")]] bool Init(JNIEnv *env, + const InitInfo &info); /// \brief Hook a Java method by providing the \p target_method together with the context object /// \p hooker_object and its callback \p callback_method. @@ -75,29 +75,32 @@ struct InitInfo { /// \param[in] callback_method The callback method to the \p hooker_object is used to replace the /// \p target_method. Whenever the \p target_method is invoked, the \p callback_method will be /// invoked instead of the original \p target_method. The signature of the \p callback_method must -/// be:
+/// be: /// \code{.java} /// public Object callback_method(Object []args) -/// \endcode
+/// \endcode /// That is, the return type must be \p Object and the parameter type must be \b Object[]. Behavior /// is undefined if the signature does not match the requirement. +/// args[0] is the this object for non-static methods and there is NOT null this object placeholder +/// for static methods. /// Extra info can be provided by defining member variables of \p hooker_object. /// This method must be a method to \p hooker_object. -/// \return The backup method. You can invoke it by reflection to invoke the original method. null -/// if fails. -/// \note This function will automatically generate a stub class for hook. To help debug, you -/// can set the generated class name, its field name, its source name and its method name -/// by setting generated_* in \p InitInfo. +/// \return The backup method. You can invoke it +/// by reflection to invoke the original method. null if fails. +/// \note This function will +/// automatically generate a stub class for hook. To help debug, you can set the generated class +/// name, its field name, its source name and its method name by setting generated_* in \ref +/// InitInfo. /// \note This function thread safe (you can call it simultaneously from multiple thread) -/// but it's not atomic to the same \b target_method. That means \p UnHook or \p IsUnhook does -/// not guarantee to work properly on the same \p target_method before it returns. Also, -/// simultaneously call on this function with the same \p target_method does not guarantee only one -/// will success. If you call this with different \p hooker_object on the same target_method -/// simultaneously, the behavior is undefined. -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] jobject Hook(JNIEnv *env, - jobject target_method, - jobject hooker_object, - jobject callback_method); +/// but it's not atomic to the same \b target_method. That means #UnHook() or #IsUnhook() does not +/// guarantee to work properly on the same \p target_method before it returns. Also, simultaneously +/// call on this function with the same \p target_method does not guarantee only one will success. +/// If you call this with different \p hooker_object on the same target_method simultaneously, the +/// behavior is undefined. +[[nodiscard, maybe_unused, gnu::visibility("default")]] jobject Hook(JNIEnv *env, + jobject target_method, + jobject hooker_object, + jobject callback_method); /// \brief Unhook a Java function that is previously hooked. /// \param[in] env The Java environment. @@ -106,8 +109,8 @@ struct InitInfo { /// \note Calling \p backup (the return method of #Hook()) after unhooking is undefined behavior. /// Please read #Hook()'s note for more details. /// \see Hook() -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] bool UnHook(JNIEnv *env, - jobject target_method); +[[nodiscard, maybe_unused, gnu::visibility("default")]] bool UnHook(JNIEnv *env, + jobject target_method); /// \brief Check if a Java function is hooked by LSPlant or not /// \param[in] env The Java environment. @@ -115,8 +118,7 @@ struct InitInfo { /// \return If \p method hooked, ture; otherwise, false. /// Please read #Hook()'s note for more details. /// \see Hook() -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] bool IsHooked(JNIEnv *env, - jobject method); +[[nodiscard, maybe_unused, gnu::visibility("default")]] bool IsHooked(JNIEnv *env, jobject method); /// \brief Deoptimize a method to avoid hooked callee not being called because of inline /// \param[in] env The Java environment. @@ -125,14 +127,15 @@ struct InitInfo { /// A, and you find that your callback to B is not invoked after hooking, then it may mean A has /// inlined B inside its method body. To force A to call your hooked B, you can deoptimize A and /// then your hook can take effect. Generally, you need to find all the callers of your hooked -/// callee and that can be hardly achieve. Use this function if you are sure the deoptimized callers +/// callee and that can be hardly achieve (but you can still search all callers by using DexHelper). +/// Use this function if you are sure the deoptimized callers /// are all you need. Otherwise, it would be better to change the hook point or to deoptimize the /// whole app manually (by simple reinstall the app without uninstalled). /// \return Indicate whether the deoptimizing succeed or not. /// \note It is safe to call deoptimizing on a hooked method because the deoptimization will /// perform on the backup method instead. -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] bool Deoptimize(JNIEnv *env, - jobject method); +[[nodiscard, maybe_unused, gnu::visibility("default")]] bool Deoptimize(JNIEnv *env, + jobject method); /// \brief Get the registered native function pointer of a native function. It helps user to hook /// native methods directly by backing up the native function pointer this function returns and @@ -141,15 +144,15 @@ struct InitInfo { /// \param[in] method The native method to get the native function pointer. /// \return The native function pointer the \p method previously registered. If it has not been /// registered or it is not a native method, null is returned instead. -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] void *GetNativeFunction( - JNIEnv *env, jobject method); +[[nodiscard, maybe_unused, gnu::visibility("default")]] void *GetNativeFunction(JNIEnv *env, + jobject method); /// \brief Make a class inheritable. It will make the class non-final and make all its private /// constructors protected. /// \param[in] env The Java environment. /// \param[in] target The target class that is to make inheritable. /// \return Indicate whether the operation has succeed. -[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] bool MakeClassInheritable( - JNIEnv *env, jclass target); +[[nodiscard, maybe_unused, gnu::visibility("default")]] bool MakeClassInheritable(JNIEnv *env, + jclass target); } // namespace v1 } // namespace lsplant diff --git a/lsplant/src/main/jni/lsplant.cc b/lsplant/src/main/jni/lsplant.cc index 32a98fd..c039f7c 100644 --- a/lsplant/src/main/jni/lsplant.cc +++ b/lsplant/src/main/jni/lsplant.cc @@ -33,7 +33,6 @@ using art::Instrumentation; using art::Thread; using art::gc::ScopedGCCriticalSection; using art::jit::JitCodeCache; -using art::mirror::Class; using art::thread_list::ScopedSuspendAll; namespace { @@ -227,10 +226,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) { LOGE("Failed to init class linker"); return false; } - if (!Class::Init(env, handler)) { - LOGE("Failed to init mirror class"); - return false; - } if (!Instrumentation::Init(handler)) { LOGE("Failed to init instrumentation"); return false;