Refactor `core` to use LSPlant as the ART hook framework (#1756)

This commit is contained in:
LoveSy 2022-03-15 07:51:30 +08:00 committed by GitHub
parent 2a258e3323
commit 0bf0948e9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 1177 additions and 3617 deletions

View File

@ -46,6 +46,7 @@ jobs:
with: with:
path: | path: |
~/.gradle/caches/build-cache-* ~/.gradle/caches/build-cache-*
~/.gradle/buildOutputCleanup/cache.properties
key: gradle-builds-core-${{ github.sha }} key: gradle-builds-core-${{ github.sha }}
restore-keys: | restore-keys: |
gradle-builds gradle-builds
@ -66,8 +67,6 @@ jobs:
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
ccache -zp ccache -zp
- name: Build with Gradle - name: Build with Gradle
env:
NDK_CCACHE: ccache
run: | run: |
[ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true [ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true
find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true
@ -117,6 +116,13 @@ jobs:
path: | path: |
core/build/outputs/mapping core/build/outputs/mapping
app/build/outputs/mapping app/build/outputs/mapping
- name: Upload symbols
uses: actions/upload-artifact@v2
with:
name: symbols
path: |
core/build/symbols
daemon/build/symbols
- name: Post to channel - name: Post to channel
if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }} if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }}
env: env:

12
.gitmodules vendored
View File

@ -1,6 +1,12 @@
[submodule "service"] [submodule "service"]
path = service path = service
url = https://github.com/libxposed/XposedService.git url = https://github.com/libxposed/XposedService.git
[submodule "core/src/main/cpp/external/DexBuilder"] [submodule "external/cxx"]
path = core/src/main/cpp/external/DexBuilder path = external/cxx
url = https://github.com/LSPosed/DexBuilder.git url = https://github.com/LSPosed/libcxx.git
[submodule "external/lsplant"]
path = external/lsplant
url = https://github.com/LSPosed/LSPlant.git
[submodule "external/dobby"]
path = external/dobby
url = https://github.com/LSPosed/Dobby.git

3
core/.gitignore vendored
View File

@ -1,3 +1,4 @@
/build /build
/release /release
/src/main/cpp/main/api/config.cpp /src/main/cpp/api/config.cpp
/.cxx

View File

@ -73,11 +73,32 @@ android {
multiDexEnabled = false multiDexEnabled = false
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
arguments += "INJECTED_AID=$injectedPackageUid" arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
arguments += "VERSION_CODE=$verCode" abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
arguments += "VERSION_NAME=$verName" val flags = arrayOf(
arguments += "-j${Runtime.getRuntime().availableProcessors()}" "-Wall",
"-Qunused-arguments",
"-Wno-gnu-string-literal-operator-template",
"-fno-rtti",
"-fvisibility=hidden",
"-fvisibility-inlines-hidden",
"-fno-exceptions",
"-fno-stack-protector",
"-fomit-frame-pointer",
"-Wno-builtin-macro-redefined",
"-Wno-unused-value",
"-D__FILE__=__FILE_NAME__",
"-DINJECTED_AID=$injectedPackageUid",
)
cppFlags("-std=c++20", *flags)
cFlags("-std=c18", *flags)
arguments(
"-DANDROID_STL=none",
"-DVERSION_CODE=$verCode",
"-DVERSION_NAME=$verName",
)
targets("lspd")
} }
} }
@ -97,14 +118,58 @@ android {
} }
buildTypes { buildTypes {
debug {
externalNativeBuild {
cmake {
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
"-DCMAKE_C_FLAGS_DEBUG=-Og",
)
)
}
}
}
release { release {
isMinifyEnabled = true isMinifyEnabled = true
proguardFiles("proguard-rules.pro") proguardFiles("proguard-rules.pro")
externalNativeBuild {
cmake {
val flags = arrayOf(
"-Wl,--exclude-libs,ALL",
"-ffunction-sections",
"-fdata-sections",
"-Wl,--gc-sections",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
"-flto=thin",
"-Wl,--thinlto-cache-policy,cache_size_bytes=300m",
"-Wl,--thinlto-cache-dir=${buildDir.absolutePath}/.lto-cache",
)
cppFlags.addAll(flags)
cFlags.addAll(flags)
val configFlags = arrayOf(
"-Oz",
"-DNDEBUG"
).joinToString(" ")
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
"-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols",
)
)
}
}
} }
} }
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
path("src/main/cpp/Android.mk") path("src/main/jni/CMakeLists.txt")
} }
} }
@ -113,22 +178,12 @@ android {
sourceCompatibility(androidSourceCompatibility) sourceCompatibility(androidSourceCompatibility)
} }
buildTypes {
all {
externalNativeBuild {
ndkBuild {
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
}
}
}
}
productFlavors { productFlavors {
all { all {
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
arguments += "MODULE_NAME=${name.toLowerCase()}_$moduleBaseId" arguments += "-DMODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
arguments += "API=${name.toLowerCase()}" arguments += "-DAPI=${name.toLowerCase()}"
} }
} }
buildConfigField("String", "API", """"$name"""") buildConfigField("String", "API", """"$name"""")
@ -137,8 +192,8 @@ android {
create("Riru") { create("Riru") {
dimension = "api" dimension = "api"
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
arguments += "API_VERSION=$moduleMaxRiruApiVersion" arguments += "-DAPI_VERSION=$moduleMaxRiruApiVersion"
} }
} }
} }
@ -146,8 +201,8 @@ android {
create("Zygisk") { create("Zygisk") {
dimension = "api" dimension = "api"
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
arguments += "API_VERSION=1" arguments += "-DAPI_VERSION=1"
} }
} }
} }
@ -157,10 +212,6 @@ android {
dependencies { dependencies {
// keep this dep since it affects ccache
implementation("dev.rikka.ndk:riru:26.0.0")
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
implementation("io.github.vvb2060.ndk:dobby:1.2")
implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.apache.commons:commons-lang3:3.12.0")
implementation("de.upb.cs.swt:axml:2.1.2") implementation("de.upb.cs.swt:axml:2.1.2")
compileOnly("androidx.annotation:annotation:1.3.0") compileOnly("androidx.annotation:annotation:1.3.0")
@ -247,8 +298,12 @@ fun afterEval() = android.applicationVariants.forEach { variant ->
rename(".*\\.apk", "daemon.apk") rename(".*\\.apk", "daemon.apk")
} }
into("lib") { into("lib") {
from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib") from("${buildDir}/intermediates/cmake/$variantCapped/obj") {
from("${project(":daemon").buildDir}/intermediates/ndkBuild/$buildTypeLowered/obj/local") include("**/liblspd.so")
}
from("${project(":daemon").buildDir}/intermediates/cmake/$buildTypeLowered/obj") {
include("**/libdaemon.so")
}
} }
val dexOutPath = if (buildTypeLowered == "release") val dexOutPath = if (buildTypeLowered == "release")
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else "$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else

View File

@ -7,9 +7,6 @@
-keepclasseswithmembers,includedescriptorclasses class * { -keepclasseswithmembers,includedescriptorclasses class * {
native <methods>; native <methods>;
} }
-keepclasseswithmembers class org.lsposed.lspd.nativebridge.ClassLinker {
public static void onPostFixupStaticTrampolines(java.lang.Class);
}
-keepclasseswithmembers class org.lsposed.lspd.service.BridgeService { -keepclasseswithmembers class org.lsposed.lspd.service.BridgeService {
public static boolean *(android.os.IBinder, int, long, long, int); public static boolean *(android.os.IBinder, int, long, long, int);
} }

View File

@ -1,60 +0,0 @@
---
Checks: >
-*,
bugprone-*,
google-*,
misc-*,
modernize-*,
performance-*,
portability-*,
readability-*,
clang-analyzer-*,
llvm-include-order,
-modernize-use-trailing-return-type,
-readability-implicit-bool-conversion,
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.ClassMemberCase
value: lower_case
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.EnumConstantCase
value: CamelCase
- key: readability-identifier-naming.EnumConstantPrefix
value: k
- key: readability-identifier-naming.FunctionCase
value: CamelCase
- key: readability-identifier-naming.GlobalConstantCase
value: CamelCase
- key: readability-identifier-naming.GlobalConstantPrefix
value: k
- key: readability-identifier-naming.StaticConstantCase
value: CamelCase
- key: readability-identifier-naming.StaticConstantPrefix
value: k
- key: readability-identifier-naming.StaticVariableCase
value: CamelCase
- key: readability-identifier-naming.StaticVariablePrefix
value: k
- key: readability-identifier-naming.MacroDefinitionCase
value: UPPER_CASE
- key: readability-identifier-naming.MemberCase
value: lower_case
- key: readability-identifier-naming.MemberSuffix
value: _
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ParameterCase
value: lower_case
- key: readability-identifier-naming.TypeAliasCase
value: CamelCase
- key: readability-identifier-naming.TypedefCase
value: CamelCase
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.IgnoreMainLikeFunctions
value: 1
- key: readability-braces-around-statements.ShortStatementLines
value: 1

View File

@ -1,7 +0,0 @@
include src/main/cpp/external/DexBuilder/Android.mk
include src/main/cpp/external/yahfa/Android.mk
include src/main/cpp/main/Android.mk
$(call import-module,prefab/cxx)
$(call import-module,prefab/riru)
$(call import-module,prefab/dobby)

View File

@ -1,15 +0,0 @@
APP_CFLAGS := -Wall -Wextra
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
APP_CPPFLAGS := -std=c++20
APP_CONLYFLAGS := -std=c18
APP_LDFLAGS := -Wl,--exclude-libs,ALL
APP_STL := none
ifneq ($(NDK_DEBUG),1)
APP_CFLAGS += -Oz -flto=thin
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 += -flto=thin -Wl,--thinlto-cache-policy,cache_size_bytes=300m -Wl,--thinlto-cache-dir=build/.lto-cache -Wl,--gc-sections -Wl,--strip-all
endif

@ -1 +0,0 @@
Subproject commit 6273ec71c0c4ded162f1569c94eac098c5f87e02

View File

@ -1,10 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := yahfa
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SRC_FILES := src/HookMain.cpp src/trampoline.cpp
LOCAL_EXPORT_LDLIBS := -llog
LOCAL_STATIC_LIBRARIES := libcxx
include $(BUILD_STATIC_LIBRARY)

View File

@ -1,34 +0,0 @@
#ifndef HOOK_MAIN_H
#define HOOK_MAIN_H
#include <jni.h>
namespace yahfa {
constexpr uint32_t kAccPublic = 0x0001; // class, field, method, ic
constexpr uint32_t kAccPrivate = 0x0002; // field, method, ic
constexpr uint32_t kAccProtected = 0x0004; // field, method, ic
constexpr uint32_t kAccStatic = 0x0008; // field, method, ic
constexpr uint32_t kAccNative = 0x0100; // method
void init(JNIEnv *env, jclass clazz, jint sdkVersion);
jobject findMethodNative(JNIEnv *env, jclass clazz,
jclass targetClass, jstring methodName,
jstring methodSig);
jboolean backupAndHookNative(JNIEnv *env, jclass clazz,
jobject target, jobject hook,
jobject backup);
void *getArtMethod(JNIEnv *env, jobject jmethod);
uint32_t getAccessFlags(void* art_method);
void setAccessFlags(void* art_method, uint32_t access_flags);
void* getEntryPoint(void* art_method);
void setEntryPoint(void* art_method, void* entry_point);
}
#endif // HOOK_MAIN_H

View File

@ -1,14 +0,0 @@
//
// Created by liuruikai756 on 05/07/2017.
//
#ifndef YAHFA_TAMPOLINE_H
#define YAHFA_TAMPOLINE_H
namespace yahfa {
void setupTrampoline();
void *genTrampoline(void *hookMethod);
}
#endif //YAHFA_TAMPOLINE_H

View File

@ -1,213 +0,0 @@
#include "jni.h"
#include <cstring>
#include <sys/mman.h>
#include <cstdlib>
#include "common.h"
#include "trampoline.h"
#include "HookMain.h"
namespace yahfa {
size_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
int SDKVersion;
namespace {
constexpr size_t OFFSET_access_flags_in_ArtMethod = 4;
constexpr uint32_t kAccCompileDontBother = 0x02000000;
constexpr uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000;
size_t ArtMethodSize;
uint32_t kAccPreCompiled = 0x00200000;
jfieldID fieldArtMethod = nullptr;
constexpr inline uint32_t read32(void *addr) {
return *((uint32_t *) addr);
}
constexpr inline void write32(void *addr, uint32_t value) {
*((uint32_t *) addr) = value;
}
constexpr inline void *readAddr(void *addr) {
return *((void **) addr);
}
constexpr inline void writeAddr(void *addr, void *value) {
*((void **) addr) = value;
}
void setNonCompilable(void *method) {
uint32_t access_flags = getAccessFlags(method);
LOGI("setNonCompilable: access flags is 0x%x", access_flags);
access_flags |= kAccCompileDontBother;
if (SDKVersion >= __ANDROID_API_R__)
access_flags &= ~kAccPreCompiled;
setAccessFlags(method, access_flags);
}
void setPrivate(void *method) {
uint32_t access_flags = getAccessFlags(method);
if (!(access_flags & kAccStatic)) {
LOGI("setPrivate: access flags is 0x%x", access_flags);
access_flags |= kAccPrivate;
access_flags &= ~kAccProtected;
access_flags &= ~kAccPublic;
setAccessFlags(method, access_flags);
}
}
int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMethod) {
LOGI("target method is at %p, hook method is at %p, backup method is at %p",
targetMethod, hookMethod, backupMethod);
// set kAccCompileDontBother for a method we do not want the compiler to compile
// so that we don't need to worry about hotness_count_
setNonCompilable(targetMethod);
setNonCompilable(hookMethod);
if (backupMethod) {// do method backup
// have to copy the whole target ArtMethod here
// if the target method calls other methods which are to be resolved
// then ToDexPC would be invoked for the caller(origin method)
// in which case ToDexPC would use the entrypoint as a base for mapping pc to dex offset
// so any changes to the target method's entrypoint would result in a wrong dex offset
// and artQuickResolutionTrampoline would fail for methods called by the origin method
memcpy(backupMethod, targetMethod, ArtMethodSize);
setPrivate(backupMethod);
}
// replace entry point
void *newEntrypoint = genTrampoline(hookMethod);
LOGI("origin ep is %p, new ep is %p",
getEntryPoint(targetMethod),
newEntrypoint
);
if (newEntrypoint) {
setEntryPoint(targetMethod, newEntrypoint);
} else {
LOGE("failed to allocate space for trampoline of target method");
return 1;
}
if (SDKVersion >= __ANDROID_API_Q__) {
uint32_t access_flags = getAccessFlags(targetMethod);
// On API 29 whether to use the fast path or not is cached in the ART method structure
access_flags &= ~kAccFastInterpreterToInterpreterInvoke;
setAccessFlags(targetMethod, access_flags);
}
LOGI("hook and backup done");
return 0;
}
}
void init(JNIEnv *env, [[maybe_unused]] jclass clazz, jint sdkVersion) {
SDKVersion = sdkVersion;
jclass classExecutable = env->FindClass("java/lang/reflect/Executable");
fieldArtMethod = env->GetFieldID(classExecutable, "artMethod", "J");
env->DeleteLocalRef(classExecutable);
LOGI("init to SDK %d", sdkVersion);
switch (sdkVersion) {
default:
LOGE("not compatible with SDK %d", sdkVersion);
case __ANDROID_API_FUTURE__:
// fallthrough
case __ANDROID_API_T__:
case 32: // stupid Google forgot to add 12L
case __ANDROID_API_S__:
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 3 + 2 * 2) + pointer_size;
ArtMethodSize = roundUpToPtrSize(4 * 3 + 2 * 2) + pointer_size * 2;
kAccPreCompiled = 0x00800000;
break;
case __ANDROID_API_R__:
case __ANDROID_API_Q__:
case __ANDROID_API_P__:
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
break;
case __ANDROID_API_O_MR1__:
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod =
roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 2;
ArtMethodSize = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size * 3;
break;
}
setupTrampoline();
}
void *getArtMethod(JNIEnv *env, jobject jmethod) {
if (jmethod == nullptr || fieldArtMethod == nullptr) {
return nullptr;
} else {
return (void *) env->GetLongField(jmethod, fieldArtMethod);
}
}
uint32_t getAccessFlags(void *art_method) {
return read32((char *) art_method + OFFSET_access_flags_in_ArtMethod);
}
void setAccessFlags(void *art_method, uint32_t access_flags) {
write32((char *) art_method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
void *getEntryPoint(void *art_method) {
return readAddr(
(char *) art_method + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
}
void setEntryPoint(void *art_method, void *entry_point) {
writeAddr((char *) art_method + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
entry_point);
}
jobject findMethodNative(JNIEnv *env, [[maybe_unused]] jclass clazz,
jclass targetClass,
jstring methodName,
jstring methodSig) {
const char *c_methodName = env->GetStringUTFChars(methodName, nullptr);
const char *c_methodSig = env->GetStringUTFChars(methodSig, nullptr);
jobject ret = nullptr;
//Try both GetMethodID and GetStaticMethodID -- Whatever works :)
jmethodID method = env->GetMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_FALSE);
} else {
env->ExceptionClear();
method = env->GetStaticMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_TRUE);
} else {
env->ExceptionClear();
}
}
env->ReleaseStringUTFChars(methodName, c_methodName);
env->ReleaseStringUTFChars(methodSig, c_methodSig);
return ret;
}
jboolean backupAndHookNative(JNIEnv *env, [[maybe_unused]] jclass clazz,
jobject target, jobject hook,
jobject backup) {
if (!doBackupAndHook(getArtMethod(env, target),
getArtMethod(env, hook),
getArtMethod(env, backup)
)) {
env->NewGlobalRef(hook); // keep a global ref so that the hook method would not be GCed
if (backup) env->NewGlobalRef(backup);
return JNI_TRUE;
}
return JNI_FALSE;
}
}

View File

@ -1,32 +0,0 @@
//
// Created by liuruikai756 on 05/07/2017.
//
#include <android/log.h>
#ifndef YAHFA_COMMON_H
#define YAHFA_COMMON_H
//#define DEBUG
//#define LOG_DISABLED
#ifdef LOG_DISABLED
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#else
#define LOG_TAG "LSPosed"
#ifndef NDEBUG
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#else
#define LOGI(...)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#endif // DEBUG
#endif // LOG_DISABLED
#define pointer_size sizeof(void*)
#define roundUpToPtrSize(v) (v + pointer_size - 1 - ((v + pointer_size - 1) & (pointer_size - 1)))
#endif //YAHFA_COMMON_H

View File

@ -1,147 +0,0 @@
//
// Created by liuruikai756 on 05/07/2017.
//
#include <sys/mman.h>
#include <cstring>
#include <cerrno>
#include <fcntl.h>
#include <cstdio>
#include <malloc.h>
#include <cstdlib>
#include <unistd.h>
#include <sys/syscall.h>
#include <atomic>
#include <bit>
#include <climits>
#include "common.h"
#include "trampoline.h"
static_assert(std::endian::native == std::endian::little, "Unsupported architecture");
namespace yahfa {
extern size_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
union Trampoline {
uintptr_t addr;
unsigned count: 12;
};
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architecture");
// trampoline:
// 1. set eax/r0/x0 to the hook ArtMethod addr
// 2. jump into its entry point
#if defined(__i386__)
// b8 78 56 34 12 ; mov eax, 0x12345678 (addr of the hook method)
// ff 70 20 ; push DWORD PTR [eax + 0x20]
// c3 ; ret
unsigned char trampoline[] = "\xb8\x78\x56\x34\x12\xff\x70\x20\xc3";
#elif defined(__x86_64__)
// 48 bf 78 56 34 12 78 56 34 12 ; movabs rdi, 0x1234567812345678
// ff 77 20 ; push QWORD PTR [rdi + 0x20]
// c3 ; ret
unsigned char trampoline[] = "\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x20\xc3";
#elif defined(__arm__)
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 20 F0 90 E5 ; ldr pc, [r0, 0x20]
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
unsigned char trampoline[] = "\x00\x00\x9f\xe5\x20\xf0\x90\xe5\x78\x56\x34\x12";
#elif defined(__aarch64__)
// 60 00 00 58 ; ldr x0, 12
// 10 00 40 F8 ; ldr x16, [x0, #0x00]
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 78 56 34 12 ; 0x1234567812345678 (addr of the hook method)
unsigned char trampoline[] = "\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12";
#endif
static std::atomic_uintptr_t trampoline_pool{0};
static std::atomic_flag trampoline_lock{false};
static constexpr size_t trampolineSize = roundUpToPtrSize(sizeof(trampoline));
static constexpr size_t pageSize = 4096;
static constexpr size_t trampolineNumPerPage = pageSize / trampolineSize;
static inline void FlushCache(void *addr, size_t size) {
__builtin___clear_cache((char *) addr, (char *) ((uintptr_t) addr + size));
}
void *genTrampoline(void *hookMethod) {
unsigned count;
uintptr_t addr;
while (true) {
auto tl = Trampoline{.addr = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = tl.count;
addr = tl.addr & ~uintptr_t(0xfff);
if (addr == 0 || count >= trampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
trampoline_lock.wait(true, std::memory_order_acquire);
continue;
} else {
addr = reinterpret_cast<uintptr_t>(mmap(nullptr, pageSize,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
if (addr == reinterpret_cast<uintptr_t>(MAP_FAILED)) {
LOGE("mmap failed, errno = %s", strerror(errno));
trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
return nullptr;
}
count = 0;
tl.addr = addr;
tl.count = count + 1;
trampoline_pool.store(tl.addr, std::memory_order_release);
trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
}
}
LOGI("trampoline: count = %u, addr = %zx, target = %zx", count, addr,
addr + count * trampolineSize);
addr = addr + count * trampolineSize;
break;
}
unsigned char *targetAddr = reinterpret_cast<unsigned char *>(addr);
memcpy(targetAddr, trampoline,
sizeof(trampoline)); // do not use trampolineSize since it's a rounded size
// replace with the actual ArtMethod addr
#if defined(__i386__)
memcpy(targetAddr + 1, &hookMethod, pointer_size);
#elif defined(__x86_64__)
memcpy((char*)targetAddr + 2, &hookMethod, pointer_size);
#elif defined(__arm__)
memcpy(targetAddr+8, &hookMethod, pointer_size);
#elif defined(__aarch64__)
memcpy(targetAddr + 12, &hookMethod, pointer_size);
#else
#error Unsupported architecture
#endif
FlushCache(targetAddr, sizeof(trampoline));
return targetAddr;
}
void setupTrampoline() {
#if defined(__i386__)
trampoline[7] = (unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__x86_64__)
trampoline[12] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__arm__)
trampoline[4] = (unsigned char)OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
#elif defined(__aarch64__)
trampoline[5] |=
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) << 4;
trampoline[6] |=
((unsigned char) OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod) >> 4;
#else
#error Unsupported architecture
#endif
}
}

View File

@ -1,28 +0,0 @@
LOCAL_PATH := $(call my-dir)
define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef
include $(CLEAR_VARS)
LOCAL_MODULE := lspd
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/src
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/src))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) api/config.cpp
LOCAL_STATIC_LIBRARIES := cxx riru yahfa dobby dex_builder
ifeq ($(API), riru)
LOCAL_SRC_FILES += api/riru_main.cpp
else ifeq ($(API), zygisk)
LOCAL_SRC_FILES += api/zygisk_main.cpp
endif
LOCAL_CFLAGS += -DINJECTED_AID=${INJECTED_AID}
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
$(LOCAL_PATH)/api/config.cpp : FORCE
$(file > $@,namespace lspd {)
$(file >> $@,extern const int versionCode = ${VERSION_CODE};)
$(file >> $@,extern const int apiVersion = ${API_VERSION};)
$(file >> $@,extern const char* const versionName = "${VERSION_NAME}";)
$(file >> $@,extern const char* const moduleName = "${MODULE_NAME}";)
$(file >> $@,})
FORCE: ;

View File

@ -1,50 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
//
// Created by 双草酸酯 on 12/19/20.
//
#ifndef LSPOSED_ART_METHOD_H
#define LSPOSED_ART_METHOD_H
#include <HookMain.h>
namespace art {
namespace art_method {
CREATE_MEM_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, void *thiz, bool with_signature) {
if (thiz == nullptr) [[unlikely]]
return "null";
if (PrettyMethodSym) [[likely]]
return PrettyMethodSym(thiz, with_signature);
else return "null sym";
}
inline std::string PrettyMethod(void *thiz) {
return PrettyMethod(thiz, true);
}
inline void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEb");
}
}
}
#endif //LSPOSED_ART_METHOD_H

View File

@ -1,149 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
#include <jni_helper.h>
#include <base/object.h>
#include <art/runtime/mirror/class.h>
#include "runtime.h"
#include "config.h"
#include "jni_env_ext.h"
#include "context.h"
#include "jni/yahfa.h"
#include "utils.h"
#include "HookMain.h"
namespace art {
class ClassLinker : public lspd::HookedObject {
private:
[[gnu::always_inline]]
static auto MaybeDelayHook(void *clazz_ptr) {
std::vector<std::tuple<void*, void*>> out;
art::mirror::Class mirror_class(clazz_ptr);
auto class_def = mirror_class.GetClassDef();
if (!class_def) return out;
auto set = lspd::isClassHooked(class_def);
if (!set.empty()) [[unlikely]] {
LOGD("Pending hook for %p (%s)", clazz_ptr,
art::mirror::Class(clazz_ptr).GetDescriptor().c_str());
for (auto art_method : set) {
out.emplace_back(art_method, yahfa::getEntryPoint(art_method));
}
}
return out;
}
[[gnu::always_inline]]
static void FixTrampoline(const std::vector<std::tuple<void*, void*>>& methods) {
for (const auto &[art_method, old_trampoline] : methods) {
auto *new_trampoline = yahfa::getEntryPoint(art_method);
auto *backup = lspd::isHooked(art_method);
if (backup && new_trampoline != old_trampoline) {
yahfa::setEntryPoint(backup, new_trampoline);
yahfa::setEntryPoint(art_method, old_trampoline);
}
}
}
CREATE_MEM_HOOK_STUB_ENTRIES(
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolines, (void * thiz, void * clazz_ptr), {
auto b = MaybeDelayHook(clazz_ptr);
backup(thiz, clazz_ptr);
FixTrampoline(b);
});
CREATE_MEM_HOOK_STUB_ENTRIES(
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolinesWithThread,
(void * thiz, void * self, void * clazz_ptr), {
auto b = MaybeDelayHook(clazz_ptr);
backup(thiz, self, clazz_ptr);
FixTrampoline(b);
});
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MakeInitializedClassesVisiblyInitialized, void *thiz,
void *self, bool wait) {
if (MakeInitializedClassesVisiblyInitializedSym) [[likely]]
MakeInitializedClassesVisiblyInitializedSym(thiz, self, wait);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, void *thiz,
void *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]]
SetEntryPointsToInterpreterSym(thiz, art_method);
}
CREATE_HOOK_STUB_ENTRIES(
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv",
bool, ShouldUseInterpreterEntrypoint, (void * art_method,
const void *quick_code), {
if (quick_code != nullptr && lspd::isHooked(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_generic_jni_trampoline, void*) {}
CREATE_HOOK_STUB_ENTRIES("_ZN3art11interpreter29ShouldStayInSwitchInterpreterEPNS_9ArtMethodE",
bool, ShouldStayInSwitchInterpreter ,(void* art_method), {
if (lspd::isHooked(art_method)) [[unlikely]] {
return false;
}
return backup(art_method);
});
public:
// @ApiSensitive(Level.MIDDLE)
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(SetEntryPointsToInterpreter,
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE");
lspd::HookSyms(handle, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter);
lspd::HookSyms(handle, FixupStaticTrampolinesWithThread, FixupStaticTrampolines);
RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge, "art_quick_to_interpreter_bridge");
RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline, "art_quick_generic_jni_trampoline");
LOGD("art_quick_to_interpreter_bridge = %p", art_quick_to_interpreter_bridgeSym);
LOGD("art_quick_generic_jni_trampoline = %p", art_quick_generic_jni_trampolineSym);
}
[[gnu::always_inline]]
static void SetEntryPointsToInterpreter(void *art_method) {
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
if (yahfa::getAccessFlags(art_method) & yahfa::kAccNative) [[unlikely]] {
yahfa::setEntryPoint(art_method,
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
} else {
yahfa::setEntryPoint(art_method,
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
}
}
SetEntryPointsToInterpreter(nullptr, art_method);
}
};
}

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
#define ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_
#include <iosfwd>
namespace art {
namespace 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 gc
} // namespace art
#endif // ART_RUNTIME_GC_COLLECTOR_GC_TYPE_H_

View File

@ -1,67 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef ART_RUNTIME_GC_GC_CAUSE_H_
#define ART_RUNTIME_GC_GC_CAUSE_H_
#include <iosfwd>
namespace art {
namespace 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 gc
} // namespace art
#endif // ART_RUNTIME_GC_GC_CAUSE_H_

View File

@ -1,65 +0,0 @@
/*
* 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) 2021 LSPosed Contributors
*/
#ifndef LSPOSED_SCOPED_GC_CRITICAL_SECTION_H
#define LSPOSED_SCOPED_GC_CRITICAL_SECTION_H
#include "gc_cause.h"
#include "collector_type.h"
namespace art {
namespace gc {
class GCCriticalSection {
private:
[[maybe_unused]] void* self_;
[[maybe_unused]] const char* section_name_;
};
class ScopedGCCriticalSection {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, void *thiz, void* self, GcCause cause, CollectorType collector_type) {
if (thiz == nullptr) [[unlikely]] return;
if (constructorSym) [[likely]]
return constructorSym(thiz, self, cause, collector_type);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, void *thiz) {
if (thiz == nullptr) [[unlikely]] return;
if (destructorSym) [[likely]]
return destructorSym(thiz);
}
public:
ScopedGCCriticalSection(void *self, GcCause cause, CollectorType collector_type) {
constructor(this, self, cause, collector_type);
}
~ScopedGCCriticalSection() {
destructor(this);
}
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE");
RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev");
}
private:
[[maybe_unused]] GCCriticalSection critical_section_;
[[maybe_unused]] const char* old_no_suspend_reason_;
};
}
}
#endif //LSPOSED_SCOPED_GC_CRITICAL_SECTION_H

View File

@ -1,52 +0,0 @@
/*
* 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) 2021 LSPosed Contributors
*/
#pragma once
#include <jni_helper.h>
#include <base/object.h>
#include "jni/yahfa.h"
namespace art {
namespace jit {
namespace jit_code_cache {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, void *thiz,
void *old_method, void *new_method) {
if (MoveObsoleteMethodSym)
[[likely]] MoveObsoleteMethodSym(thiz, old_method, new_method);
}
CREATE_MEM_HOOK_STUB_ENTRIES(
"_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE",
void, GarbageCollectCache, (void * thiz, void * self), {
LOGD("Before jit cache gc, moving hooked methods");
for (auto[target, backup] : lspd::getJitMovements()) {
MoveObsoleteMethod(thiz, target, backup);
}
backup(thiz, self);
});
inline void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(MoveObsoleteMethod,
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_");
lspd::HookSyms(handle, GarbageCollectCache);
}
}
}
}

View File

@ -1,58 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
#include "jni.h"
#include "base/object.h"
namespace art {
class JNIEnvExt : lspd::HookedObject {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(jobject, NewLocalRef, void *thiz, void *mirror_ptr) {
return NewLocalRefSym(thiz, mirror_ptr);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, DeleteLocalRef, void *thiz, jobject obj) {
return DeleteLocalRefSym(thiz, obj);
}
public:
JNIEnvExt(void *thiz) : HookedObject(thiz) {}
// @ApiSensitive(Level.MIDDLE)
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(NewLocalRef, "_ZN3art9JNIEnvExt11NewLocalRefEPNS_6mirror6ObjectE");
RETRIEVE_MEM_FUNC_SYMBOL(DeleteLocalRef, "_ZN3art9JNIEnvExt14DeleteLocalRefEP8_jobject");
}
jobject NewLocalRefer(void *mirror_ptr) {
return NewLocalRef(thiz_, mirror_ptr);
}
void DeleteLocalRef(jobject obj) {
DeleteLocalRef(thiz_, obj);
}
};
}

View File

@ -1,80 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
#include <context.h>
#include "base/object.h"
namespace art {
namespace mirror {
using namespace std;
using namespace lspd;
class Class : public HookedObject {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, void *thiz,
std::string *storage) {
if (GetDescriptorSym)
return GetDescriptorSym(thiz, storage);
else
return "";
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void*, GetClassDef, void* thiz) {
if (GetClassDefSym) [[likely]]
return GetClassDefSym(thiz);
return nullptr;
}
public:
Class(void *thiz) : HookedObject(thiz) {}
// @ApiSensitive(Level.MIDDLE)
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor, "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE");
RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv");
}
const char *GetDescriptor(std::string *storage) {
if (thiz_ && GetDescriptorSym) {
return GetDescriptor(thiz_, storage);
}
return "";
}
std::string GetDescriptor() {
std::string storage;
return GetDescriptor(&storage);
}
void *GetClassDef() {
if(thiz_ && GetClassDefSym)
return GetClassDef(thiz_);
return nullptr;
}
};
} // namespace mirror
} // namespace art

View File

@ -1,64 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
#include <base/object.h>
namespace art {
class Thread : public lspd::HookedObject {
CREATE_MEM_FUNC_SYMBOL_ENTRY(lspd::ObjPtr, DecodeJObject, void *thiz, jobject obj) {
if (DecodeJObjectSym)
return DecodeJObjectSym(thiz, obj);
else
return {.data=nullptr};
}
CREATE_FUNC_SYMBOL_ENTRY(void *, CurrentFromGdb) {
if (CurrentFromGdbSym) [[likely]]
return CurrentFromGdbSym();
else
return nullptr;
}
public:
Thread(void *thiz) : HookedObject(thiz) {}
inline static Thread Current() {
return Thread(CurrentFromGdb());
}
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(DecodeJObject,
"_ZNK3art6Thread13DecodeJObjectEP8_jobject");
RETRIEVE_FUNC_SYMBOL(CurrentFromGdb,
"_ZN3art6Thread14CurrentFromGdbEv");
}
void *DecodeJObject(jobject obj) {
if (thiz_ && DecodeJObjectSym) [[likely]] {
return DecodeJObject(thiz_, obj).data;
}
return nullptr;
}
};
}

View File

@ -1,53 +0,0 @@
/*
* 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) 2021 LSPosed Contributors
*/
#ifndef LSPOSED_THREAD_LIST_H
#define LSPOSED_THREAD_LIST_H
namespace art {
namespace thread_list {
class ScopedSuspendAll {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, void *thiz, const char * cause, bool long_suspend) {
if (thiz == nullptr) [[unlikely]] return;
if (constructorSym) [[likely]]
return constructorSym(thiz, cause, long_suspend);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, void *thiz) {
if (thiz == nullptr) [[unlikely]] return;
if (destructorSym) [[likely]]
return destructorSym(thiz);
}
public:
ScopedSuspendAll(const char * cause, bool long_suspend) {
constructor(this, cause, long_suspend);
}
~ScopedSuspendAll() {
destructor(this);
}
inline static void Setup(const SandHook::ElfImg &handle) {
RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb");
RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev");
}
};
}
}
#endif //LSPOSED_THREAD_LIST_H

View File

@ -1,277 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
#include "macros.h"
#include <dlfcn.h>
#include <sys/mman.h>
#include "config.h"
#include "native_hook.h"
#include "elf_util.h"
#include <concepts>
#define _uintval(p) reinterpret_cast<uintptr_t>(p)
#define _ptr(p) reinterpret_cast<void *>(p)
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
#define _align_down(x, n) ((x) & -(n))
#define _page_size 4096
#define _page_align(n) _align_up(static_cast<uintptr_t>(n), _page_size)
#define _ptr_align(x) _ptr(_align_down(reinterpret_cast<uintptr_t>(x), _page_size))
#define _make_rwx(p, n) ::mprotect(_ptr_align(p), \
_page_align(_uintval(p) + n) != _page_align(_uintval(p)) ? _page_align(n) + _page_size : _page_align(n), \
PROT_READ | PROT_WRITE | PROT_EXEC)
#define CONCATENATE(a, b) a##b
#define CREATE_HOOK_STUB_ENTRIES(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lspd::Hooker<RET PARAMS, decltype(CONCATENATE(SYM,_tstr))>{ \
inline static RET replace PARAMS DEF \
} FUNC
#define CREATE_MEM_HOOK_STUB_ENTRIES(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lspd::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>( \
lspd::Dlsym(handle, __VA_ARGS__))
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
name##Sym = reinterpret_cast<name##Type::FunType>( \
lspd::Dlsym(handle, __VA_ARGS__))
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
void *name = lspd::Dlsym(handle, __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 = lspd::MemberFunction<ret(__VA_ARGS__)>; \
inline static func##Type func##Sym; \
inline static ret func(thiz, ## __VA_ARGS__)
namespace lspd {
class ShadowObject {
public:
ShadowObject(void *thiz) : thiz_(thiz) {
}
[[gnu::always_inline]]
inline void *Get() {
return thiz_;
}
[[gnu::always_inline]]
inline void Reset(void *thiz) {
thiz_ = thiz;
}
[[gnu::always_inline]]
inline operator bool() const {
return thiz_ != nullptr;
}
protected:
void *thiz_;
};
class HookedObject : public ShadowObject {
public:
HookedObject(void *thiz) : ShadowObject(thiz) {}
};
struct ObjPtr {
void *data;
};
[[gnu::always_inline]]
inline void *Dlsym(void *handle, const char *name) {
return dlsym(handle, name);
}
[[gnu::always_inline]]
inline void *Dlsym(const SandHook::ElfImg &handle, const char *name) {
return handle.getSymbAddress<void *>(name);
}
template<class H, class T, class ... Args>
inline void *Dlsym(H &&handle, T first, Args... last) {
auto ret = Dlsym(std::forward<H>(handle), first);
if (ret) {
return ret;
}
return Dlsym(std::forward<H>(handle), last...);
}
inline int HookFunction(void *original, void *replace, void **backup) {
_make_rwx(original, _page_size);
if constexpr (isDebug) {
Dl_info info;
if (dladdr(original, &info))
LOGD("Hooking %s (%p) from %s (%p)",
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
}
return DobbyHook(original, replace, backup);
}
inline int UnhookFunction(void *original) {
if constexpr (isDebug) {
Dl_info info;
if (dladdr(original, &info))
LOGD("Unhooking %s (%p) from %s (%p)",
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
}
return DobbyDestroy(original);
}
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<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...);
struct {
decltype(func) p;
std::ptrdiff_t adj;
} data;
} 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>
inline auto memfun_cast(Return (*func)(T *, Args...)) {
return memfun_cast<T>(func);
}
template<typename, typename=void>
class MemberFunction;
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...);
public:
using FunType = Return (*)(This *, Args...);
private:
MemFunType f_ = nullptr;
public:
MemberFunction() = default;
MemberFunction(FunType f) : f_(memfun_cast<ThisType>(f)) {}
MemberFunction(MemFunType f) : f_(f) {}
Return operator()(This *thiz, Args... args) {
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
}
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(This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
template<typename, typename>
struct Hooker;
template<typename Ret, typename... Args, char... cs>
struct Hooker<Ret(Args...), tstring<cs...>> {
inline static Ret (*backup)(Args...) = nullptr;
inline static constexpr const char *sym = tstring<cs...>::c_str();
};
template<typename, typename>
struct MemHooker;
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 const char *sym = tstring<cs...>::c_str();
};
template<typename T>
concept HookerType = requires(T a) {
a.backup;
a.replace;
};
template<HookerType T>
inline static bool HookSymNoHandle(void *original, T &arg) {
if (original) {
if constexpr(is_instance<decltype(arg.backup), MemberFunction>::value) {
void *backup;
HookFunction(original, reinterpret_cast<void *>(arg.replace), &backup);
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
} else {
HookFunction(original, reinterpret_cast<void *>(arg.replace),
reinterpret_cast<void **>(&arg.backup));
}
return true;
} else {
return false;
}
}
template<typename H, HookerType T>
inline static bool HookSym(H &&handle, T &arg) {
auto original = Dlsym(std::forward<H>(handle), arg.sym);
return HookSymNoHandle(original, arg);
}
template<typename H, HookerType T, HookerType...Args>
inline static bool HookSyms(H &&handle, T &first, Args &...rest) {
if (!(HookSym(std::forward<H>(handle), first) || ... || HookSym(std::forward<H>(handle),
rest))) {
LOGW("Hook Fails: %s", first.sym);
return false;
}
return true;
}
} // namespace lspd
using lspd::operator ""_tstr;

View File

@ -1,363 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 - 2022 LSPosed Contributors
*/
#pragma once
#include <jni.h>
#include "macros.h"
#include <string>
#include "logging.h"
#define JNI_START JNIEnv *env, [[maybe_unused]] jclass clazz
namespace JNIHelper {
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 {
};
}
class JUTFString {
public:
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, 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 operator const char *() const { return cstr_; }
inline operator const std::string() const { return cstr_; }
inline operator const bool() const { return cstr_ != nullptr; }
inline auto get() const { return cstr_; }
inline ~JUTFString() {
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
}
JUTFString(JUTFString &&other)
: env_(std::move(other.env_)), jstr_(std::move(other.jstr_)),
cstr_(std::move(other.cstr_)) {
other.cstr_ = nullptr;
}
JUTFString &
operator=(JUTFString &&other) {
if (&other != this) {
env_ = std::move(other.env_);
jstr_ = std::move(other.jstr_);
cstr_ = std::move(other.cstr_);
other.cstr_ = nullptr;
}
return *this;
}
private:
JNIEnv *env_;
jstring jstr_;
const char *cstr_;
JUTFString(const JUTFString &) = delete;
JUTFString &operator=(const JUTFString &) = delete;
};
template<typename T>
concept JObject = std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>;
template<JObject T>
class ScopedLocalRef {
public:
ScopedLocalRef(JNIEnv *env, T localRef) : mEnv(env), mLocalRef(localRef) {
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept: mEnv(s.mEnv), mLocalRef(s.release()) {
}
template<JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept: mEnv(s.mEnv), mLocalRef((T) s.release()) {
}
explicit ScopedLocalRef(JNIEnv *env) : mEnv(env), mLocalRef(nullptr) {
}
~ScopedLocalRef() {
reset();
}
void reset(T ptr = nullptr) {
if (ptr != mLocalRef) {
if (mLocalRef != nullptr) {
mEnv->DeleteLocalRef(mLocalRef);
}
mLocalRef = ptr;
}
}
[[nodiscard]] T release() {
T localRef = mLocalRef;
mLocalRef = nullptr;
return localRef;
}
T get() const {
return mLocalRef;
}
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
// ScopedLocalRef<...> ref;
// ref.reset(...);
// Move assignment operator.
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release());
mEnv = s.mEnv;
return *this;
}
operator bool() const {
return mLocalRef;
}
template<JObject U>
friend
class ScopedLocalRef;
private:
JNIEnv *mEnv;
T mLocalRef;
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
template<typename T, typename U>
concept ScopeOrRaw = std::is_same_v<T, U> || std::is_same_v<ScopedLocalRef<T>, U>;
template<typename T>
concept ScopeOrClass = ScopeOrRaw<jclass, T>;
template<typename T>
concept ScopeOrObject = ScopeOrRaw<jobject, T>;
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);
env->DeleteLocalRef(exception);
return {env, str};
}
return {env, nullptr};
}
template<typename T>
[[maybe_unused]]
inline auto unwrap_scope(T &&x) {
if constexpr (std::is_same_v<std::decay_t<T>, std::string_view>) return x.data();
else if constexpr (JNIHelper::is_instance<std::decay_t<T>, ScopedLocalRef>::value) return x.get();
else return std::forward<T>(x);
}
template<typename T>
[[maybe_unused]]
inline auto wrap_scope(JNIEnv *env, T &&x) {
if constexpr (std::is_convertible_v<T, jobject>) {
return ScopedLocalRef(env, std::forward<T>(x));
} else return x;
}
inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
return ScopedLocalRef(env, env->NewStringUTF(sv.data()));
}
template<typename Func, typename ...Args>
requires(std::is_function_v<Func>)
[[maybe_unused]]
inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&... args) {
struct finally {
finally(JNIEnv *env) : env_(env) {}
~finally() {
if (auto exception = ClearException(env_)) {
LOGE("%s", JUTFString(env_, exception.get()).get());
}
}
JNIEnv *env_;
} _(env);
if constexpr (!std::is_same_v<void, std::invoke_result_t<Func, decltype(unwrap_scope(
std::forward<Args>(args)))...>>)
return wrap_scope(env, (env->*f)(unwrap_scope(std::forward<Args>(args))...));
else (env->*f)(unwrap_scope(std::forward<Args>(args))...);
}
[[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) {
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) {
return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, clazz, name, sig);
}
template<ScopeOrClass Class>
[[maybe_unused]]
inline auto JNI_GetObjectField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, clazz, fieldId);
}
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, Args &&... args) {
return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, obj, std::forward<Args>(args)...);
}
template<ScopeOrObject Object, typename ...Args>
[[maybe_unused]]
inline auto JNI_CallIntMethod(JNIEnv *env, const Object &obj, Args &&... args) {
return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, obj, std::forward<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) {
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) {
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) {
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) {
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, 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) {
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) {
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) {
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) {
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) {
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) {
return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array);
}
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) {
return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, clazz, methods, size);
}
template<typename T>
[[maybe_unused]]
inline auto JNI_NewGlobalRef(JNIEnv *env, T &&x) requires(std::is_convertible_v<T, jobject>){
return (T) env->NewGlobalRef(std::forward<T>(x));
}
template<typename T>
[[maybe_unused]]
inline auto
JNI_NewGlobalRef(JNIEnv *env, const ScopedLocalRef<T> &x) requires(
std::is_convertible_v<T, jobject>){
return (T) env->NewGlobalRef(x.get());
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <stddef.h> // for size_t
#include <unistd.h> // for TEMP_FAILURE_RETRY
#include <utility>
// A macro to disallow the copy constructor and operator= functions
// This must be placed in the private: declarations for a class.
//
// For disallowing only assign or copy, delete the relevant operator or
// constructor, for example:
// void operator=(const TypeName&) = delete;
// Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken
// semantically, one should either use disallow both or neither. Try to
// avoid these in new code.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
template<typename T, size_t N>
[[gnu::always_inline]] constexpr inline size_t arraysize(T(&)[N]) {
return N;
}
// Current ABI string
#if defined(__arm__)
#define ABI_STRING "arm"
#elif defined(__aarch64__)
#define ABI_STRING "arm64"
#elif defined(__i386__)
#define ABI_STRING "x86"
#elif defined(__x86_64__)
#define ABI_STRING "x86_64"
#elif defined(__mips__) && !defined(__LP64__)
#define ABI_STRING "mips"
#elif defined(__mips__) && defined(__LP64__)
#define ABI_STRING "mips64"
#endif
// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private:
// declarations in a class.
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete

View File

@ -1,73 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"
#pragma once
#include <context.h>
#include "macros.h"
#include "jni_helper.h"
#include "logging.h"
#include <cassert>
namespace lspd {
[[gnu::always_inline]]
inline bool RegisterNativeMethodsInternal(JNIEnv *env,
const char *class_name,
const JNINativeMethod *methods,
jint method_count) {
auto clazz = Context::GetInstance()->FindClassFromCurrentLoader(env, class_name);
if (clazz.get() == nullptr) {
LOGF("Couldn't find class: %s", class_name);
return false;
}
return JNI_RegisterNatives(env, clazz, methods, method_count);
}
#if defined(__cplusplus)
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
reinterpret_cast<to>
#else
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
(to)
#endif
#ifndef LSP_NATIVE_METHOD
#define LSP_NATIVE_METHOD(className, functionName, signature) \
{ #functionName, \
signature, \
_NATIVEHELPER_JNI_MACRO_CAST(void*) (Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName) \
}
#endif
#ifndef LSP_DEF_NATIVE_METHOD
#define LSP_DEF_NATIVE_METHOD(ret, className, functionName, ...) \
extern "C" ret Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName (JNI_START, ## __VA_ARGS__)
#endif
#define REGISTER_LSP_NATIVE_METHODS(class_name) \
RegisterNativeMethodsInternal(env, "org.lsposed.lspd.nativebridge." #class_name, gMethods, arraysize(gMethods))
} // namespace lspd
#pragma clang diagnostic pop

View File

@ -1,46 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#include <jni.h>
#include <native_util.h>
#include <art/runtime/class_linker.h>
#include <vector>
#include <HookMain.h>
#include <unordered_set>
#include "art_class_linker.h"
namespace lspd {
LSP_DEF_NATIVE_METHOD(void, ClassLinker, setEntryPointsToInterpreter, jobject method) {
void *reflected_method = yahfa::getArtMethod(env, method);
LOGD("deoptimizing method: %p", reflected_method);
art::ClassLinker::SetEntryPointsToInterpreter(reflected_method);
LOGD("method deoptimized: %p", reflected_method);
}
static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter,
"(Ljava/lang/reflect/Executable;)V")
};
void RegisterArtClassLinker(JNIEnv *env) {
REGISTER_LSP_NATIVE_METHODS(ClassLinker);
}
} // namespace lspd

View File

@ -1,27 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#pragma once
namespace lspd {
void RegisterArtClassLinker(JNIEnv *);
} // namespace lspd

View File

@ -1,241 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 - 2022 LSPosed Contributors
*/
#include "yahfa.h"
#include "HookMain.h"
#include "native_util.h"
#include "art/runtime/class_linker.h"
#include "art/runtime/thread_list.h"
#include "art/runtime/thread.h"
#include "art/runtime/gc/scoped_gc_critical_section.h"
#include <dex_builder.h>
#include <shared_mutex>
#include <unordered_set>
namespace lspd {
namespace {
std::unordered_map<const void *, void*> hooked_methods_;
std::shared_mutex hooked_methods_lock_;
std::vector<std::pair<void *, void *>> jit_movements_;
std::shared_mutex jit_movements_lock_;
std::unordered_map<const void *, std::unordered_set<void*>> hooked_classes_;
std::shared_mutex hooked_classes_lock_;
}
void* isHooked(void *art_method) {
std::shared_lock lk(hooked_methods_lock_);
if (auto found = hooked_methods_.find(art_method); found != hooked_methods_.end()) {
return found->second;
}
return nullptr;
}
void recordHooked(void *art_method, void *backup) {
{
std::unique_lock lk(hooked_methods_lock_);
hooked_methods_.emplace(art_method, backup);
}
auto clazz = art::mirror::Class(reinterpret_cast<void*>(*reinterpret_cast<uint32_t*>(art_method)));
{
std::unique_lock lk(hooked_classes_lock_);
hooked_classes_[clazz.GetClassDef()].emplace(art_method);
}
}
const std::unordered_set<void*> &isClassHooked(void *clazz) {
static std::unordered_set<void*> empty;
std::shared_lock lk(hooked_classes_lock_);
if (auto found = hooked_classes_.find(clazz); found != hooked_classes_.end()) {
return found->second;
}
return empty;
}
void recordJitMovement(void *target, void *backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
std::vector<std::pair<void *, void *>> getJitMovements() {
std::unique_lock lk(jit_movements_lock_);
return std::move(jit_movements_);
}
using namespace startop::dex;
LSP_DEF_NATIVE_METHOD(void, Yahfa, init, jint sdkVersion) {
yahfa::init(env, clazz, sdkVersion);
}
LSP_DEF_NATIVE_METHOD(jobject, Yahfa, findMethodNative, jclass targetClass,
jstring methodName, jstring methodSig) {
return yahfa::findMethodNative(env, clazz, targetClass, methodName,
methodSig);
}
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, backupAndHookNative, jobject target,
jobject hook, jobject backup, jboolean is_proxy) {
art::gc::ScopedGCCriticalSection section(art::Thread::Current().Get(),
art::gc::kGcCauseDebugger,
art::gc::kCollectorTypeDebugger);
art::thread_list::ScopedSuspendAll suspend("Yahfa Hook", false);
if (yahfa::backupAndHookNative(env, clazz, target, hook, backup)) {
auto *target_method = yahfa::getArtMethod(env, target);
auto *backup_method = yahfa::getArtMethod(env, backup);
recordHooked(target_method, backup_method);
if (!is_proxy) [[likely]] recordJitMovement(target_method, backup_method);
return JNI_TRUE;
}
return JNI_FALSE;
}
LSP_DEF_NATIVE_METHOD(jboolean, Yahfa, isHooked, jobject member) {
return lspd::isHooked(yahfa::getArtMethod(env, member)) != nullptr;
}
LSP_DEF_NATIVE_METHOD(jclass, Yahfa, buildHooker, jobject app_class_loader, jchar return_class,
jcharArray classes, jstring method_name, jstring hooker_name) {
static auto *kInMemoryClassloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
"dalvik/system/InMemoryDexClassLoader"));
static jmethodID kInitMid = JNI_GetMethodID(env, kInMemoryClassloader, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
DexBuilder dex_file;
auto parameter_length = env->GetArrayLength(classes);
auto parameter_types = std::vector<TypeDescriptor>();
parameter_types.reserve(parameter_length);
std::string storage;
auto return_type =
return_class == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
(char) return_class);
auto *params = env->GetCharArrayElements(classes, nullptr);
for (int i = 0; i < parameter_length; ++i) {
parameter_types.push_back(
params[i] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
(char) params[i]));
}
ClassBuilder cbuilder{dex_file.MakeClass("LspHooker_")};
cbuilder.set_source_file("LSP");
auto hooker_type =
TypeDescriptor::FromClassname(JUTFString(env, hooker_name).get());
auto *hooker_field = cbuilder.CreateField("hooker", hooker_type)
.access_flags(dex::kAccStatic)
.Encode();
auto hook_builder{cbuilder.CreateMethod(
JUTFString(env, method_name), Prototype{return_type, parameter_types})};
// allocate tmp frist because of wide
auto tmp{hook_builder.AllocRegister()};
hook_builder.BuildConst(tmp, parameter_types.size());
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, i);
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, "handleHookedMethod",
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));
if (return_type == TypeDescriptor::Void) {
hook_builder.BuildReturn();
} else if (return_type.is_primitive()) {
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.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.BuildReturn(tmp, true);
}
[[maybe_unused]] auto *hook_method = hook_builder.Encode();
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()) {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConstWide(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/false, true);
} else {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
}
[[maybe_unused]] auto *back_method = backup_builder.Encode();
slicer::MemView image{dex_file.CreateImage()};
auto *dex_buffer = env->NewDirectByteBuffer(const_cast<void *>(image.ptr()), image.size());
auto my_cl = JNI_NewObject(env, kInMemoryClassloader, kInitMid,
dex_buffer, app_class_loader);
env->DeleteLocalRef(dex_buffer);
static jmethodID kMid = JNI_GetMethodID(env, kInMemoryClassloader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
if (!kMid) {
kMid = JNI_GetMethodID(env, kInMemoryClassloader, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
}
if (my_cl) {
auto target = JNI_CallObjectMethod(env, my_cl, kMid,
JNI_NewStringUTF(env, "LspHooker_"));
if (target) return (jclass) target.release();
}
return nullptr;
}
static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(Yahfa, init, "(I)V"),
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Executable;"),
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;Z)Z"),
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Executable;)Z"),
LSP_NATIVE_METHOD(Yahfa, buildHooker,
"(Ljava/lang/ClassLoader;C[CLjava/lang/String;Ljava/lang/String;)Ljava/lang/Class;"),
};
void RegisterYahfa(JNIEnv *env) {
REGISTER_LSP_NATIVE_METHODS(Yahfa);
}
} // namespace lspd

View File

@ -1,67 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#include <dlfcn.h>
#include <string>
#include <vector>
#include <art/runtime/runtime.h>
#include <art/runtime/jni_env_ext.h>
#include <dobby.h>
#include "symbol_cache.h"
#include "logging.h"
#include "native_api.h"
#include "native_hook.h"
#include "art/runtime/mirror/class.h"
#include "art/runtime/art_method.h"
#include "art/runtime/class_linker.h"
#include "art/runtime/thread.h"
#include "art/runtime/hidden_api.h"
#include "art/runtime/thread_list.h"
#include "art/runtime/gc/scoped_gc_critical_section.h"
#include "art/runtime/jit/jit_code_cache.h"
namespace lspd {
static std::atomic_bool installed = false;
void InstallInlineHooks() {
if (installed.exchange(true)) [[unlikely]] {
LOGD("Inline hooks have been installed, skip");
return;
}
LOGD("Start to install inline hooks");
const auto &handle_libart = *GetArt();
if (!handle_libart.isValid()) {
LOGE("Failed to fetch libart.so");
}
art::Runtime::Setup(handle_libart);
art::hidden_api::DisableHiddenApi(handle_libart);
art::art_method::Setup(handle_libart);
art::Thread::Setup(handle_libart);
art::ClassLinker::Setup(handle_libart);
art::mirror::Class::Setup(handle_libart);
art::JNIEnvExt::Setup(handle_libart);
art::thread_list::ScopedSuspendAll::Setup(handle_libart);
art::gc::ScopedGCCriticalSection::Setup(handle_libart);
art::jit::jit_code_cache::Setup(handle_libart);
GetArt(true);
LOGD("Inline hooks installed");
}
} // namespace lspd

View File

@ -1,26 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed;
public interface IModuleContext {
String getApkPath();
}

View File

@ -1,23 +1,3 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed; package de.robv.android.xposed;
import android.content.res.XResources; import android.content.res.XResources;
@ -42,26 +22,15 @@ public interface IXposedHookInitPackageResources extends IXposedMod {
*/ */
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable; void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
/** /** @hide */
* @hide
*/
final class Wrapper extends XC_InitPackageResources { final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance; private final IXposedHookInitPackageResources instance;
private final String apkPath; public Wrapper(IXposedHookInitPackageResources instance) {
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
this.instance = instance; this.instance = instance;
this.apkPath = apkPath;
} }
@Override @Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam); instance.handleInitPackageResources(resparam);
} }
@Override
public String getApkPath() {
return apkPath;
}
} }
} }

View File

@ -1,23 +1,3 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed; package de.robv.android.xposed;
import android.app.Application; import android.app.Application;
@ -43,26 +23,15 @@ public interface IXposedHookLoadPackage extends IXposedMod {
*/ */
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable; void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
/** /** @hide */
* @hide
*/
final class Wrapper extends XC_LoadPackage { final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance; private final IXposedHookLoadPackage instance;
private final String apkPath; public Wrapper(IXposedHookLoadPackage instance) {
public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
this.instance = instance; this.instance = instance;
this.apkPath = apkPath;
} }
@Override @Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam); instance.handleLoadPackage(lpparam);
} }
@Override
public String getApkPath() {
return apkPath;
}
} }
} }

View File

@ -1,28 +1,5 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed; package de.robv.android.xposed;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XCallback;
/** /**
* Hook the initialization of Zygote process(es), from which all the apps are forked. * Hook the initialization of Zygote process(es), from which all the apps are forked.
* *
@ -37,30 +14,16 @@ import de.robv.android.xposed.callbacks.XCallback;
public interface IXposedHookZygoteInit extends IXposedMod { public interface IXposedHookZygoteInit extends IXposedMod {
/** /**
* Called very early during startup of Zygote. * Called very early during startup of Zygote.
*
* @param startupParam Details about the module itself and the started process. * @param startupParam Details about the module itself and the started process.
* @throws Throwable everything is caught, but will prevent further initialization of the module. * @throws Throwable everything is caught, but will prevent further initialization of the module.
*/ */
void initZygote(StartupParam startupParam) throws Throwable; void initZygote(StartupParam startupParam) throws Throwable;
/** /** Data holder for {@link #initZygote}. */
* Data holder for {@link #initZygote}. final class StartupParam {
*/ /*package*/ StartupParam() {}
final class StartupParam extends XCallback.Param {
/*package*/ StartupParam() {
}
/** /** The path to the module's APK. */
* @param callbacks
* @hide
*/
public StartupParam(XposedBridge.CopyOnWriteSortedSet<? extends XCallback> callbacks) {
super(callbacks);
}
/**
* The path to the module's APK.
*/
public String modulePath; public String modulePath;
/** /**
@ -69,29 +32,4 @@ public interface IXposedHookZygoteInit extends IXposedMod {
*/ */
public boolean startsSystemServer; public boolean startsSystemServer;
} }
/**
* @hide
*/
final class Wrapper extends XC_InitZygote {
private final IXposedHookZygoteInit instance;
private final StartupParam startupParam;
public Wrapper(IXposedHookZygoteInit instance, StartupParam startupParam) {
this.instance = instance;
this.startupParam = startupParam;
}
@Override
public void initZygote(StartupParam startupParam) throws Throwable {
// NOTE: parameter startupParam not used
// cause startupParam info is generated and saved along with instance here
instance.initZygote(this.startupParam);
}
@Override
public String getApkPath() {
return startupParam.modulePath;
}
}
} }

View File

@ -1,132 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class LspHooker {
public final XposedBridge.AdditionalHookInfo additionalInfo;
private final Executable method;
private final Method backup;
public LspHooker(XposedBridge.AdditionalHookInfo info, Executable origin, Method backup) {
this.additionalInfo = info;
this.method = origin;
this.backup = backup;
this.backup.setAccessible(true);
}
public Object invokeOriginalMethod(Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
return backup.invoke(thisObject, args);
}
@SuppressWarnings({"unused", "RedundantSuppression"})
public Object handleHookedMethod(Object[] args) throws Throwable {
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
param.method = method;
if (Modifier.isStatic(method.getModifiers())) {
param.thisObject = null;
param.args = args;
} else {
param.thisObject = args[0];
param.args = new Object[args.length - 1];
System.arraycopy(args, 1, param.args, 0, args.length - 1);
}
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return backup.invoke(param.thisObject, param.args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
// call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(backup.invoke(param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
}
// call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);
// return
if (param.hasThrowable())
throw param.getThrowable();
else {
var result = param.getResult();
if (method instanceof Method) {
var returnType = ((Method) method).getReturnType();
if (!returnType.isPrimitive())
return returnType.cast(result);
}
return result;
}
}
}

View File

@ -28,8 +28,8 @@ import android.content.res.TypedArray;
import android.util.Log; import android.util.Log;
import org.lsposed.lspd.BuildConfig; import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.nativebridge.HookBridge;
import org.lsposed.lspd.nativebridge.ResourcesHook; import org.lsposed.lspd.nativebridge.ResourcesHook;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
import java.lang.reflect.AccessibleObject; import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
@ -39,15 +39,11 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
/** /**
@ -78,10 +74,8 @@ public final class XposedBridge {
private static final Object[] EMPTY_ARRAY = new Object[0]; private static final Object[] EMPTY_ARRAY = new Object[0];
// built-in handlers // built-in handlers
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new NoValuesHashMap<>(); public static final CopyOnWriteArraySet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteArraySet<>();
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>(); /*package*/ static final CopyOnWriteArraySet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteArraySet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
private XposedBridge() { private XposedBridge() {
} }
@ -124,7 +118,7 @@ public final class XposedBridge {
ResourcesHook.makeInheritable(resClass, resClass.getDeclaredConstructors()); ResourcesHook.makeInheritable(resClass, resClass.getDeclaredConstructors());
ResourcesHook.makeInheritable(taClass, taClass.getDeclaredConstructors()); ResourcesHook.makeInheritable(taClass, taClass.getDeclaredConstructors());
ClassLoader myCL = XposedBridge.class.getClassLoader(); ClassLoader myCL = XposedBridge.class.getClassLoader();
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass); dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass.getName(), taClass.getName());
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass"); dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass"); dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
setObjectField(myCL, "parent", dummyClassLoader); setObjectField(myCL, "parent", dummyClassLoader);
@ -180,7 +174,7 @@ public final class XposedBridge {
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) { } else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
throw new IllegalArgumentException("Cannot deoptimize methods from proxy class: " + deoptimizedMethod); throw new IllegalArgumentException("Cannot deoptimize methods from proxy class: " + deoptimizedMethod);
} }
YahfaHooker.deoptMethodNative((Executable) deoptimizedMethod); HookBridge.deoptimizeMethod((Executable) deoptimizedMethod);
} }
/** /**
@ -211,28 +205,11 @@ public final class XposedBridge {
throw new IllegalArgumentException("Do not allow hooking inner methods"); throw new IllegalArgumentException("Do not allow hooking inner methods");
} }
Executable targetMethod = (Executable) hookMethod;
if (callback == null) { if (callback == null) {
throw new IllegalArgumentException("callback should not be null!"); throw new IllegalArgumentException("callback should not be null!");
} }
boolean newMethod = false; HookBridge.hookMethod((Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback);
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(targetMethod);
if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<>();
sHookedMethodCallbacks.put(targetMethod, callbacks);
newMethod = true;
}
}
callbacks.add(callback);
if (newMethod) {
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
YahfaHooker.hookMethod(targetMethod, additionalInfo);
}
return callback.new Unhook(hookMethod); return callback.new Unhook(hookMethod);
} }
@ -247,13 +224,9 @@ public final class XposedBridge {
*/ */
@Deprecated @Deprecated
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) { public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
CopyOnWriteSortedSet<XC_MethodHook> callbacks; if (hookMethod instanceof Executable) {
synchronized (sHookedMethodCallbacks) { HookBridge.unhookMethod((Executable) hookMethod, callback);
callbacks = sHookedMethodCallbacks.get(hookMethod);
if (callbacks == null)
return;
} }
callbacks.remove(callback);
} }
/** /**
@ -305,12 +278,6 @@ public final class XposedBridge {
} }
} }
public static void clearLoadedPackages() {
synchronized (sLoadedPackageCallbacks) {
sLoadedPackageCallbacks.clear();
}
}
/** /**
* Adds a callback to be executed when the resources for an app are initialized. * Adds a callback to be executed when the resources for an app are initialized.
* *
@ -326,12 +293,6 @@ public final class XposedBridge {
} }
} }
public static void hookInitZygote(XC_InitZygote callback) {
synchronized (sInitZygoteCallbacks) {
sInitZygoteCallbacks.add(callback);
}
}
/** /**
* Basically the same as {@link Method#invoke}, but calls the original method * Basically the same as {@link Method#invoke}, but calls the original method
* as it was before the interception by Xposed. Also, access permissions are not checked. * as it was before the interception by Xposed. Also, access permissions are not checked.
@ -363,39 +324,7 @@ public final class XposedBridge {
throw new IllegalArgumentException("method must be of type Method or Constructor"); throw new IllegalArgumentException("method must be of type Method or Constructor");
} }
return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args); return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
}
private static class NoValuesHashMap<K, V> extends HashMap<K, V> {
@Override
public Collection values() {
return Collections.EMPTY_LIST;
}
@Override
public void clear() {
}
@Override
public Set<K> keySet() {
return Collections.EMPTY_SET;
}
@Override
public Set<Entry<K, V>> entrySet() {
return Collections.EMPTY_SET;
}
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return true;
}
} }
/** /**
@ -449,10 +378,99 @@ public final class XposedBridge {
} }
public static class AdditionalHookInfo { public static class AdditionalHookInfo {
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks; final Executable method;
final Object[][] callbacks;
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) { private AdditionalHookInfo(Executable method, Object[][] callbacks) {
this.method = method;
this.callbacks = callbacks; this.callbacks = callbacks;
} }
public Object callback(Object[] args) throws Throwable {
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
param.method = method;
if (Modifier.isStatic(method.getModifiers())) {
param.thisObject = null;
param.args = args;
} else {
param.thisObject = args[0];
param.args = new Object[args.length - 1];
System.arraycopy(args, 1, param.args, 0, args.length - 1);
}
Object[] callbacksSnapshot = callbacks[0];
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args);
} catch (InvocationTargetException ite) {
throw ite.getCause();
}
}
// call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
}
// call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);
// return
if (param.hasThrowable())
throw param.getThrowable();
else {
var result = param.getResult();
if (method instanceof Method) {
var returnType = ((Method) method).getReturnType();
if (!returnType.isPrimitive())
return returnType.cast(result);
}
return result;
}
}
} }
} }

View File

@ -23,9 +23,6 @@ package de.robv.android.xposed;
import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient; import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
import static org.lsposed.lspd.deopt.PrebuiltMethodsDeopter.deoptResourceMethods; import static org.lsposed.lspd.deopt.PrebuiltMethodsDeopter.deoptResourceMethods;
import static de.robv.android.xposed.XposedBridge.hookAllMethods; import static de.robv.android.xposed.XposedBridge.hookAllMethods;
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
import static de.robv.android.xposed.XposedBridge.sLoadedPackageCallbacks;
import static de.robv.android.xposed.XposedHelpers.callMethod; import static de.robv.android.xposed.XposedHelpers.callMethod;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
import static de.robv.android.xposed.XposedHelpers.getObjectField; import static de.robv.android.xposed.XposedHelpers.getObjectField;
@ -58,8 +55,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitZygote;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XCallback; import de.robv.android.xposed.callbacks.XCallback;
import hidden.HiddenApiBridge; import hidden.HiddenApiBridge;
@ -219,79 +214,22 @@ public final class XposedInit {
return newRes; return newRes;
} }
/**
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
*/
private static final AtomicBoolean modulesLoaded = new AtomicBoolean(false);
private static final Object moduleLoadLock = new Object();
// @GuardedBy("moduleLoadLock")
private static final ArraySet<String> loadedModules = new ArraySet<>(); private static final ArraySet<String> loadedModules = new ArraySet<>();
public static ArraySet<String> getLoadedModules() { synchronized public static ArraySet<String> getLoadedModules() {
synchronized (moduleLoadLock) { return loadedModules;
return loadedModules;
}
} }
public static void loadModules() { synchronized public static void loadModules() {
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true); var moduleList = serviceClient.getModulesList();
if (hasLoaded) { moduleList.forEach(module -> {
return; var apk = module.apkPath;
} var name = module.packageName;
synchronized (moduleLoadLock) { var file = module.file;
var moduleList = serviceClient.getModulesList(); if (loadModule(name, apk, file)) {
var newLoadedApk = new ArraySet<String>(); loadedModules.add(apk); // temporarily add it for XSharedPreference
moduleList.forEach(module -> {
var apk = module.apkPath;
var name = module.packageName;
var file = module.file;
if (loadedModules.contains(apk)) {
newLoadedApk.add(apk);
} else {
loadedModules.add(apk); // temporarily add it for XSharedPreference
boolean loadSuccess = loadModule(name, apk, file);
if (loadSuccess) {
newLoadedApk.add(apk);
}
}
loadedModules.clear();
loadedModules.addAll(newLoadedApk);
// refresh callback according to current loaded module list
pruneCallbacks();
});
}
}
// remove deactivated or outdated module callbacks
private static void pruneCallbacks() {
synchronized (moduleLoadLock) {
Object[] loadedPkgSnapshot = sLoadedPackageCallbacks.getSnapshot();
Object[] initPkgResSnapshot = sInitPackageResourcesCallbacks.getSnapshot();
Object[] initZygoteSnapshot = sInitZygoteCallbacks.getSnapshot();
for (Object loadedPkg : loadedPkgSnapshot) {
if (loadedPkg instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) loadedPkg).getApkPath())) {
sLoadedPackageCallbacks.remove((XC_LoadPackage) loadedPkg);
}
}
} }
for (Object initPkgRes : initPkgResSnapshot) { });
if (initPkgRes instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) initPkgRes).getApkPath())) {
sInitPackageResourcesCallbacks.remove((XC_InitPackageResources) initPkgRes);
}
}
}
for (Object initZygote : initZygoteSnapshot) {
if (initZygote instanceof IModuleContext) {
if (!loadedModules.contains(((IModuleContext) initZygote).getApkPath())) {
sInitZygoteCallbacks.remove((XC_InitZygote) initZygote);
}
}
}
}
} }
/** /**
@ -321,23 +259,18 @@ public final class XposedInit {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk; param.modulePath = apk;
param.startsSystemServer = startsSystemServer; param.startsSystemServer = startsSystemServer;
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
(IXposedHookZygoteInit) moduleInstance, param));
((IXposedHookZygoteInit) moduleInstance).initZygote(param); ((IXposedHookZygoteInit) moduleInstance).initZygote(param);
count++; count++;
} }
if (moduleInstance instanceof IXposedHookLoadPackage) { if (moduleInstance instanceof IXposedHookLoadPackage) {
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper( XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
(IXposedHookLoadPackage) moduleInstance, apk));
count++; count++;
} }
if (moduleInstance instanceof IXposedHookInitPackageResources) { if (moduleInstance instanceof IXposedHookInitPackageResources) {
hookResources(); hookResources();
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper( XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
(IXposedHookInitPackageResources) moduleInstance, apk));
count++; count++;
} }
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -22,8 +22,9 @@ package de.robv.android.xposed.callbacks;
import android.content.res.XResources; import android.content.res.XResources;
import java.util.concurrent.CopyOnWriteArraySet;
import de.robv.android.xposed.IXposedHookInitPackageResources; import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/** /**
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam} * This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
@ -57,8 +58,8 @@ public abstract class XC_InitPackageResources extends XCallback implements IXpos
/** /**
* @hide * @hide
*/ */
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) { public InitPackageResourcesParam(CopyOnWriteArraySet<XC_InitPackageResources> callbacks) {
super(callbacks); super(callbacks.toArray(new XCallback[0]));
} }
/** /**

View File

@ -1,59 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package de.robv.android.xposed.callbacks;
import de.robv.android.xposed.IXposedHookZygoteInit;
/**
* This class is only used for internal purposes, except for the {@link StartupParam}
* subclass.
*/
public abstract class XC_InitZygote extends XCallback implements IXposedHookZygoteInit {
/**
* Creates a new callback with default priority.
*
* @hide
*/
@SuppressWarnings("deprecation")
public XC_InitZygote() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
* @hide
*/
public XC_InitZygote(int priority) {
super(priority);
}
/**
* @hide
*/
@Override
protected void call(Param param) throws Throwable {
if (param instanceof StartupParam)
initZygote((StartupParam) param);
}
}

View File

@ -56,7 +56,7 @@ public abstract class XC_LayoutInflated extends XCallback {
* @hide * @hide
*/ */
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) { public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
super(callbacks); super((XCallback[]) callbacks.getSnapshot());
} }
/** /**

View File

@ -22,6 +22,8 @@ package de.robv.android.xposed.callbacks;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import java.util.concurrent.CopyOnWriteArraySet;
import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
@ -57,8 +59,8 @@ public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoa
/** /**
* @hide * @hide
*/ */
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) { public LoadPackageParam(CopyOnWriteArraySet<XC_LoadPackage> callbacks) {
super(callbacks); super(callbacks.toArray(new XCallback[0]));
} }
/** /**

View File

@ -26,9 +26,7 @@ import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
import java.io.Serializable; import java.io.Serializable;
import de.robv.android.xposed.IModuleContext;
import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/** /**
* Base class for Xposed callbacks. * Base class for Xposed callbacks.
@ -36,7 +34,7 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
* This class only keeps a priority for ordering multiple callbacks. * This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses. * The actual (abstract) callback methods are added by subclasses.
*/ */
public abstract class XCallback implements Comparable<XCallback>, IModuleContext { abstract public class XCallback {
/** /**
* Callback priority, higher number means earlier execution. * Callback priority, higher number means earlier execution.
* *
@ -69,7 +67,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
/** /**
* @hide * @hide
*/ */
public final Object[] callbacks; public final XCallback[] callbacks;
private Bundle extra; private Bundle extra;
/** /**
@ -83,8 +81,8 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
/** /**
* @hide * @hide
*/ */
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) { protected Param(XCallback[] callbacks) {
this.callbacks = callbacks.getSnapshot(); this.callbacks = callbacks;
} }
/** /**
@ -147,7 +145,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
for (int i = 0; i < param.callbacks.length; i++) { for (int i = 0; i < param.callbacks.length; i++) {
try { try {
((XCallback) param.callbacks[i]).call(param); param.callbacks[i].call(param);
} catch (Throwable t) { } catch (Throwable t) {
XposedBridge.log(t); XposedBridge.log(t);
} }
@ -160,28 +158,6 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
protected void call(Param param) throws Throwable { protected void call(Param param) throws Throwable {
} }
@Override
public String getApkPath() {
return "";
}
/**
* @hide
*/
@Override
public int compareTo(XCallback other) {
if (this == other)
return 0;
// order descending by priority
if (other.priority != this.priority)
return other.priority - this.priority;
// then randomly
else if (System.identityHashCode(this) < System.identityHashCode(other))
return -1;
else
return 1;
}
/** /**
* The default priority, see {@link #priority}. * The default priority, see {@link #priority}.

View File

@ -39,7 +39,6 @@ import org.lsposed.lspd.hooker.LoadedApkCstrHooker;
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker; import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
import org.lsposed.lspd.util.ParasiticManagerHooker; import org.lsposed.lspd.util.ParasiticManagerHooker;
import org.lsposed.lspd.util.Utils; import org.lsposed.lspd.util.Utils;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
import java.io.File; import java.io.File;
@ -77,7 +76,6 @@ public class Main {
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) { public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
// init logger // init logger
YahfaHooker.init();
XposedBridge.initXResources(); XposedBridge.initXResources();
XposedInit.startsSystemServer = isSystem; XposedInit.startsSystemServer = isSystem;
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote

View File

@ -1,103 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.core.yahfa;
import org.lsposed.lspd.nativebridge.Yahfa;
import org.lsposed.lspd.util.Utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
public class HookMain {
public static void backupAndHook(Executable target, Method hook, Method backup) {
Utils.logD(String.format("target=%s, hook=%s, backup=%s", target, hook, backup));
if (target == null) {
throw new IllegalArgumentException("null target method");
}
if (hook == null) {
throw new IllegalArgumentException("null hook method");
}
if (!Modifier.isStatic(hook.getModifiers())) {
throw new IllegalArgumentException("Hook must be a static method: " + hook);
}
checkCompatibleMethods(target, hook, "Hook");
if (backup != null) {
if (!Modifier.isStatic(backup.getModifiers())) {
throw new IllegalArgumentException("Backup must be a static method: " + backup);
}
// backup is just a placeholder and the constraint could be less strict
checkCompatibleMethods(target, backup, "Backup");
}
if (!Yahfa.backupAndHookNative(target, hook, backup, Proxy.isProxyClass(target.getDeclaringClass()))) {
throw new RuntimeException("Failed to hook " + target + " with " + hook);
}
}
private static void checkCompatibleMethods(Executable original, Method replacement, String replacementName) {
ArrayList<Class<?>> originalParams;
if (original instanceof Method) {
originalParams = new ArrayList<>(Arrays.asList(original.getParameterTypes()));
} else if (original instanceof Constructor) {
originalParams = new ArrayList<>(Arrays.asList(original.getParameterTypes()));
} else {
throw new IllegalArgumentException("Type of target method is wrong");
}
ArrayList<Class<?>> replacementParams = new ArrayList<>(Arrays.asList(replacement.getParameterTypes()));
if (original instanceof Method
&& !Modifier.isStatic(original.getModifiers())) {
originalParams.add(0, original.getDeclaringClass());
} else if (original instanceof Constructor) {
originalParams.add(0, ((Constructor<?>) original).getDeclaringClass());
}
if (!Modifier.isStatic(replacement.getModifiers())) {
replacementParams.add(0, replacement.getDeclaringClass());
}
if (original instanceof Method
&& !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) {
throw new IllegalArgumentException("Incompatible return types. " + "Original" + ": " + ((Method) original).getReturnType() + ", " + replacementName + ": " + replacement.getReturnType());
} else if (original instanceof Constructor) {
if (replacement.getReturnType().equals(Void.class)) {
throw new IllegalArgumentException("Incompatible return types. " + "<init>" + ": " + "V" + ", " + replacementName + ": " + replacement.getReturnType());
}
}
if (originalParams.size() != replacementParams.size()) {
throw new IllegalArgumentException("Number of arguments don't match. " + "Original" + ": " + originalParams.size() + ", " + replacementName + ": " + replacementParams.size());
}
for (int i = 0; i < originalParams.size(); i++) {
if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) {
throw new IllegalArgumentException("Incompatible argument #" + i + ": " + "Original" + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i));
}
}
}
}

View File

@ -24,9 +24,8 @@ import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE;
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE_MIUI_RES; import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_BOOT_IMAGE_MIUI_RES;
import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_SYSTEM_SERVER; import static org.lsposed.lspd.deopt.InlinedMethodCallers.KEY_SYSTEM_SERVER;
import org.lsposed.lspd.nativebridge.Yahfa; import org.lsposed.lspd.nativebridge.HookBridge;
import org.lsposed.lspd.util.Utils; import org.lsposed.lspd.util.Utils;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.util.Arrays; import java.util.Arrays;
@ -46,9 +45,9 @@ public class PrebuiltMethodsDeopter {
if (clazz == null) { if (clazz == null) {
continue; continue;
} }
Executable method = Yahfa.findMethodNative(clazz, caller[1], caller[2]); Executable method = null;
if (method != null) { if (method != null) {
YahfaHooker.deoptMethodNative(method); HookBridge.deoptimizeMethod(method);
} }
} catch (Throwable throwable) { } catch (Throwable throwable) {
Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable); Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable);

View File

@ -1,28 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.nativebridge;
import java.lang.reflect.Executable;
public class ClassLinker {
public static native void setEntryPointsToInterpreter(Executable method);
}

View File

@ -0,0 +1,13 @@
package org.lsposed.lspd.nativebridge;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
public class HookBridge {
public static native boolean hookMethod(Executable hookMethod, Class<?> hooker, int priority, Object callback);
public static native boolean unhookMethod(Executable hookMethod, Object callback);
public static native boolean deoptimizeMethod(Executable method);
public static native Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
}

View File

@ -31,7 +31,7 @@ public class ResourcesHook {
public static native boolean makeInheritable(Class<?> clazz, Constructor<?>[] constructors); public static native boolean makeInheritable(Class<?> clazz, Constructor<?>[] constructors);
public static native ClassLoader buildDummyClassLoader(ClassLoader parent, Class<?> resourceSuperClass, Class<?> typedArraySuperClass); public static native ClassLoader buildDummyClassLoader(ClassLoader parent, String resourceSuperClass, String typedArraySuperClass);
public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes);
} }

View File

@ -1,38 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.nativebridge;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
public class Yahfa {
public static native boolean backupAndHookNative(Executable target, Method hook, Method backup, boolean isProxy);
// JNI.ToReflectedMethod() could return either Method or Constructor
public static native Executable findMethodNative(Class<?> targetClass, String methodName, String methodSig);
public static native void init(int sdkVersion);
public static native boolean isHooked(Executable member);
public static native Class<?> buildHooker(ClassLoader appClassLoader, char returnType, char[] params, String methodName, String hookerName);
}

View File

@ -1,73 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.yahfa.dexmaker;
import org.lsposed.lspd.util.Utils;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.util.concurrent.ConcurrentHashMap;
import de.robv.android.xposed.LspHooker;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
public final class DynamicBridge {
private static final ConcurrentHashMap<Executable, LspHooker> hookedInfo = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Executable, Object> lockers = new ConcurrentHashMap<>();
public static void hookMethod(Executable hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
Utils.logD("hooking " + hookMethod);
synchronized (lockers.computeIfAbsent(hookMethod, (m) -> new Object())) {
var hooker = hookedInfo.getOrDefault(hookMethod, null);
if (hooker == null) {
Utils.logD("start to generate class for: " + hookMethod);
try {
final HookerDexMaker dexMaker = new HookerDexMaker();
dexMaker.start(hookMethod, additionalHookInfo);
hookedInfo.put(hookMethod, dexMaker.getHooker());
} catch (Throwable e) {
Utils.logE("error occur when generating dex.", e);
}
} else {
for (var callback : additionalHookInfo.callbacks.getSnapshot())
hooker.additionalInfo.callbacks.add((XC_MethodHook) callback);
}
}
}
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
throws InvocationTargetException, IllegalAccessException {
if (!(method instanceof Executable)) {
throw new IllegalArgumentException("Only methods or constructors can be invoked.");
}
LspHooker hooker = hookedInfo.getOrDefault(method, null);
if (hooker == null) {
throw new IllegalStateException("method not hooked, cannot call original method.");
}
if (args == null) {
args = new Object[0];
}
return hooker.invokeOriginalMethod(thisObject, args);
}
}

View File

@ -1,123 +0,0 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.yahfa.dexmaker;
import org.lsposed.lspd.core.yahfa.HookMain;
import org.lsposed.lspd.nativebridge.Yahfa;
import org.lsposed.lspd.util.Utils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import de.robv.android.xposed.LspHooker;
import de.robv.android.xposed.XposedBridge;
@SuppressWarnings("rawtypes")
public class HookerDexMaker {
public static final String METHOD_NAME_BACKUP = "backup";
public static final String FIELD_NAME_HOOKER = "hooker";
private static final HashMap<Class<?>, Character> descriptors = new HashMap<>() {{
put(int.class, 'I');
put(boolean.class, 'Z');
put(char.class, 'C');
put(long.class, 'J');
put(short.class, 'S');
put(float.class, 'F');
put(double.class, 'D');
put(byte.class, 'B');
put(void.class, 'V');
put(Object.class, 'L');
}};
private Class<?> mReturnType;
private Class<?>[] mActualParameterTypes;
private Executable mMember;
private XposedBridge.AdditionalHookInfo mHookInfo;
private LspHooker mHooker;
private static Class<?>[] getParameterTypes(Executable method, boolean isStatic) {
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; ++i) {
parameterTypes[i] = parameterTypes[i].isPrimitive() ? parameterTypes[i] : Object.class;
}
if (isStatic) {
return parameterTypes;
}
int parameterSize = parameterTypes.length;
Class<?>[] newParameterTypes = new Class<?>[parameterSize + 1];
newParameterTypes[0] = Object.class;
System.arraycopy(parameterTypes, 0, newParameterTypes, 1, parameterSize);
return newParameterTypes;
}
private static char getDescriptor(Class<?> clazz) {
return descriptors.getOrDefault(clazz, 'L');
}
private static char[] getDescriptors(Class<?>[] classes) {
var descriptors = new char[classes.length];
for (int i = 0; i < classes.length; ++i) {
descriptors[i] = getDescriptor(classes[i]);
}
return descriptors;
}
public void start(Executable member, XposedBridge.AdditionalHookInfo hookInfo) throws Exception {
if (member instanceof Method) {
Method method = (Method) member;
mReturnType = method.getReturnType();
mActualParameterTypes = getParameterTypes(method, Modifier.isStatic(method.getModifiers()));
} else if (member instanceof Constructor) {
Constructor constructor = (Constructor) member;
mReturnType = void.class;
mActualParameterTypes = getParameterTypes(constructor, false);
}
mMember = member;
mHookInfo = hookInfo;
long startTime = System.nanoTime();
doMake(member instanceof Constructor ? "constructor" : member.getName());
long endTime = System.nanoTime();
Utils.logD("Hook time: " + (endTime - startTime) / 1e6 + "ms");
}
private void doMake(String methodName) throws Exception {
Class<?> hookClass = Yahfa.buildHooker(LspHooker.class.getClassLoader(), getDescriptor(mReturnType), getDescriptors(mActualParameterTypes), methodName, LspHooker.class.getCanonicalName());
if (hookClass == null) throw new IllegalStateException("Failed to hook " + methodName);
// Execute our newly-generated code in-process.
Method backupMethod = hookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
mHooker = new LspHooker(mHookInfo, mMember, backupMethod);
var hooker = hookClass.getDeclaredField(FIELD_NAME_HOOKER);
hooker.setAccessible(true);
hooker.set(null, mHooker);
Method hookMethod = hookClass.getMethod(methodName, mActualParameterTypes);
HookMain.backupAndHook(mMember, hookMethod, backupMethod);
}
public LspHooker getHooker() {
return mHooker;
}
}

View File

@ -1,52 +0,0 @@
/*
* 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) 2021 LSPosed Contributors
*/
package org.lsposed.lspd.yahfa.hooker;
import android.os.Build;
import org.lsposed.lspd.nativebridge.ClassLinker;
import org.lsposed.lspd.nativebridge.Yahfa;
import org.lsposed.lspd.yahfa.dexmaker.DynamicBridge;
import java.lang.reflect.Executable;
import de.robv.android.xposed.XposedBridge.AdditionalHookInfo;
public class YahfaHooker {
public static void init() {
int sdkVersion = Build.VERSION.SDK_INT;
if (Build.VERSION.PREVIEW_SDK_INT != 0) {
sdkVersion += 1;
}
Yahfa.init(sdkVersion);
}
public static void hookMethod(Executable method, AdditionalHookInfo additionalInfo) {
DynamicBridge.hookMethod(method, additionalInfo);
}
public static Object invokeOriginalMethod(Executable method, Object thisObject, Object[] args) throws Throwable {
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
}
public static void deoptMethodNative(Executable method) {
ClassLinker.setEntryPointsToInterpreter(method);
}
}

View File

@ -0,0 +1,33 @@
project(lspd)
cmake_minimum_required(VERSION 3.4.1)
add_subdirectory(${EXTERNAL_ROOT} external)
configure_file(template/config.cpp src/config.cpp)
aux_source_directory(src SRC_LIST)
aux_source_directory(src/jni SRC_LIST)
if (${API} STREQUAL "riru")
set(SRC_LIST ${SRC_LIST} api/riru_main.cpp)
elseif (${API} STREQUAL "zygisk")
set(SRC_LIST ${SRC_LIST} api/zygisk_main.cpp)
endif()
add_library(${PROJECT_NAME} SHARED ${SRC_LIST} ${CMAKE_CURRENT_BINARY_DIR}/src/config.cpp)
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_include_directories(${PROJECT_NAME} PRIVATE src)
target_link_libraries(${PROJECT_NAME} dobby dex_builder_static log lsplant_static)
if (DEFINED DEBUG_SYMBOLS_PATH)
set(DEBUG_SYMBOLS_PATH ${DEBUG_SYMBOLS_PATH}/${API})
message(STATUS "Debug symbols will be placed at ${DEBUG_SYMBOLS_PATH}")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $<TARGET_FILE:${PROJECT_NAME}>
${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:${PROJECT_NAME}>
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
$<TARGET_FILE:${PROJECT_NAME}>)
endif()

View File

@ -0,0 +1,125 @@
#ifndef RIRU_H
#define RIRU_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <jni.h>
#include <sys/types.h>
#include <stddef.h>
// ---------------------------------------------------------
typedef void(onModuleLoaded_v9)();
#ifndef RIRU_MODULE
typedef int(shouldSkipUid_v9)(int uid);
#endif
typedef void(nativeForkAndSpecializePre_v9)(
JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote,
jstring *instructionSet, jstring *appDataDir, jboolean *isTopApp,
jobjectArray *pkgDataInfoList,
jobjectArray *whitelistedDataInfoList, jboolean *bindMountAppDataDirs,
jboolean *bindMountAppStorageDirs);
typedef void(nativeForkAndSpecializePost_v9)(JNIEnv *env, jclass cls, jint res);
typedef void(nativeForkSystemServerPre_v9)(
JNIEnv *env, jclass cls, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags,
jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities);
typedef void(nativeForkSystemServerPost_v9)(JNIEnv *env, jclass cls, jint res);
typedef void(nativeSpecializeAppProcessPre_v9)(
JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags,
jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName,
jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir,
jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList,
jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs);
typedef void(nativeSpecializeAppProcessPost_v9)(JNIEnv *env, jclass cls);
typedef struct {
int supportHide;
int version;
const char *versionName;
onModuleLoaded_v9 *onModuleLoaded;
#ifndef RIRU_MODULE
shouldSkipUid_v9 *shouldSkipUid;
#else
void *unused;
#endif
nativeForkAndSpecializePre_v9 *forkAndSpecializePre;
nativeForkAndSpecializePost_v9 *forkAndSpecializePost;
nativeForkSystemServerPre_v9 *forkSystemServerPre;
nativeForkSystemServerPost_v9 *forkSystemServerPost;
nativeSpecializeAppProcessPre_v9 *specializeAppProcessPre;
nativeSpecializeAppProcessPost_v9 *specializeAppProcessPost;
} RiruModuleInfo;
typedef struct {
int moduleApiVersion;
RiruModuleInfo moduleInfo;
} RiruVersionedModuleInfo;
// ---------------------------------------------------------
typedef struct {
int riruApiVersion;
void *unused;
const char *magiskModulePath;
int *allowUnload;
} Riru;
typedef RiruVersionedModuleInfo *(RiruInit_t)(Riru *);
#ifdef RIRU_MODULE
#define RIRUD_ADDRESS "rirud"
#if __cplusplus < 201103L
#define RIRU_EXPORT __attribute__((visibility("default"))) __attribute__((used))
#else
#define RIRU_EXPORT [[gnu::visibility("default")]] [[gnu::used]]
#endif
RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) ;
extern int riru_api_version;
extern const char *riru_magisk_module_path;
extern int *riru_allow_unload;
#if !__cplusplus && __STDC_VERSION__ < 199409L
#define RIRU_INLINE __attribute__((weak)) __inline__
#elif !__cplusplus
#define RIRU_INLINE __attribute__((weak)) inline extern
#else
#define RIRU_INLINE inline
#endif
RIRU_INLINE const char *riru_get_magisk_module_path() {
if (riru_api_version >= 24) {
return riru_magisk_module_path;
}
return NULL;
}
RIRU_INLINE void riru_set_unload_allowed(int allowed) {
if (riru_api_version >= 25 && riru_allow_unload) {
*riru_allow_unload = allowed;
}
}
#undef RIRU_INLINE
#endif
#ifdef __cplusplus
}
#endif
#endif //RIRU_H

View File

@ -28,7 +28,7 @@
#include "symbol_cache.h" #include "symbol_cache.h"
#define RIRU_MODULE #define RIRU_MODULE
#include <riru.h> #include "riru.h"
namespace lspd { namespace lspd {
int *allowUnload = nullptr; int *allowUnload = nullptr;

View File

@ -22,7 +22,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/mman.h> #include <sys/mman.h>
#include "jni/zygisk.h" #include "zygisk.h"
#include "logging.h" #include "logging.h"
#include "context.h" #include "context.h"
#include "config.h" #include "config.h"
@ -328,7 +328,10 @@ namespace lspd {
auto *process = env_->FindClass("android/os/Process"); auto *process = env_->FindClass("android/os/Process");
auto *set_argv0 = env_->GetStaticMethodID(process, "setArgV0", auto *set_argv0 = env_->GetStaticMethodID(process, "setArgV0",
"(Ljava/lang/String;)V"); "(Ljava/lang/String;)V");
JNI_CallStaticVoidMethod(env_, process, set_argv0, JNI_NewStringUTF(env_, "system_server")); auto *name = env_->NewStringUTF("system_server");
env_->CallStaticVoidMethod(process, set_argv0, name);
env_->DeleteLocalRef(name);
env_->DeleteLocalRef(process);
} }
Context::GetInstance()->OnNativeForkSystemServerPost(env_); Context::GetInstance()->OnNativeForkSystemServerPost(env_);
if (*allowUnload) api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY); if (*allowUnload) api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);

View File

@ -21,14 +21,14 @@
#pragma once #pragma once
#include "symbol_cache.h" #include "symbol_cache.h"
#include "base/object.h" #include "utils/hook_helper.hpp"
#include "context.h" #include "context.h"
#include "runtime.h" #include "runtime.h"
namespace art { namespace art {
namespace hidden_api { namespace hidden_api {
using lsplant::operator""_tstr;
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv *env, jclass clazz, CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv *env, jclass clazz,
jobject j_cookie) { jobject j_cookie) {
if (DexFile_setTrustedSym != nullptr) [[likely]] { if (DexFile_setTrustedSym != nullptr) [[likely]] {
@ -36,7 +36,7 @@ namespace art {
DexFile_setTrustedSym(env, clazz, j_cookie); DexFile_setTrustedSym(env, clazz, j_cookie);
Runtime::Current()->SetJavaDebuggable(false); Runtime::Current()->SetJavaDebuggable(false);
} }
}; }
inline void inline void
maybeSetTrusted(JNIEnv *env, jclass clazz, jobject class_loader, jobject j_cookie) { maybeSetTrusted(JNIEnv *env, jclass clazz, jobject class_loader, jobject j_cookie) {
@ -54,7 +54,7 @@ namespace art {
} }
} }
CREATE_HOOK_STUB_ENTRIES( CREATE_HOOK_STUB_ENTRY(
"_ZN3artL25DexFile_openDexFileNativeEP7_JNIEnvP7_jclassP8_jstringS5_iP8_jobjectP13_jobjectArray", "_ZN3artL25DexFile_openDexFileNativeEP7_JNIEnvP7_jclassP8_jstringS5_iP8_jobjectP13_jobjectArray",
jobject, DexFile_openDexFileNative, (JNIEnv * env, jobject, DexFile_openDexFileNative, (JNIEnv * env,
jclass clazz, jclass clazz,
@ -71,7 +71,7 @@ namespace art {
} }
); );
CREATE_HOOK_STUB_ENTRIES( CREATE_HOOK_STUB_ENTRY(
"_ZN3artL34DexFile_openInMemoryDexFilesNativeEP7_JNIEnvP7_jclassP13_jobjectArrayS5_P10_jintArrayS7_P8_jobjectS5_", "_ZN3artL34DexFile_openInMemoryDexFilesNativeEP7_JNIEnvP7_jclassP13_jobjectArrayS5_P10_jintArrayS7_P8_jobjectS5_",
jobject, DexFile_openInMemoryDexFilesNative, (JNIEnv * env, jobject, DexFile_openInMemoryDexFilesNative, (JNIEnv * env,
jclass clazz, jclass clazz,
@ -89,7 +89,7 @@ namespace art {
} }
); );
CREATE_HOOK_STUB_ENTRIES( CREATE_HOOK_STUB_ENTRY(
"_ZN3artL29DexFile_createCookieWithArrayEP7_JNIEnvP7_jclassP11_jbyteArrayii", "_ZN3artL29DexFile_createCookieWithArrayEP7_JNIEnvP7_jclassP11_jbyteArrayii",
jobject, DexFile_createCookieWithArray, (JNIEnv * env, jobject, DexFile_createCookieWithArray, (JNIEnv * env,
jclass clazz, jclass clazz,
@ -102,7 +102,7 @@ namespace art {
} }
); );
CREATE_HOOK_STUB_ENTRIES( CREATE_HOOK_STUB_ENTRY(
"_ZN3artL36DexFile_createCookieWithDirectBufferEP7_JNIEnvP7_jclassP8_jobjectii", "_ZN3artL36DexFile_createCookieWithDirectBufferEP7_JNIEnvP7_jclassP8_jobjectii",
jobject, DexFile_createCookieWithDirectBuffer, (JNIEnv * env, jobject, DexFile_createCookieWithDirectBuffer, (JNIEnv * env,
jclass clazz, jclass clazz,
@ -115,20 +115,20 @@ namespace art {
} }
); );
inline void DisableHiddenApi([[maybe_unused]] const SandHook::ElfImg &handle) { inline void DisableHiddenApi(const lsplant::HookHandler &handler) {
const int api_level = lspd::GetAndroidApiLevel(); const int api_level = lspd::GetAndroidApiLevel();
if (api_level < __ANDROID_API_P__) { if (api_level < __ANDROID_API_P__) {
return; return;
} }
DexFile_setTrustedSym = reinterpret_cast<decltype(DexFile_setTrustedSym)>(lspd::symbol_cache->setTrusted); DexFile_setTrustedSym = reinterpret_cast<decltype(DexFile_setTrustedSym)>(lspd::symbol_cache->setTrusted);
lspd::HookSymNoHandle(lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative); lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative);
lspd::HookSymNoHandle(lspd::symbol_cache->openInMemoryDexFilesNative, lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openInMemoryDexFilesNative,
DexFile_openInMemoryDexFilesNative); DexFile_openInMemoryDexFilesNative);
if (api_level == __ANDROID_API_P__) { if (api_level == __ANDROID_API_P__) {
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithArray, lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithArray,
DexFile_createCookieWithArray); DexFile_createCookieWithArray);
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithDirectBuffer, lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithDirectBuffer,
DexFile_createCookieWithDirectBuffer); DexFile_createCookieWithDirectBuffer);
} }
}; };

View File

@ -20,11 +20,11 @@
#pragma once #pragma once
#include <base/object.h> #include "utils/hook_helper.hpp"
namespace art { namespace art {
class Runtime : public lspd::HookedObject { class Runtime {
private: private:
inline static Runtime *instance_; inline static Runtime *instance_;
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) { CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
@ -34,23 +34,22 @@ namespace art {
} }
public: public:
Runtime(void *thiz) : HookedObject(thiz) {}
inline static Runtime *Current() { inline static Runtime *Current() {
return instance_; return instance_;
} }
void SetJavaDebuggable(bool value) { void SetJavaDebuggable(bool value) {
SetJavaDebuggable(thiz_, value); SetJavaDebuggable(this, value);
} }
// @ApiSensitive(Level.LOW) // @ApiSensitive(Level.LOW)
inline static void Setup(const SandHook::ElfImg &handle) { inline static void Setup(const lsplant::HookHandler &handler) {
void **instance = nullptr;
RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E"); RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E");
RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb"); RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb");
void *thiz = *reinterpret_cast<void **>(instance); void *thiz = *instance;
LOGD("_ZN3art7Runtime9instance_E = %p", thiz); LOGD("_ZN3art7Runtime9instance_E = %p", thiz);
instance_ = new Runtime(thiz); instance_ = reinterpret_cast<Runtime*>(thiz);
} }
}; };

View File

@ -25,12 +25,13 @@
#include <string> #include <string>
#include "macros.h" #include "macros.h"
#include "utils.h" #include "utils.h"
#include "utils/hook_helper.hpp"
namespace lspd { namespace lspd {
//#define LOG_DISABLED //#define LOG_DISABLED
//#define DEBUG //#define DEBUG
using lsplant::operator""_tstr;
inline bool constexpr Is64() { inline bool constexpr Is64() {
#if defined(__LP64__) #if defined(__LP64__)
@ -59,9 +60,7 @@ namespace lspd {
#endif #endif
inline static constexpr auto kEntryClassName = "org.lsposed.lspd.core.Main"_tstr; inline static constexpr auto kEntryClassName = "org.lsposed.lspd.core.Main"_tstr;
inline static constexpr auto kClassLinkerClassName = "org.lsposed.lspd.nativebridge.ClassLinker"_tstr;
inline static constexpr auto kBridgeServiceClassName = "org.lsposed.lspd.service.BridgeService"_tstr; inline static constexpr auto kBridgeServiceClassName = "org.lsposed.lspd.service.BridgeService"_tstr;
inline static constexpr auto kDexPath = "framework/lspd.dex"_tstr;
inline static constexpr auto kLibArtName = "libart.so"_tstr; inline static constexpr auto kLibArtName = "libart.so"_tstr;
inline static constexpr auto kLibFwName = "libandroidfw.so"_tstr; inline static constexpr auto kLibFwName = "libandroidfw.so"_tstr;

View File

@ -22,6 +22,7 @@
#include <variant> #include <variant>
#include <cstdint> #include <cstdint>
#include "utils/hook_helper.hpp"
// @ApiSensitive(Level.MIDDLE) // @ApiSensitive(Level.MIDDLE)
namespace android { namespace android {
@ -173,7 +174,7 @@ namespace android {
return {nullptr, 0u}; return {nullptr, 0u};
} }
static bool setup(const SandHook::ElfImg &handle) { static bool setup(const lsplant::HookHandler &handler) {
RETRIEVE_MEM_FUNC_SYMBOL(stringAt, LP_SELECT("_ZNK7android13ResStringPool8stringAtEjPj", "_ZNK7android13ResStringPool8stringAtEmPm")); RETRIEVE_MEM_FUNC_SYMBOL(stringAt, LP_SELECT("_ZNK7android13ResStringPool8stringAtEjPj", "_ZNK7android13ResStringPool8stringAtEmPm"));
RETRIEVE_MEM_FUNC_SYMBOL(stringAtS, LP_SELECT("_ZNK7android13ResStringPool8stringAtEj", "_ZNK7android13ResStringPool8stringAtEm")); RETRIEVE_MEM_FUNC_SYMBOL(stringAtS, LP_SELECT("_ZNK7android13ResStringPool8stringAtEj", "_ZNK7android13ResStringPool8stringAtEm"));
return !stringAtSym || !stringAtSSym; return !stringAtSym || !stringAtSSym;

View File

@ -0,0 +1,13 @@
#pragma once
#include <stddef.h> // for size_t
#include <unistd.h> // for TEMP_FAILURE_RETRY
#include <utility>
// The arraysize(arr) macro returns the # of elements in an array arr.
// The expression is a compile-time constant, and therefore can be
// used in defining new arrays, for example. If you use arraysize on
// a pointer by mistake, you will get a compile-time error.
template<typename T, size_t N>
[[gnu::always_inline]] constexpr inline size_t arraysize(T(&)[N]) {
return N;
}

View File

@ -0,0 +1,113 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#include <dlfcn.h>
#include "dobby.h"
#include <sys/mman.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-value"
#pragma once
#include <context.h>
#include "macros.h"
#include "utils/jni_helper.hpp"
#include "logging.h"
#include "config.h"
#include <cassert>
#define _uintval(p) reinterpret_cast<uintptr_t>(p)
#define _ptr(p) reinterpret_cast<void *>(p)
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
#define _align_down(x, n) ((x) & -(n))
#define _page_size 4096
#define _page_align(n) _align_up(static_cast<uintptr_t>(n), _page_size)
#define _ptr_align(x) _ptr(_align_down(reinterpret_cast<uintptr_t>(x), _page_size))
#define _make_rwx(p, n) ::mprotect(_ptr_align(p), \
_page_align(_uintval(p) + n) != _page_align(_uintval(p)) ? _page_align(n) + _page_size : _page_align(n), \
PROT_READ | PROT_WRITE | PROT_EXEC)
namespace lspd {
[[gnu::always_inline]]
inline bool RegisterNativeMethodsInternal(JNIEnv *env,
const char *class_name,
const JNINativeMethod *methods,
jint method_count) {
auto clazz = Context::GetInstance()->FindClassFromCurrentLoader(env, class_name);
if (clazz.get() == nullptr) {
LOGF("Couldn't find class: %s", class_name);
return false;
}
return JNI_RegisterNatives(env, clazz, methods, method_count);
}
#if defined(__cplusplus)
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
reinterpret_cast<to>
#else
#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
(to)
#endif
#ifndef LSP_NATIVE_METHOD
#define LSP_NATIVE_METHOD(className, functionName, signature) \
{ #functionName, \
signature, \
_NATIVEHELPER_JNI_MACRO_CAST(void*) (Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName) \
}
#endif
#define JNI_START [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz
#ifndef LSP_DEF_NATIVE_METHOD
#define LSP_DEF_NATIVE_METHOD(ret, className, functionName, ...) \
extern "C" ret Java_org_lsposed_lspd_nativebridge_## className ## _ ## functionName (JNI_START, ## __VA_ARGS__)
#endif
#define REGISTER_LSP_NATIVE_METHODS(class_name) \
RegisterNativeMethodsInternal(env, "org.lsposed.lspd.nativebridge." #class_name, gMethods, arraysize(gMethods))
inline int HookFunction(void *original, void *replace, void **backup) {
_make_rwx(original, _page_size);
if constexpr (isDebug) {
Dl_info info;
if (dladdr(original, &info))
LOGD("Hooking %s (%p) from %s (%p)",
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
}
return DobbyHook(original, replace, backup);
}
inline int UnhookFunction(void *original) {
if constexpr (isDebug) {
Dl_info info;
if (dladdr(original, &info))
LOGD("Unhooking %s (%p) from %s (%p)",
info.dli_sname ? info.dli_sname : "(unknown symbol)", info.dli_saddr,
info.dli_fname ? info.dli_fname : "(unknown file)", info.dli_fbase);
}
return DobbyDestroy(original);
}
} // namespace lspd
#pragma clang diagnostic pop

View File

@ -43,45 +43,6 @@ namespace lspd {
}(); }();
return api_level; return api_level;
} }
template<char... chars>
struct tstring : public std::integer_sequence<char, chars...> {
inline constexpr static const char *c_str() {
return str_;
}
inline constexpr operator std::string_view() const {
return c_str();
}
private:
constexpr static char str_[]{chars..., '\0'};
};
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...> &) {
return {};
}
template<char... as>
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
char b[]{as..., '\0'};
return a + b;
}
template<char... as>
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
char a[]{as..., '\0'};
return a + b;
}
} }
#pragma clang diagnostic pop #pragma clang diagnostic pop

View File

@ -19,19 +19,23 @@
*/ */
#include <jni.h> #include <jni.h>
#include "jni_helper.h" #include "config.h"
#include "jni/art_class_linker.h" #include "utils/jni_helper.hpp"
#include "jni/yahfa.h"
#include "jni/resources_hook.h" #include "jni/resources_hook.h"
#include <art/runtime/jni_env_ext.h>
#include "context.h" #include "context.h"
#include "native_hook.h" #include "native_hook.h"
#include "elf_util.h"
#include "jni/hook_bridge.h"
#include "jni/native_api.h" #include "jni/native_api.h"
#include "service.h" #include "service.h"
#include <sys/mman.h>
#include "symbol_cache.h" #include "symbol_cache.h"
#include <linux/fs.h> #include <linux/fs.h>
#include <fcntl.h> #include <fcntl.h>
#include <native_util.h>
using namespace lsplant;
static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602)); static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602));
@ -96,15 +100,17 @@ namespace lspd {
void Context::Init() { void Context::Init() {
} }
void Context::Init(JNIEnv *env) { void Context::Init(JNIEnv *env, const lsplant::InitInfo& initInfo) {
if (!lsplant::Init(env, initInfo)) {
return;
}
if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(), if (auto entry_class = FindClassFromLoader(env, GetCurrentClassLoader(),
kEntryClassName)) { kEntryClassName)) {
entry_class_ = JNI_NewGlobalRef(env, entry_class); entry_class_ = JNI_NewGlobalRef(env, entry_class);
} }
RegisterResourcesHook(env); RegisterResourcesHook(env);
RegisterArtClassLinker(env); RegisterHookBridge(env);
RegisterYahfa(env);
RegisterNativeAPI(env); RegisterNativeAPI(env);
} }
@ -179,9 +185,22 @@ namespace lspd {
instance->HookBridge(*this, env); instance->HookBridge(*this, env);
if (application_binder) { if (application_binder) {
InstallInlineHooks(); lsplant::InitInfo initInfo{
Init(env); .inline_hooker = [](auto t, auto r) {
void* bk = nullptr;
return HookFunction(t, r, &bk) == RS_SUCCESS ? bk : nullptr;
},
.inline_unhooker = [](auto t) {
return UnhookFunction(t) == RT_SUCCESS ;
},
.art_symbol_resolver = [](auto symbol) {
return GetArt()->getSymbAddress<void*>(symbol);
},
};
InstallInlineHooks(initInfo);
Init(env, initInfo);
FindAndCall(env, "forkSystemServerPost", "(Landroid/os/IBinder;)V", application_binder); FindAndCall(env, "forkSystemServerPost", "(Landroid/os/IBinder;)V", application_binder);
GetArt(true);
} else { } else {
LOGI("skipped system server"); LOGI("skipped system server");
GetArt(true); GetArt(true);
@ -236,11 +255,23 @@ namespace lspd {
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr} auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
: instance->RequestBinder(env, nice_name); : instance->RequestBinder(env, nice_name);
if (binder) { if (binder) {
InstallInlineHooks(); lsplant::InitInfo initInfo{
.inline_hooker = [](auto t, auto r) {
void* bk = nullptr;
return HookFunction(t, r, &bk) == RS_SUCCESS ? bk : nullptr;
},
.inline_unhooker = [](auto t) {
return UnhookFunction(t) == RT_SUCCESS;
},
.art_symbol_resolver = [](auto symbol){
return GetArt()->getSymbAddress<void*>(symbol);
},
};
InstallInlineHooks(initInfo);
auto [dex_fd, size] = instance->RequestLSPDex(env, binder); auto [dex_fd, size] = instance->RequestLSPDex(env, binder);
LoadDex(env, dex_fd, size); LoadDex(env, dex_fd, size);
close(dex_fd); close(dex_fd);
Init(env); Init(env, initInfo);
LOGD("Done prepare"); LOGD("Done prepare");
FindAndCall(env, "forkAndSpecializePost", FindAndCall(env, "forkAndSpecializePost",
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", "(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
@ -248,6 +279,7 @@ namespace lspd {
binder); binder);
LOGD("injected xposed into %s", process_name.get()); LOGD("injected xposed into %s", process_name.get());
setAllowUnload(false); setAllowUnload(false);
GetArt(true);
} else { } else {
auto context = Context::ReleaseInstance(); auto context = Context::ReleaseInstance();
auto service = Service::ReleaseInstance(); auto service = Service::ReleaseInstance();

View File

@ -26,8 +26,9 @@
#include <string> #include <string>
#include <tuple> #include <tuple>
#include <string_view> #include <string_view>
#include <lsplant.hpp>
#include "utils.h" #include "utils.h"
#include "jni_helper.h" #include "utils/jni_helper.hpp"
namespace lspd { namespace lspd {
class Context { class Context {
@ -43,7 +44,7 @@ namespace lspd {
inline jobject GetCurrentClassLoader() const { return inject_class_loader_; } inline jobject GetCurrentClassLoader() const { return inject_class_loader_; }
inline ScopedLocalRef<jclass> inline lsplant::ScopedLocalRef<jclass>
FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const { FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
return FindClassFromLoader(env, GetCurrentClassLoader(), className); return FindClassFromLoader(env, GetCurrentClassLoader(), className);
}; };
@ -108,9 +109,9 @@ namespace lspd {
void LoadDex(JNIEnv *env, int fd, size_t size); void LoadDex(JNIEnv *env, int fd, size_t size);
void Init(JNIEnv *env); void Init(JNIEnv *env, const lsplant::InitInfo& initInfo);
static ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader, static lsplant::ScopedLocalRef<jclass> FindClassFromLoader(JNIEnv *env, jobject class_loader,
std::string_view class_name); std::string_view class_name);
static void setAllowUnload(bool unload); static void setAllowUnload(bool unload);

View File

@ -0,0 +1,188 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#include "hook_bridge.h"
#include "native_util.h"
#include "lsplant.hpp"
#include "unordered_map"
#include <shared_mutex>
#include <set>
using namespace lsplant;
namespace {
struct HookItem {
jobject backup {nullptr};
jobjectArray callbacks {nullptr};
std::multiset<jint> priorities {};
};
std::shared_mutex hooked_lock;
// Rehashing invalidates iterators, changes ordering between elements, and changes which buckets elements appear in, but does not invalidate pointers or references to elements.
std::unordered_map<jmethodID, HookItem> hooked_methods;
jmethodID invoke = nullptr;
}
namespace lspd {
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jobject hookMethod,
jclass hooker, jint priority, jobject callback) {
bool newHook = false;
#ifndef NDEBUG
struct finally {
std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
bool &newHook;
~finally() {
auto finish = std::chrono::steady_clock::now();
if (newHook) {
LOGV("New hook took %lldus",
std::chrono::duration_cast<std::chrono::microseconds>(finish - start).count());
}
}
} finally {
.newHook = newHook
};
#endif
auto target = env->FromReflectedMethod(hookMethod);
HookItem * hook_item = nullptr;
{
std::shared_lock lk(hooked_lock);
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
hook_item = &found->second;
}
}
if (!hook_item) {
std::unique_lock lk(hooked_lock);
hook_item = &hooked_methods[target];
if (!hook_item->callbacks) {
newHook = true;
hook_item->callbacks = (jobjectArray) env->NewGlobalRef(
env->NewObjectArray(1, env->FindClass("[Ljava/lang/Object;"),
env->NewObjectArray(0, JNI_FindClass(env, "java/lang/Object"), nullptr)));
}
}
if (newHook) {
auto init = env->GetMethodID(hooker, "<init>", "(Ljava/lang/reflect/Executable;[[Ljava/lang/Object;)V");
auto callback_method = env->ToReflectedMethod(hooker, env->GetMethodID(hooker, "callback",
"([Ljava/lang/Object;)Ljava/lang/Object;"),
false);
auto hooker_object = env->NewObject(hooker, init, hookMethod, hook_item->callbacks);
hook_item->backup = lsplant::Hook(env, hookMethod, hooker_object, callback_method);
env->DeleteLocalRef(hooker_object);
}
env->MonitorEnter(hook_item->callbacks);
auto insert_point = hook_item->priorities.emplace(priority);
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0);
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size()), env->FindClass("java/lang/Object"), nullptr);
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) {
if (current == insert_point) {
env->SetObjectArrayElement(new_array, i, callback);
passed = true;
} else {
auto element = env->GetObjectArrayElement(old_array, i - passed);
env->SetObjectArrayElement(new_array, i, element);
env->DeleteLocalRef(element);
}
}
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
env->DeleteLocalRef(old_array);
env->DeleteLocalRef(new_array);
env->MonitorExit(hook_item->callbacks);
return hook_item->backup ? JNI_TRUE : JNI_FALSE;
}
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jobject hookMethod, jobject callback) {
auto target = env->FromReflectedMethod(hookMethod);
HookItem * hook_item = nullptr;
{
std::shared_lock lk(hooked_lock);
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
hook_item = &found->second;
}
}
if (!hook_item) return JNI_FALSE;
env->MonitorEnter(hook_item->callbacks);
auto old_array = (jobjectArray) env->GetObjectArrayElement(hook_item->callbacks, 0);
auto new_array = env->NewObjectArray(static_cast<jint>(hook_item->priorities.size() - 1), env->FindClass("java/lang/Object"), nullptr);
auto to_remove = hook_item->priorities.end();
for (auto [i, current, passed] = std::make_tuple(0, hook_item->priorities.begin(), false); current != hook_item->priorities.end(); ++current, ++i) {
auto element = env->GetObjectArrayElement(old_array, i);
if (env->IsSameObject(element, callback)) {
to_remove = current;
passed = true;
} else {
if (i - passed >= hook_item->priorities.size() - 1) {
return JNI_FALSE;
}
env->SetObjectArrayElement(new_array, i - passed, element);
}
env->DeleteLocalRef(element);
}
bool removed = false;
if (to_remove != hook_item->priorities.end()) {
hook_item->priorities.erase(to_remove);
env->SetObjectArrayElement(hook_item->callbacks, 0, new_array);
removed = true;
}
env->DeleteLocalRef(old_array);
env->DeleteLocalRef(new_array);
env->MonitorExit(hook_item->callbacks);
return removed ? JNI_TRUE : JNI_FALSE;
}
LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, deoptimizeMethod, jobject hookMethod,
jclass hooker, jint priority, jobject callback) {
return lsplant::Deoptimize(env, hookMethod);
}
LSP_DEF_NATIVE_METHOD(jobject, HookBridge, invokeOriginalMethod, jobject hookMethod,
jobject thiz, jobjectArray args) {
auto target = env->FromReflectedMethod(hookMethod);
HookItem * hook_item = nullptr;
{
std::shared_lock lk(hooked_lock);
if (auto found = hooked_methods.find(target); found != hooked_methods.end()) {
hook_item = &found->second;
}
}
jobject to_call = hookMethod;
if (hook_item && hook_item->backup) {
to_call = hook_item->backup;
}
return env->CallObjectMethod(to_call, invoke, thiz, args);
}
static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(HookBridge, hookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Class;ILjava/lang/Object;)Z"),
LSP_NATIVE_METHOD(HookBridge, unhookMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;)Z"),
LSP_NATIVE_METHOD(HookBridge, deoptimizeMethod, "(Ljava/lang/reflect/Executable;)Z"),
LSP_NATIVE_METHOD(HookBridge, invokeOriginalMethod, "(Ljava/lang/reflect/Executable;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"),
};
void RegisterHookBridge(JNIEnv *env) {
auto method = env->FindClass("java/lang/reflect/Method");
invoke = env->GetMethodID(
method, "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
env->DeleteLocalRef(method);
REGISTER_LSP_NATIVE_METHODS(HookBridge);
}
} // namespace lspd

View File

@ -0,0 +1,11 @@
//
// Created by loves on 3/13/2022.
//
#pragma once
#include <jni.h>
namespace lspd {
void RegisterHookBridge(JNIEnv* env);
}

View File

@ -23,12 +23,14 @@
// //
#include "native_api.h" #include "native_api.h"
#include "native_util.h" #include "native_util.h"
#include "jni_helper.h" #include "utils/jni_helper.hpp"
#include "../native_api.h" #include "../native_api.h"
using namespace lsplant;
namespace lspd { namespace lspd {
LSP_DEF_NATIVE_METHOD(void, NativeAPI, recordNativeEntrypoint, jstring jstr) { LSP_DEF_NATIVE_METHOD(void, NativeAPI, recordNativeEntrypoint, jstring jstr) {
JUTFString str(env, jstr); lsplant::JUTFString str(env, jstr);
RegisterNativeLib(str); RegisterNativeLib(str);
} }

View File

@ -19,25 +19,24 @@
*/ */
#include <jni.h> #include <jni.h>
#include <dex_builder.h> #include "dex_builder.h"
#include <art/runtime/thread.h> #include "framework/androidfw/resource_types.h"
#include <art/runtime/mirror/class.h> #include "elf_util.h"
#include <framework/androidfw/resource_types.h>
#include <HookMain.h>
#include <elf_util.h>
#include "native_util.h" #include "native_util.h"
#include "resources_hook.h" #include "resources_hook.h"
using namespace lsplant;
namespace lspd { namespace lspd {
static constexpr const char *kXResourcesClassName = "android/content/res/XResources"; static constexpr const char *kXResourcesClassName = "android/content/res/XResources";
typedef int32_t (*TYPE_GET_ATTR_NAME_ID)(void *, int); using TYPE_GET_ATTR_NAME_ID = int32_t (*)(void *, int);
typedef char16_t *(*TYPE_STRING_AT)(const void *, int32_t, size_t *); using TYPE_STRING_AT = char16_t *(*)(const void *, int32_t, size_t *);
typedef void(*TYPE_RESTART)(void *); using TYPE_RESTART = void (*)(void *);
typedef int32_t (*TYPE_NEXT)(void *); using TYPE_NEXT = int32_t (*)(void *);
static jclass classXResources; static jclass classXResources;
static jmethodID methodXResourcesTranslateAttrId; static jmethodID methodXResourcesTranslateAttrId;
@ -65,7 +64,11 @@ namespace lspd {
"_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) { "_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) {
return false; return false;
} }
return android::ResStringPool::setup(fw); return android::ResStringPool::setup(HookHandler{
.art_symbol_resolver = [&](auto s) {
return fw.template getSymbAddress<void*>(s);
}
});
} }
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, initXResourcesNative) { LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, initXResourcesNative) {
@ -97,29 +100,14 @@ namespace lspd {
// @ApiSensitive(Level.MIDDLE) // @ApiSensitive(Level.MIDDLE)
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, makeInheritable, jclass target_class, LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, makeInheritable, jclass target_class,
jobjectArray constructors) { jobjectArray constructors) {
if (target_class) { if (lsplant::MakeClassInheritable(env, target_class)) {
static auto class_clazz = JNI_NewGlobalRef(env, JNI_FindClass(env, "java/lang/Class"));
static jfieldID java_lang_Class_accessFlags = JNI_GetFieldID(
env, class_clazz, "accessFlags", "I");
jint access_flags = env->GetIntField(target_class, java_lang_Class_accessFlags);
env->SetIntField(target_class, java_lang_Class_accessFlags, access_flags & ~kAccFinal);
for (auto i = 0; i < env->GetArrayLength(constructors); ++i) {
auto constructor = env->GetObjectArrayElement(constructors, i);
void *method = yahfa::getArtMethod(env, constructor);
uint32_t flags = yahfa::getAccessFlags(method);
if ((flags & yahfa::kAccPublic) == 0 && (flags & yahfa::kAccProtected) == 0) {
flags |= yahfa::kAccProtected;
flags &= ~yahfa::kAccPrivate;
}
yahfa::setAccessFlags(method, flags);
}
return JNI_TRUE; return JNI_TRUE;
} }
return JNI_FALSE; return JNI_FALSE;
} }
LSP_DEF_NATIVE_METHOD(jobject, ResourcesHook, buildDummyClassLoader, jobject parent, LSP_DEF_NATIVE_METHOD(jobject, ResourcesHook, buildDummyClassLoader, jobject parent,
jobject resource_super_class, jobject typed_array_super_class) { jstring resource_super_class, jstring typed_array_super_class) {
using namespace startop::dex; using namespace startop::dex;
static auto in_memory_classloader = JNI_NewGlobalRef(env, JNI_FindClass(env, static auto in_memory_classloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
"dalvik/system/InMemoryDexClassLoader")); "dalvik/system/InMemoryDexClassLoader"));
@ -127,17 +115,13 @@ namespace lspd {
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"); "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
DexBuilder dex_file; DexBuilder dex_file;
std::string storage;
auto current_thread = art::Thread::Current();
ClassBuilder xresource_builder{ ClassBuilder xresource_builder{
dex_file.MakeClass("xposed.dummy.XResourcesSuperClass")}; dex_file.MakeClass("xposed.dummy.XResourcesSuperClass")};
xresource_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class( xresource_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, resource_super_class).get()));
current_thread.DecodeJObject(resource_super_class)).GetDescriptor(&storage)));
ClassBuilder xtypearray_builder{ ClassBuilder xtypearray_builder{
dex_file.MakeClass("xposed.dummy.XTypedArraySuperClass")}; dex_file.MakeClass("xposed.dummy.XTypedArraySuperClass")};
xtypearray_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class( xtypearray_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, typed_array_super_class).get()));
current_thread.DecodeJObject(typed_array_super_class)).GetDescriptor(&storage)));
slicer::MemView image{dex_file.CreateImage()}; slicer::MemView image{dex_file.CreateImage()};
@ -222,7 +206,7 @@ namespace lspd {
LSP_NATIVE_METHOD(ResourcesHook, makeInheritable, LSP_NATIVE_METHOD(ResourcesHook, makeInheritable,
"(Ljava/lang/Class;[Ljava/lang/reflect/Constructor;)Z"), "(Ljava/lang/Class;[Ljava/lang/reflect/Constructor;)Z"),
LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader, LSP_NATIVE_METHOD(ResourcesHook, buildDummyClassLoader,
"(Ljava/lang/ClassLoader;Ljava/lang/Class;Ljava/lang/Class;)Ljava/lang/ClassLoader;"), "(Ljava/lang/ClassLoader;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/ClassLoader;"),
LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative, LSP_NATIVE_METHOD(ResourcesHook, rewriteXmlReferencesNative,
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V") "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V")
}; };

View File

@ -23,10 +23,15 @@
// //
#include "native_api.h" #include "native_api.h"
#include "logging.h"
#include "symbol_cache.h" #include "symbol_cache.h"
#include "utils/hook_helper.hpp"
#include <sys/mman.h>
#include <dobby.h> #include <dobby.h>
#include <list> #include <list>
#include <base/object.h> #include <dlfcn.h>
#include "native_util.h"
/* /*
* Module: define xposed_native file in /assets, each line is a .so file name * Module: define xposed_native file in /assets, each line is a .so file name
@ -44,26 +49,33 @@
*/ */
namespace lspd { namespace lspd {
using lsplant::operator""_tstr;
std::list<NativeOnModuleLoaded> moduleLoadedCallbacks; std::list<NativeOnModuleLoaded> moduleLoadedCallbacks;
std::list<std::string> moduleNativeLibs; std::list<std::string> moduleNativeLibs;
std::unique_ptr<void, std::function<void(void *)>> protected_page( std::unique_ptr<void, std::function<void(void *)>> protected_page(
mmap(nullptr, _page_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0), mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0),
[](void *ptr) { munmap(ptr, _page_size); }); [](void *ptr) { munmap(ptr, 4096); });
const auto[entries] = []() { const auto[entries] = []() {
auto *entries = new(protected_page.get()) NativeAPIEntries{ auto *entries = new(protected_page.get()) NativeAPIEntries{
.version = 2, .version = 2,
.hookFunc = HookFunction, .hookFunc = &HookFunction,
.unhookFunc = UnhookFunction .unhookFunc = &UnhookFunction,
}; };
mprotect(protected_page.get(), _page_size, PROT_READ); mprotect(protected_page.get(), 4096, PROT_READ);
return std::make_tuple(entries); return std::make_tuple(entries);
}(); }();
void RegisterNativeLib(const std::string &library_name) { void RegisterNativeLib(const std::string &library_name) {
static bool initialized = []() { static bool initialized = []() {
return InstallNativeAPI(); return InstallNativeAPI({
.inline_hooker = [](auto t, auto r) {
void* bk = nullptr;
return HookFunction(t, r, &bk) ? bk : nullptr;
},
});
}(); }();
if (!initialized) [[unlikely]] return; if (!initialized) [[unlikely]] return;
LOGD("native_api: Registered %s", library_name.c_str()); LOGD("native_api: Registered %s", library_name.c_str());
@ -74,12 +86,11 @@ namespace lspd {
if (fullString.length() >= ending.length()) { if (fullString.length() >= ending.length()) {
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(), return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(),
ending)); ending));
} else {
return false;
} }
return false;
} }
CREATE_HOOK_STUB_ENTRIES( CREATE_HOOK_STUB_ENTRY(
"__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv", "__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv",
void*, do_dlopen, (const char* name, int flags, const void* extinfo, void*, do_dlopen, (const char* name, int flags, const void* extinfo,
const void* caller_addr), { const void* caller_addr), {
@ -121,10 +132,10 @@ namespace lspd {
return handle; return handle;
}); });
bool InstallNativeAPI() { bool InstallNativeAPI(const lsplant::HookHandler & handler) {
LOGD("InstallNativeAPI: %p", symbol_cache->do_dlopen); LOGD("InstallNativeAPI: %p", symbol_cache->do_dlopen);
if (symbol_cache->do_dlopen) [[likely]] { if (symbol_cache->do_dlopen) [[likely]] {
HookSymNoHandle(symbol_cache->do_dlopen, do_dlopen); HookSymNoHandle(handler, symbol_cache->do_dlopen, do_dlopen);
return true; return true;
} }
return false; return false;

View File

@ -27,7 +27,8 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <base/object.h>
#include "utils/hook_helper.hpp"
typedef int (*HookFunType)(void *func, void *replace, void **backup); typedef int (*HookFunType)(void *func, void *replace, void **backup);
@ -44,7 +45,7 @@ typedef struct {
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries); typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
namespace lspd { namespace lspd {
bool InstallNativeAPI(); bool InstallNativeAPI(const lsplant::HookHandler& handler);
void RegisterNativeLib(const std::string &library_name); void RegisterNativeLib(const std::string &library_name);
} }

View File

@ -18,21 +18,28 @@
* Copyright (C) 2021 LSPosed Contributors * Copyright (C) 2021 LSPosed Contributors
*/ */
#pragma once #include <dlfcn.h>
#include <string>
#include "jni.h"
#include <unordered_set>
#include <utility>
#include <vector> #include <vector>
#include <dobby.h>
#include "symbol_cache.h"
#include "logging.h"
#include "native_api.h"
#include "native_hook.h"
#include "art/runtime/hidden_api.h"
namespace lspd { namespace lspd {
static std::atomic_bool installed = false;
void* isHooked(void* art_method); void InstallInlineHooks(const lsplant::HookHandler& handler) {
if (installed.exchange(true)) [[unlikely]] {
LOGD("Inline hooks have been installed, skip");
return;
}
LOGD("Start to install inline hooks");
art::Runtime::Setup(handler);
art::hidden_api::DisableHiddenApi(handler);
LOGD("Inline hooks installed");
}
} // namespace lspd
std::vector<std::pair<void*, void*>> getJitMovements();
const std::unordered_set<void*> &isClassHooked(void *clazz);
void RegisterYahfa(JNIEnv *);
} // namespace lspd

View File

@ -20,9 +20,9 @@
#pragma once #pragma once
#include <dobby.h> #include "utils/hook_helper.hpp"
namespace lspd { namespace lspd {
void InstallInlineHooks(); void InstallInlineHooks(const lsplant::HookHandler &handler);
} }

View File

@ -23,12 +23,14 @@
#include <dobby.h> #include <dobby.h>
#include <thread> #include <thread>
#include "base/object.h" #include "config.h"
#include "service.h" #include "service.h"
#include "context.h" #include "context.h"
#include "jni_helper.h" #include "utils/jni_helper.hpp"
#include "symbol_cache.h" #include "symbol_cache.h"
using namespace lsplant;
namespace lspd { namespace lspd {
jboolean jboolean
Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj, Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj,

View File

@ -51,13 +51,13 @@ namespace lspd {
void InitService(JNIEnv *env); void InitService(JNIEnv *env);
void HookBridge(const Context& context, JNIEnv *env); void HookBridge(const Context& context, JNIEnv *env);
ScopedLocalRef<jobject> RequestBinder(JNIEnv *env, jstring nice_name); lsplant::ScopedLocalRef<jobject> RequestBinder(JNIEnv *env, jstring nice_name);
ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env); lsplant::ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env);
ScopedLocalRef<jobject> RequestApplicationBinderFromSystemServer(JNIEnv *env, const ScopedLocalRef<jobject> &system_server_binder); lsplant::ScopedLocalRef<jobject> RequestApplicationBinderFromSystemServer(JNIEnv *env, const lsplant::ScopedLocalRef<jobject> &system_server_binder);
std::tuple<int, size_t> RequestLSPDex(JNIEnv *env, const ScopedLocalRef<jobject> &binder); std::tuple<int, size_t> RequestLSPDex(JNIEnv *env, const lsplant::ScopedLocalRef<jobject> &binder);
private: private:
inline static std::unique_ptr<Service> instance_ = std::make_unique<Service>(); inline static std::unique_ptr<Service> instance_ = std::make_unique<Service>();

View File

@ -0,0 +1,8 @@
#include "config.h"
namespace lspd {
const int versionCode = ${VERSION_CODE};
const int apiVersion = ${API_VERSION};
const char* const versionName = "${VERSION_NAME}";
const char* const moduleName = "${MODULE_NAME}";
}

3
daemon/.gitignore vendored
View File

@ -1 +1,2 @@
/build /build
/.cxx

View File

@ -65,12 +65,6 @@ android {
versionName = verName versionName = verName
multiDexEnabled = false multiDexEnabled = false
externalNativeBuild {
ndkBuild {
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
}
}
buildConfigField("int", "API_CODE", "$apiCode") buildConfigField("int", "API_CODE", "$apiCode")
buildConfigField( buildConfigField(
"String", "String",
@ -87,15 +81,58 @@ android {
} }
buildTypes { buildTypes {
debug {
externalNativeBuild {
cmake {
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
"-DCMAKE_C_FLAGS_DEBUG=-Og"
)
)
}
}
}
release { release {
isMinifyEnabled = true isMinifyEnabled = true
proguardFiles("proguard-rules.pro") proguardFiles("proguard-rules.pro")
externalNativeBuild {
cmake {
val flags = arrayOf(
"-Wl,--exclude-libs,ALL",
"-ffunction-sections",
"-fdata-sections",
"-Wl,--gc-sections",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
"-flto=thin",
"-Wl,--thinlto-cache-policy,cache_size_bytes=300m",
"-Wl,--thinlto-cache-dir=${buildDir.absolutePath}/.lto-cache",
)
cppFlags.addAll(flags)
cFlags.addAll(flags)
val configFlags = arrayOf(
"-Oz",
"-DNDEBUG"
).joinToString(" ")
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
"-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols",
)
)
}
}
} }
} }
externalNativeBuild { externalNativeBuild {
ndkBuild { cmake {
path("src/main/cpp/Android.mk") path("src/main/jni/CMakeLists.txt")
} }
} }
@ -104,12 +141,30 @@ android {
sourceCompatibility(androidSourceCompatibility) sourceCompatibility(androidSourceCompatibility)
} }
buildTypes { defaultConfig {
all { externalNativeBuild {
externalNativeBuild { cmake {
ndkBuild { arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}" abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
} val flags = arrayOf(
"-Wall",
"-Werror",
"-Qunused-arguments",
"-Wno-gnu-string-literal-operator-template",
"-fno-rtti",
"-fvisibility=hidden",
"-fvisibility-inlines-hidden",
"-fno-exceptions",
"-fno-stack-protector",
"-fomit-frame-pointer",
"-Wno-builtin-macro-redefined",
"-Wno-unused-value",
"-D__FILE__=__FILE_NAME__",
)
cppFlags("-std=c++20", *flags)
cFlags("-std=c18", *flags)
arguments("-DANDROID_STL=none")
targets("daemon")
} }
} }
} }
@ -165,7 +220,6 @@ afterEvaluate {
} }
dependencies { dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
implementation("com.android.tools.build:apksig:$agpVersion") implementation("com.android.tools.build:apksig:$agpVersion")
implementation("org.apache.commons:commons-lang3:3.12.0") implementation("org.apache.commons:commons-lang3:3.12.0")
compileOnly("androidx.annotation:annotation:1.3.0") compileOnly("androidx.annotation:annotation:1.3.0")

View File

@ -1,13 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := daemon
LOCAL_C_INCLUDES := ../core/src/main/cpp/main/include/
LOCAL_SRC_FILES := logcat.cpp obfuscation.cpp
LOCAL_STATIC_LIBRARIES := cxx dex_builder
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
LOCAL_LDLIBS := -llog -landroid
include $(BUILD_SHARED_LIBRARY)
include ../core/src/main/cpp/external/DexBuilder/Android.mk
$(call import-module,prefab/cxx)

View File

@ -1,15 +0,0 @@
APP_CFLAGS := -Wall -Wextra
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
APP_CPPFLAGS := -std=c++20
APP_CONLYFLAGS := -std=c18
APP_LDFLAGS := -Wl,--exclude-libs,ALL
APP_STL := none
ifneq ($(NDK_DEBUG),1)
APP_CFLAGS += -Oz -flto=thin
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 += -flto=thin -Wl,--thinlto-cache-policy,cache_size_bytes=10m -Wl,--thinlto-cache-dir=build/.lto-cache -Wl,--gc-sections -Wl,--strip-all
endif

View File

@ -0,0 +1,26 @@
cmake_minimum_required(VERSION 3.4.1)
project(daemon)
add_subdirectory(${EXTERNAL_ROOT} external)
set(SOURCES
logcat.cpp
obfuscation.cpp
)
add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(${PROJECT_NAME} PRIVATE lsplant_static dex_builder_static android log)
if (DEFINED DEBUG_SYMBOLS_PATH)
message(STATUS "Debug symbols will be placed at ${DEBUG_SYMBOLS_PATH}")
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}
COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $<TARGET_FILE:${PROJECT_NAME}>
${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
COMMAND ${CMAKE_STRIP} --strip-all $<TARGET_FILE:${PROJECT_NAME}>
COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME}.debug
$<TARGET_FILE:${PROJECT_NAME}>)
endif()

View File

@ -224,7 +224,7 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
shortcut = true; shortcut = true;
} }
if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH || if (verbose_ && (shortcut || buf->id() == log_id::LOG_ID_CRASH ||
entry.pid == my_pid_ || tag == "Magisk"sv || entry.pid == my_pid_ || tag == "Magisk"sv || tag == "Dobby"sv ||
tag.starts_with("Riru"sv) || tag.starts_with("zygisk"sv) || tag.starts_with("Riru"sv) || tag.starts_with("zygisk"sv) ||
tag.starts_with("LSPosed"sv))) [[unlikely]] { tag.starts_with("LSPosed"sv))) [[unlikely]] {
verbose_print_count_ += PrintLogLine(entry, verbose_file_.get()); verbose_print_count_ += PrintLogLine(entry, verbose_file_.get());

View File

@ -51,13 +51,13 @@ struct log_msg {
struct logger; struct logger;
struct logger_list; struct logger_list;
long android_logger_get_log_size(struct logger* logger); [[gnu::weak]] long android_logger_get_log_size(struct logger* logger);
int android_logger_set_log_size(struct logger *logger, unsigned long size); [[gnu::weak]] int android_logger_set_log_size(struct logger *logger, unsigned long size);
struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid); [[gnu::weak]] struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);
void android_logger_list_free(struct logger_list *logger_list); [[gnu::weak]] void android_logger_list_free(struct logger_list *logger_list);
int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg); [[gnu::weak]] int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg);
struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id); [[gnu::weak]] struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id);
int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry); [[gnu::weak]] int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -0,0 +1,51 @@
/*
* 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) 2020 EdXposed Contributors
* Copyright (C) 2021 LSPosed Contributors
*/
#ifndef _LOGGING_H
#define _LOGGING_H
#include <android/log.h>
#ifndef LOG_TAG
#define LOG_TAG "LSPosed"
#endif
#ifdef LOG_DISABLED
#define LOGD(...)
#define LOGV(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#else
#ifndef NDEBUG
#define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
#define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
#else
#define LOGD(...)
#define LOGV(...)
#endif
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
#endif
#endif // _LOGGING_H

View File

@ -34,7 +34,9 @@
#include "slicer/reader.h" #include "slicer/reader.h"
#include "slicer/writer.h" #include "slicer/writer.h"
#include "obfuscation.h" #include "obfuscation.h"
#include "logging.h"
using namespace lsplant;
namespace { namespace {
std::mutex init_lock{}; std::mutex init_lock{};
std::string obfuscated_signature; std::string obfuscated_signature;

View File

@ -19,7 +19,7 @@
#pragma once #pragma once
#include <string> #include <string>
#include "jni_helper.h" #include "utils/jni_helper.hpp"
class WA: public dex::Writer::Allocator { class WA: public dex::Writer::Allocator {
// addr: {size, fd} // addr: {size, fd}

111
external/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,111 @@
project(external)
find_program(CCACHE ccache)
if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
endif ()
macro(SET_OPTION option value)
set(${option} ${value} CACHE INTERNAL "" FORCE)
endmacro()
SET_OPTION(DOBBY_GENERATE_SHARED OFF)
if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
SET_OPTION(DOBBY_DEBUG OFF)
endif ()
set(LIBCXX_SOURCES
algorithm.cpp
any.cpp
atomic.cpp
barrier.cpp
bind.cpp
charconv.cpp
chrono.cpp
condition_variable.cpp
condition_variable_destructor.cpp
debug.cpp
exception.cpp
filesystem/directory_iterator.cpp
filesystem/int128_builtins.cpp
filesystem/operations.cpp
functional.cpp
future.cpp
hash.cpp
ios.cpp
iostream.cpp
locale.cpp
memory.cpp
mutex.cpp
mutex_destructor.cpp
new.cpp
optional.cpp
random.cpp
regex.cpp
shared_mutex.cpp
stdexcept.cpp
string.cpp
strstream.cpp
system_error.cpp
thread.cpp
typeinfo.cpp
utility.cpp
valarray.cpp
variant.cpp
vector.cpp
)
list(TRANSFORM LIBCXX_SOURCES PREPEND cxx/src/)
set(LIBCXX_EXPORT_FLAGS)
set(LIBCXX_FLAGS
-fvisibility-global-new-delete-hidden
-fvisibility=hidden
-fvisibility-inlines-hidden
-DLIBCXX_BUILDING_LIBCXXABI
-D_LIBCPP_NO_EXCEPTIONS
-D_LIBCPP_NO_RTTI
-D_LIBCPP_BUILDING_LIBRARY
-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS
-D__STDC_FORMAT_MACROS
)
set(LIBCXX_EXPORT_INCLUDES cxx/include)
set(LIBCXX_INCLUDES)
set(LIBCXXABI_SOURCES
abort_message.cpp
cxa_aux_runtime.cpp
cxa_default_handlers.cpp
cxa_exception_storage.cpp
cxa_guard.cpp
cxa_handlers.cpp
cxa_noexception.cpp
cxa_thread_atexit.cpp
cxa_unexpected.cpp
cxa_vector.cpp
cxa_virtual.cpp
stdlib_exception.cpp
stdlib_new_delete.cpp
stdlib_stdexcept.cpp
stdlib_typeinfo.cpp
)
list(TRANSFORM LIBCXXABI_SOURCES PREPEND cxx/src/abi/)
set(LIBCXXABI_FLAGS
-D_LIBCXXABI_NO_EXCEPTIONS
-Wno-macro-redefined
-Wno-unknown-attributes
-DHAS_THREAD_LOCAL)
set(LIBCXXABI_INCLUDES cxx/include/abi)
add_library(cxx STATIC ${LIBCXX_SOURCES} ${LIBCXXABI_SOURCES})
target_compile_options(cxx PUBLIC ${LIBCXX_EXPORT_FLAGS})
target_compile_options(cxx PRIVATE ${LIBCXX_FLAGS} ${LIBCXXABI_FLAGS} -ffunction-sections -fdata-sections)
target_include_directories(cxx PUBLIC ${LIBCXX_EXPORT_INCLUDES})
target_include_directories(cxx PRIVATE ${LIBCXX_INCLUDES} ${LIBCXXABI_INCLUDES})
link_libraries(cxx)
add_subdirectory(lsplant/lsplant/src/main/jni)
add_subdirectory(dobby)

1
external/cxx vendored Submodule

@ -0,0 +1 @@
Subproject commit 0aa67c3ffea069bdb58fe3b5e7aad06934afb292

Some files were not shown because too many files have changed in this diff Show More