Refactor `core` to use LSPlant as the ART hook framework (#1756)
This commit is contained in:
parent
2a258e3323
commit
0bf0948e9a
|
|
@ -46,6 +46,7 @@ jobs:
|
|||
with:
|
||||
path: |
|
||||
~/.gradle/caches/build-cache-*
|
||||
~/.gradle/buildOutputCleanup/cache.properties
|
||||
key: gradle-builds-core-${{ github.sha }}
|
||||
restore-keys: |
|
||||
gradle-builds
|
||||
|
|
@ -66,8 +67,6 @@ jobs:
|
|||
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
||||
ccache -zp
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
NDK_CCACHE: ccache
|
||||
run: |
|
||||
[ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true
|
||||
find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true
|
||||
|
|
@ -117,6 +116,13 @@ jobs:
|
|||
path: |
|
||||
core/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
|
||||
if: ${{ github.event_name != 'pull_request' && success() && github.ref == 'refs/heads/master' }}
|
||||
env:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
[submodule "service"]
|
||||
path = service
|
||||
url = https://github.com/libxposed/XposedService.git
|
||||
[submodule "core/src/main/cpp/external/DexBuilder"]
|
||||
path = core/src/main/cpp/external/DexBuilder
|
||||
url = https://github.com/LSPosed/DexBuilder.git
|
||||
[submodule "external/cxx"]
|
||||
path = external/cxx
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/build
|
||||
/release
|
||||
/src/main/cpp/main/api/config.cpp
|
||||
/src/main/cpp/api/config.cpp
|
||||
/.cxx
|
||||
|
|
|
|||
|
|
@ -73,11 +73,32 @@ android {
|
|||
multiDexEnabled = false
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "INJECTED_AID=$injectedPackageUid"
|
||||
arguments += "VERSION_CODE=$verCode"
|
||||
arguments += "VERSION_NAME=$verName"
|
||||
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
||||
cmake {
|
||||
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
|
||||
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
val flags = arrayOf(
|
||||
"-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 {
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.addAll(
|
||||
arrayOf(
|
||||
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
|
||||
"-DCMAKE_C_FLAGS_DEBUG=-Og",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
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 {
|
||||
ndkBuild {
|
||||
path("src/main/cpp/Android.mk")
|
||||
cmake {
|
||||
path("src/main/jni/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -113,22 +178,12 @@ android {
|
|||
sourceCompatibility(androidSourceCompatibility)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "MODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
|
||||
arguments += "API=${name.toLowerCase()}"
|
||||
cmake {
|
||||
arguments += "-DMODULE_NAME=${name.toLowerCase()}_$moduleBaseId"
|
||||
arguments += "-DAPI=${name.toLowerCase()}"
|
||||
}
|
||||
}
|
||||
buildConfigField("String", "API", """"$name"""")
|
||||
|
|
@ -137,8 +192,8 @@ android {
|
|||
create("Riru") {
|
||||
dimension = "api"
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "API_VERSION=$moduleMaxRiruApiVersion"
|
||||
cmake {
|
||||
arguments += "-DAPI_VERSION=$moduleMaxRiruApiVersion"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -146,8 +201,8 @@ android {
|
|||
create("Zygisk") {
|
||||
dimension = "api"
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "API_VERSION=1"
|
||||
cmake {
|
||||
arguments += "-DAPI_VERSION=1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,10 +212,6 @@ android {
|
|||
|
||||
|
||||
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("de.upb.cs.swt:axml:2.1.2")
|
||||
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||
|
|
@ -247,8 +298,12 @@ fun afterEval() = android.applicationVariants.forEach { variant ->
|
|||
rename(".*\\.apk", "daemon.apk")
|
||||
}
|
||||
into("lib") {
|
||||
from("${buildDir}/intermediates/stripped_native_libs/$variantCapped/out/lib")
|
||||
from("${project(":daemon").buildDir}/intermediates/ndkBuild/$buildTypeLowered/obj/local")
|
||||
from("${buildDir}/intermediates/cmake/$variantCapped/obj") {
|
||||
include("**/liblspd.so")
|
||||
}
|
||||
from("${project(":daemon").buildDir}/intermediates/cmake/$buildTypeLowered/obj") {
|
||||
include("**/libdaemon.so")
|
||||
}
|
||||
}
|
||||
val dexOutPath = if (buildTypeLowered == "release")
|
||||
"$buildDir/intermediates/dex/$variantCapped/minify${variantCapped}WithR8" else
|
||||
|
|
|
|||
|
|
@ -7,9 +7,6 @@
|
|||
-keepclasseswithmembers,includedescriptorclasses class * {
|
||||
native <methods>;
|
||||
}
|
||||
-keepclasseswithmembers class org.lsposed.lspd.nativebridge.ClassLinker {
|
||||
public static void onPostFixupStaticTrampolines(java.lang.Class);
|
||||
}
|
||||
-keepclasseswithmembers class org.lsposed.lspd.service.BridgeService {
|
||||
public static boolean *(android.os.IBinder, int, long, long, int);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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: ;
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
import android.content.res.XResources;
|
||||
|
|
@ -42,26 +22,15 @@ public interface IXposedHookInitPackageResources extends IXposedMod {
|
|||
*/
|
||||
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
/** @hide */
|
||||
final class Wrapper extends XC_InitPackageResources {
|
||||
private final IXposedHookInitPackageResources instance;
|
||||
private final String apkPath;
|
||||
|
||||
public Wrapper(IXposedHookInitPackageResources instance, String apkPath) {
|
||||
public Wrapper(IXposedHookInitPackageResources instance) {
|
||||
this.instance = instance;
|
||||
this.apkPath = apkPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
|
||||
instance.handleInitPackageResources(resparam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return apkPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
import android.app.Application;
|
||||
|
|
@ -43,26 +23,15 @@ public interface IXposedHookLoadPackage extends IXposedMod {
|
|||
*/
|
||||
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
/** @hide */
|
||||
final class Wrapper extends XC_LoadPackage {
|
||||
private final IXposedHookLoadPackage instance;
|
||||
private final String apkPath;
|
||||
|
||||
public Wrapper(IXposedHookLoadPackage instance, String apkPath) {
|
||||
public Wrapper(IXposedHookLoadPackage instance) {
|
||||
this.instance = instance;
|
||||
this.apkPath = apkPath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
|
||||
instance.handleLoadPackage(lpparam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApkPath() {
|
||||
return apkPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
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.
|
||||
*
|
||||
|
|
@ -37,30 +14,16 @@ import de.robv.android.xposed.callbacks.XCallback;
|
|||
public interface IXposedHookZygoteInit extends IXposedMod {
|
||||
/**
|
||||
* Called very early during startup of Zygote.
|
||||
*
|
||||
* @param startupParam Details about the module itself and the started process.
|
||||
* @throws Throwable everything is caught, but will prevent further initialization of the module.
|
||||
*/
|
||||
void initZygote(StartupParam startupParam) throws Throwable;
|
||||
|
||||
/**
|
||||
* Data holder for {@link #initZygote}.
|
||||
*/
|
||||
final class StartupParam extends XCallback.Param {
|
||||
/*package*/ StartupParam() {
|
||||
}
|
||||
/** Data holder for {@link #initZygote}. */
|
||||
final class StartupParam {
|
||||
/*package*/ StartupParam() {}
|
||||
|
||||
/**
|
||||
* @param callbacks
|
||||
* @hide
|
||||
*/
|
||||
public StartupParam(XposedBridge.CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
||||
super(callbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the module's APK.
|
||||
*/
|
||||
/** The path to the module's APK. */
|
||||
public String modulePath;
|
||||
|
||||
/**
|
||||
|
|
@ -69,29 +32,4 @@ public interface IXposedHookZygoteInit extends IXposedMod {
|
|||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -28,8 +28,8 @@ import android.content.res.TypedArray;
|
|||
import android.util.Log;
|
||||
|
||||
import org.lsposed.lspd.BuildConfig;
|
||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||
import org.lsposed.lspd.nativebridge.ResourcesHook;
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Executable;
|
||||
|
|
@ -39,15 +39,11 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
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];
|
||||
|
||||
// built-in handlers
|
||||
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new NoValuesHashMap<>();
|
||||
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
|
||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
|
||||
/*package*/ static final CopyOnWriteSortedSet<XC_InitZygote> sInitZygoteCallbacks = new CopyOnWriteSortedSet<>();
|
||||
public static final CopyOnWriteArraySet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteArraySet<>();
|
||||
/*package*/ static final CopyOnWriteArraySet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteArraySet<>();
|
||||
|
||||
private XposedBridge() {
|
||||
}
|
||||
|
|
@ -124,7 +118,7 @@ public final class XposedBridge {
|
|||
ResourcesHook.makeInheritable(resClass, resClass.getDeclaredConstructors());
|
||||
ResourcesHook.makeInheritable(taClass, taClass.getDeclaredConstructors());
|
||||
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.XTypedArraySuperClass");
|
||||
setObjectField(myCL, "parent", dummyClassLoader);
|
||||
|
|
@ -180,7 +174,7 @@ public final class XposedBridge {
|
|||
} else if (Proxy.isProxyClass(deoptimizedMethod.getDeclaringClass())) {
|
||||
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");
|
||||
}
|
||||
|
||||
Executable targetMethod = (Executable) hookMethod;
|
||||
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback should not be null!");
|
||||
}
|
||||
|
||||
boolean newMethod = false;
|
||||
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);
|
||||
}
|
||||
HookBridge.hookMethod((Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback);
|
||||
|
||||
return callback.new Unhook(hookMethod);
|
||||
}
|
||||
|
|
@ -247,13 +224,9 @@ public final class XposedBridge {
|
|||
*/
|
||||
@Deprecated
|
||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
synchronized (sHookedMethodCallbacks) {
|
||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||
if (callbacks == null)
|
||||
return;
|
||||
if (hookMethod instanceof Executable) {
|
||||
HookBridge.unhookMethod((Executable) hookMethod, callback);
|
||||
}
|
||||
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.
|
||||
*
|
||||
|
|
@ -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
|
||||
* 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");
|
||||
}
|
||||
|
||||
return YahfaHooker.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;
|
||||
}
|
||||
return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -449,10 +378,99 @@ public final class XposedBridge {
|
|||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,9 +23,6 @@ package de.robv.android.xposed;
|
|||
import static org.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||
import static org.lsposed.lspd.deopt.PrebuiltMethodsDeopter.deoptResourceMethods;
|
||||
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.findAndHookMethod;
|
||||
import static de.robv.android.xposed.XposedHelpers.getObjectField;
|
||||
|
|
@ -58,8 +55,6 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
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 hidden.HiddenApiBridge;
|
||||
|
||||
|
|
@ -219,79 +214,22 @@ public final class XposedInit {
|
|||
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<>();
|
||||
|
||||
public static ArraySet<String> getLoadedModules() {
|
||||
synchronized (moduleLoadLock) {
|
||||
return loadedModules;
|
||||
}
|
||||
synchronized public static ArraySet<String> getLoadedModules() {
|
||||
return loadedModules;
|
||||
}
|
||||
|
||||
public static void loadModules() {
|
||||
boolean hasLoaded = !modulesLoaded.compareAndSet(false, true);
|
||||
if (hasLoaded) {
|
||||
return;
|
||||
}
|
||||
synchronized (moduleLoadLock) {
|
||||
var moduleList = serviceClient.getModulesList();
|
||||
var newLoadedApk = new ArraySet<String>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
synchronized public static void loadModules() {
|
||||
var moduleList = serviceClient.getModulesList();
|
||||
moduleList.forEach(module -> {
|
||||
var apk = module.apkPath;
|
||||
var name = module.packageName;
|
||||
var file = module.file;
|
||||
if (loadModule(name, apk, file)) {
|
||||
loadedModules.add(apk); // temporarily add it for XSharedPreference
|
||||
}
|
||||
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();
|
||||
param.modulePath = apk;
|
||||
param.startsSystemServer = startsSystemServer;
|
||||
|
||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||
(IXposedHookZygoteInit) moduleInstance, param));
|
||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
|
||||
count++;
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
||||
hookResources();
|
||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
|
||||
count++;
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,9 @@ package de.robv.android.xposed.callbacks;
|
|||
|
||||
import android.content.res.XResources;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
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}
|
||||
|
|
@ -57,8 +58,8 @@ public abstract class XC_InitPackageResources extends XCallback implements IXpos
|
|||
/**
|
||||
* @hide
|
||||
*/
|
||||
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) {
|
||||
super(callbacks);
|
||||
public InitPackageResourcesParam(CopyOnWriteArraySet<XC_InitPackageResources> callbacks) {
|
||||
super(callbacks.toArray(new XCallback[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ public abstract class XC_LayoutInflated extends XCallback {
|
|||
* @hide
|
||||
*/
|
||||
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
|
||||
super(callbacks);
|
||||
super((XCallback[]) callbacks.getSnapshot());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ package de.robv.android.xposed.callbacks;
|
|||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
|
|
@ -57,8 +59,8 @@ public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoa
|
|||
/**
|
||||
* @hide
|
||||
*/
|
||||
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) {
|
||||
super(callbacks);
|
||||
public LoadPackageParam(CopyOnWriteArraySet<XC_LoadPackage> callbacks) {
|
||||
super(callbacks.toArray(new XCallback[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
|||
|
||||
import java.io.Serializable;
|
||||
|
||||
import de.robv.android.xposed.IModuleContext;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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.
|
||||
*
|
||||
|
|
@ -69,7 +67,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
|||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final Object[] callbacks;
|
||||
public final XCallback[] callbacks;
|
||||
private Bundle extra;
|
||||
|
||||
/**
|
||||
|
|
@ -83,8 +81,8 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
|||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
|
||||
this.callbacks = callbacks.getSnapshot();
|
||||
protected Param(XCallback[] callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -147,7 +145,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
|||
|
||||
for (int i = 0; i < param.callbacks.length; i++) {
|
||||
try {
|
||||
((XCallback) param.callbacks[i]).call(param);
|
||||
param.callbacks[i].call(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
}
|
||||
|
|
@ -160,28 +158,6 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
|||
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}.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import org.lsposed.lspd.hooker.LoadedApkCstrHooker;
|
|||
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
|
||||
import org.lsposed.lspd.util.ParasiticManagerHooker;
|
||||
import org.lsposed.lspd.util.Utils;
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
|
@ -77,7 +76,6 @@ public class Main {
|
|||
|
||||
public static void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
||||
// init logger
|
||||
YahfaHooker.init();
|
||||
XposedBridge.initXResources();
|
||||
XposedInit.startsSystemServer = isSystem;
|
||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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_SYSTEM_SERVER;
|
||||
|
||||
import org.lsposed.lspd.nativebridge.Yahfa;
|
||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||
import org.lsposed.lspd.util.Utils;
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.util.Arrays;
|
||||
|
|
@ -46,9 +45,9 @@ public class PrebuiltMethodsDeopter {
|
|||
if (clazz == null) {
|
||||
continue;
|
||||
}
|
||||
Executable method = Yahfa.findMethodNative(clazz, caller[1], caller[2]);
|
||||
Executable method = null;
|
||||
if (method != null) {
|
||||
YahfaHooker.deoptMethodNative(method);
|
||||
HookBridge.deoptimizeMethod(method);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
Utils.logE("error when deopting method: " + Arrays.toString(caller), throwable);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ public class ResourcesHook {
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -28,7 +28,7 @@
|
|||
#include "symbol_cache.h"
|
||||
|
||||
#define RIRU_MODULE
|
||||
#include <riru.h>
|
||||
#include "riru.h"
|
||||
|
||||
namespace lspd {
|
||||
int *allowUnload = nullptr;
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
#include <dlfcn.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "jni/zygisk.h"
|
||||
#include "zygisk.h"
|
||||
#include "logging.h"
|
||||
#include "context.h"
|
||||
#include "config.h"
|
||||
|
|
@ -328,7 +328,10 @@ namespace lspd {
|
|||
auto *process = env_->FindClass("android/os/Process");
|
||||
auto *set_argv0 = env_->GetStaticMethodID(process, "setArgV0",
|
||||
"(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_);
|
||||
if (*allowUnload) api_->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
|
||||
|
|
@ -21,14 +21,14 @@
|
|||
#pragma once
|
||||
|
||||
#include "symbol_cache.h"
|
||||
#include "base/object.h"
|
||||
#include "utils/hook_helper.hpp"
|
||||
#include "context.h"
|
||||
#include "runtime.h"
|
||||
|
||||
namespace art {
|
||||
|
||||
namespace hidden_api {
|
||||
|
||||
using lsplant::operator""_tstr;
|
||||
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv *env, jclass clazz,
|
||||
jobject j_cookie) {
|
||||
if (DexFile_setTrustedSym != nullptr) [[likely]] {
|
||||
|
|
@ -36,7 +36,7 @@ namespace art {
|
|||
DexFile_setTrustedSym(env, clazz, j_cookie);
|
||||
Runtime::Current()->SetJavaDebuggable(false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
inline void
|
||||
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",
|
||||
jobject, DexFile_openDexFileNative, (JNIEnv * env,
|
||||
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_",
|
||||
jobject, DexFile_openInMemoryDexFilesNative, (JNIEnv * env,
|
||||
jclass clazz,
|
||||
|
|
@ -89,7 +89,7 @@ namespace art {
|
|||
}
|
||||
);
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(
|
||||
CREATE_HOOK_STUB_ENTRY(
|
||||
"_ZN3artL29DexFile_createCookieWithArrayEP7_JNIEnvP7_jclassP11_jbyteArrayii",
|
||||
jobject, DexFile_createCookieWithArray, (JNIEnv * env,
|
||||
jclass clazz,
|
||||
|
|
@ -102,7 +102,7 @@ namespace art {
|
|||
}
|
||||
);
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(
|
||||
CREATE_HOOK_STUB_ENTRY(
|
||||
"_ZN3artL36DexFile_createCookieWithDirectBufferEP7_JNIEnvP7_jclassP8_jobjectii",
|
||||
jobject, DexFile_createCookieWithDirectBuffer, (JNIEnv * env,
|
||||
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();
|
||||
if (api_level < __ANDROID_API_P__) {
|
||||
return;
|
||||
}
|
||||
DexFile_setTrustedSym = reinterpret_cast<decltype(DexFile_setTrustedSym)>(lspd::symbol_cache->setTrusted);
|
||||
lspd::HookSymNoHandle(lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative);
|
||||
lspd::HookSymNoHandle(lspd::symbol_cache->openInMemoryDexFilesNative,
|
||||
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openDexFileNative, DexFile_openDexFileNative);
|
||||
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->openInMemoryDexFilesNative,
|
||||
DexFile_openInMemoryDexFilesNative);
|
||||
if (api_level == __ANDROID_API_P__) {
|
||||
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithArray,
|
||||
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithArray,
|
||||
DexFile_createCookieWithArray);
|
||||
lspd::HookSymNoHandle(lspd::symbol_cache->createCookieWithDirectBuffer,
|
||||
lsplant::HookSymNoHandle(handler, lspd::symbol_cache->createCookieWithDirectBuffer,
|
||||
DexFile_createCookieWithDirectBuffer);
|
||||
}
|
||||
};
|
||||
|
|
@ -20,11 +20,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <base/object.h>
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
namespace art {
|
||||
|
||||
class Runtime : public lspd::HookedObject {
|
||||
class Runtime {
|
||||
private:
|
||||
inline static Runtime *instance_;
|
||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
|
||||
|
|
@ -34,23 +34,22 @@ namespace art {
|
|||
}
|
||||
|
||||
public:
|
||||
Runtime(void *thiz) : HookedObject(thiz) {}
|
||||
|
||||
inline static Runtime *Current() {
|
||||
return instance_;
|
||||
}
|
||||
|
||||
void SetJavaDebuggable(bool value) {
|
||||
SetJavaDebuggable(thiz_, value);
|
||||
SetJavaDebuggable(this, value);
|
||||
}
|
||||
|
||||
// @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_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb");
|
||||
void *thiz = *reinterpret_cast<void **>(instance);
|
||||
void *thiz = *instance;
|
||||
LOGD("_ZN3art7Runtime9instance_E = %p", thiz);
|
||||
instance_ = new Runtime(thiz);
|
||||
instance_ = reinterpret_cast<Runtime*>(thiz);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -25,12 +25,13 @@
|
|||
#include <string>
|
||||
#include "macros.h"
|
||||
#include "utils.h"
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
namespace lspd {
|
||||
|
||||
//#define LOG_DISABLED
|
||||
//#define DEBUG
|
||||
|
||||
using lsplant::operator""_tstr;
|
||||
|
||||
inline bool constexpr Is64() {
|
||||
#if defined(__LP64__)
|
||||
|
|
@ -59,9 +60,7 @@ namespace lspd {
|
|||
#endif
|
||||
|
||||
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 kDexPath = "framework/lspd.dex"_tstr;
|
||||
|
||||
inline static constexpr auto kLibArtName = "libart.so"_tstr;
|
||||
inline static constexpr auto kLibFwName = "libandroidfw.so"_tstr;
|
||||
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
#include <variant>
|
||||
#include <cstdint>
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
// @ApiSensitive(Level.MIDDLE)
|
||||
namespace android {
|
||||
|
|
@ -173,7 +174,7 @@ namespace android {
|
|||
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(stringAtS, LP_SELECT("_ZNK7android13ResStringPool8stringAtEj", "_ZNK7android13ResStringPool8stringAtEm"));
|
||||
return !stringAtSym || !stringAtSSym;
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -43,45 +43,6 @@ namespace lspd {
|
|||
}();
|
||||
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
|
||||
|
|
@ -19,19 +19,23 @@
|
|||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include "jni_helper.h"
|
||||
#include "jni/art_class_linker.h"
|
||||
#include "jni/yahfa.h"
|
||||
#include "config.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
#include "jni/resources_hook.h"
|
||||
#include <art/runtime/jni_env_ext.h>
|
||||
#include "context.h"
|
||||
#include "native_hook.h"
|
||||
#include "elf_util.h"
|
||||
#include "jni/hook_bridge.h"
|
||||
#include "jni/native_api.h"
|
||||
#include "service.h"
|
||||
#include <sys/mman.h>
|
||||
#include "symbol_cache.h"
|
||||
|
||||
#include <linux/fs.h>
|
||||
#include <fcntl.h>
|
||||
#include <native_util.h>
|
||||
|
||||
using namespace lsplant;
|
||||
|
||||
static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602));
|
||||
|
||||
|
|
@ -96,15 +100,17 @@ namespace lspd {
|
|||
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(),
|
||||
kEntryClassName)) {
|
||||
entry_class_ = JNI_NewGlobalRef(env, entry_class);
|
||||
}
|
||||
|
||||
RegisterResourcesHook(env);
|
||||
RegisterArtClassLinker(env);
|
||||
RegisterYahfa(env);
|
||||
RegisterHookBridge(env);
|
||||
RegisterNativeAPI(env);
|
||||
}
|
||||
|
||||
|
|
@ -179,9 +185,22 @@ namespace lspd {
|
|||
instance->HookBridge(*this, env);
|
||||
|
||||
if (application_binder) {
|
||||
InstallInlineHooks();
|
||||
Init(env);
|
||||
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);
|
||||
Init(env, initInfo);
|
||||
FindAndCall(env, "forkSystemServerPost", "(Landroid/os/IBinder;)V", application_binder);
|
||||
GetArt(true);
|
||||
} else {
|
||||
LOGI("skipped system server");
|
||||
GetArt(true);
|
||||
|
|
@ -236,11 +255,23 @@ namespace lspd {
|
|||
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
|
||||
: instance->RequestBinder(env, nice_name);
|
||||
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);
|
||||
LoadDex(env, dex_fd, size);
|
||||
close(dex_fd);
|
||||
Init(env);
|
||||
Init(env, initInfo);
|
||||
LOGD("Done prepare");
|
||||
FindAndCall(env, "forkAndSpecializePost",
|
||||
"(Ljava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
|
||||
|
|
@ -248,6 +279,7 @@ namespace lspd {
|
|||
binder);
|
||||
LOGD("injected xposed into %s", process_name.get());
|
||||
setAllowUnload(false);
|
||||
GetArt(true);
|
||||
} else {
|
||||
auto context = Context::ReleaseInstance();
|
||||
auto service = Service::ReleaseInstance();
|
||||
|
|
@ -26,8 +26,9 @@
|
|||
#include <string>
|
||||
#include <tuple>
|
||||
#include <string_view>
|
||||
#include <lsplant.hpp>
|
||||
#include "utils.h"
|
||||
#include "jni_helper.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
|
||||
namespace lspd {
|
||||
class Context {
|
||||
|
|
@ -43,7 +44,7 @@ namespace lspd {
|
|||
|
||||
inline jobject GetCurrentClassLoader() const { return inject_class_loader_; }
|
||||
|
||||
inline ScopedLocalRef<jclass>
|
||||
inline lsplant::ScopedLocalRef<jclass>
|
||||
FindClassFromCurrentLoader(JNIEnv *env, std::string_view className) const {
|
||||
return FindClassFromLoader(env, GetCurrentClassLoader(), className);
|
||||
};
|
||||
|
|
@ -108,9 +109,9 @@ namespace lspd {
|
|||
|
||||
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);
|
||||
|
||||
static void setAllowUnload(bool unload);
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// Created by loves on 3/13/2022.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
namespace lspd {
|
||||
void RegisterHookBridge(JNIEnv* env);
|
||||
}
|
||||
|
|
@ -23,12 +23,14 @@
|
|||
//
|
||||
#include "native_api.h"
|
||||
#include "native_util.h"
|
||||
#include "jni_helper.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
#include "../native_api.h"
|
||||
|
||||
using namespace lsplant;
|
||||
|
||||
namespace lspd {
|
||||
LSP_DEF_NATIVE_METHOD(void, NativeAPI, recordNativeEntrypoint, jstring jstr) {
|
||||
JUTFString str(env, jstr);
|
||||
lsplant::JUTFString str(env, jstr);
|
||||
RegisterNativeLib(str);
|
||||
}
|
||||
|
||||
|
|
@ -19,25 +19,24 @@
|
|||
*/
|
||||
|
||||
#include <jni.h>
|
||||
#include <dex_builder.h>
|
||||
#include <art/runtime/thread.h>
|
||||
#include <art/runtime/mirror/class.h>
|
||||
#include <framework/androidfw/resource_types.h>
|
||||
#include <HookMain.h>
|
||||
#include <elf_util.h>
|
||||
#include "dex_builder.h"
|
||||
#include "framework/androidfw/resource_types.h"
|
||||
#include "elf_util.h"
|
||||
#include "native_util.h"
|
||||
#include "resources_hook.h"
|
||||
|
||||
using namespace lsplant;
|
||||
|
||||
namespace lspd {
|
||||
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 jmethodID methodXResourcesTranslateAttrId;
|
||||
|
|
@ -65,7 +64,11 @@ namespace lspd {
|
|||
"_ZNK7android12ResXMLParser18getAttributeNameIDEm")))) {
|
||||
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) {
|
||||
|
|
@ -97,29 +100,14 @@ namespace lspd {
|
|||
// @ApiSensitive(Level.MIDDLE)
|
||||
LSP_DEF_NATIVE_METHOD(jboolean, ResourcesHook, makeInheritable, jclass target_class,
|
||||
jobjectArray constructors) {
|
||||
if (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);
|
||||
}
|
||||
if (lsplant::MakeClassInheritable(env, target_class)) {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
return JNI_FALSE;
|
||||
}
|
||||
|
||||
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;
|
||||
static auto in_memory_classloader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
||||
"dalvik/system/InMemoryDexClassLoader"));
|
||||
|
|
@ -127,17 +115,13 @@ namespace lspd {
|
|||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||
DexBuilder dex_file;
|
||||
|
||||
std::string storage;
|
||||
auto current_thread = art::Thread::Current();
|
||||
ClassBuilder xresource_builder{
|
||||
dex_file.MakeClass("xposed.dummy.XResourcesSuperClass")};
|
||||
xresource_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class(
|
||||
current_thread.DecodeJObject(resource_super_class)).GetDescriptor(&storage)));
|
||||
xresource_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, resource_super_class).get()));
|
||||
|
||||
ClassBuilder xtypearray_builder{
|
||||
dex_file.MakeClass("xposed.dummy.XTypedArraySuperClass")};
|
||||
xtypearray_builder.setSuperClass(TypeDescriptor::FromDescriptor(art::mirror::Class(
|
||||
current_thread.DecodeJObject(typed_array_super_class)).GetDescriptor(&storage)));
|
||||
xtypearray_builder.setSuperClass(TypeDescriptor::FromClassname(JUTFString(env, typed_array_super_class).get()));
|
||||
|
||||
slicer::MemView image{dex_file.CreateImage()};
|
||||
|
||||
|
|
@ -222,7 +206,7 @@ namespace lspd {
|
|||
LSP_NATIVE_METHOD(ResourcesHook, makeInheritable,
|
||||
"(Ljava/lang/Class;[Ljava/lang/reflect/Constructor;)Z"),
|
||||
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,
|
||||
"(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V")
|
||||
};
|
||||
|
|
@ -23,10 +23,15 @@
|
|||
//
|
||||
|
||||
#include "native_api.h"
|
||||
#include "logging.h"
|
||||
#include "symbol_cache.h"
|
||||
#include "utils/hook_helper.hpp"
|
||||
#include <sys/mman.h>
|
||||
#include <dobby.h>
|
||||
#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
|
||||
|
|
@ -44,26 +49,33 @@
|
|||
*/
|
||||
|
||||
namespace lspd {
|
||||
|
||||
using lsplant::operator""_tstr;
|
||||
std::list<NativeOnModuleLoaded> moduleLoadedCallbacks;
|
||||
std::list<std::string> moduleNativeLibs;
|
||||
std::unique_ptr<void, std::function<void(void *)>> protected_page(
|
||||
mmap(nullptr, _page_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0),
|
||||
[](void *ptr) { munmap(ptr, _page_size); });
|
||||
mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0),
|
||||
[](void *ptr) { munmap(ptr, 4096); });
|
||||
|
||||
const auto[entries] = []() {
|
||||
auto *entries = new(protected_page.get()) NativeAPIEntries{
|
||||
.version = 2,
|
||||
.hookFunc = HookFunction,
|
||||
.unhookFunc = UnhookFunction
|
||||
.hookFunc = &HookFunction,
|
||||
.unhookFunc = &UnhookFunction,
|
||||
};
|
||||
|
||||
mprotect(protected_page.get(), _page_size, PROT_READ);
|
||||
mprotect(protected_page.get(), 4096, PROT_READ);
|
||||
return std::make_tuple(entries);
|
||||
}();
|
||||
|
||||
void RegisterNativeLib(const std::string &library_name) {
|
||||
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;
|
||||
LOGD("native_api: Registered %s", library_name.c_str());
|
||||
|
|
@ -74,12 +86,11 @@ namespace lspd {
|
|||
if (fullString.length() >= ending.length()) {
|
||||
return (0 == fullString.compare(fullString.length() - ending.length(), ending.length(),
|
||||
ending));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CREATE_HOOK_STUB_ENTRIES(
|
||||
CREATE_HOOK_STUB_ENTRY(
|
||||
"__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv",
|
||||
void*, do_dlopen, (const char* name, int flags, const void* extinfo,
|
||||
const void* caller_addr), {
|
||||
|
|
@ -121,10 +132,10 @@ namespace lspd {
|
|||
return handle;
|
||||
});
|
||||
|
||||
bool InstallNativeAPI() {
|
||||
bool InstallNativeAPI(const lsplant::HookHandler & handler) {
|
||||
LOGD("InstallNativeAPI: %p", symbol_cache->do_dlopen);
|
||||
if (symbol_cache->do_dlopen) [[likely]] {
|
||||
HookSymNoHandle(symbol_cache->do_dlopen, do_dlopen);
|
||||
HookSymNoHandle(handler, symbol_cache->do_dlopen, do_dlopen);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -27,7 +27,8 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <base/object.h>
|
||||
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
typedef int (*HookFunType)(void *func, void *replace, void **backup);
|
||||
|
||||
|
|
@ -44,7 +45,7 @@ typedef struct {
|
|||
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
|
||||
|
||||
namespace lspd {
|
||||
bool InstallNativeAPI();
|
||||
bool InstallNativeAPI(const lsplant::HookHandler& handler);
|
||||
|
||||
void RegisterNativeLib(const std::string &library_name);
|
||||
}
|
||||
|
|
@ -18,21 +18,28 @@
|
|||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <dlfcn.h>
|
||||
#include <string>
|
||||
#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 {
|
||||
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
|
||||
|
|
@ -20,9 +20,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <dobby.h>
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
namespace lspd {
|
||||
void InstallInlineHooks();
|
||||
void InstallInlineHooks(const lsplant::HookHandler &handler);
|
||||
|
||||
}
|
||||
|
|
@ -23,12 +23,14 @@
|
|||
|
||||
#include <dobby.h>
|
||||
#include <thread>
|
||||
#include "base/object.h"
|
||||
#include "config.h"
|
||||
#include "service.h"
|
||||
#include "context.h"
|
||||
#include "jni_helper.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
#include "symbol_cache.h"
|
||||
|
||||
using namespace lsplant;
|
||||
|
||||
namespace lspd {
|
||||
jboolean
|
||||
Service::exec_transact_replace(jboolean *res, JNIEnv *env, [[maybe_unused]] jobject obj,
|
||||
|
|
@ -51,13 +51,13 @@ namespace lspd {
|
|||
void InitService(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:
|
||||
inline static std::unique_ptr<Service> instance_ = std::make_unique<Service>();
|
||||
|
|
@ -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}";
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
/build
|
||||
/build
|
||||
/.cxx
|
||||
|
|
|
|||
|
|
@ -65,12 +65,6 @@ android {
|
|||
versionName = verName
|
||||
multiDexEnabled = false
|
||||
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
||||
}
|
||||
}
|
||||
|
||||
buildConfigField("int", "API_CODE", "$apiCode")
|
||||
buildConfigField(
|
||||
"String",
|
||||
|
|
@ -87,15 +81,58 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments.addAll(
|
||||
arrayOf(
|
||||
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
|
||||
"-DCMAKE_C_FLAGS_DEBUG=-Og"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
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 {
|
||||
ndkBuild {
|
||||
path("src/main/cpp/Android.mk")
|
||||
cmake {
|
||||
path("src/main/jni/CMakeLists.txt")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,12 +141,30 @@ android {
|
|||
sourceCompatibility(androidSourceCompatibility)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
ndkBuild {
|
||||
arguments += "NDK_OUT=${File(buildDir, ".cxx/$name").absolutePath}"
|
||||
}
|
||||
defaultConfig {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "external")}"
|
||||
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 {
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||
implementation("com.android.tools.build:apksig:$agpVersion")
|
||||
implementation("org.apache.commons:commons-lang3:3.12.0")
|
||||
compileOnly("androidx.annotation:annotation:1.3.0")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -224,7 +224,7 @@ void Logcat::ProcessBuffer(struct log_msg *buf) {
|
|||
shortcut = true;
|
||||
}
|
||||
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("LSPosed"sv))) [[unlikely]] {
|
||||
verbose_print_count_ += PrintLogLine(entry, verbose_file_.get());
|
||||
|
|
@ -51,13 +51,13 @@ struct log_msg {
|
|||
struct logger;
|
||||
struct logger_list;
|
||||
|
||||
long android_logger_get_log_size(struct logger* logger);
|
||||
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);
|
||||
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);
|
||||
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]] long android_logger_get_log_size(struct logger* logger);
|
||||
[[gnu::weak]] int android_logger_set_log_size(struct logger *logger, unsigned long size);
|
||||
[[gnu::weak]] struct logger_list *android_logger_list_alloc(int mode, unsigned int tail, pid_t pid);
|
||||
[[gnu::weak]] void android_logger_list_free(struct logger_list *logger_list);
|
||||
[[gnu::weak]] int android_logger_list_read(struct logger_list *logger_list, struct log_msg *log_msg);
|
||||
[[gnu::weak]] struct logger *android_logger_open(struct logger_list *logger_list, log_id_t id);
|
||||
[[gnu::weak]] int android_log_processLogBuffer(struct logger_entry *buf, AndroidLogEntry *entry);
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -34,7 +34,9 @@
|
|||
#include "slicer/reader.h"
|
||||
#include "slicer/writer.h"
|
||||
#include "obfuscation.h"
|
||||
#include "logging.h"
|
||||
|
||||
using namespace lsplant;
|
||||
namespace {
|
||||
std::mutex init_lock{};
|
||||
std::string obfuscated_signature;
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "jni_helper.h"
|
||||
#include "utils/jni_helper.hpp"
|
||||
|
||||
class WA: public dex::Writer::Allocator {
|
||||
// addr: {size, fd}
|
||||
|
|
@ -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)
|
||||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 0aa67c3ffea069bdb58fe3b5e7aad06934afb292
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue