mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-05 05:56:38 +08:00
Add clang-format and reformat all files
This commit is contained in:
parent
a28b3e1227
commit
5bab7a0e38
18
library/jni/.clang-format
Normal file
18
library/jni/.clang-format
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -39,4 +39,4 @@ enum CollectorType {
|
||||
// Fake collector type for ScopedGCCriticalSection
|
||||
kCollectorTypeCriticalSection,
|
||||
};
|
||||
} // namespace gc
|
||||
} // namespace lsplant::art::gc
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 ¶m: 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 ¶m : 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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user