Compare commits

...

111 Commits
v6.0 ... 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
LoveSy
5dbc9ff657
Update build script 2024-02-18 19:02:02 +08:00
LoveSy
1726816a98
Upgrade deps 2024-02-18 13:07:49 +08:00
5ec1cff
7ebe6b476a
Fix static method hook lost if it is deoptimized before class initialization 2024-02-18 01:09:09 +08:00
LoveSy
e0e8cc11dc
Fix Android 15 test 2024-02-17 17:30:58 +08:00
LoveSy
7d4987c80f
Use ssh-key for checkout 2024-02-17 16:53:13 +08:00
LoveSy
f240269c5a
Use VanillaIceCream runner 2024-02-17 16:38:16 +08:00
LoveSy
f82b5bf9e9
Add Android 15 to unit test 2024-02-17 16:09:23 +08:00
LoveSy
0de159c0b9
Upgrade gradle 2024-02-17 16:06:44 +08:00
dependabot[bot]
56ce2ff076
Bump agp from 8.2.1 to 8.2.2 (#66) 2024-01-25 19:36:02 +08:00
LoveSy
0305a18d7f
Run tests on Linux 2024-01-23 13:48:16 +08:00
LoveSy
3b9ec2b02e
Fix ThrowInvocationTimeError initialization
Fix #65

Co-authored-by: canyie <a1364259@163.com>
2024-01-17 15:01:10 +08:00
LoveSy
4ec86d14f4
Update pages.yml 2024-01-06 21:31:00 +08:00
dependabot[bot]
7af00b8aa7
Bump agp from 8.2.0 to 8.2.1 (#62) 2024-01-05 16:06:08 +08:00
LoveSy
92a6a727e2 Allow set null 2023-12-07 11:12:45 +08:00
Ylarod
4c62b5d558 fix size 2023-12-07 11:12:45 +08:00
LoveSy
dafa21cc38 Fix size 2023-12-07 11:12:45 +08:00
LoveSy
467d57a376 Fix size 2023-12-07 11:12:45 +08:00
LoveSy
d675c45d3f upgrade gradle 2023-12-07 11:12:45 +08:00
LoveSy
3ce7b1d1a0 Missing return 2023-12-07 11:12:45 +08:00
LoveSy
0d144a0881 fix infinity loop 2023-12-07 11:12:45 +08:00
LoveSy
c5b2c550d3 Update build.yml 2023-12-07 11:12:45 +08:00
LoveSy
a0389a2429 Upgrade ndk 2023-12-07 11:12:45 +08:00
LoveSy
4ff1786610 Add clone and fix assign of jObjArrEle 2023-12-07 11:12:45 +08:00
LoveSy
b435731080 Remove redundant inline 2023-12-07 11:12:45 +08:00
LoveSy
a1948df0dc Fix call of JObjectArrayElement 2023-12-07 11:12:45 +08:00
LoveSy
0f0f9f7bcd 1 2023-12-07 11:12:45 +08:00
Ylarod
713bb300c4 update 2023-12-07 11:12:45 +08:00
Ylarod
df7d10b1d8 update jni_helper.hpp 2023-12-07 11:12:45 +08:00
dependabot[bot]
4e95078c79
Bump agp from 8.1.4 to 8.2.0 (#60) 2023-12-06 15:26:02 +08:00
葫芦娃
439f38f48c
fixed logic bug for re-deoptimize condition in RestoreBackup (#59) 2023-11-22 17:57:39 +08:00
dependabot[bot]
7c732a7215
Bump agp from 8.1.3 to 8.1.4 (#58) 2023-11-17 05:20:17 +08:00
Ylarod
896f985017
Fix typo (#57) 2023-11-14 17:18:44 +08:00
dependabot[bot]
554136c1bb
Bump agp from 8.1.2 to 8.1.3 (#56) 2023-11-08 08:51:38 +08:00
LoveSy
c6d6773536
Fix missing PLOGE when disabled log
Fix #51
2023-10-23 18:05:39 +08:00
LoveSy
a612522188
Avoid using too much java debuggable guard 2023-10-11 13:54:25 +08:00
67 changed files with 2363 additions and 2151 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:
interval: daily
time: "21:00"
open-pull-requests-limit: 10
target-branch: master
registries:
- maven-google
- gralde-plugin
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/"
groups:
maven-dependencies:
patterns:
- "*"

View File

@ -5,6 +5,7 @@ on:
branches: ["master"]
paths-ignore:
- 'README.md'
workflow_dispatch:
pull_request:
@ -15,25 +16,34 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ]
os: [ ubuntu-latest, windows-latest, macos-14 ]
steps:
- name: Check out
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ github.sha }}
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
run: |
ccache -o cache_dir=${{ github.workspace }}/.ccache
@ -47,15 +57,16 @@ jobs:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}
- name: Upload library
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.os }}-library
path: ~/.m2
include-hidden-files: true
test:
needs: build
name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }}
runs-on: macos-latest
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
@ -130,52 +141,122 @@ jobs:
target: google_apis
arch: x86_64
- api-level: 33
target: google_apis
target: default
arch: x86_64
- api-level: 33
target: android-tv
arch: x86
- api-level: 34
target: google_apis
target: default
arch: x86_64
- api-level: 34
target: android-tv
arch: x86
- api-level: 35
target: default
arch: x86_64
- api-level: Baklava
target: google_apis
arch: x86_64
steps:
- name: checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'recursive'
- name: Set up JDK 17
uses: actions/setup-java@v3
ssh-key: ${{ secrets.SSH_KEY }}
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
java-version: '21'
cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
save: false
- name: Enable KVM group perms
run: |
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 --name-match=kvm
- name: run tests
uses: yujincheng08/android-emulator-runner@release/apilevel
uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93
with:
api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }}
target: ${{ matrix.target }}
script: |
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'
echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew :test:connectedCheck
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
avd-name: ${{ matrix.api-level }}_${{ matrix.arch }}
- name: Upload outputs
if: always()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-outputs
path: test/build/outputs
name: test-outputs-${{ matrix.arch }}-${{ matrix.target }}-${{ matrix.api-level }}
path: |
test/build/outputs
!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

@ -9,18 +9,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'zulu'
distribution: 'temurin'
java-version: '17'
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
run: ./gradlew :lsplant:publish
run: |
rm -rf $ANDROID_HOME/cmake
./gradlew :lsplant:publish
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}

View File

@ -29,15 +29,16 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
- name: Install doxygen
run: sudo apt install -y doxygen
- name: Generate doxygen
run: doxygen docs/doxygen.cfg
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
uses: actions/upload-pages-artifact@v3
with:
# Upload entire repository
path: 'docs/docs'

6
.gitmodules vendored
View File

@ -5,3 +5,9 @@
[submodule "docs/doxygen-awesome-css"]
path = docs/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git
[submodule "test/src/main/jni/external/lsparself"]
path = test/src/main/jni/external/lsparself
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
![](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://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)
@ -12,7 +12,7 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens
## 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 customized inline hook framework and ART symbol resolver
@ -34,7 +34,7 @@ android {
}
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
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)
}
val androidTargetSdkVersion by extra(34)
val androidTargetSdkVersion by extra(35)
val androidMinSdkVersion by extra(21)
val androidBuildToolsVersion by extra("34.0.0")
val androidCompileSdkVersion by extra(34)
val androidNdkVersion by extra("26.0.10792818")
val androidCmakeVersion by extra("3.22.1+")
val androidBuildToolsVersion by extra("35.0.0")
val androidCompileSdkVersion by extra(35)
val androidNdkVersion by extra("29.0.13113456")
val androidCmakeVersion by extra("3.28.0+")

@ -1 +1 @@
Subproject commit 4cd62308d825fe0396d2f66ffbab45d0e247724c
Subproject commit df88fe4fdd97714fadfd3ef17de0b4401f804052

View File

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

View File

@ -1,5 +1,5 @@
[versions]
agp = "8.1.2"
agp = "8.9.0"
[plugins]
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" }
[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" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.1.5" }
test-runner = { module = "androidx.test:runner", version = "1.5.2" }
test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.5.1" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
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
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

20
gradlew vendored
View File

@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (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.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# 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.
MAX_FD=maximum
@ -145,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@ -153,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -202,11 +204,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# 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
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

22
gradlew.bat vendored
View File

@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

View File

@ -1,5 +1,3 @@
import java.nio.file.Paths
plugins {
alias(libs.plugins.agp.lib)
alias(libs.plugins.lsplugin.jgit)
@ -81,8 +79,8 @@ cmaker {
val flags = arrayOf(
"-Werror",
"-Wno-gnu-string-literal-operator-template",
"-Wno-c++2b-extensions",
)
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
cppFlags += flags
cFlags += flags
}
@ -90,13 +88,15 @@ cmaker {
when (it.name) {
"debug", "release" -> {
arguments += "-DANDROID_STL=c++_shared"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
"standalone" -> {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLANT_STANDALONE=ON"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
arguments += "-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/${it.name}"
arguments += "-DDEBUG_SYMBOLS_PATH=${project.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}"
}
}
@ -105,17 +105,17 @@ dependencies {
}
val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") {
from("${project.buildDir.absolutePath}/symbols/release")
from(project.layout.buildDirectory.file("symbols/release"))
exclude("**/dex_builder")
archiveClassifier.set("symbols")
archiveBaseName.set("release")
archiveClassifier = "symbols"
archiveBaseName = "release"
}
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") {
from("${project.buildDir.absolutePath}/symbols/standalone")
from(project.layout.buildDirectory.file("symbols/standalone"))
exclude("**/dex_builder")
archiveClassifier.set("symbols")
archiveBaseName.set("standalone")
archiveClassifier = "symbols"
archiveBaseName = "standalone"
}
val repo = jgit.repo(true)
@ -129,24 +129,24 @@ publish {
group = "org.lsposed.lsplant"
version = ver
pom {
name.set("LSPlant")
description.set("A hook framework for Android Runtime (ART)")
url.set("https://github.com/LSPosed/LSPlant")
name = "LSPlant"
description = "A hook framework for Android Runtime (ART)"
url = "https://github.com/LSPosed/LSPlant"
licenses {
license {
name.set("GNU Lesser General Public License v3.0")
url.set("https://github.com/LSPosed/LSPlant/blob/master/LICENSE")
name = "GNU Lesser General Public License v3.0"
url = "https://github.com/LSPosed/LSPlant/blob/master/LICENSE"
}
}
developers {
developer {
name.set("Lsposed")
url.set("https://lsposed.org")
name = "Lsposed"
url = "https://lsposed.org"
}
}
scm {
connection.set("scm:git:https://github.com/LSPosed/LSPlant.git")
url.set("https://github.com/LSPosed/LSPlant")
connection = "scm:git:https://github.com/LSPosed/LSPlant.git"
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)
find_program(CCACHE ccache)
set(CMAKE_CXX_STANDARD 23)
if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
@ -13,9 +15,11 @@ if (LSPLANT_STANDALONE)
link_libraries(cxx::cxx)
endif()
add_definitions(-std=c++20)
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)
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)
message(STATUS "Building lsplant as a shared library")
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} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(${PROJECT_NAME} PRIVATE -flto)
@ -40,6 +47,9 @@ if (LSPLANT_BUILD_SHARED)
endif()
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 PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,47 +1,44 @@
#pragma once
module;
#include "art/runtime/art_method.hpp"
#include "art/runtime/handle.hpp"
#include "common.hpp"
#include <parallel_hashmap/phmap.h>
namespace lsplant::art {
class Thread;
namespace dex {
class ClassDef {};
} // namespace dex
#include "logging.hpp"
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 {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
if (GetDescriptorSym) [[likely]]
return GetDescriptorSym(thiz, storage);
else
return "";
}
inline static auto GetDescriptor_ =
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>;
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
if (GetClassDefSym) [[likely]]
return GetClassDefSym(thiz);
return nullptr;
}
inline static auto GetClassDef_ =
"_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>;
using BackupMethods = std::list<std::tuple<art::ArtMethod *, void *>>;
using BackupMethods = phmap::flat_hash_map<art::ArtMethod *, void *>;
inline static phmap::flat_hash_map<const art::Thread *,
phmap::flat_hash_map<const dex::ClassDef *, BackupMethods>>
backup_methods_;
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) {
std::list<std::tuple<art::ArtMethod *, void *>> out;
BackupMethods out;
if (!class_def) return;
{
hooked_classes_.if_contains(class_def, [&out](const auto &it) {
for (auto method : it.second) {
if (method->IsStatic()) {
LOGV("Backup hooked method %p because of initialization", method);
out.emplace_back(method, method->GetEntryPoint());
out.emplace(method, method->GetEntryPoint());
}
}
});
@ -51,7 +48,7 @@ private:
for (auto method : it.second) {
if (method->IsStatic()) {
LOGV("Backup deoptimized method %p because of initialization", method);
out.emplace_back(method, method->GetEntryPoint());
out.emplace(method, method->GetEntryPoint());
}
}
});
@ -62,62 +59,60 @@ private:
}
}
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE", void,
SetClassStatus, (TrivialHandle<Class> h, uint8_t new_status, Thread *self), {
inline static auto SetClassStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint8_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) {
BackupClassMethods(h->GetClassDef(), self);
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
});
};
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, SetStatus,
(Handle<Class> h, int new_status, Thread *self), {
inline static auto SetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(Handle<Class> h, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(h->GetClassDef(), self);
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
});
};
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void,
TrivialSetStatus, (TrivialHandle<Class> h, uint32_t new_status, Thread *self), {
inline static auto TrivialSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint32_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) {
BackupClassMethods(h->GetClassDef(), self);
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
});
};
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE", void,
ClassSetStatus, (Class * thiz, int new_status, Thread *self), {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(thiz->GetClassDef(), self);
}
return backup(thiz, new_status, self);
});
inline static uint8_t initialized_status = 0;
inline static auto ClassSetStatus_ =
"_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)) {
BackupClassMethods(GetClassDef_(thiz), self);
}
return backup(thiz, new_status, self);
};
public:
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor,
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE")) {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv")) {
if (!handler(GetDescriptor_) || !handler(GetClassDef_)) {
return false;
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) {
if (!HookSyms(handler, SetStatus, ClassSetStatus)) {
if (!handler(SetStatus_, ClassSetStatus_)) {
return false;
}
} else {
if (!HookSyms(handler, SetClassStatus, TrivialSetStatus)) {
if (!handler(SetClassStatus_, TrivialSetStatus_)) {
return false;
}
}
@ -135,22 +130,14 @@ public:
return true;
}
const char *GetDescriptor(std::string *storage) {
if (GetDescriptorSym) {
return GetDescriptor(this, storage);
}
return "";
}
const char *GetDescriptor(std::string *storage) { return GetDescriptor_(this, storage); }
std::string GetDescriptor() {
std::string storage;
return GetDescriptor(&storage);
}
const dex::ClassDef *GetClassDef() {
if (GetClassDefSym) return GetClassDef(this);
return nullptr;
}
const dex::ClassDef *GetClassDef() { return GetClassDef_(this); }
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods methods;
@ -184,5 +171,4 @@ public:
}
};
} // namespace mirror
} // namespace lsplant::art
} // namespace lsplant::art::mirror

View File

@ -1,38 +1,51 @@
#pragma once
module;
#include "art/mirror/class.hpp"
#include "common.hpp"
#include <atomic>
#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 {
CREATE_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) {
if (thiz == nullptr) [[unlikely]]
return "null";
else if (PrettyMethodSym) [[likely]]
return PrettyMethodSym(thiz, with_signature);
else
return "null sym";
inline static auto PrettyMethod_ =
"_ZN3art9ArtMethod12PrettyMethodEPS0_b"_sym.as<std::string(ArtMethod::*)(bool)>;
inline static auto PrettyMethodStatic_ =
"_ZN3art12PrettyMethodEPNS_9ArtMethodEb"_sym.as<std::string(ArtMethod *thiz, bool with_signature)>;
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:
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() {
@ -78,6 +91,18 @@ public:
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 IsProtected() { return GetAccessFlags() & kAccProtected; }
bool IsPublic() { return GetAccessFlags() & kAccPublic; }
@ -94,7 +119,7 @@ public:
if (interpreter_entry_point_offset) [[unlikely]] {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
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) {
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() {
@ -131,6 +160,23 @@ public:
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) {
if (art_method_field) [[likely]] {
return reinterpret_cast<art::ArtMethod *>(
@ -178,8 +224,8 @@ public:
LOGE("Throwable has less than 2 constructors");
return false;
}
auto &first_ctor = constructors[0];
auto &second_ctor = constructors[1];
auto first_ctor = constructors[0];
auto second_ctor = constructors[1];
auto *first = FromReflectedMethod(env, first_ctor.get());
auto *second = FromReflectedMethod(env, second_ctor.get());
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
@ -221,8 +267,8 @@ public:
env,
JNI_GetObjectField(
env,
env->ToReflectedField(executable,
JNI_GetFieldID(env, executable, name, sig), false),
JNI_ToReflectedField(env, executable,
JNI_GetFieldID(env, executable, name, sig), false),
art_field_field),
field_offset);
};
@ -249,17 +295,12 @@ public:
}
if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0;
if (!RETRIEVE_FUNC_SYMBOL(GetMethodShorty,
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID", true) &&
!RETRIEVE_FUNC_SYMBOL(GetMethodShorty,
"_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID")) {
if (!handler(GetMethodShortyL_, true, GetMethodShorty_)) {
LOGE("Failed to find GetMethodShorty");
return false;
}
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art9ArtMethod12PrettyMethodEPS0_b") &&
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_9ArtMethodEb") &&
!RETRIEVE_FUNC_SYMBOL(PrettyMethod, "_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb");
handler(PrettyMethod_, PrettyMethodStatic_, PrettyMethodMirror_);
if (sdk_int <= __ANDROID_API_O__) [[unlikely]] {
auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError");
@ -274,8 +315,9 @@ public:
LOGE("Failed to find Executable.getName");
return false;
}
handler(ThrowInvocationTimeError_);
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();
abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict);
abstract_method->ThrowInvocationTimeError();
@ -292,8 +334,7 @@ public:
kAccCompileDontBother = 0;
}
if (sdk_int <= __ANDROID_API_M__) [[unlikely]] {
if (!RETRIEVE_FUNC_SYMBOL(art_interpreter_to_compiled_code_bridge,
"artInterpreterToCompiledCodeBridge")) {
if (!handler(art_interpreter_to_compiled_code_bridge_)) {
return false;
}
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,205 +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);
if (IsDeoptimized(art_method)) {
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);
}
continue;
}
if (auto backup_method = IsHooked(art_method); backup_method) [[likely]] {
if (new_trampoline != old_trampoline) [[unlikely]] {
LOGV("propagate entrypoint for %p", backup_method);
backup_method->SetEntryPoint(new_trampoline);
}
}
}
}
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 "common.hpp"
#include "logging.hpp"
export module lsplant:instrumentation;
import :art_method;
import :common;
import hook_helper;
namespace lsplant::art {
class Instrumentation {
export class Instrumentation {
inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) {
if (auto backup = IsHooked(art_method); backup && art_method->GetEntryPoint() != quick_code)
[[unlikely]] {
@ -16,29 +21,29 @@ class Instrumentation {
return art_method;
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE",
void, UpdateMethodsCodeToInterpreterEntryPoint,
(Instrumentation * thiz, ArtMethod *art_method), {
inline static auto UpdateMethodsCodeToInterpreterEntryPoint_ =
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE"_sym.hook->*[]
<MemBackup auto backup>
(Instrumentation *thiz, ArtMethod *art_method) static -> void {
if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str());
return;
}
backup(thiz, MaybeUseBackupMethod(art_method, nullptr));
});
};
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv", void,
InitializeMethodsCode,
(Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), {
inline static auto InitializeMethodsCode_ =
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv"_sym.hook->*[]
<MemBackup auto backup>
(Instrumentation *thiz, ArtMethod *art_method, const void *quick_code) static -> void {
if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str());
return;
}
backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code);
});
};
public:
static bool Init(JNIEnv *env, const HookHandler &handler) {
@ -47,8 +52,7 @@ public:
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!HookSyms(handler, InitializeMethodsCode,
UpdateMethodsCodeToInterpreterEntryPoint)) {
if (!handler(InitializeMethodsCode_, UpdateMethodsCodeToInterpreterEntryPoint_)) {
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 <sys/system_properties.h>
@ -8,10 +9,23 @@
#include <string_view>
#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 {
kArm,
@ -37,14 +51,24 @@ consteval inline Arch GetArch() {
#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>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
inline auto GetAndroidApiLevel() {
[[gnu::const]] inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
@ -55,7 +79,7 @@ inline auto GetAndroidApiLevel() {
return kApiLevel;
}
inline auto IsJavaDebuggable(JNIEnv *env) {
inline auto IsJavaDebuggable(JNIEnv * env) {
static auto kDebuggable = [&env]() {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_P__) {
@ -66,13 +90,14 @@ inline auto IsJavaDebuggable(JNIEnv *env) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method =
JNI_GetStaticMethodID(env, runtime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
auto get_runtime_method = JNI_GetStaticMethodID(env, runtime_class, "getRuntime",
"()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method = JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
auto is_debuggable_method =
JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
@ -89,61 +114,38 @@ inline auto IsJavaDebuggable(JNIEnv *env) {
return kDebuggable;
}
inline static constexpr auto kPointerSize = sizeof(void *);
constexpr auto kPointerSize = sizeof(void *);
namespace art {
class ArtMethod;
namespace dex {
class ClassDef;
}
namespace mirror {
class Class;
}
} // namespace art
SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
namespace {
// target, backup
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
inline SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
inline SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
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_;
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
inline std::shared_mutex jit_movements_lock_;
} // namespace
std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
std::shared_mutex jit_movements_lock_;
inline art::ArtMethod *IsHooked(art::ArtMethod *art_method, bool including_backup = false) {
inline art::ArtMethod *IsHooked(art::ArtMethod * art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (!including_backup || it.second.first) backup = it.second.second;
if (including_backup || it.second.first) backup = it.second.second;
});
return backup;
}
inline art::ArtMethod *IsBackup(art::ArtMethod *art_method) {
inline art::ArtMethod *IsBackup(art::ArtMethod * art_method) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second;
});
return nullptr;
return backup;
}
inline bool IsDeoptimized(art::ArtMethod *art_method) {
inline bool IsDeoptimized(art::ArtMethod * art_method) {
return deoptimized_methods_set_.contains(art_method);
}
@ -152,7 +154,7 @@ inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements(
return std::move(jit_movements_);
}
inline void RecordHooked(art::ArtMethod *target, const art::dex::ClassDef *class_def,
inline void RecordHooked(art::ArtMethod * target, const art::dex::ClassDef *class_def,
jobject reflected_backup, art::ArtMethod *backup) {
hooked_classes_.lazy_emplace_l(
class_def, [&target](auto &it) { it.second.emplace(target); },
@ -168,9 +170,8 @@ inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMetho
deoptimized_methods_set_.insert(art_method);
}
inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) {
inline void RecordJitMovement(art::ArtMethod * target, art::ArtMethod * backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
} // namespace lsplant

@ -1 +1 @@
Subproject commit b5a00f2ea94ad4c3b92054fd53896dbb429298f9
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
#include <android/log.h>
#include <concepts>
#include "jni_helper.hpp"
#include "lsplant.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__)
#include "type_traits.hpp"
namespace lsplant {
using HookHandler = InitInfo;
template <char... chars>
struct tstring : public std::integer_sequence<char, chars...> {
inline constexpr static const char *c_str() { return str_; }
inline constexpr operator std::string_view() const { return {c_str(), sizeof...(chars)}; }
private:
inline static constexpr char str_[]{chars..., '\0'};
template <size_t N>
struct FixedString {
consteval FixedString(const char (&str)[N]) { std::copy_n(str, N, data); }
char data[N] = {};
};
template <typename T, T... chars>
inline constexpr tstring<chars...> operator""_tstr() {
return {};
}
template<typename T>
concept FuncType = std::is_function_v<T> || std::is_member_function_pointer_v<T>;
template <char... as, char... bs>
inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) {
return {};
}
template <FixedString, FuncType>
struct Function;
template <char... as>
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
char b[]{as..., '\0'};
return a + b;
}
template <char... as>
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
char a[]{as..., '\0'};
return a + b;
}
inline void *Dlsym(const HookHandler &handle, const char *name, bool match_prefix = false) {
if (auto match = handle.art_symbol_resolver(name); match) {
return match;
} else if (match_prefix && handle.art_symbol_prefix_resolver) {
return handle.art_symbol_prefix_resolver(name);
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;
}
return nullptr;
}
template <typename Class, typename Return, typename T, typename... Args>
requires(std::is_same_v<T, void> ||
std::is_same_v<Class, T>) inline static auto memfun_cast(Return (*func)(T *, Args...)) {
union {
Return (Class::*f)(Args...);
private:
inline static union {
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;
}
private:
using ThisType = std::conditional_t<std::is_same_v<This, void>, Function, This>;
inline static union {
Ret (ThisType::*function_)(Args...) const;
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;
}
void *raw_function_ = nullptr;
[[maybe_unused]] std::ptrdiff_t adj = 0;
};
} inner_;
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; }
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_) + sizeof(inner_.adj));
};
// deduction guide
template <typename This, typename Return, typename... Args>
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
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_;
template <typename This, typename Return, typename... Args>
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
static_assert(sizeof(inner_.field_) == sizeof(inner_.raw_field_));
};
template <typename, typename>
template <FixedString, FuncType>
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 <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 <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 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;
};
template <typename T>
concept HookerType = requires(T a) {
a.backup;
a.replace;
};
struct HookHandler {
HookHandler(const InitInfo &info) : info_(info) {}
template <HookerType T>
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
if (original) {
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
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)));
template <typename T>
[[gnu::always_inline]] bool operator()(T &&arg) const {
return handle(std::forward<T>(arg), false);
}
template <typename T1, typename T2, typename... U>
[[gnu::always_inline]] bool operator()(T1 &&arg1, T2 &&arg2, U &&...args) const {
if constexpr(std::is_same_v<T2, bool>)
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)...);
}
private:
[[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 true;
} else {
return false;
if (match_prefix && info_.art_symbol_prefix_resolver) [[likely]] {
return info_.art_symbol_prefix_resolver(Sym.data);
}
return nullptr;
}
[[gnu::always_inline]] void *hook(void *original, void *replace) const {
if (original) [[likely]] {
return info_.inline_hooker(original, replace);
}
return nullptr;
}
};
template<typename F>
concept Backup = std::is_function_v<std::remove_pointer_t<F>>;
template<typename F>
concept MemBackup = std::is_member_function_pointer_v<std::remove_pointer_t<F>> || Backup<F>;
template<FixedString S>
struct Symbol {
template<typename T>
inline static decltype([]{
if constexpr (FuncType<T>) {
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 <FixedString S> constexpr Symbol<S> operator""_sym() {
return {};
}
template <HookerType T>
inline static bool HookSym(const HookHandler &handler, T &arg) {
auto original = handler.art_symbol_resolver(arg.sym);
return HookSymNoHandle(handler, original, arg);
}
template <HookerType T, HookerType... Args>
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,
template<FixedString S, FixedString P>
consteval auto operator|([[maybe_unused]] Symbol<S> a, [[maybe_unused]] Symbol<P> b) {
#if defined(__LP64__)
return b;
#else
"HookHelper",
return a;
#endif
"Hook Fails: %*s", static_cast<int>(first.sym.size()),
first.sym.data());
return false;
}
return true;
}
} // 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_view>
#include "type_traits.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-partial-specialization"
#pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -16,15 +18,6 @@
void operator=(const TypeName &) = delete
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>
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
@ -61,13 +54,10 @@ public:
T get() const { return local_ref_; }
operator T() const { return local_ref_; }
ScopedLocalRef<T> clone() const {
return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_));
}
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
// ScopedLocalRef<...> ref;
// ref.reset(...);
// Move assignment operator.
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release());
env_ = s.env_;
@ -87,6 +77,8 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
class JObjectArrayElement;
template <typename T>
concept JArray = std::is_base_of_v<std::remove_pointer_t<_jarray>, std::remove_pointer_t<T>>;
@ -117,9 +109,11 @@ public:
};
template <typename T, typename U>
concept ScopeOrRaw = std::is_convertible_v<T, U> ||
(is_instance_v<std::decay_t<T>, ScopedLocalRef>
&&std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
concept ScopeOrRaw =
std::is_convertible_v<T, U> ||
(is_instance_v<std::decay_t<T>, ScopedLocalRef> &&
std::is_convertible_v<typename std::decay_t<T>::BaseType, U>) ||
(std::is_same_v<std::decay_t<T>, JObjectArrayElement> && std::is_convertible_v<jobject, U>);
template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>;
@ -130,10 +124,11 @@ concept ScopeOrObject = ScopeOrRaw<T, jobject>;
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
if (auto exception = env->ExceptionOccurred()) {
env->ExceptionClear();
static jclass log = (jclass)env->NewGlobalRef(env->FindClass("android/util/Log"));
jclass log = (jclass)env->FindClass("android/util/Log");
static jmethodID toString = env->GetStaticMethodID(
log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
env->DeleteLocalRef(log);
env->DeleteLocalRef(exception);
return {env, str};
}
@ -146,6 +141,8 @@ template <typename T>
return x.data();
else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>)
return x.get();
else if constexpr (std::is_same_v<std::decay_t<T>, JObjectArrayElement>)
return x.get();
else
return std::forward<T>(x);
}
@ -176,28 +173,27 @@ inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
class JUTFString {
public:
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
inline JUTFString(const ScopedLocalRef<jstring> &jstr)
JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr)
: env_(env), jstr_(jstr) {
JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) {
if (env_ && jstr_)
cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else
cstr_ = default_cstr;
}
inline operator const char *() const { return cstr_; }
operator const char *() const { return cstr_; }
inline operator const std::string() const { return cstr_; }
operator const std::string() const { return cstr_; }
inline operator const bool() const { return cstr_ != nullptr; }
operator const bool() const { return cstr_ != nullptr; }
inline auto get() const { return cstr_; }
auto get() const { return cstr_; }
inline ~JUTFString() {
~JUTFString() {
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
}
@ -229,8 +225,8 @@ private:
};
template <typename Func, typename... Args>
requires(std::is_function_v<Func>)
[[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {
requires(std::is_function_v<Func>)
[[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {
struct finally {
finally(JNIEnv *env) : env_(env) {}
@ -509,6 +505,13 @@ template <ScopeOrClass Class>
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
// virtual methods
@ -667,84 +670,77 @@ template <ScopeOrClass Class, typename... Args>
std::forward<Args>(args)...);
}
// non-vritual methods
// non-virtual methods
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualByteMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
[[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
@ -773,6 +769,12 @@ template <ScopeOrObject Object, ScopeOrClass Class>
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>
[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(
@ -780,11 +782,17 @@ template <ScopeOrObject Object>
}
template <typename U, typename T>
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) requires(
std::is_convertible_v<T, _jobject *>) {
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x)
requires(std::is_convertible_v<T, _jobject *>)
{
return ScopedLocalRef<U>(std::move(x));
}
template <typename U>
[[maybe_unused]] inline auto JNI_Cast(JObjectArrayElement &&x) {
return JNI_Cast<U, jobject>(std::move(x));
}
[[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) {
return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity);
}
@ -792,11 +800,6 @@ template <typename U, typename T>
template <JArray T>
struct JArrayUnderlyingTypeHelper;
template <>
struct JArrayUnderlyingTypeHelper<jobjectArray> {
using Type = ScopedLocalRef<jobject>;
};
template <>
struct JArrayUnderlyingTypeHelper<jbooleanArray> {
using Type = jboolean;
@ -842,10 +845,6 @@ using JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper<T>::Type;
template <JArray T>
class ScopedLocalRef<T> {
ScopedLocalRef(JNIEnv *env, T local_ref, size_t size, JArrayUnderlyingType<T> *elements,
bool modified) noexcept
: env_(env), local_ref_(local_ref), size_(size), elements_(elements), modified_(modified) {}
public:
class Iterator {
friend class ScopedLocalRef<T>;
@ -903,13 +902,7 @@ public:
reset(local_ref);
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept
: ScopedLocalRef(s.env_, s.local_ref_, s.size_, s.elements_, s.modified_) {
s.local_ref_ = nullptr;
s.size_ = 0;
s.elements_ = nullptr;
s.modified_ = false;
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
@ -923,25 +916,13 @@ public:
if (local_ref_ != nullptr) {
ReleaseElements(modified_ ? 0 : JNI_ABORT);
env_->DeleteLocalRef(local_ref_);
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
elements_[i].~ScopedLocalRef<jobject>();
}
operator delete[](elements_);
}
elements_ = nullptr;
}
local_ref_ = ptr;
size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;
if (!local_ref_) return;
if constexpr (std::is_same_v<T, jobjectArray>) {
elements_ = static_cast<ScopedLocalRef<jobject> *>(operator new[](
sizeof(ScopedLocalRef<jobject>) * size_));
for (size_t i = 0; i < size_; ++i) {
new (&elements_[i]) ScopedLocalRef<jobject>(
JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, i));
}
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
static_assert(!std::is_same_v<T, jobjectArray>);
if constexpr (std::is_same_v<T, jbooleanArray>) {
elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr);
} else if constexpr (std::is_same_v<T, jbyteArray>) {
elements_ = env_->GetByteArrayElements(local_ref_, nullptr);
@ -966,20 +947,12 @@ public:
size_ = 0;
local_ref_ = nullptr;
ReleaseElements(modified_ ? 0 : JNI_ABORT);
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
elements_[i].~ScopedLocalRef<jobject>();
}
operator delete[](elements_);
}
elements_ = nullptr;
return localRef;
}
T get() const { return local_ref_; }
explicit operator T() const { return local_ref_; }
JArrayUnderlyingType<T> &operator[](size_t index) {
modified_ = true;
return elements_[index];
@ -1022,11 +995,7 @@ public:
private:
void ReleaseElements(jint mode) {
if (!local_ref_ || !elements_) return;
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, local_ref_, i, elements_[i]);
}
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
if constexpr (std::is_same_v<T, jbooleanArray>) {
env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode);
} else if constexpr (std::is_same_v<T, jbyteArray>) {
env_->ReleaseByteArrayElements(local_ref_, elements_, mode);
@ -1053,6 +1022,255 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
class JObjectArrayElement {
friend class ScopedLocalRef<jobjectArray>;
auto obtain() {
if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
}
explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i, size_t size)
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
JObjectArrayElement &operator++() {
++i_;
item_ = obtain();
return *this;
}
JObjectArrayElement &operator--() {
--i_;
item_ = obtain();
return *this;
}
JObjectArrayElement operator++(int) { return JObjectArrayElement(env_, array_, i_ + 1, size_); }
JObjectArrayElement operator--(int) { return JObjectArrayElement(env_, array_, i_ - 1, size_); }
public:
JObjectArrayElement(JObjectArrayElement &&s)
: env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {}
operator ScopedLocalRef<jobject> &() & { return item_; }
operator ScopedLocalRef<jobject> &&() && { return std::move(item_); }
JObjectArrayElement &operator=(JObjectArrayElement &&s) {
reset(s.item_.release());
return *this;
}
JObjectArrayElement &operator=(const JObjectArrayElement &s) {
reset(env_->NewLocalRef(s.item_.get()));
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) {
reset(env_->NewLocalRef(s));
return *this;
}
void reset(jobject item) {
item_.reset(item);
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_);
}
ScopedLocalRef<jobject> clone() const { return item_.clone(); }
jobject get() const { return item_.get(); }
jobject release() { return item_.release(); }
jobject operator->() const { return item_.get(); }
jobject operator*() const { return item_.get(); }
private:
JNIEnv *env_;
jobjectArray array_;
int i_;
int size_;
ScopedLocalRef<jobject> item_;
JObjectArrayElement(const JObjectArrayElement &) = delete;
};
template <>
class ScopedLocalRef<jobjectArray> {
public:
class Iterator {
friend class ScopedLocalRef<jobjectArray>;
Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}
Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {}
public:
auto &operator*() { return e_; }
auto *operator->() { return e_.get(); }
Iterator &operator++() {
++e_;
return *this;
}
Iterator &operator--() {
--e_;
return *this;
}
Iterator operator++(int) { return Iterator(e_++); }
Iterator operator--(int) { 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_; }
private:
JObjectArrayElement e_;
};
class ConstIterator {
friend class ScopedLocalRef<jobjectArray>;
auto obtain() {
if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};
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()) {}
public:
auto &operator*() { return item_; }
auto *operator->() { return &item_; }
ConstIterator &operator++() {
++i_;
item_ = obtain();
return *this;
}
ConstIterator &operator--() {
--i_;
item_ = obtain();
return *this;
}
ConstIterator operator++(int) { return ConstIterator(env_, array_, i_ + 1, size_); }
ConstIterator operator--(int) { 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_; }
private:
JNIEnv *env_;
jobjectArray array_;
int i_;
int size_;
ScopedLocalRef<jobject> item_;
};
auto begin() { return Iterator(env_, local_ref_, 0, size_); }
auto end() { return Iterator(env_, local_ref_, size_, size_); }
const auto begin() const { return ConstIterator(env_, local_ref_, 0, size_); }
auto end() const { return ConstIterator(env_, local_ref_, size_, size_); }
const auto cbegin() const { return ConstIterator(env_, local_ref_, 0, size_); }
auto cend() const { return ConstIterator(env_, local_ref_, size_, size_); }
ScopedLocalRef(JNIEnv *env, jobjectArray local_ref) noexcept : env_(env), local_ref_(nullptr) {
reset(local_ref);
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept
: ScopedLocalRef(s.env_, (jobjectArray)s.release()) {}
explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, jobjectArray{nullptr}) {}
~ScopedLocalRef() { env_->DeleteLocalRef(release()); }
void reset(jobjectArray ptr = nullptr) {
if (ptr != local_ref_) {
if (local_ref_ != nullptr) {
env_->DeleteLocalRef(local_ref_);
}
local_ref_ = ptr;
size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;
if (!local_ref_) return;
}
}
[[nodiscard]] jobjectArray release() {
jobjectArray localRef = local_ref_;
size_ = 0;
local_ref_ = nullptr;
return localRef;
}
jobjectArray get() const { return local_ref_; }
JObjectArrayElement operator[](size_t index) {
return JObjectArrayElement(env_, local_ref_, index, size_);
}
const ScopedLocalRef<jobject> operator[](size_t index) const {
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, index);
}
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
// ScopedLocalRef<...> ref;
// ref.reset(...);
// Move assignment operator.
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
env_ = s.env_;
local_ref_ = s.local_ref_;
size_ = s.size_;
s.size_ = 0;
s.local_ref_ = nullptr;
return *this;
}
size_t size() const { return size_; }
operator bool() const { return local_ref_; }
template <JObject U>
friend class ScopedLocalRef;
friend class JUTFString;
private:
JNIEnv *env_;
jobjectArray local_ref_;
size_t size_;
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
// functions to array
template <ScopeOrRaw<jarray> Array>

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

@ -12,18 +12,19 @@
#define LOGI(...) 0
#define LOGW(...) 0
#define LOGE(...) 0
#define PLOGE(...) 0
#else
#ifndef NDEBUG
#define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \
"%s:%d#%s" \
"%s:%d" \
": " fmt, \
__FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__)
__FILE_NAME__, __LINE__ __VA_OPT__(, ) __VA_ARGS__)
#define LOGV(fmt, ...) \
__android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \
"%s:%d#%s" \
"%s:%d" \
": " fmt, \
__FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__)
__FILE_NAME__, __LINE__ __VA_OPT__(, ) __VA_ARGS__)
#else
#define LOGD(...) 0
#define LOGV(...) 0

View File

@ -1,32 +1,39 @@
module;
#include "lsplant.hpp"
#include <android/api-level.h>
#include <bits/sysconf.h>
#include <jni.h>
#include <sys/mman.h>
#include <sys/system_properties.h>
#include <array>
#include <atomic>
#include <bit>
#include <string_view>
#include <tuple>
#include "art/mirror/class.hpp"
#include "art/runtime/art_method.hpp"
#include "art/runtime/class_linker.hpp"
#include "art/runtime/dex_file.hpp"
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/runtime/instrumentation.hpp"
#include "art/runtime/jit/jit_code_cache.hpp"
#include "art/runtime/jni/jni_id_manager.h"
#include "art/runtime/runtime.hpp"
#include "art/runtime/thread.hpp"
#include "art/runtime/thread_list.hpp"
#include "common.hpp"
#include "dex_builder.h"
#include "utils/jni_helper.hpp"
#include "logging.hpp"
module lsplant;
import dex_builder;
import :common;
import :art_method;
import :clazz;
import :thread;
import :instrumentation;
import :runtime;
import :thread_list;
import :class_linker;
import :scope_gc_critical_section;
import :jit_code_cache;
import :jni_id_manager;
import :dex_file;
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 {
using art::ArtMethod;
@ -36,10 +43,14 @@ using art::Instrumentation;
using art::Runtime;
using art::Thread;
using art::gc::ScopedGCCriticalSection;
using art::jit::Jit;
using art::jit::JitCodeCache;
using art::jni::JniIdManager;
using art::mirror::Class;
using art::thread_list::ScopedSuspendAll;
using art::JavaDebuggableGuard;
using namespace std::string_view_literals;
namespace {
template <typename T, T... chars>
@ -71,9 +82,9 @@ consteval inline auto GetTrampoline() {
}
if constexpr (kArch == Arch::kRiscv64) {
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
uint8_t{84u}, uintptr_t{12u});
uint8_t{84u}, uintptr_t{16u});
}
}
@ -85,8 +96,8 @@ jmethodID class_get_name = nullptr;
jmethodID class_get_class_loader = nullptr;
jmethodID class_get_declared_constructors = nullptr;
jfieldID class_access_flags = nullptr;
jclass in_memory_class_loader = nullptr;
jmethodID in_memory_class_loader_init = nullptr;
jmethodID dex_file_init_with_cl = nullptr;
jmethodID dex_file_init = nullptr;
jmethodID load_class = nullptr;
jmethodID set_accessible = nullptr;
jclass executable = nullptr;
@ -95,13 +106,12 @@ jclass executable = nullptr;
jmethodID method_get_parameter_types = nullptr;
jmethodID method_get_return_type = nullptr;
// for old platform
jclass path_class_loader = nullptr;
jmethodID path_class_loader_init = nullptr;
constexpr auto kInternalMethods = std::make_tuple(
&method_get_name, &method_get_declaring_class, &class_get_name, &class_get_class_loader,
&class_get_declared_constructors, &in_memory_class_loader_init, &load_class, &set_accessible,
&method_get_parameter_types, &method_get_return_type, &path_class_loader_init);
&class_get_declared_constructors, &dex_file_init, &dex_file_init_with_cl, &load_class,
&set_accessible, &method_get_parameter_types, &method_get_return_type, &path_class_loader_init);
std::string generated_class_name;
std::string generated_source_name;
@ -194,32 +204,37 @@ bool InitJNI(JNIEnv *env) {
LOGE("Failed to find Class.accessFlags");
return false;
}
if (sdk_int >= __ANDROID_API_O__ &&
(in_memory_class_loader = JNI_NewGlobalRef(
env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader")))) [[likely]] {
in_memory_class_loader_init =
JNI_GetMethodID(env, in_memory_class_loader, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
if (!load_class) {
load_class = JNI_GetMethodID(env, in_memory_class_loader, "findClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
}
} else if (auto dex_file = JNI_FindClass(env, "dalvik/system/DexFile");
dex_file && (path_class_loader = JNI_NewGlobalRef(
env, JNI_FindClass(env, "dalvik/system/PathClassLoader")))) {
path_class_loader_init = JNI_GetMethodID(env, path_class_loader, "<init>",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
if (!path_class_loader_init) {
LOGE("Failed to find PathClassLoader.<init>");
return false;
}
load_class =
JNI_GetMethodID(env, dex_file, "loadClass",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
if (!path_class_loader) {
LOGE("Failed to find PathClassLoader");
return false;
}
if (!load_class) {
if (path_class_loader_init = JNI_GetMethodID(env, path_class_loader, "<init>",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
!path_class_loader_init) {
LOGE("Failed to find PathClassLoader.<init>");
return false;
}
auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile");
if (!dex_file_class) {
LOGE("Failed to find DexFile");
return false;
}
if (sdk_int >= __ANDROID_API_Q__) {
dex_file_init_with_cl = JNI_GetMethodID(
env, dex_file_class, "<init>",
"([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)V");
} else if (sdk_int >= __ANDROID_API_O__) {
dex_file_init = JNI_GetMethodID(env, dex_file_class, "<init>", "(Ljava/nio/ByteBuffer;)V");
}
if (sdk_int >= __ANDROID_API_O__ && !dex_file_init_with_cl && !dex_file_init) {
LOGE("Failed to find DexFile.<init>");
return false;
}
if (load_class =
JNI_GetMethodID(env, dex_file_class, "loadClass",
"(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
!load_class) {
LOGE("Failed to find a suitable way to load class");
return false;
}
@ -243,9 +258,6 @@ inline void UpdateTrampoline(uint8_t offset) {
}
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)) {
LOGE("Failed to init art method");
return false;
@ -255,14 +267,18 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init thread");
return false;
}
if (!ClassLinker::Init(handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!Class::Init(handler)) {
LOGE("Failed to init mirror class");
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)) {
LOGE("Failed to init scoped suspend all");
return false;
@ -275,6 +291,10 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jit code cache");
return false;
}
if (!Jit::Init(handler)) {
LOGE("Failed to init jit");
return false;
}
if (!DexFile::Init(env, handler)) {
LOGE("Failed to init dex file");
return false;
@ -287,10 +307,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jni id manager");
return false;
}
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
// This should always be the last one
if (IsJavaDebuggable(env)) {
@ -301,58 +317,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
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::string_view shorty, bool is_static,
std::string_view method_name,
@ -370,7 +334,6 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
auto parameter_types = std::vector<TypeDescriptor>();
parameter_types.reserve(shorty.size() - 1);
std::string storage;
auto return_type =
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]);
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
@ -390,7 +353,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
.Encode();
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})};
// allocate tmp first because of wide
auto tmp{hook_builder.AllocRegister()};
@ -437,7 +400,6 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
backup_builder.BuildReturn(zero, /*is_object=*/false, true);
} else {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
}
@ -447,21 +409,19 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
jclass target_class = nullptr;
if (in_memory_class_loader_init) [[likely]] {
auto dex_buffer = JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()),
static_cast<jlong>(image.size()));
// FIXME: a very hacky way to disable background verification of the hooker classloader,
// which fixes a crash for art 34+; there should be a better way to do this.
JavaDebuggableGuard guard;
ScopedLocalRef<jobject> java_dex_file{nullptr};
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
dex_buffer, class_loader);
if (my_cl) {
target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
env, my_cl, load_class,
JNI_NewStringUTF(env, generated_class_name.data())))
.release();
}
if (auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile"); dex_file_init_with_cl) {
java_dex_file = JNI_NewObject(
env, dex_file_class, dex_file_init_with_cl,
JNI_NewObjectArray(
env, 1, JNI_FindClass(env, "java/nio/ByteBuffer"),
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size())),
nullptr, nullptr);
} else if (dex_file_init) {
java_dex_file = JNI_NewObject(
env, dex_file_class, dex_file_init,
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size()));
} else {
void *target =
mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@ -469,21 +429,24 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
mprotect(target, image.size(), PROT_READ);
std::string err_msg;
const auto *dex = DexFile::OpenMemory(
target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name,
&err_msg);
reinterpret_cast<const uint8_t *>(target), image.size(),
generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg);
if (!dex) {
LOGE("Failed to open memory dex: %s", err_msg.data());
} else {
java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr);
}
auto java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr});
if (dex && java_dex_file) {
auto my_cl = JNI_NewObject(env, path_class_loader, path_class_loader_init,
JNI_NewStringUTF(env, ""), class_loader);
target_class =
JNI_Cast<jclass>(
JNI_CallObjectMethod(env, java_dex_file, load_class,
JNI_NewStringUTF(env, generated_class_name.data()), my_cl))
.release();
}
}
if (auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
java_dex_file) {
auto my_cl = JNI_NewObject(env, path_class_loader, path_class_loader_init,
JNI_NewStringUTF(env, ""), class_loader);
target_class =
JNI_Cast<jclass>(
JNI_CallObjectMethod(env, java_dex_file, load_class,
JNI_NewStringUTF(env, generated_class_name.data()), my_cl))
.release();
}
if (target_class) {
@ -505,7 +468,8 @@ static_assert(std::endian::native == std::endian::little, "Unsupported architect
union Trampoline {
public:
uintptr_t address;
unsigned count : 12;
unsigned count4k : 12;
unsigned count16k : 14;
};
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
@ -514,16 +478,16 @@ static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architect
std::atomic_uintptr_t trampoline_pool{0};
std::atomic_flag trampoline_lock{false};
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
constexpr uintptr_t kAddressMask = 0xFFFU;
void *GenerateTrampolineFor(art::ArtMethod *hook) {
static const size_t kPageSize = sysconf(_SC_PAGESIZE); // assume
static const size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
unsigned count;
uintptr_t address;
while (true) {
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;
if (address == 0 || count >= kTrampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
@ -541,7 +505,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
}
count = 0;
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_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
@ -575,18 +539,12 @@ bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
} else {
LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable();
hook->SetNonCompilable();
// copy after setNonCompilable
backup->CopyFrom(target);
target->ClearFastInterpretFlag();
target->BackupTo(backup);
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,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
@ -653,15 +611,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
std::string out;
auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) {
if (env->IsSameObject(type, int_type)) return 'I';
if (env->IsSameObject(type, long_type)) return 'J';
if (env->IsSameObject(type, float_type)) return 'F';
if (env->IsSameObject(type, double_type)) return 'D';
if (env->IsSameObject(type, boolean_type)) return 'Z';
if (env->IsSameObject(type, byte_type)) return 'B';
if (env->IsSameObject(type, char_type)) return 'C';
if (env->IsSameObject(type, short_type)) return 'S';
if (env->IsSameObject(type, void_type)) return 'V';
if (JNI_IsSameObject(env, type, int_type)) return 'I';
if (JNI_IsSameObject(env, type, long_type)) return 'J';
if (JNI_IsSameObject(env, type, float_type)) return 'F';
if (JNI_IsSameObject(env, type, double_type)) return 'D';
if (JNI_IsSameObject(env, type, boolean_type)) return 'Z';
if (JNI_IsSameObject(env, type, byte_type)) return 'B';
if (JNI_IsSameObject(env, type, char_type)) return 'C';
if (JNI_IsSameObject(env, type, short_type)) return 'S';
if (JNI_IsSameObject(env, type, void_type)) return 'V';
return 'L';
};
out += type_to_shorty(return_type);
@ -673,10 +631,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
} // namespace
inline namespace v2 {
extern "C++" {
using ::lsplant::IsHooked;
[[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);
return kInit;
}
@ -729,7 +692,7 @@ using ::lsplant::IsHooked;
}
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env,
BuildDex(env, callback_class_loader,
BuildDex(env, callback_class_loader.get(),
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
@ -745,8 +708,8 @@ using ::lsplant::IsHooked;
JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook.get());
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup.get());
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
@ -829,7 +792,7 @@ using ::lsplant::IsHooked;
if (auto *backup = IsHooked(art_method); backup) {
art_method = backup;
}
if (!art_method) {
if (!art_method || art_method->IsNative()) {
return false;
}
return ClassLinker::SetEntryPointsToInterpreter(art_method);
@ -871,8 +834,7 @@ using ::lsplant::IsHooked;
if (!cookie) return false;
return DexFile::SetTrusted(env, cookie);
}
}
} // namespace v2
} // namespace lsplant
#pragma clang diagnostic pop

View File

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

View File

@ -8,9 +8,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@RunWith(AndroidJUnit4.class)
@ -124,7 +122,7 @@ public class UnitTest {
var proxyInterface = Class.forName("org.lsposed.lsplant.LSPTest$ForProxy");
var proxy = Proxy.newProxyInstance(proxyInterface.getClassLoader(), new Class[]{proxyInterface}, (proxy1, method, args) -> {
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);
});

View File

@ -1,10 +1,18 @@
cmake_minimum_required(VERSION 3.18.1)
project("lsplant_test")
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 23)
add_library(test SHARED test.cpp elf_util.cpp)
add_subdirectory(external/lsparself)
add_library(test SHARED test.cpp)
set_target_properties(test PROPERTIES CXX_SCAN_FOR_MODULES ON)
find_package(dobby REQUIRED CONFIG)
find_package(lsplant REQUIRED CONFIG)
target_link_libraries(test log dobby::dobby lsplant::lsplant)
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)

View File

@ -1,284 +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) 2019 Swift Gan
* Copyright (C) 2021 LSPosed Contributors
*/
#include <malloc.h>
#include <cstring>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <cassert>
#include <sys/stat.h>
#include "logging.h"
#include "elf_util.h"
using namespace SandHook;
template<typename T>
inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) {
return reinterpret_cast<std::conditional_t<std::is_pointer_v<T>, T, T *>>(
reinterpret_cast<uintptr_t>(head) + off);
}
ElfImg::ElfImg(std::string_view base_name) : elf(base_name) {
if (!findModuleBase()) {
base = nullptr;
return;
}
//load elf
int fd = open(elf.data(), O_RDONLY);
if (fd < 0) {
LOGE("failed to open %s", elf.data());
return;
}
size = lseek(fd, 0, SEEK_END);
if (size <= 0) {
LOGE("lseek() failed for %s", elf.data());
}
header = reinterpret_cast<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0));
close(fd);
section_header = offsetOf<decltype(section_header)>(header, header->e_shoff);
auto shoff = reinterpret_cast<uintptr_t>(section_header);
char *section_str = offsetOf<char *>(header, section_header[header->e_shstrndx].sh_offset);
for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) {
auto *section_h = (ElfW(Shdr) *) shoff;
char *sname = section_h->sh_name + section_str;
auto entsize = section_h->sh_entsize;
switch (section_h->sh_type) {
case SHT_DYNSYM: {
if (bias == -4396) {
dynsym = section_h;
dynsym_offset = section_h->sh_offset;
dynsym_start = offsetOf<decltype(dynsym_start)>(header, dynsym_offset);
}
break;
}
case SHT_SYMTAB: {
if (strcmp(sname, ".symtab") == 0) {
symtab = section_h;
symtab_offset = section_h->sh_offset;
symtab_size = section_h->sh_size;
symtab_count = symtab_size / entsize;
symtab_start = offsetOf<decltype(symtab_start)>(header, symtab_offset);
}
break;
}
case SHT_STRTAB: {
if (bias == -4396) {
strtab = section_h;
symstr_offset = section_h->sh_offset;
strtab_start = offsetOf<decltype(strtab_start)>(header, symstr_offset);
}
if (strcmp(sname, ".strtab") == 0) {
symstr_offset_for_symtab = section_h->sh_offset;
}
break;
}
case SHT_PROGBITS: {
if (strtab == nullptr || dynsym == nullptr) break;
if (bias == -4396) {
bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset;
}
break;
}
case SHT_HASH: {
auto *d_un = offsetOf<ElfW(Word)>(header, section_h->sh_offset);
nbucket_ = d_un[0];
bucket_ = d_un + 2;
chain_ = bucket_ + nbucket_;
break;
}
case SHT_GNU_HASH: {
auto *d_buf = reinterpret_cast<ElfW(Word) *>(((size_t) header) +
section_h->sh_offset);
gnu_nbucket_ = d_buf[0];
gnu_symndx_ = d_buf[1];
gnu_bloom_size_ = d_buf[2];
gnu_shift2_ = d_buf[3];
gnu_bloom_filter_ = reinterpret_cast<decltype(gnu_bloom_filter_)>(d_buf + 4);
gnu_bucket_ = reinterpret_cast<decltype(gnu_bucket_)>(gnu_bloom_filter_ +
gnu_bloom_size_);
gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_;
break;
}
}
}
}
ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const {
if (nbucket_ == 0) return 0;
char *strings = (char *) strtab_start;
for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
auto *sym = dynsym_start + n;
if (name == strings + sym->st_name) {
return sym->st_value;
}
}
return 0;
}
ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const {
static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0;
auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_];
uintptr_t mask = 0
| (uintptr_t) 1 << (hash % bloom_mask_bits)
| (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits);
if ((mask & bloom_word) == mask) {
auto sym_index = gnu_bucket_[hash % gnu_nbucket_];
if (sym_index >= gnu_symndx_) {
char *strings = (char *) strtab_start;
do {
auto *sym = dynsym_start + sym_index;
if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0
&& name == strings + sym->st_name) {
return sym->st_value;
}
} while ((gnu_chain_[sym_index++] & 1) == 0);
}
}
return 0;
}
void ElfImg::MayInitLinearMap() const {
if (symtabs_.empty()) {
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
for (ElfW(Off) i = 0; i < symtab_count; i++) {
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_symtab +
symtab_start[i].st_name);
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) {
symtabs_.emplace(st_name, &symtab_start[i]);
}
}
}
}
}
ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const {
MayInitLinearMap();
if (auto i = symtabs_.find(name); i != symtabs_.end()) {
return i->second->st_value;
} else {
return 0;
}
}
ElfW(Addr) ElfImg::PrefixLookupFirst(std::string_view prefix) const {
MayInitLinearMap();
if (auto i = symtabs_.lower_bound(prefix); i != symtabs_.end() && i->first.starts_with(prefix)) {
LOGD("found prefix %s of %s %p in %s in symtab by linear lookup", prefix.data(),
i->first.data(), reinterpret_cast<void *>(i->second->st_value), elf.data());
return i->second->st_value;
} else {
return 0;
}
}
ElfImg::~ElfImg() {
//open elf file local
if (buffer) {
free(buffer);
buffer = nullptr;
}
//use mmap
if (header) {
munmap(header, size);
}
}
ElfW(Addr)
ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const {
if (auto offset = GnuLookup(name, gnu_hash); offset > 0) {
LOGD("found %s %p in %s in dynsym by gnuhash", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = ElfLookup(name, elf_hash); offset > 0) {
LOGD("found %s %p in %s in dynsym by elfhash", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = LinearLookup(name); offset > 0) {
LOGD("found %s %p in %s in symtab by linear lookup", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else {
return 0;
}
}
constexpr inline bool contains(std::string_view a, std::string_view b) {
return a.find(b) != std::string_view::npos;
}
bool ElfImg::findModuleBase() {
off_t load_addr;
bool found = false;
FILE *maps = fopen("/proc/self/maps", "r");
char *buff = nullptr;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&buff, &len, maps)) != -1) {
std::string_view line{buff, static_cast<size_t>(nread)};
if ((contains(line, "r-xp") || contains(line, "r--p")) && contains(line, elf)) {
LOGD("found: %*s", static_cast<int>(line.size()), line.data());
if (auto begin = line.find_last_of(' '); begin != std::string_view::npos &&
line[++begin] == '/') {
found = true;
elf = line.substr(begin);
if (elf.back() == '\n') elf.pop_back();
LOGD("update path: %s", elf.data());
break;
}
}
}
if (!found) {
if (buff) free(buff);
LOGE("failed to read load address for %s", elf.data());
fclose(maps);
return false;
}
if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) {
LOGE("failed to read load address for %s", elf.data());
}
if (buff) free(buff);
fclose(maps);
LOGD("get module base %s: %lx", elf.data(), load_addr);
base = reinterpret_cast<void *>(load_addr);
return true;
}

View File

@ -1,144 +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) 2019 Swift Gan
* Copyright (C) 2021 LSPosed Contributors
*/
#ifndef SANDHOOK_ELF_UTIL_H
#define SANDHOOK_ELF_UTIL_H
#include <string_view>
#include <map>
#include <linux/elf.h>
#include <sys/types.h>
#include <string>
#include <link.h>
#define SHT_GNU_HASH 0x6ffffff6
namespace SandHook {
class ElfImg {
public:
ElfImg(std::string_view elf);
template<typename T = void*>
requires(std::is_pointer_v<T>)
constexpr const T getSymbAddress(std::string_view name) const {
auto offset = getSymbOffset(name, GnuHash(name), ElfHash(name));
if (offset > 0 && base != nullptr) {
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
} else {
return nullptr;
}
}
template<typename T = void*>
requires(std::is_pointer_v<T>)
constexpr const T getSymbPrefixFirstOffset(std::string_view prefix) const {
auto offset = PrefixLookupFirst(prefix);
if (offset > 0 && base != nullptr) {
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
} else {
return nullptr;
}
}
bool isValid() const {
return base != nullptr;
}
const std::string name() const {
return elf;
}
~ElfImg();
private:
ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const;
ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const;
ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const;
ElfW(Addr) LinearLookup(std::string_view name) const;
ElfW(Addr) PrefixLookupFirst(std::string_view prefix) const;
constexpr static uint32_t ElfHash(std::string_view name);
constexpr static uint32_t GnuHash(std::string_view name);
bool findModuleBase();
void MayInitLinearMap() const;
std::string elf;
void *base = nullptr;
char *buffer = nullptr;
off_t size = 0;
off_t bias = -4396;
ElfW(Ehdr) *header = nullptr;
ElfW(Shdr) *section_header = nullptr;
ElfW(Shdr) *symtab = nullptr;
ElfW(Shdr) *strtab = nullptr;
ElfW(Shdr) *dynsym = nullptr;
ElfW(Sym) *symtab_start = nullptr;
ElfW(Sym) *dynsym_start = nullptr;
ElfW(Sym) *strtab_start = nullptr;
ElfW(Off) symtab_count = 0;
ElfW(Off) symstr_offset = 0;
ElfW(Off) symstr_offset_for_symtab = 0;
ElfW(Off) symtab_offset = 0;
ElfW(Off) dynsym_offset = 0;
ElfW(Off) symtab_size = 0;
uint32_t nbucket_{};
uint32_t *bucket_ = nullptr;
uint32_t *chain_ = nullptr;
uint32_t gnu_nbucket_{};
uint32_t gnu_symndx_{};
uint32_t gnu_bloom_size_;
uint32_t gnu_shift2_;
uintptr_t *gnu_bloom_filter_;
uint32_t *gnu_bucket_;
uint32_t *gnu_chain_;
mutable std::map<std::string_view, ElfW(Sym) *> symtabs_;
};
constexpr uint32_t ElfImg::ElfHash(std::string_view name) {
uint32_t h = 0, g;
for (unsigned char p: name) {
h = (h << 4) + p;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
constexpr uint32_t ElfImg::GnuHash(std::string_view name) {
uint32_t h = 5381;
for (unsigned char p: name) {
h += (h << 5) + p;
}
return h;
}
}
#endif //SANDHOOK_ELF_UTIL_H

@ -0,0 +1 @@
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 <lsplant.hpp>
#include <sys/mman.h>
#include "elf_util.h"
#include <string_view>
#include "logging.h"
import lsplant;
import lsparself;
#define _uintval(p) reinterpret_cast<uintptr_t>(p)
#define _ptr(p) reinterpret_cast<void *>(p)
#define _align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1))
@ -55,10 +58,7 @@ JNI_OnLoad(JavaVM* vm, void* reserved) {
if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
SandHook::ElfImg art("libart.so");
#if !defined(__i386__)
dobby_enable_near_branch_trampoline();
#endif
lsparself::Elf art("/libart.so");
lsplant::InitInfo initInfo{
.inline_hooker = InlineHooker,
.inline_unhooker = InlineUnhooker,
@ -66,7 +66,7 @@ JNI_OnLoad(JavaVM* vm, void* reserved) {
return art.getSymbAddress(symbol);
},
.art_symbol_prefix_resolver = [&art](auto symbol) {
return art.getSymbPrefixFirstOffset(symbol);
return art.getSymbPrefixFirstAddress(symbol);
},
};
init_result = lsplant::Init(env, initInfo);