module; #include #include #include #include "logging.hpp" export module lsplant:art_method; import :common; import hook_helper; export namespace lsplant::art { class ArtMethod { inline static auto PrettyMethod_ = "_ZN3art9ArtMethod12PrettyMethodEPS0_b"_sym.as; inline static auto PrettyMethodStatic_ = "_ZN3art12PrettyMethodEPNS_9ArtMethodEb"_sym.as; inline static auto PrettyMethodMirror_ = "_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb"_sym.as; inline static auto GetMethodShortyL_ = "_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as; inline static auto GetMethodShorty_ = "_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as; inline static auto SetNotIntrinsic_ = "_ZN3art9ArtMethod15SetNotIntrinsicEv"_sym.as; inline static auto ThrowInvocationTimeError_ = "_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv"_sym.as; inline static auto art_interpreter_to_compiled_code_bridge_ = "artInterpreterToCompiledCodeBridge"_sym.as; inline void ThrowInvocationTimeError() { if (ThrowInvocationTimeError_) { [[likely]] ThrowInvocationTimeError_(this); } } public: inline static const char *GetMethodShorty(JNIEnv *env, jobject method) { if (GetMethodShortyL_) { return GetMethodShortyL_(env, env->FromReflectedMethod(method)); } return GetMethodShorty_(env, env->FromReflectedMethod(method)); } void SetNonCompilable() { auto access_flags = GetAccessFlags(); access_flags |= kAccCompileDontBother; access_flags &= ~kAccPreCompiled; SetAccessFlags(access_flags); } void ClearFastInterpretFlag() { auto access_flags = GetAccessFlags(); access_flags &= ~kAccFastInterpreterToInterpreterInvoke; SetAccessFlags(access_flags); } void SetPrivate() { auto access_flags = GetAccessFlags(); access_flags |= kAccPrivate; access_flags &= ~kAccProtected; access_flags &= ~kAccPublic; SetAccessFlags(access_flags); } void SetPublic() { auto access_flags = GetAccessFlags(); access_flags |= kAccPublic; access_flags &= ~kAccProtected; access_flags &= ~kAccPrivate; SetAccessFlags(access_flags); } void SetProtected() { auto access_flags = GetAccessFlags(); access_flags |= kAccProtected; access_flags &= ~kAccPrivate; access_flags &= ~kAccPublic; SetAccessFlags(access_flags); } void SetNonFinal() { auto access_flags = GetAccessFlags(); access_flags &= ~kAccFinal; SetAccessFlags(access_flags); } void SetNative() { auto access_flags = GetAccessFlags(); access_flags |= kAccNative; SetAccessFlags(access_flags); } void SetNonNative() { auto access_flags = GetAccessFlags(); access_flags &= ~kAccNative; SetAccessFlags(access_flags); } void SetNonIntrinsic() { if (SetNotIntrinsic_) [[likely]] { SetNotIntrinsic_(this); } else { auto access_flags = GetAccessFlags(); access_flags &= ~kAccIntrinsic; SetAccessFlags(access_flags); } } bool IsPrivate() { return GetAccessFlags() & kAccPrivate; } bool IsProtected() { return GetAccessFlags() & kAccProtected; } bool IsPublic() { return GetAccessFlags() & kAccPublic; } bool IsFinal() { return GetAccessFlags() & kAccFinal; } bool IsStatic() { return GetAccessFlags() & kAccStatic; } bool IsNative() { return GetAccessFlags() & kAccNative; } bool IsConstructor() { return GetAccessFlags() & kAccConstructor; } bool IsIntrinsic() { return GetAccessFlags() & kAccIntrinsic; } void CopyFrom(const ArtMethod *other) { memcpy(this, other, art_method_size); } void SetEntryPoint(void *entry_point) { *reinterpret_cast(reinterpret_cast(this) + entry_point_offset) = entry_point; if (interpreter_entry_point_offset) [[unlikely]] { *reinterpret_cast(reinterpret_cast(this) + interpreter_entry_point_offset) = reinterpret_cast(&art_interpreter_to_compiled_code_bridge_); } } void *GetEntryPoint() { return *reinterpret_cast(reinterpret_cast(this) + entry_point_offset); } void *GetData() { return *reinterpret_cast(reinterpret_cast(this) + data_offset); } void SetData(void *data) { *reinterpret_cast(reinterpret_cast(this) + data_offset) = data; } uint32_t GetAccessFlags() { return (reinterpret_cast *>(reinterpret_cast(this) + access_flags_offset)) ->load(std::memory_order_relaxed); } void SetAccessFlags(uint32_t flags) { return (reinterpret_cast *>(reinterpret_cast(this) + access_flags_offset)) ->store(flags, std::memory_order_relaxed); } std::string PrettyMethod(bool with_signature = true) { if (PrettyMethod_) [[likely]] return PrettyMethod_(this, with_signature); if (PrettyMethodStatic_) return PrettyMethodStatic_(this, with_signature); if (PrettyMethodMirror_) return PrettyMethodMirror_(this, with_signature); return "null sym"; } mirror::Class *GetDeclaringClass() { return reinterpret_cast(*reinterpret_cast( reinterpret_cast(this) + declaring_class_offset)); } std::unique_ptr Clone() { auto *method = reinterpret_cast(::operator new(art_method_size)); method->CopyFrom(this); return std::unique_ptr(method); } void BackupTo(ArtMethod *backup) { SetNonCompilable(); // copy after setNonCompilable backup->CopyFrom(this); ClearFastInterpretFlag(); if (!backup->IsStatic()) backup->SetPrivate(); } static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { if (art_method_field) [[likely]] { return reinterpret_cast( JNI_GetLongField(env, method, art_method_field)); } else { return reinterpret_cast(env->FromReflectedMethod(method)); } } static bool Init(JNIEnv *env, const HookHandler handler) { auto sdk_int = GetAndroidApiLevel(); ScopedLocalRef executable{env, nullptr}; if (sdk_int >= __ANDROID_API_O__) { executable = JNI_FindClass(env, "java/lang/reflect/Executable"); } else if (sdk_int >= __ANDROID_API_M__) { executable = JNI_FindClass(env, "java/lang/reflect/AbstractMethod"); } else { executable = JNI_FindClass(env, "java/lang/reflect/ArtMethod"); } if (!executable) { LOGE("Failed to found Executable/AbstractMethod/ArtMethod"); return false; } if (sdk_int >= __ANDROID_API_M__) [[likely]] { if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J"); !art_method_field) { LOGE("Failed to find artMethod field"); return false; } } auto throwable = JNI_FindClass(env, "java/lang/Throwable"); if (!throwable) { LOGE("Failed to found Executable"); return false; } auto clazz = JNI_FindClass(env, "java/lang/Class"); static_assert(std::is_same_v); jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors", "()[Ljava/lang/reflect/Constructor;"); const auto constructors = JNI_Cast(JNI_CallObjectMethod(env, throwable, get_declared_constructors)); if (constructors.size() < 2) { LOGE("Throwable has less than 2 constructors"); return false; } auto first_ctor = constructors[0]; auto second_ctor = constructors[1]; auto *first = FromReflectedMethod(env, first_ctor.get()); auto *second = FromReflectedMethod(env, second_ctor.get()); art_method_size = reinterpret_cast(second) - reinterpret_cast(first); LOGD("ArtMethod size: %zu", art_method_size); if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] { if (sdk_int >= __ANDROID_API_M__) { LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); } } entry_point_offset = art_method_size - kPointerSize; data_offset = entry_point_offset - kPointerSize; if (sdk_int >= __ANDROID_API_M__) [[likely]] { if (auto access_flags_field = JNI_GetFieldID(env, executable, "accessFlags", "I"); access_flags_field) { uint32_t real_flags = JNI_GetIntField(env, first_ctor, access_flags_field); for (size_t i = 0; i < art_method_size; i += sizeof(uint32_t)) { if (*reinterpret_cast(reinterpret_cast(first) + i) == real_flags) { access_flags_offset = i; break; } } } if (access_flags_offset == 0) { LOGW("Failed to find accessFlags field. Fallback to 4."); access_flags_offset = 4U; } } else { auto art_field = JNI_FindClass(env, "java/lang/reflect/ArtField"); auto field = JNI_FindClass(env, "java/lang/reflect/Field"); auto art_field_field = JNI_GetFieldID(env, field, "artField", "Ljava/lang/reflect/ArtField;"); auto field_offset = JNI_GetFieldID(env, art_field, "offset", "I"); auto get_offset_from_art_method = [&](const char *name, const char *sig) { return JNI_GetIntField( env, JNI_GetObjectField( env, JNI_ToReflectedField(env, executable, JNI_GetFieldID(env, executable, name, sig), false), art_field_field), 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"); interpreter_entry_point_offset = get_offset_from_art_method("entryPointFromInterpreter", "J"); 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); if (sdk_int < __ANDROID_API_R__) { kAccPreCompiled = 0; } else if (sdk_int >= __ANDROID_API_S__) { kAccPreCompiled = 0x00800000; } if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0; if (!handler(GetMethodShortyL_, true, GetMethodShorty_)) { LOGE("Failed to find GetMethodShorty"); return false; } handler(PrettyMethod_, PrettyMethodStatic_, PrettyMethodMirror_); if (sdk_int >= __ANDROID_API_O__) { handler(SetNotIntrinsic_); } if (sdk_int <= __ANDROID_API_O__) [[unlikely]] { auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError"); if (!abstract_method_error) { LOGE("Failed to find AbstractMethodError"); return false; } if (sdk_int == __ANDROID_API_O__) [[unlikely]] { auto executable_get_name = JNI_GetMethodID(env, executable, "getName", "()Ljava/lang/String;"); if (!executable_get_name) { LOGE("Failed to find Executable.getName"); return false; } handler(ThrowInvocationTimeError_); auto abstract_method = FromReflectedMethod( env, JNI_ToReflectedMethod(env, executable, executable_get_name, false).get()); uint32_t access_flags = abstract_method->GetAccessFlags(); abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict); abstract_method->ThrowInvocationTimeError(); abstract_method->SetAccessFlags(access_flags); } if (auto exception = env->ExceptionOccurred(); env->ExceptionClear(), (!exception || JNI_IsInstanceOf(env, exception, abstract_method_error))) [[likely]] { kAccCompileDontBother = kAccDefaultConflict; } } if (sdk_int < __ANDROID_API_N__) { kAccCompileDontBother = 0; } if (sdk_int <= __ANDROID_API_M__) [[unlikely]] { if (!handler(art_interpreter_to_compiled_code_bridge_)) { return false; } if (sdk_int >= __ANDROID_API_L_MR1__) { interpreter_entry_point_offset = entry_point_offset - 2 * kPointerSize; } } return true; } static size_t GetEntryPointOffset() { return entry_point_offset; } constexpr static uint32_t kAccPublic = 0x0001; // class, field, method, ic constexpr static uint32_t kAccPrivate = 0x0002; // field, method, ic constexpr static uint32_t kAccProtected = 0x0004; // field, method, ic constexpr static uint32_t kAccStatic = 0x0008; // field, method, ic constexpr static uint32_t kAccNative = 0x0100; // method constexpr static uint32_t kAccFinal = 0x0010; // class, field, method, ic constexpr static uint32_t kAccConstructor = 0x00010000; private: inline static jfieldID art_method_field = nullptr; inline static size_t art_method_size = 0; inline static size_t entry_point_offset = 0; 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; inline static uint32_t kAccDefaultConflict = 0x01000000; inline static uint32_t kAccIntrinsic = 0x80000000; }; } // namespace lsplant::art