Add clang-format and reformat all files

This commit is contained in:
LoveSy 2022-02-19 03:47:26 +08:00
parent a28b3e1227
commit 5bab7a0e38
13 changed files with 504 additions and 561 deletions

18
library/jni/.clang-format Normal file
View File

@ -0,0 +1,18 @@
---
BasedOnStyle: Google
IndentWidth: 4
UseCRLF: false
UseTab: false
---
Language: Cpp
DerivePointerAlignment: true
PointerAlignment: Right
ColumnLimit: 100
AlignEscapedNewlines: Right
Cpp11BracedListStyle: true
Standard: Latest
# IndentAccessModifiers: false
IndentCaseLabels: false
IndentExternBlock: false
AccessModifierOffset: -4
# EmptyLineBeforeAccessModifier: true

View File

@ -3,7 +3,7 @@ LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/external/dex_builder/include
LOCAL_SRC_FILES := lsplant.cc
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := dex_builder
@ -16,7 +16,7 @@ include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant_static
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/external/dex_builder/include
LOCAL_SRC_FILES := lsplant.cc
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_STATIC_LIBRARIES := dex_builder_static

View File

@ -1,14 +1,13 @@
#pragma once
#include "art/thread.hpp"
#include "common.hpp"
namespace lsplant::art {
namespace dex {
class ClassDef {
};
}
class ClassDef {};
} // namespace dex
namespace mirror {
@ -71,8 +70,7 @@ public:
}
const dex::ClassDef *GetClassDef() {
if (GetClassDefSym)
return GetClassDef(this);
if (GetClassDefSym) return GetClassDef(this);
return nullptr;
}
@ -94,5 +92,5 @@ private:
inline static bool is_unsigned = false;
};
}
}
} // namespace mirror
} // namespace lsplant::art

View File

@ -1,15 +1,18 @@
#pragma once
#include "common.hpp"
#include "art/mirror/class.hpp"
#include "common.hpp"
namespace lsplant::art {
class ArtMethod {
CREATE_MEM_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) {
if (thiz == nullptr) [[unlikely]] return "null";
else if (PrettyMethodSym) [[likely]] return PrettyMethodSym(thiz, with_signature);
else return "null sym";
if (thiz == nullptr) [[unlikely]]
return "null";
else if (PrettyMethodSym) [[likely]]
return PrettyMethodSym(thiz, with_signature);
else
return "null sym";
}
public:
@ -36,43 +39,35 @@ public:
}
}
bool IsStatic() {
return GetAccessFlags() & kAccStatic;
}
bool IsStatic() { return GetAccessFlags() & kAccStatic; }
bool IsNative() {
return GetAccessFlags() & kAccNative;
}
bool IsNative() { return GetAccessFlags() & kAccNative; }
void CopyFrom(const ArtMethod *other) {
memcpy(this, other, art_method_size);
}
void CopyFrom(const ArtMethod *other) { memcpy(this, other, art_method_size); }
void SetEntryPoint(void *entry_point) {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
entry_point_offset) = entry_point;
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + entry_point_offset) =
entry_point;
}
void *GetEntryPoint() {
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
entry_point_offset);
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + entry_point_offset);
}
void *GetData() {
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
data_offset);
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset);
}
uint32_t GetAccessFlags() {
return (reinterpret_cast<const std::atomic<uint32_t> *>(
reinterpret_cast<uintptr_t>(this) + access_flags_offset))->load(
std::memory_order_relaxed);
return (reinterpret_cast<const std::atomic<uint32_t> *>(reinterpret_cast<uintptr_t>(this) +
access_flags_offset))
->load(std::memory_order_relaxed);
}
void SetAccessFlags(uint32_t flags) {
return (reinterpret_cast<std::atomic<uint32_t> *>(
reinterpret_cast<uintptr_t>(this) + access_flags_offset))->store(
flags, std::memory_order_relaxed);
return (reinterpret_cast<std::atomic<uint32_t> *>(reinterpret_cast<uintptr_t>(this) +
access_flags_offset))
->store(flags, std::memory_order_relaxed);
}
std::string PrettyMethod(bool with_signature = true) {
@ -90,8 +85,8 @@ public:
return false;
}
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod",
"J"); !art_method_field) {
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J");
!art_method_field) {
LOGE("Failed to find artMethod field");
return false;
}
@ -105,8 +100,8 @@ public:
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
"()[Ljava/lang/reflect/Constructor;");
auto constructors = JNI_Cast<jobjectArray>(
JNI_CallObjectMethod(env, throwable, get_declared_constructors));
auto constructors =
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, throwable, get_declared_constructors));
auto length = JNI_GetArrayLength(env, constructors);
if (length < 2) {
LOGE("Throwable has less than 2 constructors");
@ -130,7 +125,7 @@ public:
LOGD("ArtMethod::data offset: %zu", data_offset);
if (auto access_flags_field = JNI_GetFieldID(env, executable, "accessFlags", "I");
access_flags_field) {
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<uint32_t *>(reinterpret_cast<uintptr_t>(first) + i) ==
@ -151,26 +146,24 @@ public:
if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0;
get_method_shorty_symbol = GetArtSymbol<decltype(get_method_shorty_symbol)>(
info.art_symbol_resolver,
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID");
info.art_symbol_resolver, "_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID");
if (!get_method_shorty_symbol) return false;
return true;
}
static const char *GetMethodShorty(_JNIEnv *env, _jmethodID *method) {
if (get_method_shorty_symbol) [[likely]] return get_method_shorty_symbol(env, method);
if (get_method_shorty_symbol) [[likely]]
return get_method_shorty_symbol(env, method);
return nullptr;
}
static size_t GetEntryPointOffset() {
return entry_point_offset;
}
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 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 kAccStatic = 0x0008; // field, method, ic
constexpr static uint32_t kAccNative = 0x0100; // method
private:
inline static jfieldID art_method_field = nullptr;
@ -182,8 +175,8 @@ private:
inline static uint32_t kAccPreCompiled = 0x00200000;
inline static uint32_t kAccCompileDontBother = 0x02000000;
inline static const char *
(*get_method_shorty_symbol)(_JNIEnv *env, _jmethodID *method) = nullptr;
inline static const char *(*get_method_shorty_symbol)(_JNIEnv *env,
_jmethodID *method) = nullptr;
};
}
} // namespace lsplant::art

View File

@ -1,24 +1,21 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/mirror/class.hpp"
#include "art/runtime/art_method.hpp"
#include "art/thread.hpp"
#include "common.hpp"
namespace lsplant::art {
class ClassLinker {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(
void, SetEntryPointsToInterpreter, ClassLinker *thiz, ArtMethod *art_method) {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, ClassLinker *thiz,
ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreterSym(thiz, art_method);
}
}
[[gnu::always_inline]]
static void MaybeDelayHook(mirror::Class *clazz) {
[[gnu::always_inline]] static void MaybeDelayHook(mirror::Class *clazz) {
const auto *class_def = clazz->GetClassDef();
bool should_intercept = class_def && IsPending(class_def);
if (should_intercept) [[unlikely]] {
@ -28,60 +25,58 @@ private:
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolines, (ClassLinker * thiz, mirror::Class * clazz), {
backup(thiz, clazz);
MaybeDelayHook(clazz);
});
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void,
FixupStaticTrampolines, (ClassLinker * thiz, mirror::Class *clazz), {
backup(thiz, clazz);
MaybeDelayHook(clazz);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolinesWithThread,
(ClassLinker * thiz, Thread * self, mirror::Class * clazz), {
backup(thiz, self, clazz);
MaybeDelayHook(clazz);
});
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolinesWithThread,
(ClassLinker * thiz, Thread *self, mirror::Class *clazz), {
backup(thiz, self, clazz);
MaybeDelayHook(clazz);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker20MarkClassInitializedEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEE",
void*, MarkClassInitialized, (ClassLinker * thiz, Thread * self, uint32_t * clazz_ptr),
{
void *result = backup(thiz, self, clazz_ptr);
auto clazz = reinterpret_cast<mirror::Class *>(*clazz_ptr);
MaybeDelayHook(clazz);
return result;
});
"_ZN3art11ClassLinker20MarkClassInitializedEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEE",
void *, MarkClassInitialized, (ClassLinker * thiz, Thread *self, uint32_t *clazz_ptr), {
void *result = backup(thiz, self, clazz_ptr);
auto clazz = reinterpret_cast<mirror::Class *>(*clazz_ptr);
MaybeDelayHook(clazz);
return result;
});
CREATE_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv",
bool, ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code),
{
if (quick_code != nullptr &&
(IsHooked(art_method) || IsPending(art_method))) [[unlikely]] {
return false;
}
return backup(art_method, quick_code);
});
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv", bool,
ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code), {
if (quick_code != nullptr && (IsHooked(art_method) || IsPending(art_method)))
[[unlikely]] {
return false;
}
return backup(art_method, quick_code);
});
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void*) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void *) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void*) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void *) {}
CREATE_HOOK_STUB_ENTRY(
"_ZN3art11interpreter29ShouldStayInSwitchInterpreterEPNS_9ArtMethodE",
bool, ShouldStayInSwitchInterpreter, (ArtMethod * art_method), {
if (IsHooked(art_method) || IsPending(art_method)) [[unlikely]] {
return false;
}
return backup(art_method);
});
CREATE_HOOK_STUB_ENTRY("_ZN3art11interpreter29ShouldStayInSwitchInterpreterEPNS_9ArtMethodE",
bool, ShouldStayInSwitchInterpreter, (ArtMethod * art_method), {
if (IsHooked(art_method) || IsPending(art_method)) [[unlikely]] {
return false;
}
return backup(art_method);
});
public:
static bool Init(const HookHandler &handler) {
int api_level = GetAndroidApiLevel();
if (!RETRIEVE_MEM_FUNC_SYMBOL(SetEntryPointsToInterpreter,
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(
SetEntryPointsToInterpreter,
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) {
return false;
}
@ -123,15 +118,14 @@ public:
return true;
}
[[gnu::always_inline]]
static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
[[gnu::always_inline]] static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
if (art_method->GetAccessFlags() & ArtMethod::kAccNative) [[unlikely]] {
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
} else {
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
}
return true;
}
@ -141,6 +135,5 @@ public:
}
return false;
}
};
}
} // namespace lsplant::art

View File

@ -39,4 +39,4 @@ enum CollectorType {
// Fake collector type for ScopedGCCriticalSection
kCollectorTypeCriticalSection,
};
} // namespace gc
} // namespace lsplant::art::gc

View File

@ -16,7 +16,8 @@ enum GcCause {
kGcCauseForNativeAlloc,
// GC triggered for a collector transition.
kGcCauseCollectorTransition,
// Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
// Not a real GC cause, used when we disable moving GC (currently for
// GetPrimitiveArrayCritical).
kGcCauseDisableMovingGc,
// Not a real GC cause, used when we trim the heap.
kGcCauseTrim,
@ -41,4 +42,4 @@ enum GcCause {
// GC cause for the profile saver.
kGcCauseProfileSaver,
};
} // namespace art
} // namespace lsplant::art::gc

View File

@ -1,10 +1,9 @@
#pragma once
#include "gc_cause.hpp"
#include "collector_type.hpp"
#include "art/thread.hpp"
#include "collector_type.hpp"
#include "common.hpp"
#include "gc_cause.hpp"
namespace lsplant::art::gc {
@ -17,13 +16,15 @@ 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 (thiz == nullptr) [[unlikely]]
return;
if (constructorSym) [[likely]]
return constructorSym(thiz, self, cause, collector_type);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedGCCriticalSection *thiz) {
if (thiz == nullptr) [[unlikely]] return;
if (thiz == nullptr) [[unlikely]]
return;
if (destructorSym) [[likely]]
return destructorSym(thiz);
}
@ -33,13 +34,12 @@ public:
constructor(this, self, cause, collector_type);
}
~ScopedGCCriticalSection() {
destructor(this);
}
~ScopedGCCriticalSection() { destructor(this); }
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE")) {
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_"
"7GcCauseENS0_13CollectorTypeE")) {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev")) {
@ -52,4 +52,4 @@ private:
[[maybe_unused]] GCCriticalSection critical_section_;
[[maybe_unused]] const char *old_no_suspend_reason_;
};
}
} // namespace lsplant::art::gc

View File

@ -5,25 +5,25 @@
namespace lsplant::art::jit {
class JitCodeCache {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, JitCodeCache *thiz,
ArtMethod * old_method, ArtMethod * new_method) {
if (MoveObsoleteMethodSym)
[[likely]] MoveObsoleteMethodSym(thiz, old_method, new_method);
ArtMethod *old_method, ArtMethod *new_method) {
if (MoveObsoleteMethodSym) [[likely]]
MoveObsoleteMethodSym(thiz, old_method, new_method);
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE",
void, GarbageCollectCache, (JitCodeCache * thiz, Thread * self), {
LOGD("Before jit cache gc, moving hooked methods");
for (auto[target, backup] : GetJitMovements()) {
MoveObsoleteMethod(thiz, target, backup);
}
backup(thiz, self);
});
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE", void,
GarbageCollectCache, (JitCodeCache * thiz, Thread *self), {
LOGD("Before jit cache gc, moving hooked methods");
for (auto [target, backup] : GetJitMovements()) {
MoveObsoleteMethod(thiz, target, backup);
}
backup(thiz, self);
});
public:
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(MoveObsoleteMethod,
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_")) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(
MoveObsoleteMethod,
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_")) {
return false;
}
if (!HookSyms(handler, GarbageCollectCache)) {
@ -32,4 +32,4 @@ public:
return true;
}
};
}
} // namespace lsplant::art::jit

View File

@ -1,6 +1,7 @@
#pragma once
#include <jni.h>
#include <string_view>
/// \namespace namespace of LSPlant
@ -58,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.
@ -93,9 +94,10 @@ bool Init(JNIEnv *env, const InitInfo &info);
/// 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);
[[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.
@ -104,8 +106,8 @@ Hook(JNIEnv *env, jobject target_method, jobject hooker_object, jobject callback
/// \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.
@ -113,33 +115,33 @@ bool UnHook(JNIEnv *env, jobject target_method);
/// \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.
/// \param[in] method The method to deoptimize. By deoptimizing the method, the method will back all
/// callee without inlining. For example, if you hooked a short method B that is invoked by method
/// 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
/// 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
/// 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
/// \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
/// env->registerNatives another native function pointer.
/// \param[in] env The Java environment.
/// \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);
}
} // namespace lsplant
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]] void *GetNativeFunction(
JNIEnv *env, jobject method);
} // namespace v1
} // namespace lsplant

View File

@ -1,82 +1,74 @@
#pragma once
#include "logging.hpp"
#include "jni_helper.hpp"
#include "hook_helper.hpp"
#include <concepts>
#include "hook_helper.hpp"
#include "jni_helper.hpp"
#include "logging.hpp"
#define CONCATENATE(a, b) a##b
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::Hooker<RET PARAMS, decltype(CONCATENATE(SYM,_tstr))>{ \
inline static RET replace PARAMS DEF \
} FUNC
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::Hooker<RET PARAMS, decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::MemHooker<RET PARAMS, decltype(CONCATENATE(SYM,_tstr))>{ \
inline static RET replace PARAMS DEF \
} FUNC
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::MemHooker<RET PARAMS, \
decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type>( \
lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type::FunType>( \
lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type::FunType>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
(name = reinterpret_cast<decltype(name)>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
(name = reinterpret_cast<decltype(name)>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
typedef ret (*func##Type)(__VA_ARGS__); \
inline static ret (*func##Sym)(__VA_ARGS__); \
inline static ret func(__VA_ARGS__)
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
typedef ret (*func##Type)(__VA_ARGS__); \
inline static ret (*func##Sym)(__VA_ARGS__); \
inline static ret func(__VA_ARGS__)
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
using func##Type = lsplant::MemberFunction<ret(__VA_ARGS__)>; \
inline static func##Type func##Sym; \
inline static ret func(thiz, ## __VA_ARGS__)
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
using func##Type = lsplant::MemberFunction<ret(__VA_ARGS__)>; \
inline static func##Type func##Sym; \
inline static ret func(thiz, ##__VA_ARGS__)
namespace lsplant {
using HookHandler = InitInfo;
template<char... chars>
template <char... chars>
struct tstring : public std::integer_sequence<char, chars...> {
inline constexpr static const char *c_str() {
return str_;
}
inline constexpr static const char *c_str() { return str_; }
inline constexpr operator std::string_view() const {
return { c_str(), sizeof...(chars) };
}
inline constexpr operator std::string_view() const { return {c_str(), sizeof...(chars)}; }
private:
inline static constexpr char str_[]{ chars..., '\0' };
inline static constexpr char str_[]{chars..., '\0'};
};
template<typename T, T... chars>
inline constexpr tstring<chars...> operator ""_tstr() {
template <typename T, T... chars>
inline constexpr tstring<chars...> operator""_tstr() {
return {};
}
template<char... as, char... bs>
inline constexpr tstring<as..., bs...>
operator+(const tstring<as...> &, const tstring<bs...> &) {
template <char... as, char... bs>
inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) {
return {};
}
template<char... as>
template <char... as>
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
char b[]{ as..., '\0' };
char b[]{as..., '\0'};
return a + b;
}
template<char... as>
template <char... as>
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
char a[]{ as..., '\0' };
char a[]{as..., '\0'};
return a + b;
}
@ -84,9 +76,9 @@ inline void *Dlsym(const HookHandler &handle, const char *name) {
return handle.art_symbol_resolver(name);
}
template<typename Class, typename Return, typename T, typename... Args>
requires (std::is_same_v<T, void> || std::is_same_v<Class, T>)
inline static auto memfun_cast(Return (*func)(T *, Args...)) {
template <typename Class, typename Return, typename T, typename... Args>
requires(std::is_same_v<T, void> ||
std::is_same_v<Class, T>) inline static auto memfun_cast(Return (*func)(T *, Args...)) {
union {
Return (Class::*f)(Args...);
@ -94,28 +86,31 @@ inline static auto memfun_cast(Return (*func)(T *, Args...)) {
decltype(func) p;
std::ptrdiff_t adj;
} data;
} u{ .data = { func, 0 }};
} u{.data = {func, 0}};
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
return u.f;
}
template<std::same_as<void> T, typename Return, typename... Args>
template <std::same_as<void> T, typename Return, typename... Args>
inline auto memfun_cast(Return (*func)(T *, Args...)) {
return memfun_cast<T>(func);
}
template<typename, typename=void>
template <typename, typename = void>
class MemberFunction;
template<typename This, typename Return, typename ... Args>
template <typename This, typename Return, typename... Args>
class MemberFunction<Return(Args...), This> {
using SelfType = MemberFunction<Return(This *, Args...), This>;
using ThisType = std::conditional_t<std::is_same_v<This, void>, SelfType, This>;
using MemFunType = Return(ThisType::*)(Args...);
using MemFunType = Return (ThisType::*)(Args...);
public:
using FunType = Return (*)(This *, Args...);
private:
MemFunType f_ = nullptr;
public:
MemberFunction() = default;
@ -127,51 +122,49 @@ public:
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
}
inline operator bool() {
return f_ != nullptr;
}
inline operator bool() { return f_ != nullptr; }
};
// deduction guide
template<typename This, typename Return, typename...Args>
MemberFunction(Return(*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
template <typename This, typename Return, typename... Args>
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
template<typename This, typename Return, typename...Args>
MemberFunction(Return(This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
template <typename This, typename Return, typename... Args>
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
template<typename, typename>
template <typename, typename>
struct Hooker;
template<typename Ret, typename... Args, char... cs>
template <typename Ret, typename... Args, char... cs>
struct Hooker<Ret(Args...), tstring<cs...>> {
inline static Ret (*backup)(Args...) = nullptr;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
template<typename, typename>
template <typename, typename>
struct MemHooker;
template<typename Ret, typename This, typename... Args, char... cs>
template <typename Ret, typename This, typename... Args, char... cs>
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
inline static MemberFunction<Ret(Args...)> backup;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
template<typename T>
template <typename T>
concept HookerType = requires(T a) {
a.backup;
a.replace;
};
template<HookerType T>
template <HookerType T>
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
if (original) {
if constexpr(is_instance_v<decltype(arg.backup), MemberFunction>) {
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
void *backup = handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace));
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
} else {
arg.backup = reinterpret_cast<decltype(arg.backup)>(handler.inline_hooker(original,
reinterpret_cast<void *>(arg.replace)));
arg.backup = reinterpret_cast<decltype(arg.backup)>(
handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace)));
}
return true;
} else {
@ -179,13 +172,13 @@ inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T
}
}
template<HookerType T>
template <HookerType T>
inline static bool HookSym(const HookHandler &handler, T &arg) {
auto original = handler.art_symbol_resolver(arg.sym);
return HookSymNoHandle(handler, original, arg);
}
template<HookerType T, HookerType...Args>
template <HookerType T, HookerType... Args>
inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
LOGW("Hook Fails: %*s", static_cast<int>(first.sym.size()), first.sym.data());
@ -194,4 +187,4 @@ inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest)
return true;
}
}
} // namespace lsplant

View File

@ -4,49 +4,43 @@
#pragma once
#include <jni.h>
#include <string>
#include "logging.hpp"
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName &) = delete; \
void operator=(const TypeName &) = delete
namespace lsplant {
template<class, template<class, class...> class>
struct is_instance : public std::false_type {
};
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
template<class...Ts, template<class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {
};
template <class... Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
template<class T, template<class, class...> class U>
template <class T, template <class, class...> class U>
inline constexpr bool is_instance_v = is_instance<T, U>::value;
template<typename T>
template <typename T>
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
template<JObject T>
template <JObject T>
class ScopedLocalRef {
public:
using BaseType [[maybe_unused]] = T;
ScopedLocalRef(JNIEnv *env, T localRef) : env_(env), local_ref_(localRef) {
}
ScopedLocalRef(JNIEnv *env, T localRef) : env_(env), local_ref_(localRef) {}
ScopedLocalRef(ScopedLocalRef &&s) noexcept: env_(s.env_), local_ref_(s.release()) {
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept : env_(s.env_), local_ref_(s.release()) {}
template<JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept: env_(s.env_), local_ref_((T) s.release()) {
}
template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : env_(s.env_), local_ref_((T)s.release()) {}
explicit ScopedLocalRef(JNIEnv *env) noexcept: env_(env), local_ref_(nullptr) {
}
explicit ScopedLocalRef(JNIEnv *env) noexcept : env_(env), local_ref_(nullptr) {}
~ScopedLocalRef() {
reset();
}
~ScopedLocalRef() { reset(); }
void reset(T ptr = nullptr) {
if (ptr != local_ref_) {
@ -63,13 +57,9 @@ public:
return localRef;
}
T get() const {
return local_ref_;
}
T get() const { return local_ref_; }
operator T() const {
return local_ref_;
}
operator T() const { return local_ref_; }
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
@ -82,16 +72,12 @@ public:
return *this;
}
operator bool() const {
return local_ref_;
}
operator bool() const { return local_ref_; }
template<JObject U>
friend
class ScopedLocalRef;
template <JObject U>
friend class ScopedLocalRef;
friend
class JUTFString;
friend class JUTFString;
private:
JNIEnv *env_;
@ -99,70 +85,65 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
class JNIScopeFrame {
JNIEnv *env_;
public:
JNIScopeFrame(JNIEnv *env, jint size) : env_(env) {
env_->PushLocalFrame(size);
}
~JNIScopeFrame() {
env_->PopLocalFrame(nullptr);
}
public:
JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalFrame(size); }
~JNIScopeFrame() { env_->PopLocalFrame(nullptr); }
};
template<typename T, typename U>
template <typename T, typename U>
concept ScopeOrRaw = std::is_convertible_v<T, U> ||
(is_instance_v<std::decay_t<T>, ScopedLocalRef> &&
std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
(is_instance_v<std::decay_t<T>, ScopedLocalRef>
&&std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
template<typename T>
template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>;
template<typename T>
template <typename T>
concept ScopeOrObject = ScopeOrRaw<T, jobject>;
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
if (auto exception = env->ExceptionOccurred()) {
env->ExceptionClear();
static jclass log = (jclass) env->NewGlobalRef(env->FindClass("android/util/Log"));
static jmethodID toString = env->GetStaticMethodID(log, "getStackTraceString",
"(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring) env->CallStaticObjectMethod(log, toString, exception);
static jclass log = (jclass)env->NewGlobalRef(env->FindClass("android/util/Log"));
static jmethodID toString = env->GetStaticMethodID(
log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
env->DeleteLocalRef(exception);
return { env, str };
return {env, str};
}
return { env, nullptr };
return {env, nullptr};
}
template<typename T>
[[maybe_unused]]
inline auto UnwrapScope(T &&x) {
if constexpr(std::is_same_v<std::decay_t<T>, std::string_view>)
template <typename T>
[[maybe_unused]] inline auto UnwrapScope(T &&x) {
if constexpr (std::is_same_v<std::decay_t<T>, std::string_view>)
return x.data();
else if constexpr(is_instance_v<std::decay_t<T>, ScopedLocalRef>)
else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>)
return x.get();
else return std::forward<T>(x);
else
return std::forward<T>(x);
}
template<typename T>
[[maybe_unused]]
inline auto WrapScope(JNIEnv *env, T &&x) {
if constexpr(std::is_convertible_v<T, _jobject *>) {
template <typename T>
[[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) {
if constexpr (std::is_convertible_v<T, _jobject *>) {
return ScopedLocalRef(env, std::forward<T>(x));
} else return x;
} else
return x;
}
template<typename ...T, size_t ...I>
[[maybe_unused]]
inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x, std::index_sequence<I...>) {
template <typename... T, size_t... I>
[[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x,
std::index_sequence<I...>) {
return std::make_tuple(WrapScope(env, std::forward<T>(std::get<I>(x)))...);
}
template<typename ...T>
[[maybe_unused]]
inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x) {
template <typename... T>
[[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x) {
return WrapScope(env, std::forward<std::tuple<T...>>(x),
std::make_index_sequence<sizeof...(T)>());
}
@ -173,17 +154,17 @@ inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
class JUTFString {
public:
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {
}
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
inline JUTFString(const ScopedLocalRef<jstring> &jstr) : JUTFString(jstr.env_, jstr.local_ref_,
nullptr) {
}
inline JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env),
jstr_(jstr) {
if (env_ && jstr_) cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else cstr_ = default_cstr;
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr)
: env_(env), jstr_(jstr) {
if (env_ && jstr_)
cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else
cstr_ = default_cstr;
}
inline operator const char *() const { return cstr_; }
@ -199,13 +180,13 @@ public:
}
JUTFString(JUTFString &&other)
: env_(std::move(other.env_)), jstr_(std::move(other.jstr_)),
cstr_(std::move(other.cstr_)) {
: env_(std::move(other.env_)),
jstr_(std::move(other.jstr_)),
cstr_(std::move(other.cstr_)) {
other.cstr_ = nullptr;
}
JUTFString &
operator=(JUTFString &&other) {
JUTFString &operator=(JUTFString &&other) {
if (&other != this) {
env_ = std::move(other.env_);
jstr_ = std::move(other.jstr_);
@ -225,11 +206,9 @@ private:
JUTFString &operator=(const JUTFString &) = delete;
};
template<typename Func, typename ...Args>
template <typename Func, typename... Args>
requires(std::is_function_v<Func>)
[[maybe_unused]]
inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&... args) {
[[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {
struct finally {
finally(JNIEnv *env) : env_(env) {}
@ -242,185 +221,166 @@ inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&... args) {
JNIEnv *env_;
} _(env);
if constexpr(!std::is_same_v<void, std::invoke_result_t<Func, decltype(UnwrapScope(
std::forward<Args>(args)))... >>)
if constexpr (!std::is_same_v<void,
std::invoke_result_t<Func, decltype(UnwrapScope(
std::forward<Args>(args)))...>>)
return WrapScope(env, (env->*f)(UnwrapScope(std::forward<Args>(args))...));
else (env->*f)(UnwrapScope(std::forward<Args>(args))...);
else
(env->*f)(UnwrapScope(std::forward<Args>(args))...);
}
[[maybe_unused]]
inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {
[[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {
return JNI_SafeInvoke(env, &JNIEnv::FindClass, name);
}
template<ScopeOrObject Object>
[[maybe_unused]]
inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {
return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_GetFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, const Class &clazz, std::string_view name,
std::string_view sig) {
return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, clazz, name, sig);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_ToReflectedMethod(JNIEnv *env, const Class &clazz, jmethodID method, jboolean isStatic) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_ToReflectedMethod(JNIEnv *env, const Class &clazz,
jmethodID method, jboolean isStatic) {
return JNI_SafeInvoke(env, &JNIEnv::ToReflectedMethod, clazz, method, isStatic);
}
template<ScopeOrObject Object>
[[maybe_unused]]
inline auto JNI_GetObjectField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_GetObjectField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, obj, fieldId);
}
template<ScopeOrObject Object>
[[maybe_unused]]
inline auto JNI_GetLongField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_GetLongField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetLongField, obj, fieldId);
}
template<ScopeOrObject Object>
[[maybe_unused]]
inline auto JNI_GetIntField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_GetIntField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetIntField, obj, fieldId);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_GetMethodID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetMethodID(JNIEnv *env, const Class &clazz, std::string_view name,
std::string_view sig) {
return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, clazz, name, sig);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto
JNI_CallObjectMethod(JNIEnv *env, const Object &obj, jmethodID method, Args &&... args) {
template <ScopeOrObject Object, typename... Args>
[[maybe_unused]] inline auto JNI_CallObjectMethod(JNIEnv *env, const Object &obj, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, obj, method, std::forward<Args>(args)...);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallIntMethod(JNIEnv *env, const Object &obj, jmethodID method, Args &&... args) {
template <ScopeOrObject Object, typename... Args>
[[maybe_unused]] inline auto JNI_CallIntMethod(JNIEnv *env, const Object &obj, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, obj, method, std::forward<Args>(args)...);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallLongMethod(JNIEnv *env, const Object &obj, Args &&... args) {
template <ScopeOrObject Object, typename... Args>
[[maybe_unused]] inline auto JNI_CallLongMethod(JNIEnv *env, const Object &obj, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, obj, std::forward<Args>(args)...);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallVoidMethod(JNIEnv *env, const Object &obj, Args &&...args) {
template <ScopeOrObject Object, typename... Args>
[[maybe_unused]] inline auto JNI_CallVoidMethod(JNIEnv *env, const Object &obj, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, obj, std::forward<Args>(args)...);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallBooleanMethod(JNIEnv *env, const Object &obj, Args &&...args) {
template <ScopeOrObject Object, typename... Args>
[[maybe_unused]] inline auto JNI_CallBooleanMethod(JNIEnv *env, const Object &obj, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, obj, std::forward<Args>(args)...);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_GetStaticFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetStaticFieldID(JNIEnv *env, const Class &clazz,
std::string_view name, std::string_view sig) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, clazz, name, sig);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz,
jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz,
jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_GetStaticMethodID(JNIEnv *env, const Class &clazz, std::string_view name,
std::string_view sig) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_GetStaticMethodID(JNIEnv *env, const Class &clazz,
std::string_view name, std::string_view sig) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, clazz, name, sig);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallStaticVoidMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
template <ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallStaticVoidMethod(JNIEnv *env, const Class &clazz,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, clazz, std::forward<Args>(args)...);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallStaticObjectMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
template <ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallStaticObjectMethod(JNIEnv *env, const Class &clazz,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, clazz, std::forward<Args>(args)...);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
template <ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, clazz, std::forward<Args>(args)...);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
template <ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz,
std::forward<Args>(args)...);
}
template<ScopeOrRaw<jarray> Array>
[[maybe_unused]]
inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {
template <ScopeOrRaw<jarray> Array>
[[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {
return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array);
}
template<ScopeOrRaw<jobjectArray> Array>
[[maybe_unused]]
inline auto JNI_GetObjectArrayElement(JNIEnv *env, const Array &array, jsize idx) {
template <ScopeOrRaw<jobjectArray> Array>
[[maybe_unused]] inline auto JNI_GetObjectArrayElement(JNIEnv *env, const Array &array, jsize idx) {
return JNI_SafeInvoke(env, &JNIEnv::GetObjectArrayElement, array, idx);
}
template<ScopeOrClass Class, typename ...Args>
[[maybe_unused]]
inline auto JNI_NewObject(JNIEnv *env, const Class &clazz, Args &&...args) {
template <ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_NewObject(JNIEnv *env, const Class &clazz, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::NewObject, clazz, std::forward<Args>(args)...);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto
JNI_RegisterNatives(JNIEnv *env, const Class &clazz, const JNINativeMethod *methods, jint size) {
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_RegisterNatives(JNIEnv *env, const Class &clazz,
const JNINativeMethod *methods, jint size) {
return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, clazz, methods, size);
}
template<ScopeOrObject Object>
[[maybe_unused]]
inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x)))) env->NewGlobalRef(
UnwrapScope(std::forward<Object>(x)));
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(
UnwrapScope(std::forward<Object>(x)));
}
template<typename U, typename T>
[[maybe_unused]]
inline auto
JNI_Cast(ScopedLocalRef<T> &&x)requires(std::is_convertible_v<T, _jobject *>) {
template <typename U, typename T>
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) requires(
std::is_convertible_v<T, _jobject *>) {
return ScopedLocalRef<U>(std::move(x));
}
}
} // namespace lsplant
#undef DISALLOW_COPY_AND_ASSIGN

View File

@ -1,20 +1,22 @@
#include "lsplant.hpp"
#include <sys/mman.h>
#include <android/api-level.h>
#include <atomic>
#include <array>
#include <sys/mman.h>
#include <sys/system_properties.h>
#include "utils/jni_helper.hpp"
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/thread.hpp"
#include <array>
#include <atomic>
#include "art/instrumentation.hpp"
#include "art/runtime/jit/jit_code_cache.hpp"
#include "art/runtime/art_method.hpp"
#include "art/thread_list.hpp"
#include "art/runtime/class_linker.hpp"
#include "dex_builder.h"
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/runtime/jit/jit_code_cache.hpp"
#include "art/thread.hpp"
#include "art/thread_list.hpp"
#include "common.hpp"
#include "dex_builder.h"
#include "utils/jni_helper.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -24,48 +26,45 @@
namespace lsplant {
using art::ArtMethod;
using art::thread_list::ScopedSuspendAll;
using art::ClassLinker;
using art::mirror::Class;
using art::Thread;
using art::Instrumentation;
using art::Thread;
using art::gc::ScopedGCCriticalSection;
using art::jit::JitCodeCache;
using art::mirror::Class;
using art::thread_list::ScopedSuspendAll;
namespace {
template<typename T, T... chars>
inline consteval auto operator ""_uarr() {
return std::array<uint8_t, sizeof...(chars)>{ static_cast<uint8_t>(chars)... };
template <typename T, T... chars>
inline consteval auto operator""_uarr() {
return std::array<uint8_t, sizeof...(chars)>{static_cast<uint8_t>(chars)...};
}
consteval inline auto GetTrampoline() {
if constexpr(kArch == Arch::kArm) {
return std::make_tuple(
"\x00\x00\x9f\xe5\x20\xf0\x90\xe5\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{ 32u }, uintptr_t{ 8u });
if constexpr (kArch == Arch::kArm) {
return std::make_tuple("\x00\x00\x9f\xe5\x20\xf0\x90\xe5\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{32u}, uintptr_t{8u});
}
if constexpr(kArch == Arch::kArm64) {
if constexpr (kArch == Arch::kArm64) {
return std::make_tuple(
"\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{ 44u }, uintptr_t{ 12u });
"\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE
uint8_t{44u}, uintptr_t{12u});
}
if constexpr(kArch == Arch::kX86) {
return std::make_tuple(
"\xb8\x78\x56\x34\x12\xff\x70\x20\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{ 56u }, uintptr_t{ 1u });
if constexpr (kArch == Arch::kX86) {
return std::make_tuple("\xb8\x78\x56\x34\x12\xff\x70\x20\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{56u}, uintptr_t{1u});
}
if constexpr(kArch == Arch::kX8664) {
return std::make_tuple(
"\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x20\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{ 96u }, uintptr_t{ 2u });
if constexpr (kArch == Arch::kX8664) {
return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x20\xc3"_uarr,
// NOLINTNEXTLINE
uint8_t{96u}, uintptr_t{2u});
}
}
auto[trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
auto [trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
jmethodID method_get_name = nullptr;
jmethodID method_get_declaring_class = nullptr;
@ -110,13 +109,14 @@ bool InitJNI(JNIEnv *env) {
return false;
}
if (method_get_name = JNI_GetMethodID(env, executable, "getName",
"()Ljava/lang/String;"); !method_get_name) {
if (method_get_name = JNI_GetMethodID(env, executable, "getName", "()Ljava/lang/String;");
!method_get_name) {
LOGE("Failed to find getName method");
return false;
}
if (method_get_declaring_class = JNI_GetMethodID(env, executable, "getDeclaringClass",
"()Ljava/lang/Class;"); !method_get_declaring_class) {
if (method_get_declaring_class =
JNI_GetMethodID(env, executable, "getDeclaringClass", "()Ljava/lang/Class;");
!method_get_declaring_class) {
LOGE("Failed to find getName method");
return false;
}
@ -126,15 +126,15 @@ bool InitJNI(JNIEnv *env) {
return false;
}
if (class_get_class_loader = JNI_GetMethodID(env, clazz, "getClassLoader",
"()Ljava/lang/ClassLoader;");
!class_get_class_loader) {
if (class_get_class_loader =
JNI_GetMethodID(env, clazz, "getClassLoader", "()Ljava/lang/ClassLoader;");
!class_get_class_loader) {
LOGE("Failed to find getClassLoader");
return false;
}
if (class_get_name = JNI_GetMethodID(env, clazz, "getName", "()Ljava/lang/String;");
!class_get_name) {
!class_get_name) {
LOGE("Failed to find getName");
return false;
}
@ -144,10 +144,10 @@ bool InitJNI(JNIEnv *env) {
return false;
}
in_memory_class_loader = JNI_NewGlobalRef(env, JNI_FindClass(env,
"dalvik/system/InMemoryDexClassLoader"));
in_memory_class_loader_init = JNI_GetMethodID(env, in_memory_class_loader, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
in_memory_class_loader =
JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader"));
in_memory_class_loader_init = JNI_GetMethodID(
env, in_memory_class_loader, "<init>", "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
@ -161,7 +161,7 @@ bool InitJNI(JNIEnv *env) {
return false;
}
if (set_accessible = JNI_GetMethodID(env, accessible_object, "setAccessible", "(Z)V");
!set_accessible) {
!set_accessible) {
LOGE("Failed to find AccessibleObject.setAccessible");
return false;
}
@ -171,7 +171,7 @@ bool InitJNI(JNIEnv *env) {
inline void UpdateTrampoline(uint8_t offset) {
trampoline[entry_point_offset / CHAR_BIT] |= offset << (entry_point_offset % CHAR_BIT);
trampoline[entry_point_offset / CHAR_BIT + 1] |=
offset >> (CHAR_BIT - entry_point_offset % CHAR_BIT);
offset >> (CHAR_BIT - entry_point_offset % CHAR_BIT);
}
bool InitNative(JNIEnv *env, const HookHandler &handler) {
@ -214,16 +214,17 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
return true;
}
std::tuple<jclass, jfieldID, jmethodID, jmethodID>
BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static, std::string_view method_name,
std::string_view hooker_class, std::string_view callback_name) {
std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static,
std::string_view method_name,
std::string_view hooker_class,
std::string_view callback_name) {
// NOLINTNEXTLINE
using namespace startop::dex;
if (shorty.empty()) {
LOGE("Invalid shorty");
return { nullptr, nullptr, nullptr, nullptr };
return {nullptr, nullptr, nullptr, nullptr};
}
DexBuilder dex_file;
@ -232,66 +233,62 @@ BuildDex(JNIEnv *env, jobject class_loader,
parameter_types.reserve(shorty.size() - 1);
std::string storage;
auto return_type =
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
shorty[0]);
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
for (const char &param: shorty.substr(1)) {
parameter_types.push_back(
param == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
static_cast<char>(param)));
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]);
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
for (const char &param : shorty.substr(1)) {
parameter_types.push_back(param == 'L'
? TypeDescriptor::Object
: TypeDescriptor::FromDescriptor(static_cast<char>(param)));
}
ClassBuilder cbuilder{ dex_file.MakeClass(generated_class_name) };
ClassBuilder cbuilder{dex_file.MakeClass(generated_class_name)};
if (!generated_source_name.empty()) cbuilder.set_source_file(generated_source_name);
auto hooker_type = TypeDescriptor::FromClassname(hooker_class.data());
auto *hooker_field = cbuilder.CreateField(generated_field_name, hooker_type)
.access_flags(dex::kAccStatic)
.Encode();
.access_flags(dex::kAccStatic)
.Encode();
auto hook_builder{ cbuilder.CreateMethod(
generated_method_name == "{target}" ? method_name.data() : generated_method_name,
Prototype{ return_type, parameter_types }) };
auto hook_builder{cbuilder.CreateMethod(
generated_method_name == "{target}" ? method_name.data() : generated_method_name,
Prototype{return_type, parameter_types})};
// allocate tmp first because of wide
auto tmp{ hook_builder.AllocRegister() };
auto tmp{hook_builder.AllocRegister()};
hook_builder.BuildConst(tmp, static_cast<int>(parameter_types.size()));
auto hook_params_array{ hook_builder.AllocRegister() };
auto hook_params_array{hook_builder.AllocRegister()};
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
Value::Parameter(j));
hook_builder.BuildConst(tmp, static_cast<int>(i));
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array,
Value::Parameter(j), tmp);
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array, Value::Parameter(j),
tmp);
if (parameter_types[i].is_wide()) ++j;
}
auto handle_hook_method{ dex_file.GetOrDeclareMethod(
hooker_type, callback_name.data(),
Prototype{ TypeDescriptor::Object, TypeDescriptor::Object.ToArray() }) };
auto handle_hook_method{dex_file.GetOrDeclareMethod(
hooker_type, callback_name.data(),
Prototype{TypeDescriptor::Object, TypeDescriptor::Object.ToArray()})};
hook_builder.AddInstruction(
Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp));
hook_builder.AddInstruction(Instruction::InvokeVirtualObject(
handle_hook_method.id, tmp, tmp, hook_params_array));
Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp));
hook_builder.AddInstruction(
Instruction::InvokeVirtualObject(handle_hook_method.id, tmp, tmp, hook_params_array));
if (return_type == TypeDescriptor::Void) {
hook_builder.BuildReturn();
} else if (return_type.is_primitive()) {
auto box_type{ return_type.ToBoxType() };
auto box_type{return_type.ToBoxType()};
const ir::Type *type_def = dex_file.GetOrAddType(box_type);
hook_builder.AddInstruction(
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.AddInstruction(Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.BuildUnBoxIfPrimitive(tmp, box_type, tmp);
hook_builder.BuildReturn(tmp, false, return_type.is_wide());
} else {
const ir::Type *type_def = dex_file.GetOrAddType(return_type);
hook_builder.AddInstruction(
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.AddInstruction(Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
hook_builder.BuildReturn(tmp, true);
}
auto *hook_method = hook_builder.Encode();
auto backup_builder{
cbuilder.CreateMethod("backup", Prototype{ return_type, parameter_types }) };
auto backup_builder{cbuilder.CreateMethod("backup", Prototype{return_type, parameter_types})};
if (return_type == TypeDescriptor::Void) {
backup_builder.BuildReturn();
} else if (return_type.is_wide()) {
@ -307,30 +304,32 @@ BuildDex(JNIEnv *env, jobject class_loader,
}
auto *backup_method = backup_builder.Encode();
slicer::MemView image{ dex_file.CreateImage() };
slicer::MemView image{dex_file.CreateImage()};
auto *dex_buffer = env->NewDirectByteBuffer(const_cast<void *>(image.ptr()), image.size());
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
dex_buffer, class_loader);
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init, dex_buffer,
class_loader);
env->DeleteLocalRef(dex_buffer);
if (my_cl) {
auto *target_class = JNI_Cast<jclass>(
auto *target_class =
JNI_Cast<jclass>(
JNI_CallObjectMethod(env, my_cl, load_class,
JNI_NewStringUTF(env, generated_class_name.data()))).release();
JNI_NewStringUTF(env, generated_class_name.data())))
.release();
if (target_class) {
return {
target_class,
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
hooker_field->decl->type->descriptor->c_str()),
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
hook_method->decl->prototype->Signature().data()),
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
backup_method->decl->prototype->Signature().data()),
target_class,
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
hooker_field->decl->type->descriptor->c_str()),
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
hook_method->decl->prototype->Signature().data()),
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
backup_method->decl->prototype->Signature().data()),
};
}
}
return { nullptr, nullptr, nullptr, nullptr };
return {nullptr, nullptr, nullptr, nullptr};
}
static_assert(std::endian::native == std::endian::little, "Unsupported architecture");
@ -338,16 +337,16 @@ static_assert(std::endian::native == std::endian::little, "Unsupported architect
union Trampoline {
public:
uintptr_t address;
unsigned count: 12;
unsigned count : 12;
};
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architecture");
std::atomic_uintptr_t trampoline_pool{ 0 };
std::atomic_flag trampoline_lock{ false };
std::atomic_uintptr_t trampoline_pool{0};
std::atomic_flag trampoline_lock{false};
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
constexpr uintptr_t kAddressMask = 0xFFFU;
@ -355,7 +354,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
unsigned count;
uintptr_t address;
while (true) {
auto tl = Trampoline{ .address = trampoline_pool.fetch_add(1, std::memory_order_release) };
auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = tl.count;
address = tl.address & ~kAddressMask;
if (address == 0 || count >= kTrampolineNumPerPage) {
@ -378,7 +377,6 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
trampoline_pool.store(tl.address, std::memory_order_release);
trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
}
LOGV("trampoline: count = %u, address = %zx, target = %zx", count, address,
address + count * kTrampolineSize);
@ -396,8 +394,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
}
bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
ScopedGCCriticalSection section(art::Thread::Current(),
art::gc::kGcCauseDebugger,
ScopedGCCriticalSection section(art::Thread::Current(), art::gc::kGcCauseDebugger,
art::gc::kCollectorTypeDebugger);
ScopedSuspendAll suspend("LSPlant Hook", false);
LOGV("Hooking: target = %p, hook = %p, backup = %p", target, hook, backup);
@ -421,27 +418,25 @@ bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p",
target, target->GetAccessFlags(), target->GetEntryPoint(),
backup, backup->GetAccessFlags(), backup->GetEntryPoint(),
hook, hook->GetAccessFlags(), hook->GetEntryPoint());
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
return true;
}
}
bool DoUnHook(ArtMethod *target, ArtMethod *backup) {
ScopedGCCriticalSection section(art::Thread::Current(),
art::gc::kGcCauseDebugger,
ScopedGCCriticalSection section(art::Thread::Current(), art::gc::kGcCauseDebugger,
art::gc::kCollectorTypeDebugger);
ScopedSuspendAll suspend("LSPlant Hook", false);
LOGV("Unhooking: target = %p, backup = %p", target, backup);
auto access_flags = target->GetAccessFlags();
target->CopyFrom(backup);
target->SetAccessFlags(access_flags);
LOGV("Done unhook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p;",
target, target->GetAccessFlags(), target->GetEntryPoint(),
backup, backup->GetAccessFlags(), backup->GetEntryPoint());
LOGV("Done unhook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p;", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint());
return true;
}
@ -458,16 +453,13 @@ inline namespace v1 {
using ::lsplant::IsHooked;
[[maybe_unused]]
bool Init(JNIEnv *env, const InitInfo &info) {
[[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) {
bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info);
return kInit;
}
[[maybe_unused]]
jobject
Hook(JNIEnv *env, jobject target_method, jobject hooker_object, jobject callback_method) {
[[maybe_unused]] jobject Hook(JNIEnv *env, jobject target_method, jobject hooker_object,
jobject callback_method) {
if (!env->IsInstanceOf(target_method, executable)) {
LOGE("target method is not an executable");
return nullptr;
@ -481,8 +473,8 @@ Hook(JNIEnv *env, jobject target_method, jobject hooker_object, jobject callback
jmethodID backup_method = nullptr;
jfieldID hooker_field = nullptr;
auto target_class = JNI_Cast<jclass>(
JNI_CallObjectMethod(env, target_method, method_get_declaring_class));
auto target_class =
JNI_Cast<jclass>(JNI_CallObjectMethod(env, target_method, method_get_declaring_class));
bool is_proxy = JNI_CallBooleanMethod(env, target_class, class_is_proxy);
auto *target = ArtMethod::FromReflectedMethod(env, target_method);
bool is_static = target->IsStatic();
@ -492,30 +484,26 @@ Hook(JNIEnv *env, jobject target_method, jobject hooker_object, jobject callback
return nullptr;
}
ScopedLocalRef<jclass> built_class{ env };
ScopedLocalRef<jclass> built_class{env};
{
auto callback_name = JNI_Cast<jstring>(
JNI_CallObjectMethod(env, callback_method, method_get_name));
auto callback_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_method, method_get_name));
JUTFString method_name(callback_name);
auto callback_class = JNI_Cast<jclass>(JNI_CallObjectMethod(env, callback_method,
method_get_declaring_class));
auto callback_class_loader = JNI_CallObjectMethod(env, callback_class,
class_get_class_loader);
auto callback_class_name = JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_class,
class_get_name));
auto callback_class = JNI_Cast<jclass>(
JNI_CallObjectMethod(env, callback_method, method_get_declaring_class));
auto callback_class_loader =
JNI_CallObjectMethod(env, callback_class, class_get_class_loader);
auto callback_class_name =
JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_class, class_get_name));
JUTFString class_name(callback_class_name);
if (!env->IsInstanceOf(hooker_object, callback_class)) {
LOGE("callback_method is not a method of hooker_object");
return nullptr;
}
std::tie(built_class, hooker_field, hook_method, backup_method) =
WrapScope(env, BuildDex(env, callback_class_loader,
ArtMethod::GetMethodShorty(env, env->FromReflectedMethod(
target_method)),
is_static,
method_name.get(),
class_name.get(),
method_name.get()));
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env, BuildDex(env, callback_class_loader,
ArtMethod::GetMethodShorty(env, env->FromReflectedMethod(target_method)),
is_static, method_name.get(), class_name.get(), method_name.get()));
if (!built_class || !hooker_field || !hook_method || !backup_method) {
LOGE("Failed to generate hooker");
return nullptr;
@ -550,15 +538,15 @@ Hook(JNIEnv *env, jobject target_method, jobject hooker_object, jobject callback
if (DoHook(target, hook, backup)) {
jobject global_backup = JNI_NewGlobalRef(env, reflected_backup);
RecordHooked(target, global_backup);
if (!is_proxy) [[likely]] RecordJitMovement(target, backup);
if (!is_proxy) [[likely]]
RecordJitMovement(target, backup);
return global_backup;
}
return nullptr;
}
[[maybe_unused]]
bool UnHook(JNIEnv *env, jobject target_method) {
[[maybe_unused]] bool UnHook(JNIEnv *env, jobject target_method) {
if (!env->IsInstanceOf(target_method, executable)) {
LOGE("target method is not an executable");
return false;
@ -574,7 +562,7 @@ bool UnHook(JNIEnv *env, jobject target_method) {
}
{
std::unique_lock lk(hooked_methods_lock_);
if (auto it = hooked_methods_.find(target);it != hooked_methods_.end()) {
if (auto it = hooked_methods_.find(target); it != hooked_methods_.end()) {
reflected_backup = it->second;
hooked_methods_.erase(it);
}
@ -588,8 +576,7 @@ bool UnHook(JNIEnv *env, jobject target_method) {
return DoUnHook(target, backup);
}
[[maybe_unused]]
bool IsHooked(JNIEnv *env, jobject method) {
[[maybe_unused]] bool IsHooked(JNIEnv *env, jobject method) {
if (!env->IsInstanceOf(method, executable)) {
LOGE("method is not an executable");
return false;
@ -605,8 +592,7 @@ bool IsHooked(JNIEnv *env, jobject method) {
return false;
}
[[maybe_unused]]
bool Deoptimize(JNIEnv *env, jobject method) {
[[maybe_unused]] bool Deoptimize(JNIEnv *env, jobject method) {
if (!env->IsInstanceOf(method, executable)) {
LOGE("method is not an executable");
return false;
@ -626,8 +612,7 @@ bool Deoptimize(JNIEnv *env, jobject method) {
return ClassLinker::SetEntryPointsToInterpreter(art_method);
}
[[maybe_unused]]
void *GetNativeFunction(JNIEnv *env, jobject method) {
[[maybe_unused]] void *GetNativeFunction(JNIEnv *env, jobject method) {
if (!env->IsInstanceOf(method, executable)) {
LOGE("method is not an executable");
return nullptr;