Modularize codes

This commit is contained in:
LoveSy 2024-04-22 23:51:47 +08:00
parent 386200f0c9
commit 2db8b66b29
No known key found for this signature in database
28 changed files with 571 additions and 603 deletions

View File

@ -1,27 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant
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
LOCAL_LDLIBS := -llog
LOCAL_EXPORT_LDLIBS := $(LOCAL_LDLIBS)
LOCAL_CFLAGS := -flto
LOCAL_LDFLAGS := -flto
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant_static
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
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include jni/external/dex_builder/Android.mk

View File

@ -1,14 +0,0 @@
APP_CFLAGS := -Wall -Wextra
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__ -Wno-gnu-string-literal-operator-template
APP_CPPFLAGS := -std=c++20
APP_CONLYFLAGS := -std=c18
APP_STL := c++_shared
ifneq ($(NDK_DEBUG),1)
APP_CFLAGS += -Oz
APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
APP_LDFLAGS += -Wl,--exclude-libs,ALL -Wl,--gc-sections -Wl,--strip-all
endif

View File

@ -1,8 +1,11 @@
cmake_minimum_required(VERSION 3.4.1)
cmake_minimum_required(VERSION 3.28)
project(lsplant)
find_program(CCACHE ccache)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
@ -13,9 +16,8 @@ if (LSPLANT_STANDALONE)
link_libraries(cxx::cxx)
endif()
add_definitions(-std=c++20)
set(SOURCES lsplant.cc)
file(GLOB_RECURSE MODULE_SOURCES "*.cxx")
set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE)
add_subdirectory(external/dex_builder)
@ -25,6 +27,7 @@ option(LSPLANT_BUILD_SHARED "If ON, lsplant will also build shared library" ON)
if (LSPLANT_BUILD_SHARED)
message(STATUS "Building lsplant as a shared library")
add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_sources(${PROJECT_NAME} PRIVATE FILE_SET CXX_MODULES FILES ${MODULE_SOURCES})
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(${PROJECT_NAME} PRIVATE -flto)
@ -40,6 +43,7 @@ if (LSPLANT_BUILD_SHARED)
endif()
add_library(${PROJECT_NAME}_static STATIC ${SOURCES})
target_sources(${PROJECT_NAME}_static PRIVATE FILE_SET CXX_MODULES FILES ${MODULE_SOURCES})
target_include_directories(${PROJECT_NAME}_static PUBLIC include)
target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,18 +1,20 @@
#pragma once
module;
#include "art/runtime/art_method.hpp"
#include "art/runtime/handle.hpp"
#include "common.hpp"
#include <parallel_hashmap/phmap.h>
namespace lsplant::art {
class Thread;
namespace dex {
class ClassDef {};
} // namespace dex
#include "logging.hpp"
#include "utils/hook_helper.hpp"
namespace mirror {
export module clazz;
class Class {
import common;
import art_method;
import thread;
import handle;
namespace lsplant::art::mirror {
export class Class {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
if (GetDescriptorSym) [[likely]]
@ -184,5 +186,4 @@ public:
}
};
} // namespace mirror
} // namespace lsplant::art
} // namespace lsplant::art::mirror

View File

@ -1,11 +1,20 @@
#pragma once
module;
#include "art/mirror/class.hpp"
#include "common.hpp"
#include "logging.hpp"
#define JNI_HELPER_NO_NS
#include "utils/hook_helper.hpp"
export module art_method;
import common;
namespace lsplant::art {
namespace mirror {
class Class;
}
class ArtMethod {
export class ArtMethod {
CREATE_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) {
if (thiz == nullptr) [[unlikely]]
return "null";
@ -274,7 +283,8 @@ public:
LOGE("Failed to find Executable.getName");
return false;
}
RETRIEVE_MEM_FUNC_SYMBOL(ThrowInvocationTimeError, "_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv");
RETRIEVE_MEM_FUNC_SYMBOL(ThrowInvocationTimeError,
"_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv");
auto abstract_method = FromReflectedMethod(
env, JNI_ToReflectedMethod(env, executable, executable_get_name, false));
uint32_t access_flags = abstract_method->GetAccessFlags();

View File

@ -1,12 +1,18 @@
#pragma once
module;
#include "art/runtime/art_method.hpp"
#include "art/runtime/obj_ptr.hpp"
#include "art/runtime/thread.hpp"
#include "common.hpp"
#include "include/utils/hook_helper.hpp"
#include "logging.hpp"
export module class_linker;
import art_method;
import thread;
import common;
import clazz;
import handle;
namespace lsplant::art {
class ClassLinker {
export class ClassLinker {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, ClassLinker *thiz,
ArtMethod *art_method) {
@ -16,14 +22,13 @@ private:
}
CREATE_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv", bool,
ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code), {
if (quick_code != nullptr && IsHooked(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)) [[unlikely]] {
return false;
}
return backup(art_method, quick_code);
});
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void *) {}
@ -130,10 +135,11 @@ private:
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker26VisiblyInitializedCallback22MarkVisiblyInitializedEPNS_6ThreadE",
void, MarkVisiblyInitialized, (void *thiz, Thread* self), {
void, MarkVisiblyInitialized, (void *thiz, Thread *self), {
backup(thiz, self);
RestoreBackup(nullptr, self);
});
public:
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
@ -155,7 +161,7 @@ public:
}
if (sdk_int >= __ANDROID_API_R__) {
if constexpr (GetArch() != Arch::kX86 && GetArch() != Arch::kX86_64) {
if constexpr (kArch != Arch::kX86 && kArch != Arch::kX86_64) {
// fixup static trampoline may have been inlined
HookSyms(handler, AdjustThreadVisibilityCounter, MarkVisiblyInitialized);
}

View File

@ -1,13 +1,18 @@
#pragma once
module;
#include <memory>
#include <string>
#include <vector>
#include "common.hpp"
#include "logging.hpp"
#include "utils/hook_helper.hpp"
export module dex_file;
import common;
namespace lsplant::art {
class DexFile {
export class DexFile {
struct Header {
[[maybe_unused]] uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_

View File

@ -1,42 +0,0 @@
#pragma once
namespace lsplant::art::gc {
// Which types of collections are able to be performed.
enum CollectorType {
// No collector selected.
kCollectorTypeNone,
// Non concurrent mark-sweep.
kCollectorTypeMS,
// Concurrent mark-sweep.
kCollectorTypeCMS,
// Semi-space / mark-sweep hybrid, enables compaction.
kCollectorTypeSS,
// Heap trimming collector, doesn't do any actual collecting.
kCollectorTypeHeapTrim,
// A (mostly) concurrent copying collector.
kCollectorTypeCC,
// The background compaction of the concurrent copying collector.
kCollectorTypeCCBackground,
// Instrumentation critical section fake collector.
kCollectorTypeInstrumentation,
// Fake collector for adding or removing application image spaces.
kCollectorTypeAddRemoveAppImageSpace,
// Fake collector used to implement exclusion between GC and debugger.
kCollectorTypeDebugger,
// A homogeneous space compaction collector used in background transition
// when both foreground and background collector are CMS.
kCollectorTypeHomogeneousSpaceCompact,
// Class linker fake collector.
kCollectorTypeClassLinker,
// JIT Code cache fake collector.
kCollectorTypeJitCodeCache,
// Hprof fake collector.
kCollectorTypeHprof,
// Fake collector for installing/removing a system-weak holder.
kCollectorTypeAddRemoveSystemWeakHolder,
// Fake collector type for GetObjectsAllocated
kCollectorTypeGetObjectsAllocated,
// Fake collector type for ScopedGCCriticalSection
kCollectorTypeCriticalSection,
};
} // namespace lsplant::art::gc

View File

@ -1,45 +0,0 @@
#pragma once
namespace lsplant::art::gc {
// What caused the GC?
enum GcCause {
// Invalid GC cause used as a placeholder.
kGcCauseNone,
// GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
// retrying allocation.
kGcCauseForAlloc,
// A background GC trying to ensure there is free memory ahead of allocations.
kGcCauseBackground,
// An explicit System.gc() call.
kGcCauseExplicit,
// GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
// (This may be a blocking GC depending on whether we run a non-concurrent collector).
kGcCauseForNativeAlloc,
// GC triggered for a collector transition.
kGcCauseCollectorTransition,
// 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,
// Not a real GC cause, used to implement exclusion between GC and instrumentation.
kGcCauseInstrumentation,
// Not a real GC cause, used to add or remove app image spaces.
kGcCauseAddRemoveAppImageSpace,
// Not a real GC cause, used to implement exclusion between GC and debugger.
kGcCauseDebugger,
// GC triggered for background transition when both foreground and background collector are CMS.
kGcCauseHomogeneousSpaceCompact,
// Class linker cause, used to guard filling art methods with special values.
kGcCauseClassLinker,
// Not a real GC cause, used to implement exclusion between code cache metadata and GC.
kGcCauseJitCodeCache,
// Not a real GC cause, used to add or remove system-weak holders.
kGcCauseAddRemoveSystemWeakHolder,
// Not a real GC cause, used to prevent hprof running in the middle of GC.
kGcCauseHprof,
// Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
kGcCauseGetObjectsAllocated,
// GC cause for the profile saver.
kGcCauseProfileSaver,
};
} // namespace lsplant::art::gc

View File

@ -0,0 +1,139 @@
module;
#include "utils/hook_helper.hpp"
export module scope_gc_critical_section;
import thread;
import common;
namespace lsplant::art::gc {
// Which types of collections are able to be performed.
enum CollectorType {
// No collector selected.
kCollectorTypeNone,
// Non concurrent mark-sweep.
kCollectorTypeMS,
// Concurrent mark-sweep.
kCollectorTypeCMS,
// Semi-space / mark-sweep hybrid, enables compaction.
kCollectorTypeSS,
// Heap trimming collector, doesn't do any actual collecting.
kCollectorTypeHeapTrim,
// A (mostly) concurrent copying collector.
kCollectorTypeCC,
// The background compaction of the concurrent copying collector.
kCollectorTypeCCBackground,
// Instrumentation critical section fake collector.
kCollectorTypeInstrumentation,
// Fake collector for adding or removing application image spaces.
kCollectorTypeAddRemoveAppImageSpace,
// Fake collector used to implement exclusion between GC and debugger.
kCollectorTypeDebugger,
// A homogeneous space compaction collector used in background transition
// when both foreground and background collector are CMS.
kCollectorTypeHomogeneousSpaceCompact,
// Class linker fake collector.
kCollectorTypeClassLinker,
// JIT Code cache fake collector.
kCollectorTypeJitCodeCache,
// Hprof fake collector.
kCollectorTypeHprof,
// Fake collector for installing/removing a system-weak holder.
kCollectorTypeAddRemoveSystemWeakHolder,
// Fake collector type for GetObjectsAllocated
kCollectorTypeGetObjectsAllocated,
// Fake collector type for ScopedGCCriticalSection
kCollectorTypeCriticalSection,
};
// What caused the GC?
enum GcCause {
// Invalid GC cause used as a placeholder.
kGcCauseNone,
// GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
// retrying allocation.
kGcCauseForAlloc,
// A background GC trying to ensure there is free memory ahead of allocations.
kGcCauseBackground,
// An explicit System.gc() call.
kGcCauseExplicit,
// GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
// (This may be a blocking GC depending on whether we run a non-concurrent collector).
kGcCauseForNativeAlloc,
// GC triggered for a collector transition.
kGcCauseCollectorTransition,
// 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,
// Not a real GC cause, used to implement exclusion between GC and instrumentation.
kGcCauseInstrumentation,
// Not a real GC cause, used to add or remove app image spaces.
kGcCauseAddRemoveAppImageSpace,
// Not a real GC cause, used to implement exclusion between GC and debugger.
kGcCauseDebugger,
// GC triggered for background transition when both foreground and background collector are CMS.
kGcCauseHomogeneousSpaceCompact,
// Class linker cause, used to guard filling art methods with special values.
kGcCauseClassLinker,
// Not a real GC cause, used to implement exclusion between code cache metadata and GC.
kGcCauseJitCodeCache,
// Not a real GC cause, used to add or remove system-weak holders.
kGcCauseAddRemoveSystemWeakHolder,
// Not a real GC cause, used to prevent hprof running in the middle of GC.
kGcCauseHprof,
// Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
kGcCauseGetObjectsAllocated,
// GC cause for the profile saver.
kGcCauseProfileSaver,
};
export class GCCriticalSection {
private:
[[maybe_unused]] void *self_;
[[maybe_unused]] const char *section_name_;
};
export class ScopedGCCriticalSection {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedGCCriticalSection *thiz, Thread *self,
GcCause cause, CollectorType collector_type) {
if (thiz && constructorSym) [[likely]]
return constructorSym(thiz, self, cause, collector_type);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedGCCriticalSection *thiz) {
if (thiz && destructorSym) [[likely]]
destructorSym(thiz);
}
public:
ScopedGCCriticalSection(Thread *self, GcCause cause, CollectorType collector_type) {
constructor(this, self, cause, collector_type);
}
~ScopedGCCriticalSection() { destructor(this); }
static bool Init(const HookHandler &handler) {
// for Android M, it's safe to not found since we have suspendVM & resumeVM
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_"
"7GcCauseENS0_13CollectorTypeE")) [[unlikely]] {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev"))
[[unlikely]] {
return false;
}
}
return true;
}
private:
[[maybe_unused]] GCCriticalSection critical_section_;
[[maybe_unused]] const char *old_no_suspend_reason_;
};
} // namespace lsplant::art::gc

View File

@ -1,56 +0,0 @@
#pragma once
#include "art/runtime/thread.hpp"
#include "collector_type.hpp"
#include "common.hpp"
#include "gc_cause.hpp"
namespace lsplant::art::gc {
class GCCriticalSection {
private:
[[maybe_unused]] void *self_;
[[maybe_unused]] const char *section_name_;
};
class ScopedGCCriticalSection {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedGCCriticalSection *thiz, Thread *self,
GcCause cause, CollectorType collector_type) {
if (thiz && constructorSym) [[likely]]
return constructorSym(thiz, self, cause, collector_type);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedGCCriticalSection *thiz) {
if (thiz && destructorSym) [[likely]]
destructorSym(thiz);
}
public:
ScopedGCCriticalSection(Thread *self, GcCause cause, CollectorType collector_type) {
constructor(this, self, cause, collector_type);
}
~ScopedGCCriticalSection() { destructor(this); }
static bool Init(const HookHandler &handler) {
// for Android M, it's safe to not found since we have suspendVM & resumeVM
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_"
"7GcCauseENS0_13CollectorTypeE")) [[unlikely]] {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev"))
[[unlikely]] {
return false;
}
}
return true;
}
private:
[[maybe_unused]] GCCriticalSection critical_section_;
[[maybe_unused]] const char *old_no_suspend_reason_;
};
} // namespace lsplant::art::gc

View File

@ -0,0 +1,117 @@
module;
#include <cstdint>
#include <type_traits>
export module handle;
import art_method;
namespace lsplant::art {
export {
template <typename MirrorType>
class ObjPtr {
public:
inline MirrorType *operator->() const { return Ptr(); }
inline MirrorType *Ptr() const { return reference_; }
inline operator MirrorType *() const { return Ptr(); }
private:
MirrorType *reference_;
};
template <bool kPoisonReferences, class MirrorType>
class alignas(4) [[gnu::packed]] ObjectReference {
static MirrorType *Decompress(uint32_t ref) {
uintptr_t as_bits = kPoisonReferences ? -ref : ref;
return reinterpret_cast<MirrorType *>(as_bits);
}
uint32_t reference_;
public:
MirrorType *AsMirrorPtr() const { return Decompress(reference_); }
};
template <class MirrorType>
class alignas(4) [[gnu::packed]] CompressedReference
: public ObjectReference<false, MirrorType> {};
template <class MirrorType>
class alignas(4) [[gnu::packed]] StackReference : public CompressedReference<MirrorType> {};
template <typename To, typename From> // use like this: down_cast<T*>(foo);
inline To down_cast(From * f) { // so we only accept pointers
static_assert(std::is_base_of_v<From, std::remove_pointer_t<To>>,
"down_cast unsafe as To is not a subtype of From");
return static_cast<To>(f);
}
class ValueObject {};
template <class ReflectiveType>
class ReflectiveReference {
public:
static_assert(std::is_same_v<ReflectiveType, ArtMethod>, "Unknown type!");
ReflectiveType *Ptr() { return val_; }
void Assign(ReflectiveType *r) { val_ = r; }
private:
ReflectiveType *val_;
};
template <typename T>
class ReflectiveHandle : public ValueObject {
public:
static_assert(std::is_same_v<T, ArtMethod>, "Expected ArtField or ArtMethod");
T *Get() { return reference_->Ptr(); }
void Set(T *val) { reference_->Assign(val); }
protected:
ReflectiveReference<T> *reference_;
};
template <typename T>
class Handle : public ValueObject {
public:
Handle(const Handle<T> &handle) : reference_(handle.reference_) {}
Handle<T> &operator=(const Handle<T> &handle) {
reference_ = handle.reference_;
return *this;
}
// static_assert(std::is_same_v<T, mirror::Class>, "Expected mirror::Class");
auto operator->() { return Get(); }
T *Get() { return down_cast<T *>(reference_->AsMirrorPtr()); }
protected:
StackReference<T> *reference_;
};
// static_assert(!std::is_trivially_copyable_v<Handle<mirror::Class>>);
// https://cs.android.com/android/_/android/platform/art/+/38cea84b362a10859580e788e984324f36272817
template <typename T>
class TrivialHandle : public ValueObject {
public:
// static_assert(std::is_same_v<T, mirror::Class>, "Expected mirror::Class");
auto operator->() { return Get(); }
T *Get() { return down_cast<T *>(reference_->AsMirrorPtr()); }
protected:
StackReference<T> *reference_;
};
}
} // namespace lsplant::art

View File

@ -1,50 +0,0 @@
#pragma once
#include <type_traits>
#include "object_reference.hpp"
#include "value_object.hpp"
namespace lsplant::art {
namespace mirror {
class Class;
};
template <typename T>
class Handle : public ValueObject {
public:
Handle(const Handle<T>& handle) : reference_(handle.reference_) {}
Handle<T>& operator=(const Handle<T>& handle) {
reference_ = handle.reference_;
return *this;
}
static_assert(std::is_same_v<T, mirror::Class>, "Expected mirror::Class");
auto operator->() { return Get(); }
T* Get() { return down_cast<T*>(reference_->AsMirrorPtr()); }
protected:
StackReference<T>* reference_;
};
static_assert(!std::is_trivially_copyable_v<Handle<mirror::Class>>);
// https://cs.android.com/android/_/android/platform/art/+/38cea84b362a10859580e788e984324f36272817
template <typename T>
class TrivialHandle : public ValueObject {
public:
static_assert(std::is_same_v<T, mirror::Class>, "Expected mirror::Class");
auto operator->() { return Get(); }
T* Get() { return down_cast<T*>(reference_->AsMirrorPtr()); }
protected:
StackReference<T>* reference_;
};
static_assert(std::is_trivially_copyable_v<TrivialHandle<mirror::Class>>);
} // namespace lsplant::art

View File

@ -1,11 +1,16 @@
#pragma once
module;
#include "art_method.hpp"
#include "common.hpp"
#include "logging.hpp"
#include "utils/hook_helper.hpp"
export module instrumentation;
import art_method;
import common;
namespace lsplant::art {
class Instrumentation {
export class Instrumentation {
inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) {
if (auto backup = IsHooked(art_method); backup && art_method->GetEntryPoint() != quick_code)
[[unlikely]] {

View File

@ -1,9 +1,16 @@
#pragma once
module;
#include "common.hpp"
#include "logging.hpp"
#include "utils/hook_helper.hpp"
export module jit_code_cache;
import art_method;
import common;
import thread;
namespace lsplant::art::jit {
class JitCodeCache {
export class JitCodeCache {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, JitCodeCache *thiz,
ArtMethod *old_method, ArtMethod *new_method) {
if (MoveObsoleteMethodSym) [[likely]] {

View File

@ -1,12 +1,17 @@
#pragma once
module;
#include "art/runtime/art_method.hpp"
#include "art/runtime/reflective_handle.hpp"
#include "common.hpp"
#include "logging.hpp"
#include "utils/hook_helper.hpp"
export module jni_id_manager;
import art_method;
import common;
import handle;
namespace lsplant::art::jni {
class JniIdManager {
export class JniIdManager {
private:
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art3jni12JniIdManager15EncodeGenericIdINS_9ArtMethodEEEmNS_16ReflectiveHandleIT_EE",

View File

@ -1,18 +0,0 @@
#pragma once
namespace lsplant::art {
template <typename MirrorType>
class ObjPtr {
public:
inline MirrorType *operator->() const { return Ptr(); }
inline MirrorType *Ptr() const { return reference_; }
inline operator MirrorType *() const { return Ptr(); }
private:
MirrorType *reference_;
};
} // namespace lsplant::art

View File

@ -1,32 +0,0 @@
#pragma once
#include <cstdint>
namespace lsplant::art {
template <bool kPoisonReferences, class MirrorType>
class alignas(4) [[gnu::packed]] ObjectReference {
static MirrorType* Decompress(uint32_t ref) {
uintptr_t as_bits = kPoisonReferences ? -ref : ref;
return reinterpret_cast<MirrorType*>(as_bits);
}
uint32_t reference_;
public:
MirrorType* AsMirrorPtr() const { return Decompress(reference_); }
};
template <class MirrorType>
class alignas(4) [[gnu::packed]] CompressedReference : public ObjectReference<false, MirrorType> {};
template <class MirrorType>
class alignas(4) [[gnu::packed]] StackReference : public CompressedReference<MirrorType> {};
template <typename To, typename From> // use like this: down_cast<T*>(foo);
inline To down_cast(From* f) { // so we only accept pointers
static_assert(std::is_base_of_v<From, std::remove_pointer_t<To>>,
"down_cast unsafe as To is not a subtype of From");
return static_cast<To>(f);
}
} // namespace lsplant::art

View File

@ -1,25 +0,0 @@
#pragma once
#include <type_traits>
#include "reflective_reference.hpp"
#include "value_object.hpp"
namespace lsplant::art {
class ArtMethod;
template <typename T>
class ReflectiveHandle : public ValueObject {
public:
static_assert(std::is_same_v<T, ArtMethod>, "Expected ArtField or ArtMethod");
T *Get() { return reference_->Ptr(); }
void Set(T *val) { reference_->Assign(val); }
protected:
ReflectiveReference<T> *reference_;
};
} // namespace lsplant::art

View File

@ -1,19 +0,0 @@
#pragma once
#include <type_traits>
namespace lsplant::art {
template <class ReflectiveType>
class ReflectiveReference {
public:
static_assert(std::is_same_v<ReflectiveType, ArtMethod>, "Unknown type!");
ReflectiveType *Ptr() { return val_; }
void Assign(ReflectiveType *r) { val_ = r; }
private:
ReflectiveType *val_;
};
} // namespace lsplant::art

View File

@ -1,29 +1,15 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2022 LSPosed Contributors
*/
#pragma once
module;
#include "logging.hpp"
#include "utils/hook_helper.hpp"
export module runtime;
import common;
namespace lsplant::art {
class Runtime {
export class Runtime {
public:
enum class RuntimeDebugState {
// This doesn't support any debug features / method tracing. This is the expected state

View File

@ -1,10 +1,11 @@
#pragma once
module;
#include "common.hpp"
#include "utils/hook_helper.hpp"
export module thread;
namespace lsplant::art {
class Thread {
export class Thread {
CREATE_FUNC_SYMBOL_ENTRY(Thread *, CurrentFromGdb) {
if (CurrentFromGdbSym) [[likely]]
return CurrentFromGdbSym();

View File

@ -1,10 +1,12 @@
#pragma once
module;
#include "common.hpp"
#include "include/utils/hook_helper.hpp"
export module thread_list;
namespace lsplant::art::thread_list {
class ScopedSuspendAll {
export class ScopedSuspendAll {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedSuspendAll *thiz, const char *cause,
bool long_suspend) {
if (thiz && constructorSym) [[likely]] {

View File

@ -1,5 +0,0 @@
#pragma once
namespace lsplant::art {
class ValueObject {};
} // namespace lsplant::art

View File

@ -0,0 +1,178 @@
module;
#include <jni.h>
#include <parallel_hashmap/phmap.h>
#include <sys/system_properties.h>
#include <list>
#include <shared_mutex>
#include <string_view>
#include "logging.hpp"
#include "utils/jni_helper.hpp"
export module common;
namespace lsplant {
namespace art {
class ArtMethod;
namespace mirror {
class Class;
}
namespace dex {
export class ClassDef {};
} // namespace dex
} // namespace art
export enum class Arch {
kArm,
kArm64,
kX86,
kX86_64,
kRiscv64,
};
consteval inline Arch GetArch() {
#if defined(__i386__)
return Arch::kX86;
#elif defined(__x86_64__)
return Arch::kX86_64;
#elif defined(__arm__)
return Arch::kArm;
#elif defined(__aarch64__)
return Arch::kArm64;
#elif defined(__riscv)
return Arch::kRiscv64;
#else
#error "unsupported architecture"
#endif
}
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
export {
constexpr auto kArch = GetArch();
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
int base = atoi(prop_value.data());
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
return base + atoi(prop_value.data());
}();
return kApiLevel;
}
inline auto IsJavaDebuggable(JNIEnv * env) {
static auto kDebuggable = [&env]() {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_P__) {
return false;
}
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
if (!runtime_class) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method = JNI_GetStaticMethodID(env, runtime_class, "getRuntime",
"()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method =
JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
}
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
if (!runtime) {
LOGE("Failed to get VMRuntime");
return false;
}
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
return is_debuggable;
}();
return kDebuggable;
}
constexpr auto kPointerSize = sizeof(void *);
SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
hooked_classes_;
SharedHashSet<art::ArtMethod *> deoptimized_methods_set_;
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
deoptimized_classes_;
std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
std::shared_mutex jit_movements_lock_;
inline art::ArtMethod *IsHooked(art::ArtMethod * art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (!including_backup || it.second.first) backup = it.second.second;
});
return backup;
}
inline art::ArtMethod *IsBackup(art::ArtMethod * art_method) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second;
});
return nullptr;
}
inline bool IsDeoptimized(art::ArtMethod * art_method) {
return deoptimized_methods_set_.contains(art_method);
}
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
std::unique_lock lk(jit_movements_lock_);
return std::move(jit_movements_);
}
inline void RecordHooked(art::ArtMethod * target, const art::dex::ClassDef *class_def,
jobject reflected_backup, art::ArtMethod *backup) {
hooked_classes_.lazy_emplace_l(
class_def, [&target](auto &it) { it.second.emplace(target); },
[&class_def, &target](const auto &ctor) {
ctor(class_def, phmap::flat_hash_set<art::ArtMethod *>{target});
});
hooked_methods_.insert({std::make_pair(target, std::make_pair(reflected_backup, backup)),
std::make_pair(backup, std::make_pair(nullptr, target))});
}
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
{ deoptimized_classes_[class_def].emplace(art_method); }
deoptimized_methods_set_.insert(art_method);
}
inline void RecordJitMovement(art::ArtMethod * target, art::ArtMethod * backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
}
} // namespace lsplant

View File

@ -1,176 +0,0 @@
#pragma once
#include <parallel_hashmap/phmap.h>
#include <sys/system_properties.h>
#include <list>
#include <shared_mutex>
#include <string_view>
#include "logging.hpp"
#include "lsplant.hpp"
#include "utils/hook_helper.hpp"
namespace lsplant {
enum class Arch {
kArm,
kArm64,
kX86,
kX86_64,
kRiscv64,
};
consteval inline Arch GetArch() {
#if defined(__i386__)
return Arch::kX86;
#elif defined(__x86_64__)
return Arch::kX86_64;
#elif defined(__arm__)
return Arch::kArm;
#elif defined(__aarch64__)
return Arch::kArm64;
#elif defined(__riscv)
return Arch::kRiscv64;
#else
#error "unsupported architecture"
#endif
}
inline static constexpr auto kArch = GetArch();
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
int base = atoi(prop_value.data());
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
return base + atoi(prop_value.data());
}();
return kApiLevel;
}
inline auto IsJavaDebuggable(JNIEnv *env) {
static auto kDebuggable = [&env]() {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_P__) {
return false;
}
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
if (!runtime_class) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method =
JNI_GetStaticMethodID(env, runtime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method = JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
}
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
if (!runtime) {
LOGE("Failed to get VMRuntime");
return false;
}
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
return is_debuggable;
}();
return kDebuggable;
}
inline static constexpr auto kPointerSize = sizeof(void *);
namespace art {
class ArtMethod;
namespace dex {
class ClassDef;
}
namespace mirror {
class Class;
}
} // namespace art
namespace {
// target, backup
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
inline SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
inline SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
hooked_classes_;
inline SharedHashSet<art::ArtMethod *> deoptimized_methods_set_;
inline SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
deoptimized_classes_;
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
inline std::shared_mutex jit_movements_lock_;
} // namespace
inline art::ArtMethod *IsHooked(art::ArtMethod *art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (!including_backup || it.second.first) backup = it.second.second;
});
return backup;
}
inline art::ArtMethod *IsBackup(art::ArtMethod *art_method) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second;
});
return nullptr;
}
inline bool IsDeoptimized(art::ArtMethod *art_method) {
return deoptimized_methods_set_.contains(art_method);
}
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
std::unique_lock lk(jit_movements_lock_);
return std::move(jit_movements_);
}
inline void RecordHooked(art::ArtMethod *target, const art::dex::ClassDef *class_def,
jobject reflected_backup, art::ArtMethod *backup) {
hooked_classes_.lazy_emplace_l(
class_def, [&target](auto &it) { it.second.emplace(target); },
[&class_def, &target](const auto &ctor) {
ctor(class_def, phmap::flat_hash_set<art::ArtMethod *>{target});
});
hooked_methods_.insert({std::make_pair(target, std::make_pair(reflected_backup, backup)),
std::make_pair(backup, std::make_pair(nullptr, target))});
}
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
{ deoptimized_classes_[class_def].emplace(art_method); }
deoptimized_methods_set_.insert(art_method);
}
inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
} // namespace lsplant

View File

@ -15,7 +15,9 @@
TypeName(const TypeName &) = delete; \
void operator=(const TypeName &) = delete
#ifndef JNI_HELPER_NO_NS // workaroud for https://github.com/llvm/llvm-project/issues/88400
namespace lsplant {
#endif
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
@ -1338,7 +1340,9 @@ template <ScopeOrObject Object>
env, o, JNI_GetFieldID(env, JNI_GetObjectClass(env, o), field_name, field_class));
}
#ifndef JNI_HELPER_NO_NS
} // namespace lsplant
#endif
#undef DISALLOW_COPY_AND_ASSIGN

View File

@ -1,33 +1,40 @@
#include "lsplant.hpp"
#include <android/api-level.h>
#include <bits/sysconf.h>
#include <dex_builder.h>
#include <sys/mman.h>
#include <sys/system_properties.h>
#include <array>
#include <atomic>
#include <bits/sysconf.h>
#include "art/mirror/class.hpp"
#include "art/runtime/art_method.hpp"
#include "art/runtime/class_linker.hpp"
#include "art/runtime/dex_file.hpp"
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/runtime/instrumentation.hpp"
#include "art/runtime/jit/jit_code_cache.hpp"
#include "art/runtime/jni/jni_id_manager.h"
#include "art/runtime/runtime.hpp"
#include "art/runtime/thread.hpp"
#include "art/runtime/thread_list.hpp"
#include "common.hpp"
#include "dex_builder.h"
#include "logging.hpp"
#include "lsplant.hpp"
#define JNI_HELPER_NO_NS
#include "utils/hook_helper.hpp"
#include "utils/jni_helper.hpp"
import common;
import art_method;
import clazz;
import thread;
import instrumentation;
import runtime;
import thread_list;
import class_linker;
import scope_gc_critical_section;
import jit_code_cache;
import jni_id_manager;
import dex_file;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "ConstantConditionsOC"
#pragma ide diagnostic ignored "Simplify"
#pragma ide diagnostic ignored "UnreachableCode"
namespace lsplant {
using art::ArtMethod;