mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-04 20:42:02 +08:00
Compare commits
110 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
e91129897c | ||
|
ff815781ba | ||
|
0110cf7e6e | ||
|
e2a35a4fab | ||
|
53070cff33 | ||
|
c657d08dcf | ||
|
ee6124f21e | ||
|
c54fb307f2 | ||
|
4a2293e222 | ||
|
4a18848a86 | ||
|
e9a016836b | ||
|
ce08314ca3 | ||
|
8c3c6e6b2c | ||
|
90d8d433d4 | ||
|
08b65e436f | ||
|
a60403bd54 | ||
|
f5c423fd20 | ||
|
8ebc00ec8a | ||
|
f8120339d2 | ||
|
16c6820b86 | ||
|
05289b548a | ||
|
ae4bc68b12 | ||
|
0b1e5f8b7a | ||
|
56a9cf7e53 | ||
|
897c70b74a | ||
|
68f8a63e8f | ||
|
14b753e1dc | ||
|
39334baed3 | ||
|
c9bf82a36c | ||
|
a7b0b022c2 | ||
|
0d9faca38d | ||
|
5121a21064 | ||
|
bd64ecbc16 | ||
|
85ab3c8c11 | ||
|
87d2caede7 | ||
|
3d9257fc11 | ||
|
9d63f3bb50 | ||
|
8fe8c6da1f | ||
|
368fed7c4f | ||
|
172d11641a | ||
|
5c8cdbd985 | ||
|
6c08583233 | ||
|
b96a512319 | ||
|
95e3790500 | ||
|
b1fe35c0c4 | ||
|
ac41dc6c18 | ||
|
da6b276f4d | ||
|
c35a50a60f | ||
|
b1e6d18494 | ||
|
b858504dec | ||
|
4ec6de77ad | ||
|
2d8204774b | ||
|
1e1b221dcb | ||
|
1744bde5ac | ||
|
d3ed8751db | ||
|
11aa507da9 | ||
|
29f124c576 | ||
|
2db8b66b29 | ||
|
386200f0c9 | ||
|
94e6e6236b | ||
|
01d26b03c5 | ||
|
3e97d85b0e | ||
|
68360babc5 | ||
|
fb753a7daf | ||
|
c935b27b9a | ||
|
7217ac6f41 | ||
|
c774d42b76 | ||
|
33504c455c | ||
|
62533694c5 | ||
|
bd4b53c331 | ||
|
2cccfae8c1 | ||
|
34b41249fa | ||
|
1e36d0ab2f | ||
|
6bb4abdae0 | ||
|
e203bfdf0e | ||
|
460c5aec1d | ||
|
5dbc9ff657 | ||
|
1726816a98 | ||
|
7ebe6b476a | ||
|
e0e8cc11dc | ||
|
7d4987c80f | ||
|
f240269c5a | ||
|
f82b5bf9e9 | ||
|
0de159c0b9 | ||
|
56ce2ff076 | ||
|
0305a18d7f | ||
|
3b9ec2b02e | ||
|
4ec86d14f4 | ||
|
7af00b8aa7 | ||
|
92a6a727e2 | ||
|
4c62b5d558 | ||
|
dafa21cc38 | ||
|
467d57a376 | ||
|
d675c45d3f | ||
|
3ce7b1d1a0 | ||
|
0d144a0881 | ||
|
c5b2c550d3 | ||
|
a0389a2429 | ||
|
4ff1786610 | ||
|
b435731080 | ||
|
a1948df0dc | ||
|
0f0f9f7bcd | ||
|
713bb300c4 | ||
|
df7d10b1d8 | ||
|
4e95078c79 | ||
|
439f38f48c | ||
|
7c732a7215 | ||
|
896f985017 | ||
|
554136c1bb | ||
|
c6d6773536 |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: yujincheng08
|
94
.github/cuttlefish.sh
vendored
Executable file
94
.github/cuttlefish.sh
vendored
Executable 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
|
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -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:
|
||||
- "*"
|
||||
|
121
.github/workflows/build.yml
vendored
121
.github/workflows/build.yml
vendored
@ -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
|
||||
|
16
.github/workflows/maven.yml
vendored
16
.github/workflows/maven.yml
vendored
@ -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 }}
|
||||
|
5
.github/workflows/pages.yml
vendored
5
.github/workflows/pages.yml
vendored
@ -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
6
.gitmodules
vendored
@ -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
|
||||
|
@ -1,7 +1,7 @@
|
||||
# LSPlant
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@ -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:+"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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" }
|
||||
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
20
gradlew
vendored
@ -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
22
gradlew.bat
vendored
@ -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
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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})
|
||||
|
||||
|
@ -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
|
@ -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__) {
|
239
lsplant/src/main/jni/art/runtime/class_linker.cxx
Normal file
239
lsplant/src/main/jni/art/runtime/class_linker.cxx
Normal 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
|
@ -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
|
143
lsplant/src/main/jni/art/runtime/dex_file.cxx
Normal file
143
lsplant/src/main/jni/art/runtime/dex_file.cxx
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
117
lsplant/src/main/jni/art/runtime/handle.cxx
Normal file
117
lsplant/src/main/jni/art/runtime/handle.cxx
Normal 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
|
@ -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
|
@ -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;
|
||||
}
|
||||
}
|
55
lsplant/src/main/jni/art/runtime/jit/jit.cxx
Normal file
55
lsplant/src/main/jni/art/runtime/jit/jit.cxx
Normal 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
|
62
lsplant/src/main/jni/art/runtime/jit/jit_code_cache.cxx
Normal file
62
lsplant/src/main/jni/art/runtime/jit/jit_code_cache.cxx
Normal 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
|
@ -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
|
39
lsplant/src/main/jni/art/runtime/jni/jni_id_manager.cxx
Normal file
39
lsplant/src/main/jni/art/runtime/jni/jni_id_manager.cxx
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
143
lsplant/src/main/jni/art/runtime/runtime.cxx
Normal file
143
lsplant/src/main/jni/art/runtime/runtime.cxx
Normal 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
|
@ -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
|
25
lsplant/src/main/jni/art/runtime/thread.cxx
Normal file
25
lsplant/src/main/jni/art/runtime/thread.cxx
Normal 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
|
@ -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
|
47
lsplant/src/main/jni/art/runtime/thread_list.cxx
Normal file
47
lsplant/src/main/jni/art/runtime/thread_list.cxx
Normal 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
|
@ -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
|
@ -1,5 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace lsplant::art {
|
||||
class ValueObject {};
|
||||
} // namespace lsplant::art
|
@ -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
|
2
lsplant/src/main/jni/external/dex_builder
vendored
2
lsplant/src/main/jni/external/dex_builder
vendored
@ -1 +1 @@
|
||||
Subproject commit b5a00f2ea94ad4c3b92054fd53896dbb429298f9
|
||||
Subproject commit 67077df2a53be0cabb9f16e8a050acdd825f7d69
|
17
lsplant/src/main/jni/include/lsplant.ixx
Normal file
17
lsplant/src/main/jni/include/lsplant.ixx
Normal 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;
|
||||
}
|
@ -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
|
||||
|
14
lsplant/src/main/jni/include/utils/hook_helper.ixx
Normal file
14
lsplant/src/main/jni/include/utils/hook_helper.ixx
Normal 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
|
@ -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>
|
||||
|
142
lsplant/src/main/jni/include/utils/jni_helper.ixx
Normal file
142
lsplant/src/main/jni/include/utils/jni_helper.ixx
Normal 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
|
14
lsplant/src/main/jni/include/utils/type_traits.hpp
Normal file
14
lsplant/src/main/jni/include/utils/type_traits.hpp
Normal 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
|
@ -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
|
||||
|
@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,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;
|
||||
@ -259,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;
|
||||
@ -279,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;
|
||||
@ -291,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)) {
|
||||
@ -305,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,
|
||||
@ -393,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()};
|
||||
@ -440,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);
|
||||
}
|
||||
@ -470,12 +429,12 @@ 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 = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr});
|
||||
java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,7 +468,8 @@ static_assert(std::endian::native == std::endian::little, "Unsupported architect
|
||||
union Trampoline {
|
||||
public:
|
||||
uintptr_t address;
|
||||
unsigned count : 12;
|
||||
unsigned count4k : 12;
|
||||
unsigned count16k : 14;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
|
||||
@ -518,16 +478,16 @@ static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architect
|
||||
std::atomic_uintptr_t trampoline_pool{0};
|
||||
std::atomic_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)) {
|
||||
@ -545,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();
|
||||
@ -579,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());
|
||||
@ -657,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);
|
||||
@ -677,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;
|
||||
}
|
||||
@ -733,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(),
|
||||
@ -749,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);
|
||||
|
||||
@ -833,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);
|
||||
@ -875,8 +834,7 @@ using ::lsplant::IsHooked;
|
||||
if (!cookie) return false;
|
||||
return DexFile::SetTrusted(env, cookie);
|
||||
}
|
||||
}
|
||||
} // namespace v2
|
||||
|
||||
} // namespace lsplant
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
@ -6,7 +6,7 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
1
test/src/main/jni/external/lsparself
vendored
Submodule
1
test/src/main/jni/external/lsparself
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 73b146d780339e0ec3fc91368828c5f4441761c4
|
1
test/src/main/jni/external/lsprism
vendored
Submodule
1
test/src/main/jni/external/lsprism
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ca815ab41425fbc3d8ddc9bfcec964848fc1ec23
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user