Compare commits

..

76 Commits
v6.3 ... master

Author SHA1 Message Date
5ec1cff
e91129897c
Update build.yml 2025-03-31 18:54:27 +08:00
LoveSy
ff815781ba
Update hook_helper.hpp 2025-03-07 09:10:12 +08:00
LoveSy
0110cf7e6e upgrade to ndk 29 an use module partition 2025-03-06 11:53:03 +08:00
LoveSy
e2a35a4fab Setup Android SDK 2025-03-06 11:52:43 +08:00
dependabot[bot]
53070cff33
Bump the maven-dependencies group across 1 directory with 2 updates (#143) 2025-03-05 16:59:16 +08:00
LoveSy
c657d08dcf Upgrade gradle 2025-03-05 16:58:31 +08:00
LoveSy
ee6124f21e Simplify hookhandler usage 2025-03-05 15:36:13 +08:00
LoveSy
c54fb307f2
Refactor symbol define and use static operator() (#142) 2025-03-05 14:49:47 +08:00
LoveSy
4a2293e222
We only need to deoptimize non-native methods
Fix #140, close #141
2025-03-03 21:44:33 +08:00
LoveSy
4a18848a86
Use GetRuntimeQuickGenericJniStub for art_quick_generic_jni_trampoline 2025-03-01 20:45:22 +08:00
LoveSy
e9a016836b
Fix unit test 2025-02-26 21:30:33 +08:00
LoveSy
ce08314ca3
Fix art_quick_* not found
Close #136
2025-02-26 20:58:24 +08:00
烂泥扶上墙
8c3c6e6b2c
Fix the SIGSEGV caused by premature initialization of ClassLinker (#137)
ClassLinker should be initialized after Class because FixupStaticTrampolines references Class::GetClassDef, causing a SIGSEGV.
2025-02-26 19:29:45 +08:00
南宫雪珊
90d8d433d4
add cuttlefish test (#133) 2025-02-12 17:44:18 +08:00
dependabot[bot]
08b65e436f
Bump the maven-dependencies group with 2 updates (#116) 2024-11-06 23:04:27 +08:00
dependabot[bot]
a60403bd54
Bump AGP (#114) 2024-10-15 10:09:13 +08:00
dependabot[bot]
f5c423fd20
Bump AGP to 8.6.0 (#108) 2024-08-30 06:29:05 +08:00
dependabot[bot]
8ebc00ec8a
Bump androidx.test:runner from 1.6.1 to 1.6.2 in the maven-dependencies group (#106) 2024-08-18 19:30:28 +08:00
LoveSy
f8120339d2
Upgrade gradle 2024-08-16 23:00:18 +08:00
LoveSy
16c6820b86
Fix cmake 2024-08-15 01:14:59 +08:00
LoveSy
05289b548a
Fix IsBackup 2024-08-12 00:22:14 +08:00
LoveSy
ae4bc68b12
Fix including backup 2024-08-12 00:07:48 +08:00
dependabot[bot]
0b1e5f8b7a
Bump the maven-dependencies group with 2 updates (#105) 2024-08-10 09:28:40 +08:00
LoveSy
56a9cf7e53
Avoid hook called with unsupported arguments 2024-08-09 11:00:15 +08:00
LoveSy
897c70b74a
Make backup logic as a function 2024-08-06 23:39:03 +08:00
LoveSy
68f8a63e8f
Make member function const 2024-08-06 10:26:51 +08:00
LoveSy
14b753e1dc
Modularize hook helper 2024-08-06 00:33:24 +08:00
LoveSy
39334baed3
No need to setup android sdk 2024-08-02 01:38:11 +08:00
LoveSy
c9bf82a36c
Replace tstring with fixedstring 2024-08-02 01:10:32 +08:00
LoveSy
a7b0b022c2
Propagate hotness counter for backup method to target to avoid crash
Fix #99

See https://5ec1cff.github.io/my-blog/2024/06/27/lsp-crash-analysis/
2024-08-02 00:54:16 +08:00
LoveSy
0d9faca38d
Fix GarbageCollectCache got removed
Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2024-07-24 00:39:03 +08:00
LoveSy
5121a21064
Remove dangerous implicit cast 2024-07-23 11:40:35 +08:00
LoveSy
bd64ecbc16
Fix array manipulation 2024-07-23 03:01:05 +08:00
LoveSy
85ab3c8c11
Upgrade deps 2024-07-17 11:50:46 +08:00
dependabot[bot]
87d2caede7
Bump the maven-dependencies group with 2 updates (#96) 2024-07-12 15:41:40 +08:00
LoveSy
3d9257fc11
Group dependencies 2024-07-12 15:39:27 +08:00
LoveSy
9d63f3bb50
Use kotlint dsl simple property assignment 2024-07-10 22:42:06 +08:00
dependabot[bot]
8fe8c6da1f
Bump androidx.test:runner from 1.6.0 to 1.6.1 (#92) 2024-06-28 07:03:27 +08:00
dependabot[bot]
368fed7c4f
Bump androidx.test.espresso:espresso-core from 3.6.0 to 3.6.1 (#93) 2024-06-28 07:02:31 +08:00
dependabot[bot]
172d11641a
Bump androidx.test.ext:junit from 1.2.0 to 1.2.1 (#94) 2024-06-28 07:02:01 +08:00
dependabot[bot]
5c8cdbd985
Bump androidx.test:runner from 1.5.2 to 1.6.0 (#90) 2024-06-25 16:37:59 +08:00
dependabot[bot]
6c08583233
Bump androidx.test.ext:junit from 1.1.5 to 1.2.0 (#91) 2024-06-25 16:37:59 +08:00
dependabot[bot]
b96a512319
Bump androidx.test.espresso:espresso-core from 3.5.1 to 3.6.0 (#89) 2024-06-25 16:37:59 +08:00
dependabot[bot]
95e3790500
Bump agp from 8.4.2 to 8.5.0 (#87) 2024-06-25 16:37:59 +08:00
dependabot[bot]
b1fe35c0c4
Bump agp from 8.4.1 to 8.4.2 (#86) 2024-06-25 16:37:59 +08:00
LoveSy
ac41dc6c18
Dont log function name to avoid too verbose log 2024-06-25 16:37:49 +08:00
LoveSy
da6b276f4d
Update actions 2024-06-05 11:32:19 +08:00
LoveSy
c35a50a60f
Test use modularized lsparself 2024-06-05 10:29:23 +08:00
LoveSy
b1e6d18494
Upgrade gradle & ndk 2024-06-05 10:04:30 +08:00
LoveSy
b858504dec
Upgrade deps 2024-06-05 10:04:30 +08:00
LoveSy
4ec6de77ad
No dobby_enable_near_branch_trampoline which may crash on Android 15 2024-06-05 10:04:30 +08:00
LoveSy
2d8204774b
Fix test build 2024-06-05 10:04:30 +08:00
LoveSy
1e1b221dcb
Use c++ 23 2024-06-05 10:04:30 +08:00
LoveSy
1744bde5ac
Fix compare 2024-06-05 10:04:30 +08:00
LoveSy
d3ed8751db
Use module for dex_builder 2024-06-05 10:04:30 +08:00
LoveSy
11aa507da9
Use module for test 2024-06-05 10:04:30 +08:00
LoveSy
29f124c576
Modularize interfaces 2024-06-05 10:04:30 +08:00
LoveSy
2db8b66b29
Modularize codes 2024-06-05 10:04:29 +08:00
LoveSy
386200f0c9
Run CI on Apple silicon 2024-05-23 16:35:48 +08:00
LoveSy
94e6e6236b
[skip ci] Announce support for Android 15 Beta2 2024-05-23 16:34:28 +08:00
LoveSy
01d26b03c5
[skip ci] Create FUNDING.yml 2024-05-23 16:32:52 +08:00
dependabot[bot]
3e97d85b0e
Bump agp from 8.4.0 to 8.4.1 (#84) 2024-05-23 16:31:55 +08:00
残页
68360babc5
Remove useless register in backup of non-wide methods (#81) 2024-05-03 18:32:45 +08:00
LoveSy
fb753a7daf
Upgrade AGP 2024-05-01 01:41:32 +08:00
LoveSy
c935b27b9a
[skip ci] Update README.md 2024-04-18 22:14:11 +08:00
LoveSy
7217ac6f41
Enable riscv64 for standalone build as well 2024-04-18 21:33:47 +08:00
LoveSy
c774d42b76
Use android-actions/setup-android@v3 to accept license 2024-04-18 10:32:53 +08:00
LoveSy
33504c455c
Upgrade ndk and enable riscv64 abi 2024-04-18 10:25:34 +08:00
LoveSy
62533694c5
Upgrade deps 2024-04-11 21:20:16 +08:00
dependabot[bot]
bd4b53c331
Bump agp from 8.3.1 to 8.3.2 (#77) 2024-04-11 08:48:26 +08:00
LoveSy
2cccfae8c1
Fix riscv trampoline 2024-03-27 02:04:47 +08:00
LoveSy
34b41249fa
Try support 16k page size 2024-03-25 23:03:12 +08:00
dependabot[bot]
1e36d0ab2f
Bump agp from 8.3.0 to 8.3.1 (#74) 2024-03-19 08:05:21 +08:00
LoveSy
6bb4abdae0
Upgrade SDK 2024-03-18 22:22:53 +08:00
LoveSy
e203bfdf0e
[skip ci] Update README.md 2024-03-18 21:49:06 +08:00
dependabot[bot]
460c5aec1d
Bump agp from 8.2.2 to 8.3.0 (#71) 2024-03-08 16:12:53 +08:00
63 changed files with 2014 additions and 1626 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
github: yujincheng08

94
.github/cuttlefish.sh vendored Executable file
View File

@ -0,0 +1,94 @@
#!/usr/bin/env bash
set -xe
export PATH="$PATH:$ANDROID_HOME/platform-tools"
sdk="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"
cvd_args="-daemon -enable_sandbox=false -memory_mb=8192 -report_anonymous_usage_stats=n"
print_title() {
echo -e "\n\033[44;39m${1}\033[0m\n"
}
print_error() {
echo -e "\n\033[41;39m${1}\033[0m\n"
}
cleanup() {
print_error "! An error occurred"
run_cvd_bin stop_cvd || true
}
run_cvd_bin() {
local exe=$1
shift
HOME=$CF_HOME $CF_HOME/bin/$exe "$@"
}
setup_env() {
curl -LO https://github.com/user-attachments/files/18728876/cuttlefish-base_1.2.0_amd64.zip
sudo dpkg -i ./cuttlefish-base_*_*64.zip || sudo apt-get install -f
rm cuttlefish-base_*_*64.zip
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -aG kvm,cvdnetwork,render $USER
yes | "$sdk" --licenses > /dev/null
"$sdk" --channel=3 platform-tools
}
download_cf() {
local branch=$1
local device=$2
if [ -z $branch ]; then
branch='aosp-main'
fi
if [ -z $device ]; then
device='aosp_cf_x86_64_phone'
fi
local target="${device}-trunk_staging-userdebug"
local build_id=$(curl -sL https://ci.android.com/builds/branches/${branch}/status.json | \
jq -r ".targets[] | select(.name == \"$target\") | .last_known_good_build")
local sys_img_url="https://ci.android.com/builds/submitted/${build_id}/${target}/latest/raw/${device}-img-${build_id}.zip"
local host_pkg_url="https://ci.android.com/builds/submitted/${build_id}/${target}/latest/raw/cvd-host_package.tar.gz"
curl -L $sys_img_url -o aosp_cf_phone-img.zip
curl -LO $host_pkg_url
rm -rf $CF_HOME
mkdir -p $CF_HOME
tar xvf cvd-host_package.tar.gz -C $CF_HOME
unzip aosp_cf_phone-img.zip -d $CF_HOME
rm -f cvd-host_package.tar.gz aosp_cf_phone-img.zip
}
test_main() {
run_cvd_bin launch_cvd $cvd_args
adb wait-for-device
./gradlew connectedCheck
run_cvd_bin stop_cvd || true
}
if [ -z $CF_HOME ]; then
print_error "! Environment variable CF_HOME is required"
exit 1
fi
case "$1" in
setup )
setup_env
;;
download )
download_cf $2 $3
;;
test )
trap cleanup EXIT
export -f run_cvd_bin
test_main
trap - EXIT
;;
* )
exit 1
;;
esac

View File

@ -5,16 +5,8 @@ updates:
schedule: schedule:
interval: daily interval: daily
time: "21:00" time: "21:00"
open-pull-requests-limit: 10
target-branch: master target-branch: master
registries: groups:
- maven-google maven-dependencies:
- gralde-plugin patterns:
- "*"
registries:
maven-google:
type: maven-repository
url: "https://dl.google.com/dl/android/maven2/"
gralde-plugin:
type: maven-repository
url: "https://plugins.gradle.org/m2/"

View File

@ -5,6 +5,7 @@ on:
branches: ["master"] branches: ["master"]
paths-ignore: paths-ignore:
- 'README.md' - 'README.md'
workflow_dispatch:
pull_request: pull_request:
@ -15,7 +16,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-13 ] os: [ ubuntu-latest, windows-latest, macos-14 ]
steps: steps:
- name: Check out - name: Check out
@ -24,17 +25,25 @@ jobs:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }} ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 17 - name: Set up JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '21'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1 uses: hendrikmuhs/ccache-action@v1
with: with:
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }} restore-keys: ${{ runner.os }}
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Remove Android's cmake
shell: bash
run: rm -rf $ANDROID_HOME/cmake
- name: Build with Gradle - name: Build with Gradle
run: | run: |
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
@ -52,6 +61,7 @@ jobs:
with: with:
name: ${{ matrix.os }}-library name: ${{ matrix.os }}-library
path: ~/.m2 path: ~/.m2
include-hidden-files: true
test: test:
needs: build needs: build
@ -142,7 +152,10 @@ jobs:
- api-level: 34 - api-level: 34
target: android-tv target: android-tv
arch: x86 arch: x86
- api-level: VanillaIceCream - api-level: 35
target: default
arch: x86_64
- api-level: Baklava
target: google_apis target: google_apis
arch: x86_64 arch: x86_64
steps: steps:
@ -151,12 +164,15 @@ jobs:
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }} ssh-key: ${{ secrets.SSH_KEY }}
- name: Set up JDK 17 - name: Set up JDK 21
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '21'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1 uses: hendrikmuhs/ccache-action@v1
with: with:
@ -169,12 +185,13 @@ jobs:
sudo udevadm control --reload-rules sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm sudo udevadm trigger --name-match=kvm
- name: run tests - name: run tests
uses: yujincheng08/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
script: | script: |
rm -rf $ANDROID_HOME/cmake
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
ccache -o hash_dir=false ccache -o hash_dir=false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
@ -190,3 +207,56 @@ jobs:
path: | path: |
test/build/outputs test/build/outputs
!test/build/outputs/apk !test/build/outputs/apk
cf-test:
name: Test ${{ matrix.device }}
runs-on: ubuntu-24.04
env:
CF_HOME: /home/runner/aosp_cf_phone
strategy:
fail-fast: false
matrix:
include:
- branch: "aosp-main"
device: "aosp_cf_x86_64_phone"
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
save: false
- name: Setup Cuttlefish environment
run: |
.github/cuttlefish.sh setup
.github/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
curl -LO https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip
unzip ninja-linux.zip -d $ANDROID_HOME/platform-tools
chmod +x $ANDROID_HOME/platform-tools/ninja
rm -rf $ANDROID_HOME/cmake
ccache -o cache_dir=${{ github.workspace }}/.ccache
ccache -o hash_dir=false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
- name: Run Cuttlefish test
timeout-minutes: 10
run: su $USER -c '.github/cuttlefish.sh test'
- name: Upload logs on error
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: "cvd-logs-${{ matrix.device }}"
path: |
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json

View File

@ -17,11 +17,18 @@ jobs:
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'zulu' distribution: 'temurin'
java-version: '17' java-version: '17'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.0
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew :lsplant:publish run: |
rm -rf $ANDROID_HOME/cmake
./gradlew :lsplant:publish
env: env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}

3
.gitmodules vendored
View File

@ -8,3 +8,6 @@
[submodule "test/src/main/jni/external/lsparself"] [submodule "test/src/main/jni/external/lsparself"]
path = test/src/main/jni/external/lsparself path = test/src/main/jni/external/lsparself
url = git@github.com:LSPosed/lsparself.git url = git@github.com:LSPosed/lsparself.git
[submodule "test/src/main/jni/external/lsprism"]
path = test/src/main/jni/external/lsprism
url = git@github.com:LSPosed/lsprism.git

View File

@ -1,7 +1,7 @@
# LSPlant # LSPlant
![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg) ![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg)
![](https://img.shields.io/badge/Android-5.0%20--%2014-blue.svg) ![](https://img.shields.io/badge/Android-5.0%20--%2015%20Beta2-blue.svg)
![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86--64%7C%20riscv64-brightgreen.svg) ![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86--64%7C%20riscv64-brightgreen.svg)
![](https://github.com/LSPosed/LSPlant/actions/workflows/build.yml/badge.svg?branch=master&event=push) ![](https://github.com/LSPosed/LSPlant/actions/workflows/build.yml/badge.svg?branch=master&event=push)
![](https://img.shields.io/maven-central/v/org.lsposed.lsplant/lsplant.svg) ![](https://img.shields.io/maven-central/v/org.lsposed.lsplant/lsplant.svg)
@ -12,7 +12,7 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens
## Features ## Features
+ Support Android 5.0 - 14 (API level 21 - 34) + Support Android 5.0 - 15 Beta2 (API level 21 - 35)
+ Support armeabi-v7a, arm64-v8a, x86, x86-64, riscv64 + Support armeabi-v7a, arm64-v8a, x86, x86-64, riscv64
+ Support customized inline hook framework and ART symbol resolver + Support customized inline hook framework and ART symbol resolver
@ -34,7 +34,7 @@ android {
} }
dependencies { dependencies {
implementation "org.lsposed.lsplant:lsplant:5.2" implementation "org.lsposed.lsplant:lsplant:+"
} }
``` ```
@ -42,7 +42,7 @@ If you don't want to include `libc++_shared.so` in your APK, you can use `lsplan
```gradle ```gradle
dependencies { dependencies {
implementation "org.lsposed.lsplant:lsplant-standalone:5.2" implementation "org.lsposed.lsplant:lsplant-standalone:+"
} }
``` ```

View File

@ -2,9 +2,9 @@ plugins {
alias(libs.plugins.lsplugin.publish) alias(libs.plugins.lsplugin.publish)
} }
val androidTargetSdkVersion by extra(34) val androidTargetSdkVersion by extra(35)
val androidMinSdkVersion by extra(21) val androidMinSdkVersion by extra(21)
val androidBuildToolsVersion by extra("34.0.0") val androidBuildToolsVersion by extra("35.0.0")
val androidCompileSdkVersion by extra(34) val androidCompileSdkVersion by extra(35)
val androidNdkVersion by extra("26.1.10909125") val androidNdkVersion by extra("29.0.13113456")
val androidCmakeVersion by extra("3.22.1+") val androidCmakeVersion by extra("3.28.0+")

View File

@ -2,5 +2,4 @@ android.nonTransitiveRClass=true
android.useAndroidX=true android.useAndroidX=true
android.experimental.testOptions.managedDevices.allowOldApiLevelDevices=true android.experimental.testOptions.managedDevices.allowOldApiLevelDevices=true
android.library.defaults.buildfeatures.androidresources=false android.library.defaults.buildfeatures.androidresources=false
android.defaults.buildfeatures.buildconfig=true
android.nonFinalResIds=false android.nonFinalResIds=false

View File

@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.2.2" agp = "8.9.0"
[plugins] [plugins]
agp-app = { id = "com.android.application", version.ref = "agp" } agp-app = { id = "com.android.application", version.ref = "agp" }
@ -9,9 +9,9 @@ lsplugin-publish = { id = "org.lsposed.lsplugin.publish", version = "1.1" }
lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" } lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" }
[libraries] [libraries]
cxx = { module = "dev.rikka.ndk.thirdparty:cxx", version = "1.2.0" } cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
dobby = { module = "io.github.vvb2060.ndk:dobby", version = "1.2" } dobby = { module = "io.github.vvb2060.ndk:dobby", version = "1.2" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.1.5" } test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
test-runner = { module = "androidx.test:runner", version = "1.5.2" } test-runner = { module = "androidx.test:runner", version = "1.6.2" }
test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.5.1" } test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.6.1" }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

8
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.

2
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################

View File

@ -79,8 +79,8 @@ cmaker {
val flags = arrayOf( val flags = arrayOf(
"-Werror", "-Werror",
"-Wno-gnu-string-literal-operator-template", "-Wno-gnu-string-literal-operator-template",
"-Wno-c++2b-extensions",
) )
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
cppFlags += flags cppFlags += flags
cFlags += flags cFlags += flags
} }
@ -88,10 +88,12 @@ cmaker {
when (it.name) { when (it.name) {
"debug", "release" -> { "debug", "release" -> {
arguments += "-DANDROID_STL=c++_shared" arguments += "-DANDROID_STL=c++_shared"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
} }
"standalone" -> { "standalone" -> {
arguments += "-DANDROID_STL=none" arguments += "-DANDROID_STL=none"
arguments += "-DLSPLANT_STANDALONE=ON" arguments += "-DLSPLANT_STANDALONE=ON"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
} }
} }
arguments += "-DDEBUG_SYMBOLS_PATH=${project.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}" arguments += "-DDEBUG_SYMBOLS_PATH=${project.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}"
@ -105,15 +107,15 @@ dependencies {
val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") { val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") {
from(project.layout.buildDirectory.file("symbols/release")) from(project.layout.buildDirectory.file("symbols/release"))
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier.set("symbols") archiveClassifier = "symbols"
archiveBaseName.set("release") archiveBaseName = "release"
} }
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") { val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") {
from(project.layout.buildDirectory.file("symbols/standalone")) from(project.layout.buildDirectory.file("symbols/standalone"))
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier.set("symbols") archiveClassifier = "symbols"
archiveBaseName.set("standalone") archiveBaseName = "standalone"
} }
val repo = jgit.repo(true) val repo = jgit.repo(true)
@ -127,24 +129,24 @@ publish {
group = "org.lsposed.lsplant" group = "org.lsposed.lsplant"
version = ver version = ver
pom { pom {
name.set("LSPlant") name = "LSPlant"
description.set("A hook framework for Android Runtime (ART)") description = "A hook framework for Android Runtime (ART)"
url.set("https://github.com/LSPosed/LSPlant") url = "https://github.com/LSPosed/LSPlant"
licenses { licenses {
license { license {
name.set("GNU Lesser General Public License v3.0") name = "GNU Lesser General Public License v3.0"
url.set("https://github.com/LSPosed/LSPlant/blob/master/LICENSE") url = "https://github.com/LSPosed/LSPlant/blob/master/LICENSE"
} }
} }
developers { developers {
developer { developer {
name.set("Lsposed") name = "Lsposed"
url.set("https://lsposed.org") url = "https://lsposed.org"
} }
} }
scm { scm {
connection.set("scm:git:https://github.com/LSPosed/LSPlant.git") connection = "scm:git:https://github.com/LSPosed/LSPlant.git"
url.set("https://github.com/LSPosed/LSPlant") url = "https://github.com/LSPosed/LSPlant"
} }
} }
} }

View File

@ -1,27 +0,0 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/external/dex_builder/include
LOCAL_SRC_FILES := lsplant.cc
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := dex_builder
LOCAL_LDLIBS := -llog
LOCAL_EXPORT_LDLIBS := $(LOCAL_LDLIBS)
LOCAL_CFLAGS := -flto
LOCAL_LDFLAGS := -flto
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := lsplant_static
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/external/dex_builder/include
LOCAL_SRC_FILES := lsplant.cc
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
LOCAL_STATIC_LIBRARIES := dex_builder_static
LOCAL_EXPORT_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include jni/external/dex_builder/Android.mk

View File

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

View File

@ -1,8 +1,10 @@
cmake_minimum_required(VERSION 3.4.1) cmake_minimum_required(VERSION 3.28)
project(lsplant) project(lsplant)
find_program(CCACHE ccache) find_program(CCACHE ccache)
set(CMAKE_CXX_STANDARD 23)
if (CCACHE) if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
@ -13,9 +15,11 @@ if (LSPLANT_STANDALONE)
link_libraries(cxx::cxx) link_libraries(cxx::cxx)
endif() endif()
add_definitions(-std=c++20)
set(SOURCES lsplant.cc) set(SOURCES lsplant.cc)
file(GLOB_RECURSE MODULE_SOURCES CONFIGURE_DEPENDS "*.cxx")
file(GLOB_RECURSE MODULE_INTERFACES CONFIGURE_DEPENDS "*.ixx")
list(FILTER MODULE_SOURCES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
list(FILTER MODULE_INTERFACES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE) set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE)
add_subdirectory(external/dex_builder) add_subdirectory(external/dex_builder)
@ -25,6 +29,9 @@ option(LSPLANT_BUILD_SHARED "If ON, lsplant will also build shared library" ON)
if (LSPLANT_BUILD_SHARED) if (LSPLANT_BUILD_SHARED)
message(STATUS "Building lsplant as a shared library") message(STATUS "Building lsplant as a shared library")
add_library(${PROJECT_NAME} SHARED ${SOURCES}) add_library(${PROJECT_NAME} SHARED ${SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME} PRIVATE FILE_SET ms TYPE CXX_MODULES FILES ${MODULE_SOURCES})
target_sources(${PROJECT_NAME} PUBLIC FILE_SET CXX_MODULES FILES ${MODULE_INTERFACES})
target_include_directories(${PROJECT_NAME} PUBLIC include) target_include_directories(${PROJECT_NAME} PUBLIC include)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(${PROJECT_NAME} PRIVATE -flto) target_compile_options(${PROJECT_NAME} PRIVATE -flto)
@ -40,6 +47,9 @@ if (LSPLANT_BUILD_SHARED)
endif() endif()
add_library(${PROJECT_NAME}_static STATIC ${SOURCES}) add_library(${PROJECT_NAME}_static STATIC ${SOURCES})
set_target_properties(${PROJECT_NAME}_static PROPERTIES CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME}_static PRIVATE FILE_SET ms TYPE CXX_MODULES FILES ${MODULE_SOURCES})
target_sources(${PROJECT_NAME}_static PUBLIC FILE_SET CXX_MODULES FILES ${MODULE_INTERFACES})
target_include_directories(${PROJECT_NAME}_static PUBLIC include) target_include_directories(${PROJECT_NAME}_static PUBLIC include)
target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,31 +1,26 @@
#pragma once module;
#include "art/runtime/art_method.hpp" #include <parallel_hashmap/phmap.h>
#include "art/runtime/handle.hpp"
#include "common.hpp"
namespace lsplant::art { #include "logging.hpp"
class Thread;
namespace dex {
class ClassDef {};
} // namespace dex
namespace mirror { export module lsplant:clazz;
import :common;
import :art_method;
import :thread;
import :handle;
import hook_helper;
export namespace lsplant::art::mirror {
class Class { class Class {
private: private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) { inline static auto GetDescriptor_ =
if (GetDescriptorSym) [[likely]] "_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>;
return GetDescriptorSym(thiz, storage);
else
return "";
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) { inline static auto GetClassDef_ =
if (GetClassDefSym) [[likely]] "_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>;
return GetClassDefSym(thiz);
return nullptr;
}
using BackupMethods = phmap::flat_hash_map<art::ArtMethod *, void *>; using BackupMethods = phmap::flat_hash_map<art::ArtMethod *, void *>;
inline static phmap::flat_hash_map<const art::Thread *, inline static phmap::flat_hash_map<const art::Thread *,
@ -33,6 +28,8 @@ private:
backup_methods_; backup_methods_;
inline static std::mutex backup_methods_lock_; inline static std::mutex backup_methods_lock_;
inline static uint8_t initialized_status = 0;
static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) { static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods out; BackupMethods out;
if (!class_def) return; if (!class_def) return;
@ -62,62 +59,60 @@ private:
} }
} }
CREATE_HOOK_STUB_ENTRY( inline static auto SetClassStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE", void, "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE"_sym.hook->*[]
SetClassStatus, (TrivialHandle<Class> h, uint8_t new_status, Thread *self), { <Backup auto backup>
(TrivialHandle<Class> h, uint8_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) { if (new_status == initialized_status) {
BackupClassMethods(h->GetClassDef(), self); BackupClassMethods(GetClassDef_(h.Get()), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}); };
CREATE_HOOK_STUB_ENTRY( inline static auto SetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, SetStatus, "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
(Handle<Class> h, int new_status, Thread *self), { <Backup auto backup>
(Handle<Class> h, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) { if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(h->GetClassDef(), self); BackupClassMethods(GetClassDef_(h.Get()), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}); };
CREATE_HOOK_STUB_ENTRY( inline static auto TrivialSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
TrivialSetStatus, (TrivialHandle<Class> h, uint32_t new_status, Thread *self), { <Backup auto backup>
(TrivialHandle<Class> h, uint32_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) { if (new_status == initialized_status) {
BackupClassMethods(h->GetClassDef(), self); BackupClassMethods(GetClassDef_(h.Get()), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}); };
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE", void, inline static auto ClassSetStatus_ =
ClassSetStatus, (Class * thiz, int new_status, Thread *self), { "_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(Class *thiz, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) { if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(thiz->GetClassDef(), self); BackupClassMethods(GetClassDef_(thiz), self);
} }
return backup(thiz, new_status, self); return backup(thiz, new_status, self);
}); };
inline static uint8_t initialized_status = 0;
public: public:
static bool Init(const HookHandler &handler) { static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor, if (!handler(GetDescriptor_) || !handler(GetClassDef_)) {
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE")) {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv")) {
return false; return false;
} }
int sdk_int = GetAndroidApiLevel(); int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) { if (sdk_int < __ANDROID_API_O__) {
if (!HookSyms(handler, SetStatus, ClassSetStatus)) { if (!handler(SetStatus_, ClassSetStatus_)) {
return false; return false;
} }
} else { } else {
if (!HookSyms(handler, SetClassStatus, TrivialSetStatus)) { if (!handler(SetClassStatus_, TrivialSetStatus_)) {
return false; return false;
} }
} }
@ -135,22 +130,14 @@ public:
return true; return true;
} }
const char *GetDescriptor(std::string *storage) { const char *GetDescriptor(std::string *storage) { return GetDescriptor_(this, storage); }
if (GetDescriptorSym) {
return GetDescriptor(this, storage);
}
return "";
}
std::string GetDescriptor() { std::string GetDescriptor() {
std::string storage; std::string storage;
return GetDescriptor(&storage); return GetDescriptor(&storage);
} }
const dex::ClassDef *GetClassDef() { const dex::ClassDef *GetClassDef() { return GetClassDef_(this); }
if (GetClassDefSym) return GetClassDef(this);
return nullptr;
}
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) { static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods methods; BackupMethods methods;
@ -184,5 +171,4 @@ public:
} }
}; };
} // namespace mirror } // namespace lsplant::art::mirror
} // namespace lsplant::art

View File

@ -1,38 +1,51 @@
#pragma once module;
#include "art/mirror/class.hpp" #include <atomic>
#include "common.hpp" #include <string>
#include <memory>
namespace lsplant::art { #include "logging.hpp"
export module lsplant:art_method;
import :common;
import hook_helper;
export namespace lsplant::art {
class ArtMethod { class ArtMethod {
CREATE_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) { inline static auto PrettyMethod_ =
if (thiz == nullptr) [[unlikely]] "_ZN3art9ArtMethod12PrettyMethodEPS0_b"_sym.as<std::string(ArtMethod::*)(bool)>;
return "null";
else if (PrettyMethodSym) [[likely]] inline static auto PrettyMethodStatic_ =
return PrettyMethodSym(thiz, with_signature); "_ZN3art12PrettyMethodEPNS_9ArtMethodEb"_sym.as<std::string(ArtMethod *thiz, bool with_signature)>;
else
return "null sym"; inline static auto PrettyMethodMirror_ =
"_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb"_sym.as<std::string(ArtMethod *thiz, bool with_signature)>;
inline static auto GetMethodShortyL_ =
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as<const char *(JNIEnv *env, jmethodID method)>;
inline static auto GetMethodShorty_ =
"_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as<const char *(JNIEnv *env, jmethodID mid)>;
inline static auto ThrowInvocationTimeError_ =
"_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv"_sym.as<void(ArtMethod::*)()>;
inline static auto art_interpreter_to_compiled_code_bridge_ =
"artInterpreterToCompiledCodeBridge"_sym.as<void()>;
inline void ThrowInvocationTimeError() {
if (ThrowInvocationTimeError_) {
[[likely]] ThrowInvocationTimeError_(this);
} }
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, ThrowInvocationTimeError, ArtMethod *thiz) {
if (thiz && ThrowInvocationTimeErrorSym) [[likely]]
return ThrowInvocationTimeErrorSym(thiz);
} }
CREATE_FUNC_SYMBOL_ENTRY(const char *, GetMethodShorty, JNIEnv *env, jmethodID mid) {
if (GetMethodShortySym) [[likely]]
return GetMethodShortySym(env, mid);
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(void, art_interpreter_to_compiled_code_bridge) {}
inline void ThrowInvocationTimeError() { ThrowInvocationTimeError(this); }
public: public:
inline static const char *GetMethodShorty(JNIEnv *env, jobject method) { inline static const char *GetMethodShorty(JNIEnv *env, jobject method) {
return GetMethodShorty(env, env->FromReflectedMethod(method)); if (GetMethodShortyL_) {
return GetMethodShortyL_(env, env->FromReflectedMethod(method));
}
return GetMethodShorty_(env, env->FromReflectedMethod(method));
} }
void SetNonCompilable() { void SetNonCompilable() {
@ -78,6 +91,18 @@ public:
SetAccessFlags(access_flags); SetAccessFlags(access_flags);
} }
void SetNative() {
auto access_flags = GetAccessFlags();
access_flags |= kAccNative;
SetAccessFlags(access_flags);
}
void SetNonNative() {
auto access_flags = GetAccessFlags();
access_flags &= ~kAccNative;
SetAccessFlags(access_flags);
}
bool IsPrivate() { return GetAccessFlags() & kAccPrivate; } bool IsPrivate() { return GetAccessFlags() & kAccPrivate; }
bool IsProtected() { return GetAccessFlags() & kAccProtected; } bool IsProtected() { return GetAccessFlags() & kAccProtected; }
bool IsPublic() { return GetAccessFlags() & kAccPublic; } bool IsPublic() { return GetAccessFlags() & kAccPublic; }
@ -94,7 +119,7 @@ public:
if (interpreter_entry_point_offset) [[unlikely]] { if (interpreter_entry_point_offset) [[unlikely]] {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
interpreter_entry_point_offset) = interpreter_entry_point_offset) =
reinterpret_cast<void *>(art_interpreter_to_compiled_code_bridgeSym); reinterpret_cast<void *>(&art_interpreter_to_compiled_code_bridge_);
} }
} }
@ -123,7 +148,11 @@ public:
} }
std::string PrettyMethod(bool with_signature = true) { std::string PrettyMethod(bool with_signature = true) {
return PrettyMethod(this, with_signature); if (PrettyMethod_) [[likely]]
return PrettyMethod_(this, with_signature);
if (PrettyMethodStatic_) return PrettyMethodStatic_(this, with_signature);
if (PrettyMethodMirror_) return PrettyMethodMirror_(this, with_signature);
return "null sym";
} }
mirror::Class *GetDeclaringClass() { mirror::Class *GetDeclaringClass() {
@ -131,6 +160,23 @@ public:
reinterpret_cast<uintptr_t>(this) + declaring_class_offset)); reinterpret_cast<uintptr_t>(this) + declaring_class_offset));
} }
std::unique_ptr<ArtMethod> Clone() {
auto *method = reinterpret_cast<ArtMethod*>(::operator new(art_method_size));
method->CopyFrom(this);
return std::unique_ptr<ArtMethod>(method);
}
void BackupTo(ArtMethod *backup) {
SetNonCompilable();
// copy after setNonCompilable
backup->CopyFrom(this);
ClearFastInterpretFlag();
if (!backup->IsStatic()) backup->SetPrivate();
}
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
if (art_method_field) [[likely]] { if (art_method_field) [[likely]] {
return reinterpret_cast<art::ArtMethod *>( return reinterpret_cast<art::ArtMethod *>(
@ -221,7 +267,7 @@ public:
env, env,
JNI_GetObjectField( JNI_GetObjectField(
env, env,
env->ToReflectedField(executable, JNI_ToReflectedField(env, executable,
JNI_GetFieldID(env, executable, name, sig), false), JNI_GetFieldID(env, executable, name, sig), false),
art_field_field), art_field_field),
field_offset); field_offset);
@ -249,17 +295,12 @@ public:
} }
if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0; if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0;
if (!RETRIEVE_FUNC_SYMBOL(GetMethodShorty, if (!handler(GetMethodShortyL_, true, GetMethodShorty_)) {
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID", true) &&
!RETRIEVE_FUNC_SYMBOL(GetMethodShorty,
"_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID")) {
LOGE("Failed to find GetMethodShorty"); LOGE("Failed to find GetMethodShorty");
return false; return false;
} }
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEPS0_b") && handler(PrettyMethod_, PrettyMethodStatic_, PrettyMethodMirror_);
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_9ArtMethodEb") &&
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb");
if (sdk_int <= __ANDROID_API_O__) [[unlikely]] { if (sdk_int <= __ANDROID_API_O__) [[unlikely]] {
auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError"); auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError");
@ -274,9 +315,9 @@ public:
LOGE("Failed to find Executable.getName"); LOGE("Failed to find Executable.getName");
return false; return false;
} }
RETRIEVE_MEM_FUNC_SYMBOL(ThrowInvocationTimeError, "_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv"); handler(ThrowInvocationTimeError_);
auto abstract_method = FromReflectedMethod( auto abstract_method = FromReflectedMethod(
env, JNI_ToReflectedMethod(env, executable, executable_get_name, false)); env, JNI_ToReflectedMethod(env, executable, executable_get_name, false).get());
uint32_t access_flags = abstract_method->GetAccessFlags(); uint32_t access_flags = abstract_method->GetAccessFlags();
abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict); abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict);
abstract_method->ThrowInvocationTimeError(); abstract_method->ThrowInvocationTimeError();
@ -293,8 +334,7 @@ public:
kAccCompileDontBother = 0; kAccCompileDontBother = 0;
} }
if (sdk_int <= __ANDROID_API_M__) [[unlikely]] { if (sdk_int <= __ANDROID_API_M__) [[unlikely]] {
if (!RETRIEVE_FUNC_SYMBOL(art_interpreter_to_compiled_code_bridge, if (!handler(art_interpreter_to_compiled_code_bridge_)) {
"artInterpreterToCompiledCodeBridge")) {
return false; return false;
} }
if (sdk_int >= __ANDROID_API_L_MR1__) { if (sdk_int >= __ANDROID_API_L_MR1__) {

View File

@ -0,0 +1,239 @@
module;
#include <sys/types.h>
#include "logging.hpp"
export module lsplant:class_linker;
import :art_method;
import :thread;
import :common;
import :clazz;
import :handle;
import :runtime;
import hook_helper;
namespace lsplant::art {
export class ClassLinker {
private:
inline static auto SetEntryPointsToInterpreter_ =
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE"_sym.as<void(ClassLinker::*)(ArtMethod *)>;
inline static auto ShouldUseInterpreterEntrypoint_ =
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv"_sym.hook->*[]
<Backup auto backup>
(ArtMethod *art_method, const void *quick_code)static -> bool {
if (quick_code != nullptr && IsHooked(art_method)) [[unlikely]] {
return false;
}
return backup(art_method, quick_code);
};
inline static auto art_quick_to_interpreter_bridge_ =
"art_quick_to_interpreter_bridge"_sym.as<void(void *)>;
inline static auto GetOptimizedCodeFor_ =
"_ZN3art15instrumentationL19GetOptimizedCodeForEPNS_9ArtMethodE"_sym.as<void *(ArtMethod *)>;
inline static auto GetRuntimeQuickGenericJniStub_=
"_ZNK3art11ClassLinker29GetRuntimeQuickGenericJniStubEv"_sym.as<void *(ClassLinker::*)()>;
inline static art::ArtMethod *MayGetBackup(art::ArtMethod *method) {
if (auto backup = IsHooked(method); backup) [[unlikely]] {
method = backup;
LOGV("propagate native method: %s", method->PrettyMethod(true).data());
}
return method;
}
inline static auto RegisterNativeThread_ =
"_ZN3art6mirror9ArtMethod14RegisterNativeEPNS_6ThreadEPKvb"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method, Thread *thread, const void *native_method, bool is_fast) static -> void {
return backup(thiz, MayGetBackup(method), thread, native_method, is_fast);
};
inline static auto UnregisterNativeThread_ =
"_ZN3art6mirror9ArtMethod16UnregisterNativeEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method, Thread *thread) static -> void {
return backup(thiz, MayGetBackup(method), thread);
};
inline static auto RegisterNativeFast_ =
"_ZN3art9ArtMethod14RegisterNativeEPKvb"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method, const void *native_method, bool is_fast) static -> void {
return backup(thiz, MayGetBackup(method), native_method, is_fast);
};
inline static auto UnregisterNativeFast_ =
"_ZN3art9ArtMethod16UnregisterNativeEv"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method) static -> void{
return backup(thiz, MayGetBackup(method));
};
inline static auto RegisterNative_ =
"_ZN3art9ArtMethod14RegisterNativeEPKv"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method, const void *native_method) static -> const void * {
return backup(thiz, MayGetBackup(method), native_method);
};
inline static auto UnregisterNative_ =
"_ZN3art9ArtMethod16UnregisterNativeEv"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ArtMethod *method) static -> const void * {
return backup(thiz, MayGetBackup(method));
};
inline static auto RegisterNativeClassLinker_ =
"_ZN3art11ClassLinker14RegisterNativeEPNS_6ThreadEPNS_9ArtMethodEPKv"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, Thread *self, ArtMethod *method, const void *native_method) static -> const void *{
return backup(thiz, self, MayGetBackup(method), native_method);
};
inline static auto UnregisterNativeClassLinker_ =
"_ZN3art11ClassLinker16UnregisterNativeEPNS_6ThreadEPNS_9ArtMethodE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, Thread *self, ArtMethod *method) static -> const void * {
return backup(thiz, self, MayGetBackup(method));
};
static void RestoreBackup(const dex::ClassDef *class_def, art::Thread *self) {
auto methods = mirror::Class::PopBackup(class_def, self);
for (const auto &[art_method, old_trampoline] : methods) {
auto new_trampoline = art_method->GetEntryPoint();
art_method->SetEntryPoint(old_trampoline);
auto deoptimized = IsDeoptimized(art_method);
auto backup_method = IsHooked(art_method);
if (backup_method) {
// If deoptimized, the backup entrypoint should be already set to interpreter
if (!deoptimized && new_trampoline != old_trampoline) [[unlikely]] {
LOGV("propagate entrypoint for orig %p backup %p", art_method, backup_method);
backup_method->SetEntryPoint(new_trampoline);
}
} else if (deoptimized) {
if (new_trampoline != &art_quick_to_interpreter_bridge_ && !art_method->IsNative()) {
LOGV("re-deoptimize for %p", art_method);
SetEntryPointsToInterpreter(art_method);
}
}
}
}
inline static auto FixupStaticTrampolines_ =
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, ObjPtr<mirror::Class> mirror_class) static -> void {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
};
inline static auto FixupStaticTrampolinesWithThread_ =
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, Thread *self, ObjPtr<mirror::Class> mirror_class) static -> void {
backup(thiz, self, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), self);
};
inline static auto FixupStaticTrampolinesRaw_ =
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, mirror::Class *mirror_class)static -> void {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
};
inline static auto AdjustThreadVisibilityCounter_ =
("_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEi"_sym |
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEl"_sym).hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, Thread *self, ssize_t adjustment) static -> void {
backup(thiz, self, adjustment);
RestoreBackup(nullptr, self);
};
inline static auto MarkVisiblyInitialized_ =
"_ZN3art11ClassLinker26VisiblyInitializedCallback22MarkVisiblyInitializedEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(ClassLinker *thiz, Thread *self) static -> void {
backup(thiz, self);
RestoreBackup(nullptr, self);
};
public:
static bool Init(JNIEnv *env, const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_N__ && sdk_int < __ANDROID_API_T__) {
handler(ShouldUseInterpreterEntrypoint_);
}
if (!handler(FixupStaticTrampolinesWithThread_, FixupStaticTrampolines_,
FixupStaticTrampolinesRaw_)) {
return false;
}
if (!handler(RegisterNativeClassLinker_, RegisterNative_, RegisterNativeFast_,
RegisterNativeThread_) ||
!handler(UnregisterNativeClassLinker_, UnregisterNative_, UnregisterNativeFast_,
UnregisterNativeThread_)) {
return false;
}
if (sdk_int >= __ANDROID_API_R__) {
if constexpr (kArch != Arch::kX86 && kArch != Arch::kX86_64) {
// fixup static trampoline may have been inlined
handler(AdjustThreadVisibilityCounter_, MarkVisiblyInitialized_);
}
}
if (!handler(SetEntryPointsToInterpreter_)) [[likely]] {
if (handler(GetOptimizedCodeFor_, true)) [[likely]] {
auto obj = JNI_FindClass(env, "java/lang/Object");
if (!obj) {
return false;
}
auto method = JNI_GetMethodID(env, obj, "equals", "(Ljava/lang/Object;)Z");
if (!method) {
return false;
}
auto dummy = ArtMethod::FromReflectedMethod(
env, JNI_ToReflectedMethod(env, obj, method, false).get())->Clone();
JavaDebuggableGuard guard;
// just in case
dummy->SetNonNative();
art_quick_to_interpreter_bridge_ = GetOptimizedCodeFor_(dummy.get());
} else if (!handler(art_quick_to_interpreter_bridge_)) [[unlikely]] {
return false;
}
}
LOGD("art_quick_to_interpreter_bridge = %p", &art_quick_to_interpreter_bridge_);
return true;
}
[[gnu::always_inline]] static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
if (art_method->IsNative()) {
return false;
}
if (SetEntryPointsToInterpreter_) [[likely]] {
SetEntryPointsToInterpreter_(nullptr, art_method);
return true;
}
// Android 13
if (art_quick_to_interpreter_bridge_) [[likely]] {
LOGV("deoptimize method %s from %p to %p", art_method->PrettyMethod(true).data(),
art_method->GetEntryPoint(), &art_quick_to_interpreter_bridge_);
art_method->SetEntryPoint(
reinterpret_cast<void *>(&art_quick_to_interpreter_bridge_));
return true;
}
return false;
}
};
} // namespace lsplant::art

View File

@ -1,206 +0,0 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/runtime/obj_ptr.hpp"
#include "art/runtime/thread.hpp"
#include "common.hpp"
namespace lsplant::art {
class ClassLinker {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, ClassLinker *thiz,
ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreterSym(thiz, art_method);
}
}
CREATE_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv", bool,
ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code), {
if (quick_code != nullptr && IsHooked(art_method)) [[unlikely]] {
return false;
}
return backup(art_method, quick_code);
});
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void *) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void *) {}
inline static art::ArtMethod *MayGetBackup(art::ArtMethod *method) {
if (auto backup = IsHooked(method); backup) [[unlikely]] {
method = backup;
LOGV("propagate native method: %s", method->PrettyMethod(true).data());
}
return method;
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art6mirror9ArtMethod14RegisterNativeEPNS_6ThreadEPKvb", void, RegisterNativeThread,
(art::ArtMethod * method, art::Thread *thread, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), thread, native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror9ArtMethod16UnregisterNativeEPNS_6ThreadE", void,
UnregisterNativeThread,
(art::ArtMethod * method, art::Thread *thread),
{ return backup(MayGetBackup(method), thread); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKvb", void, RegisterNativeFast,
(art::ArtMethod * method, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", void, UnregisterNativeFast,
(art::ArtMethod * method), { return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKv", const void *,
RegisterNative, (art::ArtMethod * method, const void *native_method),
{ return backup(MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", const void *,
UnregisterNative, (art::ArtMethod * method),
{ return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker14RegisterNativeEPNS_6ThreadEPNS_9ArtMethodEPKv", const void *,
RegisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method,
const void *native_method),
{ return backup(thiz, self, MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker16UnregisterNativeEPNS_6ThreadEPNS_9ArtMethodE",
const void *, UnregisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method),
{ return backup(thiz, self, MayGetBackup(method)); });
static auto RestoreBackup(const dex::ClassDef *class_def, art::Thread *self) {
auto methods = mirror::Class::PopBackup(class_def, self);
for (const auto &[art_method, old_trampoline] : methods) {
auto new_trampoline = art_method->GetEntryPoint();
art_method->SetEntryPoint(old_trampoline);
auto deoptimized = IsDeoptimized(art_method);
auto backup_method = IsHooked(art_method);
if (backup_method) {
// If deoptimized, the backup entrypoint should be already set to interpreter
if (!deoptimized && new_trampoline != old_trampoline) [[unlikely]] {
LOGV("propagate entrypoint for orig %p backup %p", art_method, backup_method);
backup_method->SetEntryPoint(new_trampoline);
}
} else if (deoptimized) {
if (new_trampoline != art_quick_to_interpreter_bridge &&
new_trampoline != art_quick_generic_jni_trampoline) {
LOGV("re-deoptimize for %p", art_method);
SetEntryPointsToInterpreter(art_method);
}
}
}
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void,
FixupStaticTrampolines, (ClassLinker * thiz, ObjPtr<mirror::Class> mirror_class), {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolinesWithThread,
(ClassLinker * thiz, art::Thread *self, ObjPtr<mirror::Class> mirror_class), {
backup(thiz, self, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), self);
});
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE",
void, FixupStaticTrampolinesRaw,
(ClassLinker * thiz, mirror::Class *mirror_class), {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
});
CREATE_MEM_HOOK_STUB_ENTRY(
LP_SELECT(
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEi",
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEl"),
void, AdjustThreadVisibilityCounter, (void *thiz, art::Thread *self, ssize_t adjustment), {
backup(thiz, self, adjustment);
RestoreBackup(nullptr, self);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker26VisiblyInitializedCallback22MarkVisiblyInitializedEPNS_6ThreadE",
void, MarkVisiblyInitialized, (void *thiz, Thread* self), {
backup(thiz, self);
RestoreBackup(nullptr, self);
});
public:
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_N__ && sdk_int < __ANDROID_API_T__) {
HookSyms(handler, ShouldUseInterpreterEntrypoint);
}
if (!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines,
FixupStaticTrampolinesRaw)) {
return false;
}
if (!HookSyms(handler, RegisterNativeClassLinker, RegisterNative, RegisterNativeFast,
RegisterNativeThread) ||
!HookSyms(handler, UnregisterNativeClassLinker, UnregisterNative, UnregisterNativeFast,
UnregisterNativeThread)) {
return false;
}
if (sdk_int >= __ANDROID_API_R__) {
if constexpr (GetArch() != Arch::kX86 && GetArch() != Arch::kX86_64) {
// fixup static trampoline may have been inlined
HookSyms(handler, AdjustThreadVisibilityCounter, MarkVisiblyInitialized);
}
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(
SetEntryPointsToInterpreter,
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE"))
[[unlikely]] {
if (!RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge,
"art_quick_to_interpreter_bridge")) [[unlikely]] {
return false;
}
if (!RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline,
"art_quick_generic_jni_trampoline")) [[unlikely]] {
return false;
}
LOGD("art_quick_to_interpreter_bridge = %p", art_quick_to_interpreter_bridgeSym);
LOGD("art_quick_generic_jni_trampoline = %p", art_quick_generic_jni_trampolineSym);
}
return true;
}
[[gnu::always_inline]] static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreter(nullptr, art_method);
return true;
}
// Android 13
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
if (art_method->GetAccessFlags() & ArtMethod::kAccNative) [[unlikely]] {
LOGV("deoptimize native method %s from %p to %p",
art_method->PrettyMethod(true).data(), art_method->GetEntryPoint(),
art_quick_generic_jni_trampolineSym);
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
} else {
LOGV("deoptimize method %s from %p to %p", art_method->PrettyMethod(true).data(),
art_method->GetEntryPoint(), art_quick_to_interpreter_bridgeSym);
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
}
return true;
}
return false;
}
};
} // namespace lsplant::art

View File

@ -0,0 +1,143 @@
module;
#include <memory>
#include <string>
#include <vector>
#include "logging.hpp"
export module lsplant:dex_file;
import :common;
import hook_helper;
namespace lsplant::art {
export class DexFile {
struct Header {
[[maybe_unused]] uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_
};
inline static auto OpenMemory_ =
("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"_sym |
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_"_sym).as<
std::unique_ptr<DexFile>(const uint8_t* dex_file, size_t size, const std::string& location,
uint32_t location_checksum, void* mem_map,
const void* oat_dex_file, std::string* error_msg)>;
inline static auto OpenMemoryRaw_ =
("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_"_sym |
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_"_sym).as<
const DexFile*(const uint8_t* dex_file, size_t size, const std::string& location,
uint32_t location_checksum, void* mem_map, const void* oat_dex_file,
std::string* error_msg)>;
inline static auto OpenMemoryWithoutOdex_ =
("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"_sym |
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"_sym).as<
const DexFile*(const uint8_t* dex_file, size_t size, const std::string& location,
uint32_t location_checksum, void* mem_map, std::string* error_msg)>;
inline static auto DexFile_setTrusted_ =
"_ZN3artL18DexFile_setTrustedEP7_JNIEnvP7_jclassP8_jobject"_sym.as<void(JNIEnv* env, jclass clazz, jobject j_cookie)>;
public:
static const DexFile* OpenMemory(const uint8_t* dex_file, size_t size, std::string location,
std::string* error_msg) {
if (OpenMemory_) [[likely]] {
return OpenMemory_(dex_file, size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg)
.release();
}
if (OpenMemoryRaw_) [[likely]] {
return OpenMemoryRaw_(dex_file, size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg);
}
if (OpenMemoryWithoutOdex_) [[likely]] {
return OpenMemoryWithoutOdex_(dex_file, size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_,
nullptr, error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
jobject ToJavaDexFile(JNIEnv* env) const {
auto* java_dex_file = env->AllocObject(dex_file_class);
auto cookie = JNI_NewLongArray(env, dex_file_start_index + 1);
if (dex_file_start_index != size_t(-1)) [[likely]] {
cookie[oat_file_index] = 0;
cookie[dex_file_start_index] = reinterpret_cast<jlong>(this);
cookie.commit();
JNI_SetObjectField(env, java_dex_file, cookie_field, cookie);
if (internal_cookie_field) {
JNI_SetObjectField(env, java_dex_file, internal_cookie_field, cookie);
}
} else {
JNI_SetLongField(
env, java_dex_file, cookie_field,
static_cast<jlong>(reinterpret_cast<uintptr_t>(new std::vector{this})));
}
JNI_SetObjectField(env, java_dex_file, file_name_field, JNI_NewStringUTF(env, ""));
return java_dex_file;
}
static bool SetTrusted(JNIEnv* env, jobject cookie) {
if (!DexFile_setTrusted_) return false;
DexFile_setTrusted_(env, nullptr, cookie);
return true;
}
static bool Init(JNIEnv* env, const HookHandler& handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!handler(DexFile_setTrusted_, true)) {
LOGW("DexFile.setTrusted not found, MakeDexFileTrusted will not work.");
}
}
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
return true;
}
if (!handler(OpenMemory_, OpenMemoryRaw_, OpenMemoryWithoutOdex_)) [[unlikely]] {
LOGE("Failed to find OpenMemory");
return false;
}
dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile"));
if (!dex_file_class) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_M__) [[unlikely]] {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;");
} else {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J");
dex_file_start_index = -1;
}
if (!cookie_field) [[unlikely]] {
return false;
}
file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;");
if (!file_name_field) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
internal_cookie_field =
JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;");
if (!internal_cookie_field) [[unlikely]] {
return false;
}
dex_file_start_index = 1u;
}
return true;
}
private:
inline static jclass dex_file_class = nullptr;
inline static jfieldID cookie_field = nullptr;
inline static jfieldID file_name_field = nullptr;
inline static jfieldID internal_cookie_field = nullptr;
inline static size_t oat_file_index = 0u;
inline static size_t dex_file_start_index = 0u;
};
} // namespace lsplant::art

View File

@ -1,172 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "common.hpp"
namespace lsplant::art {
class DexFile {
struct Header {
[[maybe_unused]] uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_
};
CREATE_FUNC_SYMBOL_ENTRY(std::unique_ptr<DexFile>, OpenMemory, const uint8_t* dex_file,
size_t size, const std::string& location, uint32_t location_checksum,
void* mem_map, const void* oat_dex_file, std::string* error_msg) {
if (OpenMemorySym) [[likely]] {
return OpenMemorySym(dex_file, size, location, location_checksum, mem_map, oat_dex_file,
error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryRaw, const uint8_t* dex_file, size_t size,
const std::string& location, uint32_t location_checksum, void* mem_map,
const void* oat_dex_file, std::string* error_msg) {
if (OpenMemoryRawSym) [[likely]] {
return OpenMemoryRawSym(dex_file, size, location, location_checksum, mem_map,
oat_dex_file, error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryWithoutOdex, const uint8_t* dex_file,
size_t size, const std::string& location, uint32_t location_checksum,
void* mem_map, std::string* error_msg) {
if (OpenMemoryWithoutOdexSym) [[likely]] {
return OpenMemoryWithoutOdexSym(dex_file, size, location, location_checksum, mem_map,
error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv* env, jclass clazz,
jobject j_cookie) {
if (DexFile_setTrustedSym != nullptr) [[likely]] {
DexFile_setTrustedSym(env, clazz, j_cookie);
}
}
public:
static const DexFile* OpenMemory(const void* dex_file, size_t size, std::string location,
std::string* error_msg) {
if (OpenMemorySym) [[likely]] {
return OpenMemory(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg)
.release();
} else if (OpenMemoryRawSym) [[likely]] {
return OpenMemoryRaw(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg);
} else if (OpenMemoryWithoutOdexSym) [[likely]] {
return OpenMemoryWithoutOdex(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_,
nullptr, error_msg);
} else {
if (error_msg) *error_msg = "no sym";
return nullptr;
}
}
jobject ToJavaDexFile(JNIEnv* env) const {
auto java_dex_file = env->AllocObject(dex_file_class);
auto cookie = JNI_NewLongArray(env, dex_file_start_index + 1);
if (dex_file_start_index != size_t(-1)) [[likely]] {
cookie[oat_file_index] = 0;
cookie[dex_file_start_index] = reinterpret_cast<jlong>(this);
cookie.commit();
JNI_SetObjectField(env, java_dex_file, cookie_field, cookie);
if (internal_cookie_field) {
JNI_SetObjectField(env, java_dex_file, internal_cookie_field, cookie);
}
} else {
JNI_SetLongField(
env, java_dex_file, cookie_field,
static_cast<jlong>(reinterpret_cast<uintptr_t>(new std::vector{this})));
}
JNI_SetObjectField(env, java_dex_file, file_name_field, JNI_NewStringUTF(env, ""));
return java_dex_file;
}
static bool SetTrusted(JNIEnv* env, jobject cookie) {
if (!DexFile_setTrustedSym) return false;
DexFile_setTrusted(env, nullptr, cookie);
return true;
}
static bool Init(JNIEnv* env, const HookHandler& handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!RETRIEVE_FUNC_SYMBOL(DexFile_setTrusted,
"_ZN3artL18DexFile_setTrustedEP7_JNIEnvP7_jclassP8_jobject",
true)) {
LOGW("DexFile.setTrusted not found, MakeDexFileTrusted will not work.");
}
}
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
return true;
}
if (!RETRIEVE_FUNC_SYMBOL(
OpenMemory,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryRaw,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryWithoutOdex,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"))) [[unlikely]] {
LOGE("Failed to find OpenMemory");
return false;
}
dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile"));
if (!dex_file_class) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_M__) [[unlikely]] {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;");
} else {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J");
dex_file_start_index = -1;
}
if (!cookie_field) [[unlikely]] {
return false;
}
file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;");
if (!file_name_field) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
internal_cookie_field =
JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;");
if (!internal_cookie_field) [[unlikely]] {
return false;
}
dex_file_start_index = 1u;
}
return true;
}
private:
inline static jclass dex_file_class = nullptr;
inline static jfieldID cookie_field = nullptr;
inline static jfieldID file_name_field = nullptr;
inline static jfieldID internal_cookie_field = nullptr;
inline static size_t oat_file_index = 0u;
inline static size_t dex_file_start_index = 0u;
};
} // namespace lsplant::art

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,16 @@
#pragma once module;
#include "art_method.hpp" #include "logging.hpp"
#include "common.hpp"
export module lsplant:instrumentation;
import :art_method;
import :common;
import hook_helper;
namespace lsplant::art { namespace lsplant::art {
class Instrumentation { export class Instrumentation {
inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) { inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) {
if (auto backup = IsHooked(art_method); backup && art_method->GetEntryPoint() != quick_code) if (auto backup = IsHooked(art_method); backup && art_method->GetEntryPoint() != quick_code)
[[unlikely]] { [[unlikely]] {
@ -16,29 +21,29 @@ class Instrumentation {
return art_method; return art_method;
} }
CREATE_MEM_HOOK_STUB_ENTRY( inline static auto UpdateMethodsCodeToInterpreterEntryPoint_ =
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE", "_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE"_sym.hook->*[]
void, UpdateMethodsCodeToInterpreterEntryPoint, <MemBackup auto backup>
(Instrumentation * thiz, ArtMethod *art_method), { (Instrumentation *thiz, ArtMethod *art_method) static -> void {
if (IsDeoptimized(art_method)) { if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s", LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str()); art_method->PrettyMethod(true).c_str());
return; return;
} }
backup(thiz, MaybeUseBackupMethod(art_method, nullptr)); backup(thiz, MaybeUseBackupMethod(art_method, nullptr));
}); };
CREATE_MEM_HOOK_STUB_ENTRY( inline static auto InitializeMethodsCode_ =
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv", void, "_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv"_sym.hook->*[]
InitializeMethodsCode, <MemBackup auto backup>
(Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), { (Instrumentation *thiz, ArtMethod *art_method, const void *quick_code) static -> void {
if (IsDeoptimized(art_method)) { if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s", LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str()); art_method->PrettyMethod(true).c_str());
return; return;
} }
backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code); backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code);
}); };
public: public:
static bool Init(JNIEnv *env, const HookHandler &handler) { static bool Init(JNIEnv *env, const HookHandler &handler) {
@ -47,8 +52,7 @@ public:
} }
int sdk_int = GetAndroidApiLevel(); int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] { if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!HookSyms(handler, InitializeMethodsCode, if (!handler(InitializeMethodsCode_, UpdateMethodsCodeToInterpreterEntryPoint_)) {
UpdateMethodsCodeToInterpreterEntryPoint)) {
return false; return false;
} }
} }

View File

@ -0,0 +1,55 @@
module;
#include "logging.hpp"
export module lsplant:jit;
import :art_method;
import :common;
import :thread;
import hook_helper;
namespace lsplant::art::jit {
enum class CompilationKind {
kOsr [[maybe_unused]],
kBaseline [[maybe_unused]],
kOptimized,
};
export class Jit {
inline static auto EnqueueOptimizedCompilation_ =
"_ZN3art3jit3Jit27EnqueueOptimizedCompilationEPNS_9ArtMethodEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(Jit *thiz, ArtMethod *method, Thread *self) static -> void {
if (auto target = IsBackup(method); target) [[unlikely]] {
LOGD("Propagate enqueue compilation: %p -> %p", method, target);
method = target;
}
return backup(thiz, method, self);
};
inline static auto AddCompileTask_ =
"_ZN3art3jit3Jit14AddCompileTaskEPNS_6ThreadEPNS_9ArtMethodENS_15CompilationKindEb"_sym.hook->*[]
<MemBackup auto backup>
(Jit *thiz, Thread *self, ArtMethod *method, CompilationKind compilation_kind, bool precompile) static -> void {
if (compilation_kind == CompilationKind::kOptimized && !precompile) {
if (auto b = IsHooked(method); b) [[unlikely]] {
LOGD("Propagate compile task: %p -> %p", method, b);
method = b;
}
}
return backup(thiz, self, method, compilation_kind, precompile);
};
public:
static bool Init(const HookHandler &handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int <= __ANDROID_API_U__) [[likely]] {
handler(EnqueueOptimizedCompilation_);
handler(AddCompileTask_);
}
return true;
}
};
} // namespace lsplant::art::jit

View File

@ -0,0 +1,62 @@
module;
#include "logging.hpp"
export module lsplant:jit_code_cache;
import :art_method;
import :common;
import :thread;
import hook_helper;
namespace lsplant::art::jit {
export class JitCodeCache {
inline static auto MoveObsoleteMethod_ =
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_"_sym.as<void(JitCodeCache::*)(ArtMethod *, ArtMethod *)>;
static void MoveObsoleteMethods(JitCodeCache *thiz) {
auto movements = GetJitMovements();
LOGD("Before jit cache collection, moving %zu hooked methods", movements.size());
for (auto [target, backup] : movements) {
if (MoveObsoleteMethod_) [[likely]]
MoveObsoleteMethod_(thiz, target, backup);
else {
backup->SetData(backup->GetData());
target->SetData(nullptr);
}
}
}
inline static auto GarbageCollectCache_ =
"_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(JitCodeCache *thiz, Thread *self) static -> void {
MoveObsoleteMethods(thiz);
backup(thiz, self);
};
inline static auto DoCollection_ =
"_ZN3art3jit12JitCodeCache12DoCollectionEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(JitCodeCache *thiz, Thread *self) static -> void {
MoveObsoleteMethods(thiz);
backup(thiz, self);
};
public:
static bool Init(const HookHandler &handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
if (!handler(MoveObsoleteMethod_)) [[unlikely]] {
return false;
}
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
if (!handler(GarbageCollectCache_, DoCollection_)) [[unlikely]] {
return false;
}
}
return true;
}
};
} // namespace lsplant::art::jit

View File

@ -1,48 +0,0 @@
#pragma once
#include "common.hpp"
namespace lsplant::art::jit {
class JitCodeCache {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, MoveObsoleteMethod, JitCodeCache *thiz,
ArtMethod *old_method, ArtMethod *new_method) {
if (MoveObsoleteMethodSym) [[likely]] {
MoveObsoleteMethodSym(thiz, old_method, new_method);
} else {
// fallback to set data
new_method->SetData(old_method->GetData());
old_method->SetData(nullptr);
}
}
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE", void,
GarbageCollectCache, (JitCodeCache * thiz, Thread *self), {
auto movements = GetJitMovements();
LOGD("Before jit cache gc, moving %zu hooked methods",
movements.size());
for (auto [target, backup] : movements) {
MoveObsoleteMethod(thiz, target, backup);
}
backup(thiz, self);
});
public:
static bool Init(const HookHandler &handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
if (!RETRIEVE_MEM_FUNC_SYMBOL(
MoveObsoleteMethod,
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_"))
[[unlikely]] {
return false;
}
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
if (!HookSyms(handler, GarbageCollectCache)) [[unlikely]] {
return false;
}
}
return true;
}
};
} // namespace lsplant::art::jit

View File

@ -0,0 +1,39 @@
module;
#include "logging.hpp"
export module lsplant:jni_id_manager;
import :art_method;
import :common;
import :handle;
import hook_helper;
namespace lsplant::art::jni {
export class JniIdManager {
private:
inline static auto EncodeGenericId_ =
"_ZN3art3jni12JniIdManager15EncodeGenericIdINS_9ArtMethodEEEmNS_16ReflectiveHandleIT_EE"_sym.hook->*[]
<MemBackup auto backup>
(JniIdManager *thiz, ReflectiveHandle<ArtMethod> method) static -> uintptr_t {
if (auto target = IsBackup(method.Get()); target) {
LOGD("get generic id for %s", method.Get()->PrettyMethod().c_str());
method.Set(target);
}
return backup(thiz, method);
};
public:
static bool Init(JNIEnv *env, const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_R__) {
if (IsJavaDebuggable(env) && !handler(EncodeGenericId_)) {
LOGW("Failed to hook EncodeGenericId, attaching debugger may crash the process");
}
}
return true;
}
};
} // namespace lsplant::art::jni

View File

@ -1,33 +0,0 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/runtime/reflective_handle.hpp"
#include "common.hpp"
namespace lsplant::art::jni {
class JniIdManager {
private:
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art3jni12JniIdManager15EncodeGenericIdINS_9ArtMethodEEEmNS_16ReflectiveHandleIT_EE",
uintptr_t, EncodeGenericId, (JniIdManager * thiz, ReflectiveHandle<ArtMethod> method), {
if (auto target = IsBackup(method.Get()); target) {
LOGD("get generic id for %s", method.Get()->PrettyMethod().c_str());
method.Set(target);
}
return backup(thiz, method);
});
public:
static bool Init(JNIEnv *env, const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_R__) {
if (IsJavaDebuggable(env) && !HookSyms(handler, EncodeGenericId)) {
LOGW("Failed to hook EncodeGenericId, attaching debugger may crash the process");
}
}
return true;
}
};
} // namespace lsplant::art::jni

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,143 @@
module;
#include <array>
#include <atomic>
#include "logging.hpp"
export module lsplant:runtime;
import :common;
import hook_helper;
namespace lsplant::art {
export class Runtime {
public:
enum class RuntimeDebugState {
// This doesn't support any debug features / method tracing. This is the expected state
// usually.
kNonJavaDebuggable,
// This supports method tracing and a restricted set of debug features (for ex: redefinition
// isn't supported). We transition to this state when method tracing has started or when the
// debugger was attached and transition back to NonDebuggable once the tracing has stopped /
// the debugger agent has detached..
kJavaDebuggable,
// The runtime was started as a debuggable runtime. This allows us to support the extended
// set
// of debug features (for ex: redefinition). We never transition out of this state.
kJavaDebuggableAtInit
};
private:
inline static auto instance_ = "_ZN3art7Runtime9instance_E"_sym.as<Runtime *>;
inline static auto SetJavaDebuggable_ =
"_ZN3art7Runtime17SetJavaDebuggableEb"_sym.as<void (Runtime::*)(bool)>;
inline static auto SetRuntimeDebugState_ =
"_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE"_sym.as<void (Runtime::*)(RuntimeDebugState)>;
inline static size_t debug_state_offset = 0U;
public:
inline static Runtime *Current() { return *instance_; }
void SetJavaDebuggable(RuntimeDebugState value) {
if (SetJavaDebuggable_) {
SetJavaDebuggable_(this, value != RuntimeDebugState::kNonJavaDebuggable);
} else if (debug_state_offset > 0) {
*reinterpret_cast<RuntimeDebugState *>(reinterpret_cast<uintptr_t>(*instance_) +
debug_state_offset) = value;
}
}
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (!handler(instance_) || !*instance_) {
return false;
}
LOGD("runtime instance = %p", *instance_);
if (sdk_int >= __ANDROID_API_O__) {
if (!handler(SetJavaDebuggable_, SetRuntimeDebugState_)) {
return false;
}
}
if (SetRuntimeDebugState_) {
static constexpr size_t kLargeEnoughSizeForRuntime = 4096;
std::array<uint8_t, kLargeEnoughSizeForRuntime> code;
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggable) != 0);
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggableAtInit) != 0);
code.fill(uint8_t{0});
auto *const fake_runtime = reinterpret_cast<Runtime *>(code.data());
SetRuntimeDebugState_(fake_runtime, RuntimeDebugState::kJavaDebuggable);
for (size_t i = 0; i < kLargeEnoughSizeForRuntime; ++i) {
if (*reinterpret_cast<RuntimeDebugState *>(
reinterpret_cast<uintptr_t>(fake_runtime) + i) ==
RuntimeDebugState::kJavaDebuggable) {
LOGD("found debug_state at offset %zu", i);
debug_state_offset = i;
break;
}
}
if (debug_state_offset == 0) {
LOGE("failed to find debug_state");
return false;
}
}
return true;
}
};
export struct JavaDebuggableGuard {
JavaDebuggableGuard() {
while (true) {
size_t expected = 0;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
count.fetch_add(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected + 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
~JavaDebuggableGuard() {
while (true) {
size_t expected = 2;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kNonJavaDebuggable);
count.fetch_sub(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected - 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
private:
inline static std::atomic_size_t count{0};
static_assert(std::atomic_size_t::is_always_lock_free, "Unsupported architecture");
static_assert(std::is_same_v<std::atomic_size_t::value_type, size_t>,
"Unsupported architecture");
};
} // namespace lsplant::art

View File

@ -1,110 +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) 2022 LSPosed Contributors
*/
#pragma once
#include "utils/hook_helper.hpp"
namespace lsplant::art {
class Runtime {
public:
enum class RuntimeDebugState {
// This doesn't support any debug features / method tracing. This is the expected state
// usually.
kNonJavaDebuggable,
// This supports method tracing and a restricted set of debug features (for ex: redefinition
// isn't supported). We transition to this state when method tracing has started or when the
// debugger was attached and transition back to NonDebuggable once the tracing has stopped /
// the debugger agent has detached..
kJavaDebuggable,
// The runtime was started as a debuggable runtime. This allows us to support the extended
// set
// of debug features (for ex: redefinition). We never transition out of this state.
kJavaDebuggableAtInit
};
private:
inline static Runtime *instance_;
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
SetJavaDebuggableSym(thiz, value);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetRuntimeDebugState, void *thiz, RuntimeDebugState value) {
SetRuntimeDebugStateSym(thiz, value);
}
inline static size_t debug_state_offset = 0U;
public:
inline static Runtime *Current() { return instance_; }
void SetJavaDebuggable(RuntimeDebugState value) {
if (SetJavaDebuggableSym) {
SetJavaDebuggable(this, value != RuntimeDebugState::kNonJavaDebuggable);
} else if (debug_state_offset > 0) {
*reinterpret_cast<RuntimeDebugState *>(reinterpret_cast<uintptr_t>(instance_) +
debug_state_offset) = value;
}
}
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (void **instance; !RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E")) {
return false;
} else if (instance_ = reinterpret_cast<Runtime *>(*instance); !instance_) {
return false;
}
LOGD("runtime instance = %p", instance_);
if (sdk_int >= __ANDROID_API_O__) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable,
"_ZN3art7Runtime17SetJavaDebuggableEb") &&
!RETRIEVE_MEM_FUNC_SYMBOL(
SetRuntimeDebugState,
"_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE")) {
return false;
}
}
if (SetRuntimeDebugStateSym) {
static constexpr size_t kLargeEnoughSizeForRuntime = 4096;
std::array<uint8_t, kLargeEnoughSizeForRuntime> code;
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggable) != 0);
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggableAtInit) != 0);
code.fill(uint8_t{0});
auto *const fake_runtime = reinterpret_cast<Runtime *>(code.data());
SetRuntimeDebugState(fake_runtime, RuntimeDebugState::kJavaDebuggable);
for (size_t i = 0; i < kLargeEnoughSizeForRuntime; ++i) {
if (*reinterpret_cast<RuntimeDebugState *>(
reinterpret_cast<uintptr_t>(fake_runtime) + i) ==
RuntimeDebugState::kJavaDebuggable) {
LOGD("found debug_state at offset %zu", i);
debug_state_offset = i;
break;
}
}
if (debug_state_offset == 0) {
LOGE("failed to find debug_state");
return false;
}
}
return true;
}
};
} // namespace lsplant::art

View File

@ -0,0 +1,25 @@
module;
export module lsplant:thread;
import hook_helper;
namespace lsplant::art {
export class Thread {
inline static auto CurrentFromGdb_ = "_ZN3art6Thread14CurrentFromGdbEv"_sym.as<Thread *()>;
public:
static Thread *Current() {
if (CurrentFromGdb_) [[likely]]
return CurrentFromGdb_();
return nullptr;
}
static bool Init(const HookHandler &handler) {
if (!handler(CurrentFromGdb_)) [[unlikely]] {
return false;
}
return true;
}
};
} // namespace lsplant::art

View File

@ -1,26 +0,0 @@
#pragma once
#include "common.hpp"
namespace lsplant::art {
class Thread {
CREATE_FUNC_SYMBOL_ENTRY(Thread *, CurrentFromGdb) {
if (CurrentFromGdbSym) [[likely]]
return CurrentFromGdbSym();
else
return nullptr;
}
public:
static Thread *Current() { return CurrentFromGdb(); }
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_FUNC_SYMBOL(CurrentFromGdb, "_ZN3art6Thread14CurrentFromGdbEv"))
[[unlikely]] {
return false;
}
return true;
}
};
} // namespace lsplant::art

View File

@ -0,0 +1,47 @@
module;
export module lsplant:thread_list;
import hook_helper;
namespace lsplant::art::thread_list {
export class ScopedSuspendAll {
inline static auto constructor_ =
"_ZN3art16ScopedSuspendAllC2EPKcb"_sym.as<void(ScopedSuspendAll::*)(const char *, bool)>;
inline static auto destructor_ =
"_ZN3art16ScopedSuspendAllD2Ev"_sym.as<void(ScopedSuspendAll::*)()>;
inline static auto SuspendVM_ = "_ZN3art3Dbg9SuspendVMEv"_sym.as<void()>;
inline static auto ResumeVM_ = "_ZN3art3Dbg8ResumeVMEv"_sym.as<void()>;
public:
ScopedSuspendAll(const char *cause, bool long_suspend) {
if (constructor_) {
constructor_(this, cause, long_suspend);
} else if (SuspendVM_) {
SuspendVM_();
}
}
~ScopedSuspendAll() {
if (destructor_) {
destructor_(this);
} else if (ResumeVM_) {
ResumeVM_();
}
}
static bool Init(const HookHandler &handler) {
if (!handler(constructor_, SuspendVM_)) [[unlikely]] {
return false;
}
if (!handler(destructor_, ResumeVM_)) [[unlikely]] {
return false;
}
return true;
}
};
} // namespace lsplant::art::thread_list

View File

@ -1,57 +0,0 @@
#pragma once
#include "common.hpp"
namespace lsplant::art::thread_list {
class ScopedSuspendAll {
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, constructor, ScopedSuspendAll *thiz, const char *cause,
bool long_suspend) {
if (thiz && constructorSym) [[likely]] {
return constructorSym(thiz, cause, long_suspend);
} else {
SuspendVM();
}
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedSuspendAll *thiz) {
if (thiz && destructorSym) [[likely]] {
return destructorSym(thiz);
} else {
ResumeVM();
}
}
CREATE_FUNC_SYMBOL_ENTRY(void, SuspendVM) {
if (SuspendVMSym) [[likely]] {
SuspendVMSym();
}
}
CREATE_FUNC_SYMBOL_ENTRY(void, ResumeVM) {
if (ResumeVMSym) [[likely]] {
ResumeVMSym();
}
}
public:
ScopedSuspendAll(const char *cause, bool long_suspend) {
constructor(this, cause, long_suspend);
}
~ScopedSuspendAll() { destructor(this); }
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor, "_ZN3art16ScopedSuspendAllC2EPKcb") &&
!RETRIEVE_FUNC_SYMBOL(SuspendVM, "_ZN3art3Dbg9SuspendVMEv")) [[unlikely]] {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev") &&
!RETRIEVE_FUNC_SYMBOL(ResumeVM, "_ZN3art3Dbg8ResumeVMEv")) [[unlikely]] {
return false;
}
return true;
}
};
} // namespace lsplant::art::thread_list

View File

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

View File

@ -1,5 +1,6 @@
#pragma once module;
#include <jni.h>
#include <parallel_hashmap/phmap.h> #include <parallel_hashmap/phmap.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
@ -8,10 +9,23 @@
#include <string_view> #include <string_view>
#include "logging.hpp" #include "logging.hpp"
#include "lsplant.hpp"
#include "utils/hook_helper.hpp"
namespace lsplant { export module lsplant:common;
export import jni_helper;
export import hook_helper;
export namespace lsplant {
namespace art {
class ArtMethod;
namespace mirror {
class Class;
}
namespace dex {
class ClassDef {};
} // namespace dex
} // namespace art
enum class Arch { enum class Arch {
kArm, kArm,
@ -37,14 +51,24 @@ consteval inline Arch GetArch() {
#endif #endif
} }
inline static constexpr auto kArch = GetArch(); template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
constexpr auto kArch = GetArch();
template <typename T> template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) { constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1)); return v + size - 1 - ((v + size - 1) & (size - 1));
} }
inline auto GetAndroidApiLevel() { [[gnu::const]] inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() { static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value; std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data()); __system_property_get("ro.build.version.sdk", prop_value.data());
@ -66,13 +90,14 @@ inline auto IsJavaDebuggable(JNIEnv *env) {
LOGE("Failed to find VMRuntime"); LOGE("Failed to find VMRuntime");
return false; return false;
} }
auto get_runtime_method = auto get_runtime_method = JNI_GetStaticMethodID(env, runtime_class, "getRuntime",
JNI_GetStaticMethodID(env, runtime_class, "getRuntime", "()Ldalvik/system/VMRuntime;"); "()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) { if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()"); LOGE("Failed to find VMRuntime.getRuntime()");
return false; return false;
} }
auto is_debuggable_method = JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z"); auto is_debuggable_method =
JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) { if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()"); LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false; return false;
@ -89,48 +114,25 @@ inline auto IsJavaDebuggable(JNIEnv *env) {
return kDebuggable; return kDebuggable;
} }
inline static constexpr auto kPointerSize = sizeof(void *); constexpr auto kPointerSize = sizeof(void *);
namespace art { SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
class ArtMethod;
namespace dex {
class ClassDef;
}
namespace mirror {
class Class;
}
} // namespace art
namespace { SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
// target, backup
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
inline SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
inline SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
hooked_classes_; hooked_classes_;
inline SharedHashSet<art::ArtMethod *> deoptimized_methods_set_; SharedHashSet<art::ArtMethod *> deoptimized_methods_set_;
inline SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>> SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
deoptimized_classes_; deoptimized_classes_;
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_; std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
inline std::shared_mutex jit_movements_lock_; std::shared_mutex jit_movements_lock_;
} // namespace
inline art::ArtMethod *IsHooked(art::ArtMethod * art_method, bool including_backup = false) { inline art::ArtMethod *IsHooked(art::ArtMethod * art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr; art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) { hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (!including_backup || it.second.first) backup = it.second.second; if (including_backup || it.second.first) backup = it.second.second;
}); });
return backup; return backup;
} }
@ -140,7 +142,7 @@ inline art::ArtMethod *IsBackup(art::ArtMethod *art_method) {
hooked_methods_.if_contains(art_method, [&backup](const auto &it) { hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second; if (!it.second.first) backup = it.second.second;
}); });
return nullptr; return backup;
} }
inline bool IsDeoptimized(art::ArtMethod * art_method) { inline bool IsDeoptimized(art::ArtMethod * art_method) {
@ -172,5 +174,4 @@ inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) {
std::unique_lock lk(jit_movements_lock_); std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup); jit_movements_.emplace_back(target, backup);
} }
} // namespace lsplant } // namespace lsplant

@ -1 +1 @@
Subproject commit 9d57844a301077abf4c29e061b2458c56a363c8f Subproject commit 67077df2a53be0cabb9f16e8a050acdd825f7d69

View File

@ -0,0 +1,17 @@
module;
#include "lsplant.hpp"
export module lsplant;
export namespace lsplant::inline v2{
using lsplant::v2::InitInfo;
using lsplant::v2::Init;
using lsplant::v2::Hook;
using lsplant::v2::UnHook;
using lsplant::v2::IsHooked;
using lsplant::v2::Deoptimize;
using lsplant::v2::GetNativeFunction;
using lsplant::v2::MakeClassInheritable;
using lsplant::v2::MakeDexFileTrusted;
}

View File

@ -1,207 +1,223 @@
#pragma once #pragma once
#include <android/log.h>
#include <concepts> #include <concepts>
#include "jni_helper.hpp"
#include "lsplant.hpp" #include "lsplant.hpp"
#include "type_traits.hpp"
#if defined(__LP64__)
#define LP_SELECT(lp32, lp64) lp64
#else
#define LP_SELECT(lp32, lp64) lp32
#endif
#define CONCATENATE(a, b) a##b
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::Hooker<RET PARAMS, decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::MemHooker<RET PARAMS, \
decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type::FunType>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
(name = reinterpret_cast<decltype(name)>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
typedef ret (*func##Type)(__VA_ARGS__); \
inline static ret (*func##Sym)(__VA_ARGS__); \
inline static ret func(__VA_ARGS__)
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
using func##Type = lsplant::MemberFunction<ret(__VA_ARGS__)>; \
inline static func##Type func##Sym; \
inline static ret func(thiz, ##__VA_ARGS__)
namespace lsplant { namespace lsplant {
using HookHandler = InitInfo; template <size_t N>
struct FixedString {
template <char... chars> consteval FixedString(const char (&str)[N]) { std::copy_n(str, N, data); }
struct tstring : public std::integer_sequence<char, chars...> { char data[N] = {};
inline constexpr static const char *c_str() { return str_; }
inline constexpr operator std::string_view() const { return {c_str(), sizeof...(chars)}; }
private:
inline static constexpr char str_[]{chars..., '\0'};
}; };
template <typename T, T... chars> template<typename T>
inline constexpr tstring<chars...> operator""_tstr() { concept FuncType = std::is_function_v<T> || std::is_member_function_pointer_v<T>;
return {};
template <FixedString, FuncType>
struct Function;
template <FixedString Sym, typename Ret, typename... Args>
struct Function<Sym, Ret(Args...)> {
[[gnu::always_inline]] static Ret operator()(Args... args) {
return inner_.function_(args...);
}
[[gnu::always_inline]] operator bool() { return inner_.raw_function_; }
[[gnu::always_inline]] auto operator&() const { return inner_.function_; }
[[gnu::always_inline]] Function &operator=(void *function) {
inner_.raw_function_ = function;
return *this;
} }
template <char... as, char... bs> private:
inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) { inline static union {
return {}; Ret (*function_)(Args...);
void *raw_function_ = nullptr;
} inner_;
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_));
};
template <FixedString Sym, class This, typename Ret, typename... Args>
struct Function<Sym, Ret(This::*)(Args...)> {
[[gnu::always_inline]] static Ret operator()(This *thiz, Args... args) {
return (reinterpret_cast<ThisType *>(thiz)->*inner_.function_)(args...);
}
[[gnu::always_inline]] operator bool() { return inner_.raw_function_; }
[[gnu::always_inline]] auto operator&() const { return inner_.function_; }
[[gnu::always_inline]] Function &operator=(void *function) {
inner_.raw_function_ = function;
return *this;
} }
template <char... as> private:
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) { using ThisType = std::conditional_t<std::is_same_v<This, void>, Function, This>;
char b[]{as..., '\0'}; inline static union {
return a + b; Ret (ThisType::*function_)(Args...) const;
struct {
void *raw_function_ = nullptr;
[[maybe_unused]] std::ptrdiff_t adj = 0;
};
} inner_;
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_) + sizeof(inner_.adj));
};
template <FixedString, typename T>
struct Field {
[[gnu::always_inline]] T *operator->() { return inner_.field_; }
[[gnu::always_inline]] T &operator*() { return *inner_.field_; }
[[gnu::always_inline]] operator bool() { return inner_.raw_field_ != nullptr; }
[[gnu::always_inline]] Field &operator=(void *field) {
inner_.raw_field_ = field;
return *this;
}
private:
inline static union {
void *raw_field_ = nullptr;
T *field_;
} inner_;
static_assert(sizeof(inner_.field_) == sizeof(inner_.raw_field_));
};
template <FixedString, FuncType>
struct Hooker;
template <FixedString Sym, typename Ret, typename... Args>
struct Hooker<Sym, Ret(Args...)> : Function<Sym, Ret(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(Args...)) {
replace_ = replace;
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(Args...) = nullptr;
};
template <FixedString Sym, class This, typename Ret, typename... Args>
struct Hooker<Sym, Ret(This::*)(Args...)> : Function<Sym, Ret(This::*)(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(This::*)(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(This *, Args...)) {
replace_ = replace;
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(This *, Args...) = nullptr;
};
struct HookHandler {
HookHandler(const InitInfo &info) : info_(info) {}
template <typename T>
[[gnu::always_inline]] bool operator()(T &&arg) const {
return handle(std::forward<T>(arg), false);
} }
template <char... as> template <typename T1, typename T2, typename... U>
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) { [[gnu::always_inline]] bool operator()(T1 &&arg1, T2 &&arg2, U &&...args) const {
char a[]{as..., '\0'}; if constexpr(std::is_same_v<T2, bool>)
return a + b; return handle(std::forward<T1>(arg1), std::forward<T2>(arg2)) || this->operator()(std::forward<U>(args)...);
else
return handle(std::forward<T1>(arg1), false) || this->operator()(std::forward<T2>(arg2), std::forward<U>(args)...);
} }
inline void *Dlsym(const HookHandler &handle, const char *name, bool match_prefix = false) { private:
if (auto match = handle.art_symbol_resolver(name); match) { [[gnu::always_inline]] bool operator()() const {
return false;
}
const InitInfo &info_;
template<FixedString Sym, typename ...Us, template<FixedString, typename...> typename T>
requires(!requires { T<Sym, Us...>::replace_; })
[[gnu::always_inline]] bool handle(T<Sym, Us...> &target, bool match_prefix) const {
return target = dlsym<Sym>(match_prefix);
}
template<FixedString Sym, typename ...Us, template<FixedString, typename...> typename T>
requires(requires { T<Sym, Us...>::replace_; })
[[gnu::always_inline]] bool handle(T<Sym, Us...> &hooker, bool match_prefix) const {
return hooker = hook(dlsym<Sym>(match_prefix), reinterpret_cast<void *>(hooker.replace_));
}
template <FixedString Sym>
[[gnu::always_inline]] void *dlsym(bool match_prefix = false) const {
if (auto match = info_.art_symbol_resolver(Sym.data); match) {
return match; return match;
} else if (match_prefix && handle.art_symbol_prefix_resolver) { }
return handle.art_symbol_prefix_resolver(name); if (match_prefix && info_.art_symbol_prefix_resolver) [[likely]] {
return info_.art_symbol_prefix_resolver(Sym.data);
} }
return nullptr; return nullptr;
} }
template <typename Class, typename Return, typename T, typename... Args> [[gnu::always_inline]] void *hook(void *original, void *replace) const {
requires(std::is_same_v<T, void> || if (original) [[likely]] {
std::is_same_v<Class, T>) inline static auto memfun_cast(Return (*func)(T *, Args...)) { return info_.inline_hooker(original, replace);
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;
} }
return nullptr;
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 F>
template <typename This, typename Return, typename... Args> concept Backup = std::is_function_v<std::remove_pointer_t<F>>;
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
template <typename This, typename Return, typename... Args> template<typename F>
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>; concept MemBackup = std::is_member_function_pointer_v<std::remove_pointer_t<F>> || Backup<F>;
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 std::string_view sym = tstring<cs...>{};
};
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 std::string_view sym = tstring<cs...>{};
};
template<FixedString S>
struct Symbol {
template<typename T> template<typename T>
concept HookerType = requires(T a) { inline static decltype([]{
a.backup; if constexpr (FuncType<T>) {
a.replace; return Function<S, T>{};
} else {
return Field<S, T>{};
}
}()) as{};
[[no_unique_address]] struct Hook {
template<typename F>
auto operator->*(F&&) const {
using Signature = decltype(F::template operator()<&decltype([] static {})::operator()>);
if constexpr (requires { F::template operator()<&decltype([] {})::operator()>; }) {
using HookerType = Hooker<S, decltype([]<class This, typename Ret, typename... Args>(Ret(*)(This*, Args...)) -> Ret(This::*)(Args...) {
return {};
}.template operator()(std::declval<Signature>()))>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
} else {
using HookerType = Hooker<S, Signature>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
}
};
} hook;
}; };
template <HookerType T> template <FixedString S> constexpr Symbol<S> operator""_sym() {
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) { return {};
if (original) {
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
void *backup = handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace));
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
} else {
arg.backup = reinterpret_cast<decltype(arg.backup)>(
handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace)));
}
return true;
} else {
return false;
}
} }
template <HookerType T> template<FixedString S, FixedString P>
inline static bool HookSym(const HookHandler &handler, T &arg) { consteval auto operator|([[maybe_unused]] Symbol<S> a, [[maybe_unused]] Symbol<P> b) {
auto original = handler.art_symbol_resolver(arg.sym); #if defined(__LP64__)
return HookSymNoHandle(handler, original, arg); return b;
}
template <HookerType T, HookerType... Args>
inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
__android_log_print(ANDROID_LOG_ERROR,
#ifdef LOG_TAG
LOG_TAG,
#else #else
"HookHelper", return a;
#endif #endif
"Hook Fails: %*s", static_cast<int>(first.sym.size()),
first.sym.data());
return false;
} }
return true;
}
} // namespace lsplant } // namespace lsplant

View File

@ -0,0 +1,14 @@
module;
#include "hook_helper.hpp"
export module hook_helper;
export namespace lsplant {
using lsplant::FixedString;
using lsplant::HookHandler;
using lsplant::operator""_sym;
using lsplant::Backup;
using lsplant::MemBackup;
using lsplant::operator|;
} // namespace lsplant

View File

@ -6,6 +6,8 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include "type_traits.hpp"
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-partial-specialization" #pragma clang diagnostic ignored "-Winvalid-partial-specialization"
#pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -16,15 +18,6 @@
void operator=(const TypeName &) = delete void operator=(const TypeName &) = delete
namespace lsplant { namespace lsplant {
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
template <class... Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
template <class T, template <class, class...> class U>
inline constexpr bool is_instance_v = is_instance<T, U>::value;
template <typename T> template <typename T>
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>; concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
@ -65,8 +58,6 @@ public:
return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_)); return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_));
} }
operator T() const { return local_ref_; }
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept { ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release()); reset(s.release());
env_ = s.env_; env_ = s.env_;
@ -118,11 +109,11 @@ public:
}; };
template <typename T, typename U> template <typename T, typename U>
concept ScopeOrRaw = std::is_convertible_v<T, U> || concept ScopeOrRaw =
(is_instance_v<std::decay_t<T>, ScopedLocalRef> std::is_convertible_v<T, U> ||
&&std::is_convertible_v<typename std::decay_t<T>::BaseType, U>) || (is_instance_v<std::decay_t<T>, ScopedLocalRef> &&
(std::is_same_v<std::decay_t<T>, JObjectArrayElement> std::is_convertible_v<typename std::decay_t<T>::BaseType, U>) ||
&&std::is_convertible_v<jobject, U>); (std::is_same_v<std::decay_t<T>, JObjectArrayElement> && std::is_convertible_v<jobject, U>);
template <typename T> template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>; concept ScopeOrClass = ScopeOrRaw<T, jclass>;
@ -133,10 +124,11 @@ concept ScopeOrObject = ScopeOrRaw<T, jobject>;
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) { inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
if (auto exception = env->ExceptionOccurred()) { if (auto exception = env->ExceptionOccurred()) {
env->ExceptionClear(); env->ExceptionClear();
static jclass log = (jclass)env->NewGlobalRef(env->FindClass("android/util/Log")); jclass log = (jclass)env->FindClass("android/util/Log");
static jmethodID toString = env->GetStaticMethodID( static jmethodID toString = env->GetStaticMethodID(
log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception); auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
env->DeleteLocalRef(log);
env->DeleteLocalRef(exception); env->DeleteLocalRef(exception);
return {env, str}; return {env, str};
} }
@ -186,8 +178,7 @@ public:
JUTFString(const ScopedLocalRef<jstring> &jstr) JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {} : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) {
: env_(env), jstr_(jstr) {
if (env_ && jstr_) if (env_ && jstr_)
cstr_ = env_->GetStringUTFChars(jstr, nullptr); cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else else
@ -514,6 +505,13 @@ template <ScopeOrClass Class>
isStatic); isStatic);
} }
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_ToReflectedField(JNIEnv *env, Class &&clazz, jfieldID field,
jboolean isStatic = JNI_FALSE) {
return JNI_SafeInvoke(env, &JNIEnv::ToReflectedField, std::forward<Class>(clazz), field,
isStatic);
}
// functions to method // functions to method
// virtual methods // virtual methods
@ -675,15 +673,14 @@ template <ScopeOrClass Class, typename... Args>
// non-virtual methods // non-virtual methods
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj),
@ -691,7 +688,7 @@ template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj),
@ -699,55 +696,49 @@ template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualByteMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, Class &&clazz,
Class &&clazz, jmethodID method, jmethodID method, Args &&...args) {
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj),
@ -778,6 +769,12 @@ template <ScopeOrObject Object, ScopeOrClass Class>
std::forward<Class>(clazz)); std::forward<Class>(clazz));
} }
template <ScopeOrObject Object1, ScopeOrObject Object2>
[[maybe_unused]] inline auto JNI_IsSameObject(JNIEnv *env, Object1 &&a, Object2 &&b) {
return JNI_SafeInvoke(env, &JNIEnv::IsSameObject, std::forward<Object1>(a),
std::forward<Object2>(b));
}
template <ScopeOrObject Object> template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) { [[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef( return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(
@ -785,8 +782,9 @@ template <ScopeOrObject Object>
} }
template <typename U, typename T> template <typename U, typename T>
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) requires( [[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x)
std::is_convertible_v<T, _jobject *>) { requires(std::is_convertible_v<T, _jobject *>)
{
return ScopedLocalRef<U>(std::move(x)); return ScopedLocalRef<U>(std::move(x));
} }
@ -904,9 +902,7 @@ public:
reset(local_ref); reset(local_ref);
} }
ScopedLocalRef(ScopedLocalRef &&s) noexcept { ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
*this = std::move(s);
}
template <JObject U> template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
@ -957,8 +953,6 @@ public:
T get() const { return local_ref_; } T get() const { return local_ref_; }
explicit operator T() const { return local_ref_; }
JArrayUnderlyingType<T> &operator[](size_t index) { JArrayUnderlyingType<T> &operator[](size_t index) {
modified_ = true; modified_ = true;
return elements_[index]; return elements_[index];
@ -1036,9 +1030,8 @@ class JObjectArrayElement {
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_); return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
} }
explicit JObjectArrayElement(JNIEnv * env, jobjectArray array, int i, size_t size) : explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i, size_t size)
env_(env), array_(array), i_(i), size_(size), : env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
item_(obtain()) {}
JObjectArrayElement &operator++() { JObjectArrayElement &operator++() {
++i_; ++i_;
@ -1052,24 +1045,17 @@ class JObjectArrayElement {
return *this; return *this;
} }
JObjectArrayElement operator++(int) { JObjectArrayElement operator++(int) { return JObjectArrayElement(env_, array_, i_ + 1, size_); }
return JObjectArrayElement(env_, array_, i_ + 1, size_);
}
JObjectArrayElement operator--(int) { JObjectArrayElement operator--(int) { return JObjectArrayElement(env_, array_, i_ - 1, size_); }
return JObjectArrayElement(env_, array_, i_ - 1, size_);
}
public: public:
JObjectArrayElement(JObjectArrayElement&& s): env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {} JObjectArrayElement(JObjectArrayElement &&s)
: env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {}
operator ScopedLocalRef<jobject>& () & { operator ScopedLocalRef<jobject> &() & { return item_; }
return item_;
}
operator ScopedLocalRef<jobject>&& () && { operator ScopedLocalRef<jobject> &&() && { return std::move(item_); }
return std::move(item_);
}
JObjectArrayElement &operator=(JObjectArrayElement &&s) { JObjectArrayElement &operator=(JObjectArrayElement &&s) {
reset(s.item_.release()); reset(s.item_.release());
@ -1081,6 +1067,18 @@ public:
return *this; return *this;
} }
template<JObject T>
JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {
reset(s.release());
return *this;
}
template<JObject T>
JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {
reset(s.clone());
return *this;
}
JObjectArrayElement &operator=(jobject s) { JObjectArrayElement &operator=(jobject s) {
reset(env_->NewLocalRef(s)); reset(env_->NewLocalRef(s));
return *this; return *this;
@ -1091,13 +1089,7 @@ public:
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_); JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_);
} }
ScopedLocalRef<jobject> clone() const { ScopedLocalRef<jobject> clone() const { return item_.clone(); }
return item_.clone();
}
operator jobject() const {
return item_;
}
jobject get() const { return item_.get(); } jobject get() const { return item_.get(); }
@ -1106,6 +1098,7 @@ public:
jobject operator->() const { return item_.get(); } jobject operator->() const { return item_.get(); }
jobject operator*() const { return item_.get(); } jobject operator*() const { return item_.get(); }
private: private:
JNIEnv *env_; JNIEnv *env_;
jobjectArray array_; jobjectArray array_;
@ -1123,6 +1116,7 @@ public:
Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {} Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}
Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {} Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {}
public: public:
auto &operator*() { return e_; } auto &operator*() { return e_; }
@ -1138,17 +1132,14 @@ public:
return *this; return *this;
} }
Iterator operator++(int) { Iterator operator++(int) { return Iterator(e_++); }
return Iterator(e_++);
}
Iterator operator--(int) { Iterator operator--(int) { return Iterator(e_--); }
return Iterator(e_--);
}
bool operator==(const Iterator &other) const { return other.e_.i_ == e_.i_; } bool operator==(const Iterator &other) const { return other.e_.i_ == e_.i_; }
bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; } bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; }
private: private:
JObjectArrayElement e_; JObjectArrayElement e_;
}; };
@ -1161,7 +1152,9 @@ public:
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_); return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
} }
ConstIterator(JNIEnv * env, jobjectArray array, int i, int size) : env_(env), array_(array), i_(i), size_(size), item_(obtain()) {} ConstIterator(JNIEnv *env, jobjectArray array, int i, int size)
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
public: public:
auto &operator*() { return item_; } auto &operator*() { return item_; }
@ -1179,17 +1172,14 @@ public:
return *this; return *this;
} }
ConstIterator operator++(int) { ConstIterator operator++(int) { return ConstIterator(env_, array_, i_ + 1, size_); }
return ConstIterator(env_, array_, i_ + 1, size_);
}
ConstIterator operator--(int) { ConstIterator operator--(int) { return ConstIterator(env_, array_, i_ - 1, size_); }
return ConstIterator(env_, array_, i_ - 1, size_);
}
bool operator==(const ConstIterator &other) const { return other.i_ == i_; } bool operator==(const ConstIterator &other) const { return other.i_ == i_; }
bool operator!=(const ConstIterator &other) const { return other.i_ != i_; } bool operator!=(const ConstIterator &other) const { return other.i_ != i_; }
private: private:
JNIEnv *env_; JNIEnv *env_;
jobjectArray array_; jobjectArray array_;
@ -1214,12 +1204,11 @@ public:
reset(local_ref); reset(local_ref);
} }
ScopedLocalRef(ScopedLocalRef &&s) noexcept { ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
*this = std::move(s);
}
template <JObject U> template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (jobjectArray) s.release()) {} ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept
: ScopedLocalRef(s.env_, (jobjectArray)s.release()) {}
explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, jobjectArray{nullptr}) {} explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, jobjectArray{nullptr}) {}
@ -1246,7 +1235,7 @@ public:
jobjectArray get() const { return local_ref_; } jobjectArray get() const { return local_ref_; }
JObjectArrayElement operator[](size_t index) { JObjectArrayElement operator[](size_t index) {
return JObjectArrayElement(env_, local_ref_, index, JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, index)); return JObjectArrayElement(env_, local_ref_, index, size_);
} }
const ScopedLocalRef<jobject> operator[](size_t index) const { const ScopedLocalRef<jobject> operator[](size_t index) const {

View File

@ -0,0 +1,142 @@
module;
#include "utils/jni_helper.hpp"
export module jni_helper;
export {
using ::jboolean;
using ::jbooleanArray;
using ::jbyte;
using ::jbyteArray;
using ::jchar;
using ::jcharArray;
using ::jclass;
using ::jdouble;
using ::jdoubleArray;
using ::jfieldID;
using ::jfloat;
using ::jfloatArray;
using ::jint;
using ::jintArray;
using ::jlong;
using ::jlongArray;
using ::jmethodID;
using ::JNIEnv;
using ::jobject;
using ::jobjectArray;
using ::jshort;
using ::jshortArray;
using ::jsize;
using ::jstring;
using ::jthrowable;
using ::jvalue;
}
export namespace lsplant {
using lsplant::JNIMonitor;
using lsplant::JNIScopeFrame;
using lsplant::JUTFString;
using lsplant::ScopedLocalRef;
using lsplant::UnwrapScope;
using lsplant::WrapScope;
using lsplant::JNI_GetBooleanField;
using lsplant::JNI_GetByteField;
using lsplant::JNI_GetCharField;
using lsplant::JNI_GetDoubleField;
using lsplant::JNI_GetFieldID;
using lsplant::JNI_GetFloatField;
using lsplant::JNI_GetIntField;
using lsplant::JNI_GetLongField;
using lsplant::JNI_GetObjectField;
using lsplant::JNI_GetShortField;
using lsplant::JNI_SetBooleanField;
using lsplant::JNI_SetByteField;
using lsplant::JNI_SetCharField;
using lsplant::JNI_SetDoubleField;
using lsplant::JNI_SetFloatField;
using lsplant::JNI_SetIntField;
using lsplant::JNI_SetLongField;
using lsplant::JNI_SetObjectField;
using lsplant::JNI_SetShortField;
using lsplant::JNI_GetStaticBooleanField;
using lsplant::JNI_GetStaticByteField;
using lsplant::JNI_GetStaticCharField;
using lsplant::JNI_GetStaticDoubleField;
using lsplant::JNI_GetStaticFieldID;
using lsplant::JNI_GetStaticFloatField;
using lsplant::JNI_GetStaticIntField;
using lsplant::JNI_GetStaticLongField;
using lsplant::JNI_GetStaticObjectField;
using lsplant::JNI_GetStaticShortField;
using lsplant::JNI_SetStaticBooleanField;
using lsplant::JNI_SetStaticByteField;
using lsplant::JNI_SetStaticCharField;
using lsplant::JNI_SetStaticDoubleField;
using lsplant::JNI_SetStaticFloatField;
using lsplant::JNI_SetStaticIntField;
using lsplant::JNI_SetStaticLongField;
using lsplant::JNI_SetStaticObjectField;
using lsplant::JNI_SetStaticShortField;
using lsplant::JNI_CallBooleanMethod;
using lsplant::JNI_CallByteMethod;
using lsplant::JNI_CallCharMethod;
using lsplant::JNI_CallDoubleMethod;
using lsplant::JNI_CallFloatMethod;
using lsplant::JNI_CallIntMethod;
using lsplant::JNI_CallLongMethod;
using lsplant::JNI_CallNonvirtualBooleanMethod;
using lsplant::JNI_CallNonvirtualByteMethod;
using lsplant::JNI_CallNonvirtualCharMethod;
using lsplant::JNI_CallNonvirtualDoubleMethod;
using lsplant::JNI_CallNonvirtualFloatMethod;
using lsplant::JNI_CallNonvirtualIntMethod;
using lsplant::JNI_CallNonvirtualLongMethod;
using lsplant::JNI_CallNonvirtualObjectMethod;
using lsplant::JNI_CallNonvirtualShortMethod;
using lsplant::JNI_CallNonvirtualVoidMethod;
using lsplant::JNI_CallObjectMethod;
using lsplant::JNI_CallShortMethod;
using lsplant::JNI_CallStaticBooleanMethod;
using lsplant::JNI_CallStaticByteMethod;
using lsplant::JNI_CallStaticCharMethod;
using lsplant::JNI_CallStaticDoubleMethod;
using lsplant::JNI_CallStaticFloatMethod;
using lsplant::JNI_CallStaticIntMethod;
using lsplant::JNI_CallStaticLongMethod;
using lsplant::JNI_CallStaticObjectMethod;
using lsplant::JNI_CallStaticShortMethod;
using lsplant::JNI_CallStaticVoidMethod;
using lsplant::JNI_CallVoidMethod;
using lsplant::JNI_GetMethodID;
using lsplant::JNI_GetStaticMethodID;
using lsplant::JNI_ToReflectedMethod;
using lsplant::JNI_ToReflectedField;
using lsplant::JNI_NewBooleanArray;
using lsplant::JNI_NewByteArray;
using lsplant::JNI_NewCharArray;
using lsplant::JNI_NewDirectByteBuffer;
using lsplant::JNI_NewDoubleArray;
using lsplant::JNI_NewFloatArray;
using lsplant::JNI_NewIntArray;
using lsplant::JNI_NewLongArray;
using lsplant::JNI_NewObject;
using lsplant::JNI_NewObjectArray;
using lsplant::JNI_NewShortArray;
using lsplant::JNI_Cast;
using lsplant::JNI_FindClass;
using lsplant::JNI_GetArrayLength;
using lsplant::JNI_GetObjectClass;
using lsplant::JNI_GetObjectFieldOf;
using lsplant::JNI_IsInstanceOf;
using lsplant::JNI_IsSameObject;
using lsplant::JNI_NewGlobalRef;
using lsplant::JNI_NewStringUTF;
using lsplant::JNI_RegisterNatives;
} // namespace lsplant

View File

@ -0,0 +1,14 @@
#pragma once
#include <type_traits>
namespace lsplant {
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
template <class... Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
template <class T, template <class, class...> class U>
inline constexpr bool is_instance_v = is_instance<T, U>::value;
} // namespace lsplant

View File

@ -17,14 +17,14 @@
#ifndef NDEBUG #ifndef NDEBUG
#define LOGD(fmt, ...) \ #define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \
"%s:%d#%s" \ "%s:%d" \
": " fmt, \ ": " fmt, \
__FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) __FILE_NAME__, __LINE__ __VA_OPT__(, ) __VA_ARGS__)
#define LOGV(fmt, ...) \ #define LOGV(fmt, ...) \
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \ __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \
"%s:%d#%s" \ "%s:%d" \
": " fmt, \ ": " fmt, \
__FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) __FILE_NAME__, __LINE__ __VA_OPT__(, ) __VA_ARGS__)
#else #else
#define LOGD(...) 0 #define LOGD(...) 0
#define LOGV(...) 0 #define LOGV(...) 0

View File

@ -1,32 +1,39 @@
module;
#include "lsplant.hpp" #include "lsplant.hpp"
#include <android/api-level.h> #include <android/api-level.h>
#include <bits/sysconf.h>
#include <jni.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <bit>
#include <string_view>
#include <tuple>
#include "art/mirror/class.hpp" #include "logging.hpp"
#include "art/runtime/art_method.hpp"
#include "art/runtime/class_linker.hpp" module lsplant;
#include "art/runtime/dex_file.hpp"
#include "art/runtime/gc/scoped_gc_critical_section.hpp" import dex_builder;
#include "art/runtime/instrumentation.hpp"
#include "art/runtime/jit/jit_code_cache.hpp" import :common;
#include "art/runtime/jni/jni_id_manager.h" import :art_method;
#include "art/runtime/runtime.hpp" import :clazz;
#include "art/runtime/thread.hpp" import :thread;
#include "art/runtime/thread_list.hpp" import :instrumentation;
#include "common.hpp" import :runtime;
#include "dex_builder.h" import :thread_list;
#include "utils/jni_helper.hpp" import :class_linker;
import :scope_gc_critical_section;
import :jit_code_cache;
import :jni_id_manager;
import :dex_file;
import :jit;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "ConstantConditionsOC"
#pragma ide diagnostic ignored "Simplify"
#pragma ide diagnostic ignored "UnreachableCode"
namespace lsplant { namespace lsplant {
using art::ArtMethod; using art::ArtMethod;
@ -36,10 +43,14 @@ using art::Instrumentation;
using art::Runtime; using art::Runtime;
using art::Thread; using art::Thread;
using art::gc::ScopedGCCriticalSection; using art::gc::ScopedGCCriticalSection;
using art::jit::Jit;
using art::jit::JitCodeCache; using art::jit::JitCodeCache;
using art::jni::JniIdManager; using art::jni::JniIdManager;
using art::mirror::Class; using art::mirror::Class;
using art::thread_list::ScopedSuspendAll; using art::thread_list::ScopedSuspendAll;
using art::JavaDebuggableGuard;
using namespace std::string_view_literals;
namespace { namespace {
template <typename T, T... chars> template <typename T, T... chars>
@ -71,9 +82,9 @@ consteval inline auto GetTrampoline() {
} }
if constexpr (kArch == Arch::kRiscv64) { if constexpr (kArch == Arch::kRiscv64) {
return std::make_tuple( return std::make_tuple(
"\x17\x05\x00\x00\x03\x35\xc5\x00\x67\x00\x05\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr, "\x17\x05\x00\x00\x03\x35\x05\x01\x83\x3f\x05\x00\x67\x80\x0f\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE // NOLINTNEXTLINE
uint8_t{84u}, uintptr_t{12u}); uint8_t{84u}, uintptr_t{16u});
} }
} }
@ -247,9 +258,6 @@ inline void UpdateTrampoline(uint8_t offset) {
} }
bool InitNative(JNIEnv *env, const HookHandler &handler) { bool InitNative(JNIEnv *env, const HookHandler &handler) {
if (!handler.inline_hooker || !handler.inline_unhooker || !handler.art_symbol_resolver) {
return false;
}
if (!ArtMethod::Init(env, handler)) { if (!ArtMethod::Init(env, handler)) {
LOGE("Failed to init art method"); LOGE("Failed to init art method");
return false; return false;
@ -259,14 +267,18 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init thread"); LOGE("Failed to init thread");
return false; return false;
} }
if (!ClassLinker::Init(handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!Class::Init(handler)) { if (!Class::Init(handler)) {
LOGE("Failed to init mirror class"); LOGE("Failed to init mirror class");
return false; return false;
} }
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
if (!ClassLinker::Init(env, handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!ScopedSuspendAll::Init(handler)) { if (!ScopedSuspendAll::Init(handler)) {
LOGE("Failed to init scoped suspend all"); LOGE("Failed to init scoped suspend all");
return false; return false;
@ -279,6 +291,10 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jit code cache"); LOGE("Failed to init jit code cache");
return false; return false;
} }
if (!Jit::Init(handler)) {
LOGE("Failed to init jit");
return false;
}
if (!DexFile::Init(env, handler)) { if (!DexFile::Init(env, handler)) {
LOGE("Failed to init dex file"); LOGE("Failed to init dex file");
return false; return false;
@ -291,10 +307,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jni id manager"); LOGE("Failed to init jni id manager");
return false; return false;
} }
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
// This should always be the last one // This should always be the last one
if (IsJavaDebuggable(env)) { if (IsJavaDebuggable(env)) {
@ -305,58 +317,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
return true; return true;
} }
struct JavaDebuggableGuard {
JavaDebuggableGuard() {
while (true) {
size_t expected = 0;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
count.fetch_add(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected + 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
~JavaDebuggableGuard() {
while (true) {
size_t expected = 2;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kNonJavaDebuggable);
count.fetch_sub(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected - 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
private:
inline static std::atomic_size_t count{0};
static_assert(std::atomic_size_t::is_always_lock_free, "Unsupported architecture");
static_assert(std::is_same_v<std::atomic_size_t::value_type, size_t>,
"Unsupported architecture");
};
std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader, std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static, std::string_view shorty, bool is_static,
std::string_view method_name, std::string_view method_name,
@ -393,7 +353,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
.Encode(); .Encode();
auto hook_builder{cbuilder.CreateMethod( auto hook_builder{cbuilder.CreateMethod(
generated_method_name == "{target}" ? method_name.data() : generated_method_name, generated_method_name == "{target}"sv ? method_name.data() : generated_method_name,
Prototype{return_type, parameter_types})}; Prototype{return_type, parameter_types})};
// allocate tmp first because of wide // allocate tmp first because of wide
auto tmp{hook_builder.AllocRegister()}; auto tmp{hook_builder.AllocRegister()};
@ -440,7 +400,6 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
backup_builder.BuildReturn(zero, /*is_object=*/false, true); backup_builder.BuildReturn(zero, /*is_object=*/false, true);
} else { } else {
LiveRegister zero = backup_builder.AllocRegister(); LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0); backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false); backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
} }
@ -470,12 +429,12 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
mprotect(target, image.size(), PROT_READ); mprotect(target, image.size(), PROT_READ);
std::string err_msg; std::string err_msg;
const auto *dex = DexFile::OpenMemory( const auto *dex = DexFile::OpenMemory(
target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name, reinterpret_cast<const uint8_t *>(target), image.size(),
&err_msg); generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg);
if (!dex) { if (!dex) {
LOGE("Failed to open memory dex: %s", err_msg.data()); LOGE("Failed to open memory dex: %s", err_msg.data());
} else { } else {
java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr}); java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr);
} }
} }
@ -509,7 +468,8 @@ static_assert(std::endian::native == std::endian::little, "Unsupported architect
union Trampoline { union Trampoline {
public: public:
uintptr_t address; uintptr_t address;
unsigned count : 12; unsigned count4k : 12;
unsigned count16k : 14;
}; };
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture"); static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
@ -518,16 +478,16 @@ static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architect
std::atomic_uintptr_t trampoline_pool{0}; std::atomic_uintptr_t trampoline_pool{0};
std::atomic_flag trampoline_lock{false}; std::atomic_flag trampoline_lock{false};
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize); constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
constexpr uintptr_t kAddressMask = 0xFFFU; constexpr uintptr_t kAddressMask = 0xFFFU;
void *GenerateTrampolineFor(art::ArtMethod *hook) { void *GenerateTrampolineFor(art::ArtMethod *hook) {
static const size_t kPageSize = sysconf(_SC_PAGESIZE); // assume
static const size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
unsigned count; unsigned count;
uintptr_t address; uintptr_t address;
while (true) { while (true) {
auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)}; auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = tl.count; count = kPageSize == 16384 ? tl.count16k : tl.count4k;
address = tl.address & ~kAddressMask; address = tl.address & ~kAddressMask;
if (address == 0 || count >= kTrampolineNumPerPage) { if (address == 0 || count >= kTrampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) { if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
@ -545,7 +505,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
} }
count = 0; count = 0;
tl.address = address; tl.address = address;
tl.count = count + 1; kPageSize == 16384 ? tl.count16k = count + 1 : tl.count4k = count + 1;
trampoline_pool.store(tl.address, std::memory_order_release); trampoline_pool.store(tl.address, std::memory_order_release);
trampoline_lock.clear(std::memory_order_release); trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all(); trampoline_lock.notify_all();
@ -579,18 +539,12 @@ bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
} else { } else {
LOGV("Generated trampoline %p", entrypoint); LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable();
hook->SetNonCompilable(); hook->SetNonCompilable();
// copy after setNonCompilable target->BackupTo(backup);
backup->CopyFrom(target);
target->ClearFastInterpretFlag();
target->SetEntryPoint(entrypoint); target->SetEntryPoint(entrypoint);
if (!backup->IsStatic()) backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target, LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(), target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint()); backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
@ -657,15 +611,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
std::string out; std::string out;
auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) { auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) {
if (env->IsSameObject(type, int_type)) return 'I'; if (JNI_IsSameObject(env, type, int_type)) return 'I';
if (env->IsSameObject(type, long_type)) return 'J'; if (JNI_IsSameObject(env, type, long_type)) return 'J';
if (env->IsSameObject(type, float_type)) return 'F'; if (JNI_IsSameObject(env, type, float_type)) return 'F';
if (env->IsSameObject(type, double_type)) return 'D'; if (JNI_IsSameObject(env, type, double_type)) return 'D';
if (env->IsSameObject(type, boolean_type)) return 'Z'; if (JNI_IsSameObject(env, type, boolean_type)) return 'Z';
if (env->IsSameObject(type, byte_type)) return 'B'; if (JNI_IsSameObject(env, type, byte_type)) return 'B';
if (env->IsSameObject(type, char_type)) return 'C'; if (JNI_IsSameObject(env, type, char_type)) return 'C';
if (env->IsSameObject(type, short_type)) return 'S'; if (JNI_IsSameObject(env, type, short_type)) return 'S';
if (env->IsSameObject(type, void_type)) return 'V'; if (JNI_IsSameObject(env, type, void_type)) return 'V';
return 'L'; return 'L';
}; };
out += type_to_shorty(return_type); out += type_to_shorty(return_type);
@ -677,10 +631,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
} // namespace } // namespace
inline namespace v2 { inline namespace v2 {
extern "C++" {
using ::lsplant::IsHooked; using ::lsplant::IsHooked;
[[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) { [[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) {
if (!info.inline_hooker || !info.inline_unhooker || !info.art_symbol_resolver ||
!info.art_symbol_prefix_resolver) {
return false;
}
bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info); bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info);
return kInit; return kInit;
} }
@ -733,7 +692,7 @@ using ::lsplant::IsHooked;
} }
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope( std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env, env,
BuildDex(env, callback_class_loader, BuildDex(env, callback_class_loader.get(),
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method) __builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method), : ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(), is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
@ -749,8 +708,8 @@ using ::lsplant::IsHooked;
JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE); JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook); auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook.get());
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup); auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup.get());
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object); JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
@ -833,7 +792,7 @@ using ::lsplant::IsHooked;
if (auto *backup = IsHooked(art_method); backup) { if (auto *backup = IsHooked(art_method); backup) {
art_method = backup; art_method = backup;
} }
if (!art_method) { if (!art_method || art_method->IsNative()) {
return false; return false;
} }
return ClassLinker::SetEntryPointsToInterpreter(art_method); return ClassLinker::SetEntryPointsToInterpreter(art_method);
@ -875,8 +834,7 @@ using ::lsplant::IsHooked;
if (!cookie) return false; if (!cookie) return false;
return DexFile::SetTrusted(env, cookie); return DexFile::SetTrusted(env, cookie);
} }
}
} // namespace v2 } // namespace v2
} // namespace lsplant } // namespace lsplant
#pragma clang diagnostic pop

View File

@ -6,7 +6,7 @@ pluginManagement {
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

View File

@ -8,9 +8,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@ -124,7 +122,7 @@ public class UnitTest {
var proxyInterface = Class.forName("org.lsposed.lsplant.LSPTest$ForProxy"); var proxyInterface = Class.forName("org.lsposed.lsplant.LSPTest$ForProxy");
var proxy = Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, (proxy1, method, args) -> { var proxy = Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, (proxy1, method, args) -> {
if (method.getName().equals("abstractMethod")) { if (method.getName().equals("abstractMethod")) {
return (String) args[0] + (boolean) args[1] + (byte) args[2] + (short) args[3] + (int) args[4] + (long) args[5] + (float) args[6] + (double) args[7] + (Integer) args[8] + (Long) args[9]; return (String) args[0] + args[1] + args[2] + args[3] + args[4] + args[5] + args[6] + args[7] + args[8] + args[9];
} }
return method.invoke(proxy1, args); return method.invoke(proxy1, args);
}); });

View File

@ -1,11 +1,18 @@
cmake_minimum_required(VERSION 3.18.1) cmake_minimum_required(VERSION 3.18.1)
project("lsplant_test") project("lsplant_test")
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_subdirectory(external/lsparself) add_subdirectory(external/lsparself)
add_library(test SHARED test.cpp) add_library(test SHARED test.cpp)
set_target_properties(test PROPERTIES CXX_SCAN_FOR_MODULES ON)
find_package(dobby REQUIRED CONFIG) find_package(dobby REQUIRED CONFIG)
find_package(lsplant REQUIRED CONFIG) find_package(lsplant REQUIRED CONFIG)
get_property(lsplant_INCLUDE TARGET lsplant::lsplant PROPERTY INTERFACE_INCLUDE_DIRECTORIES)
file(GLOB_RECURSE lsplant_MODULES "${lsplant_INCLUDE}/*.ixx")
target_sources(lsplant::lsplant INTERFACE FILE_SET CXX_MODULES BASE_DIRS ${lsplant_INCLUDE} FILES ${lsplant_MODULES})
set_target_properties(lsplant::lsplant PROPERTIES IMPORTED_CXX_MODULES_COMPILE_FEATURES "cxx_std_23")
set_target_properties(lsplant::lsplant PROPERTIES IMPORTED_CXX_MODULES_INCLUDE_DIRECTORIES "${lsplant_INCLUDE}")
target_link_libraries(test log dobby::dobby lsplant::lsplant lsparself) target_link_libraries(test log dobby::dobby lsplant::lsplant lsparself)

@ -1 +1 @@
Subproject commit 28d6aca7ac8006f8d14d87bf76bed29e1c0d576f Subproject commit 73b146d780339e0ec3fc91368828c5f4441761c4

1
test/src/main/jni/external/lsprism vendored Submodule

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

View File

@ -1,9 +1,12 @@
#include <jni.h>
#include <dobby.h> #include <dobby.h>
#include <lsplant.hpp>
#include <sys/mman.h> #include <sys/mman.h>
#include <lsparself.hpp> #include <string_view>
#include "logging.h" #include "logging.h"
import lsplant;
import lsparself;
#define _uintval(p) reinterpret_cast<uintptr_t>(p) #define _uintval(p) reinterpret_cast<uintptr_t>(p)
#define _ptr(p) reinterpret_cast<void *>(p) #define _ptr(p) reinterpret_cast<void *>(p)
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1)) #define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
@ -56,9 +59,6 @@ JNI_OnLoad(JavaVM* vm, void* reserved) {
return JNI_ERR; return JNI_ERR;
} }
lsparself::Elf art("/libart.so"); lsparself::Elf art("/libart.so");
#if !defined(__i386__)
dobby_enable_near_branch_trampoline();
#endif
lsplant::InitInfo initInfo{ lsplant::InitInfo initInfo{
.inline_hooker = InlineHooker, .inline_hooker = InlineHooker,
.inline_unhooker = InlineUnhooker, .inline_unhooker = InlineUnhooker,