mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-05 05:56:38 +08:00
Compare commits
174 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 | ||
|
a612522188 | ||
|
899766f2bd | ||
|
cb005a88bc | ||
|
e48d78c6ad | ||
|
a4f6979c28 | ||
|
780cbe09a3 | ||
|
79f2f13517 | ||
|
d55d96929a | ||
|
c18f276177 | ||
|
7a6940e722 | ||
|
cd7c81128b | ||
|
bbabd25043 | ||
|
c907c3b402 | ||
|
ab5830a020 | ||
|
78e4dac54a | ||
|
8169d387b7 | ||
|
a49aecfb74 | ||
|
9c33903c87 | ||
|
20853b0d7d | ||
|
909e91b8f2 | ||
|
5f475c56b7 | ||
|
254b21edd4 | ||
|
35612201f9 | ||
|
8c92a2d5a1 | ||
|
261e2c2503 | ||
|
7766263eeb | ||
|
03517ca86d | ||
|
d48c639849 | ||
|
c4b670709b | ||
|
25fff8a2a8 | ||
|
77d02b29ec | ||
|
25c617d1fd | ||
|
28f17fee3e | ||
|
b77771ffc9 | ||
|
70a68d9fb4 | ||
|
9ad8f22da2 | ||
|
89cdf4cd2d | ||
|
56ab000184 | ||
|
bdc1e6f8a7 | ||
|
845ec5dc40 | ||
|
9e437ffcd6 | ||
|
d5a057b025 | ||
|
65a276ff91 | ||
|
a2b5d87159 | ||
|
492a20bab9 | ||
|
1a5932a119 | ||
|
2b784e114f | ||
|
a15d854930 | ||
|
6e0ef3c855 | ||
|
f7e3a0cef6 | ||
|
7480a21779 | ||
|
bde8b5ed16 | ||
|
3f513a43d1 | ||
|
a67f36b695 | ||
|
d2082eda78 | ||
|
a124dcb547 | ||
|
ace0c08a9b | ||
|
2c1fba450b | ||
|
96f217d0bf | ||
|
c6cc93ae7e | ||
|
c812fb503c | ||
|
8979bf7c4d | ||
|
cf9b95f6d6 | ||
|
0864159fc4 |
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
|
12
.github/dependabot.yml
vendored
Normal file
12
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "21:00"
|
||||
target-branch: master
|
||||
groups:
|
||||
maven-dependencies:
|
||||
patterns:
|
||||
- "*"
|
181
.github/workflows/build.yml
vendored
181
.github/workflows/build.yml
vendored
@ -2,8 +2,10 @@ name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["master"]
|
||||
paths-ignore:
|
||||
- 'README.md'
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
|
||||
|
||||
@ -14,41 +16,57 @@ 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@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
ssh-key: ${{ secrets.SSH_KEY }}
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v2
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'gradle'
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
|
||||
version: 1.12.1
|
||||
- name: ccache
|
||||
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
|
||||
ccache -o hash_dir=false
|
||||
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
|
||||
ccache -p
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
./gradlew :lsplant:publishToMavenLocal
|
||||
./gradlew :lsplant:publishToMavenLocal :lsplant:prefabDebugPackage
|
||||
./gradlew --stop
|
||||
env:
|
||||
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
|
||||
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}
|
||||
- name: Upload library
|
||||
uses: actions/upload-artifact@v2
|
||||
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:
|
||||
@ -123,59 +141,122 @@ jobs:
|
||||
target: google_apis
|
||||
arch: x86_64
|
||||
- api-level: 33
|
||||
target: default
|
||||
arch: x86_64
|
||||
- api-level: 33
|
||||
target: android-tv
|
||||
arch: x86
|
||||
- api-level: 34
|
||||
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@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
ssh-key: ${{ secrets.SSH_KEY }}
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: AVD cache
|
||||
uses: actions/cache@v2
|
||||
id: avd-cache
|
||||
distribution: 'temurin'
|
||||
java-version: '21'
|
||||
cache: 'gradle'
|
||||
- uses: seanmiddleditch/gha-setup-ninja@master
|
||||
with:
|
||||
path: |
|
||||
~/.android/avd/*
|
||||
~/.android/adb*
|
||||
key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }}
|
||||
- name: Gradle cache
|
||||
uses: actions/cache@v2
|
||||
version: 1.12.1
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
~/.gradle/wrapper
|
||||
key: gradle-${{ runner.os }}-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
|
||||
restore-keys: gradle-${{ runner.os }}-
|
||||
- name: create AVD and generate snapshot for caching
|
||||
if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||
uses: reactivecircus/android-emulator-runner@release/v2
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
arch: ${{ matrix.arch }}
|
||||
target: ${{ matrix.target }}
|
||||
script: echo "Generated AVD snapshot for caching."
|
||||
force-avd-creation: false
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
|
||||
disable-animations: false
|
||||
avd-name: ${{ matrix.api-level }}_${{ matrix.arch }}
|
||||
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: reactivecircus/android-emulator-runner@v2
|
||||
uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93
|
||||
with:
|
||||
api-level: ${{ matrix.api-level }}
|
||||
arch: ${{ matrix.arch }}
|
||||
target: ${{ matrix.target }}
|
||||
script: ./gradlew :lsplant:prefabDebugConfigurePackage; ./gradlew :test:connectedCheck
|
||||
force-avd-creation: false
|
||||
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'
|
||||
./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@v2
|
||||
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
|
||||
|
21
.github/workflows/maven.yml
vendored
21
.github/workflows/maven.yml
vendored
@ -9,15 +9,26 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
ssh-key: ${{ secrets.SSH_KEY }}
|
||||
fetch-depth: 0
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '11'
|
||||
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 }}
|
||||
|
17
.github/workflows/pages.yml
vendored
17
.github/workflows/pages.yml
vendored
@ -9,12 +9,6 @@ on:
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow one concurrent deployment
|
||||
concurrency:
|
||||
group: "pages"
|
||||
@ -23,21 +17,28 @@ concurrency:
|
||||
jobs:
|
||||
# Single deploy job since we're just deploying
|
||||
deploy:
|
||||
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
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
|
||||
|
12
README.md
12
README.md
@ -1,8 +1,8 @@
|
||||
# LSPlant
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
@ -12,8 +12,8 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens
|
||||
|
||||
## Features
|
||||
|
||||
+ Support Android 5.0 - 13 (API level 21 - 33)
|
||||
+ Support armeabi-v7a, arm64-v8a, x86, x86-64
|
||||
+ 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
|
||||
|
||||
## Documentation
|
||||
@ -34,7 +34,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.lsposed.lsplant:lsplant:5.0"
|
||||
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.0"
|
||||
implementation "org.lsposed.lsplant:lsplant-standalone:+"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,6 +1,10 @@
|
||||
val androidTargetSdkVersion by extra(33)
|
||||
plugins {
|
||||
alias(libs.plugins.lsplugin.publish)
|
||||
}
|
||||
|
||||
val androidTargetSdkVersion by extra(35)
|
||||
val androidMinSdkVersion by extra(21)
|
||||
val androidBuildToolsVersion by extra("33.0.0")
|
||||
val androidCompileSdkVersion by extra(33)
|
||||
val androidNdkVersion by extra("25.1.8937393")
|
||||
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
|
@ -1,2 +1,5 @@
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
android.experimental.testOptions.managedDevices.allowOldApiLevelDevices=true
|
||||
android.library.defaults.buildfeatures.androidresources=false
|
||||
android.nonFinalResIds=false
|
||||
|
17
gradle/libs.versions.toml
Normal file
17
gradle/libs.versions.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[versions]
|
||||
agp = "8.9.0"
|
||||
|
||||
[plugins]
|
||||
agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||
agp-lib = { id = "com.android.library", version.ref = "agp" }
|
||||
lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" }
|
||||
lsplugin-publish = { id = "org.lsposed.lsplugin.publish", version = "1.1" }
|
||||
lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" }
|
||||
|
||||
[libraries]
|
||||
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.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.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
43
gradlew
vendored
43
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/master/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/.
|
||||
@ -80,13 +82,11 @@ do
|
||||
esac
|
||||
done
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
APP_NAME="Gradle"
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# 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"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
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
|
||||
@ -133,22 +133,29 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
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=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@ -193,11 +200,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# 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.
|
||||
|
||||
# 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 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" \
|
||||
@ -205,6 +216,12 @@ set -- \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
|
37
gradlew.bat
vendored
37
gradlew.bat
vendored
@ -13,8 +13,10 @@
|
||||
@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
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@ -25,7 +27,8 @@
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
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
|
||||
|
||||
@ -56,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
|
||||
|
||||
@ -75,13 +78,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
@ -1,7 +1,10 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("maven-publish")
|
||||
id("signing")
|
||||
alias(libs.plugins.agp.lib)
|
||||
alias(libs.plugins.lsplugin.jgit)
|
||||
alias(libs.plugins.lsplugin.publish)
|
||||
alias(libs.plugins.lsplugin.cmaker)
|
||||
`maven-publish`
|
||||
signing
|
||||
}
|
||||
|
||||
val androidTargetSdkVersion: Int by rootProject.extra
|
||||
@ -23,7 +26,7 @@ android {
|
||||
prefab = true
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
packaging {
|
||||
jniLibs {
|
||||
excludes += "**.so"
|
||||
}
|
||||
@ -37,85 +40,12 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
minSdk = androidMinSdkVersion
|
||||
targetSdk = androidTargetSdkVersion
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
all {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
|
||||
val flags = arrayOf(
|
||||
"-Wall",
|
||||
"-Werror",
|
||||
"-Qunused-arguments",
|
||||
"-Wno-gnu-string-literal-operator-template",
|
||||
"-fno-rtti",
|
||||
"-fvisibility=hidden",
|
||||
"-fvisibility-inlines-hidden",
|
||||
"-fno-exceptions",
|
||||
"-fno-stack-protector",
|
||||
"-fomit-frame-pointer",
|
||||
"-Wno-builtin-macro-redefined",
|
||||
"-ffunction-sections",
|
||||
"-fdata-sections",
|
||||
"-Wno-unused-value",
|
||||
"-D__FILE__=__FILE_NAME__",
|
||||
"-Wl,--exclude-libs,ALL",
|
||||
)
|
||||
cppFlags("-std=c++20", *flags)
|
||||
cFlags("-std=c18", *flags)
|
||||
val configFlags = arrayOf(
|
||||
"-Oz",
|
||||
"-DNDEBUG"
|
||||
).joinToString(" ")
|
||||
arguments(
|
||||
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
|
||||
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
|
||||
"-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/$name",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
release {
|
||||
externalNativeBuild {
|
||||
val flags = arrayOf(
|
||||
"-Wl,--gc-sections",
|
||||
"-flto",
|
||||
"-fno-unwind-tables",
|
||||
"-fno-asynchronous-unwind-tables",
|
||||
)
|
||||
cmake {
|
||||
cppFlags += flags
|
||||
cFlags += flags
|
||||
arguments += "-DANDROID_STL=c++_shared"
|
||||
arguments += "-DCMAKE_BUILD_TYPE=Release"
|
||||
}
|
||||
}
|
||||
}
|
||||
debug {
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
arguments += "-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
}
|
||||
create("standalone") {
|
||||
initWith(getByName("release"))
|
||||
externalNativeBuild {
|
||||
val flags = arrayOf(
|
||||
"-Wl,--gc-sections",
|
||||
"-flto",
|
||||
"-fno-unwind-tables",
|
||||
"-fno-asynchronous-unwind-tables",
|
||||
)
|
||||
cmake {
|
||||
cppFlags += flags
|
||||
cFlags += flags
|
||||
arguments += "-DANDROID_STL=none"
|
||||
arguments += "-DCMAKE_BUILD_TYPE=Release"
|
||||
}
|
||||
}
|
||||
matchingFallbacks += "release"
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,42 +74,79 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
cmaker {
|
||||
default {
|
||||
val flags = arrayOf(
|
||||
"-Werror",
|
||||
"-Wno-gnu-string-literal-operator-template",
|
||||
)
|
||||
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
cppFlags += flags
|
||||
cFlags += flags
|
||||
}
|
||||
buildTypes {
|
||||
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.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
"standaloneCompileOnly"(libs.cxx)
|
||||
}
|
||||
|
||||
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")
|
||||
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")
|
||||
archiveClassifier = "symbols"
|
||||
archiveBaseName = "standalone"
|
||||
}
|
||||
|
||||
publishing {
|
||||
val repo = jgit.repo(true)
|
||||
val ver = repo?.latestTag?.removePrefix("v") ?: "0.0"
|
||||
println("${rootProject.name} version: $ver")
|
||||
|
||||
publish {
|
||||
githubRepo = "LSPosed/LSPlant"
|
||||
publications {
|
||||
fun MavenPublication.setup() {
|
||||
group = "org.lsposed.lsplant"
|
||||
version = "5.0"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -200,31 +167,4 @@ publishing {
|
||||
setup()
|
||||
}
|
||||
}
|
||||
repositories {
|
||||
maven {
|
||||
name = "ossrh"
|
||||
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
|
||||
credentials(PasswordCredentials::class)
|
||||
}
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/LSPosed/LSPlant")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
"standaloneCompileOnly"("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||
}
|
||||
}
|
||||
|
||||
signing {
|
||||
val signingKey = findProperty("signingKey") as String?
|
||||
val signingPassword = findProperty("signingPassword") as String?
|
||||
if (signingKey != null && signingPassword != null) {
|
||||
useInMemoryPgpKeys(signingKey, signingPassword)
|
||||
}
|
||||
sign(publishing.publications)
|
||||
}
|
||||
|
@ -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,21 +1,25 @@
|
||||
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})
|
||||
endif ()
|
||||
|
||||
if ("${ANDROID_STL}" STREQUAL "none")
|
||||
if (LSPLANT_STANDALONE)
|
||||
find_package(cxx REQUIRED CONFIG)
|
||||
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})
|
||||
|
||||
|
174
lsplant/src/main/jni/art/mirror/class.cxx
Normal file
174
lsplant/src/main/jni/art/mirror/class.cxx
Normal file
@ -0,0 +1,174 @@
|
||||
module;
|
||||
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
|
||||
#include "logging.hpp"
|
||||
|
||||
export module lsplant:clazz;
|
||||
|
||||
import :common;
|
||||
import :art_method;
|
||||
import :thread;
|
||||
import :handle;
|
||||
import hook_helper;
|
||||
|
||||
export namespace lsplant::art::mirror {
|
||||
|
||||
class Class {
|
||||
private:
|
||||
inline static auto GetDescriptor_ =
|
||||
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>;
|
||||
|
||||
inline static auto GetClassDef_ =
|
||||
"_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>;
|
||||
|
||||
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) {
|
||||
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(method, method->GetEntryPoint());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
{
|
||||
deoptimized_classes_.if_contains(class_def, [&out](const auto &it) {
|
||||
for (auto method : it.second) {
|
||||
if (method->IsStatic()) {
|
||||
LOGV("Backup deoptimized method %p because of initialization", method);
|
||||
out.emplace(method, method->GetEntryPoint());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!out.empty()) [[unlikely]] {
|
||||
std::unique_lock lk(backup_methods_lock_);
|
||||
backup_methods_[self].emplace(class_def, std::move(out));
|
||||
}
|
||||
}
|
||||
|
||||
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(GetClassDef_(h.Get()), self);
|
||||
}
|
||||
return backup(h, new_status, 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(GetClassDef_(h.Get()), self);
|
||||
}
|
||||
return backup(h, new_status, 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(GetClassDef_(h.Get()), self);
|
||||
}
|
||||
return backup(h, new_status, self);
|
||||
};
|
||||
|
||||
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 (!handler(GetDescriptor_) || !handler(GetClassDef_)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int sdk_int = GetAndroidApiLevel();
|
||||
|
||||
if (sdk_int < __ANDROID_API_O__) {
|
||||
if (!handler(SetStatus_, ClassSetStatus_)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!handler(SetClassStatus_, TrivialSetStatus_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (sdk_int >= __ANDROID_API_R__) {
|
||||
initialized_status = 15;
|
||||
} else if (sdk_int >= __ANDROID_API_P__) {
|
||||
initialized_status = 14;
|
||||
} else if (sdk_int == __ANDROID_API_O_MR1__) {
|
||||
initialized_status = 11;
|
||||
} else {
|
||||
initialized_status = 10;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *GetDescriptor(std::string *storage) { return GetDescriptor_(this, storage); }
|
||||
|
||||
std::string GetDescriptor() {
|
||||
std::string storage;
|
||||
return GetDescriptor(&storage);
|
||||
}
|
||||
|
||||
const dex::ClassDef *GetClassDef() { return GetClassDef_(this); }
|
||||
|
||||
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
|
||||
BackupMethods methods;
|
||||
if (!backup_methods_.size()) [[likely]] {
|
||||
return methods;
|
||||
}
|
||||
if (class_def) {
|
||||
std::unique_lock lk(backup_methods_lock_);
|
||||
for (auto it = backup_methods_.begin(); it != backup_methods_.end();) {
|
||||
if (auto found = it->second.find(class_def); found != it->second.end()) {
|
||||
methods.merge(std::move(found->second));
|
||||
it->second.erase(found);
|
||||
}
|
||||
if (it->second.empty()) {
|
||||
backup_methods_.erase(it++);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
} else if (self) {
|
||||
std::unique_lock lk(backup_methods_lock_);
|
||||
if (auto found = backup_methods_.find(self); found != backup_methods_.end()) {
|
||||
for (auto it = found->second.begin(); it != found->second.end();) {
|
||||
methods.merge(std::move(it->second));
|
||||
found->second.erase(it++);
|
||||
}
|
||||
backup_methods_.erase(found);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lsplant::art::mirror
|
@ -1,60 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
namespace dex {
|
||||
class ClassDef {};
|
||||
} // namespace dex
|
||||
|
||||
namespace 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 "";
|
||||
}
|
||||
|
||||
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
|
||||
if (GetClassDefSym) [[likely]]
|
||||
return GetClassDefSym(thiz);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char *GetDescriptor(std::string *storage) {
|
||||
if (GetDescriptorSym) {
|
||||
return GetDescriptor(this, storage);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string GetDescriptor() {
|
||||
std::string storage;
|
||||
return GetDescriptor(&storage);
|
||||
}
|
||||
|
||||
const dex::ClassDef *GetClassDef() {
|
||||
if (GetClassDefSym) return GetClassDef(this);
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mirror
|
||||
} // namespace lsplant::art
|
@ -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,192 +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_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 GetBackupMethods(mirror::Class *mirror_class) {
|
||||
std::list<std::tuple<art::ArtMethod *, void *>> out;
|
||||
auto class_def = mirror_class->GetClassDef();
|
||||
if (!class_def) return out;
|
||||
{
|
||||
std::shared_lock lk(hooked_classes_lock_);
|
||||
if (auto found = hooked_classes_.find(class_def); found != hooked_classes_.end())
|
||||
[[unlikely]] {
|
||||
LOGV("Before fixup %s, backup %zu hooked methods' trampoline",
|
||||
mirror_class->GetDescriptor().c_str(), found->second.size());
|
||||
for (auto method : found->second) {
|
||||
out.emplace_back(method, method->GetEntryPoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::shared_lock lk(deoptimized_methods_lock_);
|
||||
if (auto found = deoptimized_classes_.find(class_def);
|
||||
found != deoptimized_classes_.end()) [[unlikely]] {
|
||||
LOGV("Before fixup %s, backup %zu deoptimized methods' trampoline",
|
||||
mirror_class->GetDescriptor().c_str(), found->second.size());
|
||||
for (auto method : found->second) {
|
||||
out.emplace_back(method, method->GetEntryPoint());
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void FixTrampoline(const std::list<std::tuple<art::ArtMethod *, void *>> &methods) {
|
||||
std::shared_lock lk(hooked_methods_lock_);
|
||||
for (const auto &[art_method, old_trampoline] : methods) {
|
||||
auto new_trampoline = art_method->GetEntryPoint();
|
||||
art_method->SetEntryPoint(old_trampoline);
|
||||
if (IsDeoptimized(art_method)) continue;
|
||||
if (auto backup_method = IsHooked(art_method); backup_method) [[likely]] {
|
||||
if (new_trampoline != old_trampoline) [[unlikely]] {
|
||||
LOGV("propagate entrypoint for %s", backup_method->PrettyMethod(true).data());
|
||||
backup_method->SetEntryPoint(new_trampoline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void,
|
||||
FixupStaticTrampolines, (ClassLinker * thiz, ObjPtr<mirror::Class> mirror_class), {
|
||||
auto backup_methods = GetBackupMethods(mirror_class);
|
||||
backup(thiz, mirror_class);
|
||||
FixTrampoline(backup_methods);
|
||||
});
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
|
||||
void, FixupStaticTrampolinesWithThread,
|
||||
(ClassLinker * thiz, art::Thread *self, ObjPtr<mirror::Class> mirror_class), {
|
||||
auto backup_methods = GetBackupMethods(mirror_class);
|
||||
backup(thiz, self, mirror_class);
|
||||
FixTrampoline(backup_methods);
|
||||
});
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE",
|
||||
void, FixupStaticTrampolinesRaw,
|
||||
(ClassLinker * thiz, mirror::Class *mirror_class), {
|
||||
auto backup_methods = GetBackupMethods(mirror_class);
|
||||
backup(thiz, mirror_class);
|
||||
FixTrampoline(backup_methods);
|
||||
});
|
||||
|
||||
public:
|
||||
static bool Init(const HookHandler &handler) {
|
||||
if (!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines,
|
||||
FixupStaticTrampolinesRaw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HookSyms(handler, RegisterNativeClassLinker, RegisterNative, RegisterNativeFast,
|
||||
RegisterNativeThread) ||
|
||||
!HookSyms(handler, UnregisterNativeClassLinker, UnregisterNative, UnregisterNativeFast,
|
||||
UnregisterNativeThread)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
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
|
63
lsplant/src/main/jni/art/runtime/instrumentation.cxx
Normal file
63
lsplant/src/main/jni/art/runtime/instrumentation.cxx
Normal file
@ -0,0 +1,63 @@
|
||||
module;
|
||||
|
||||
#include "logging.hpp"
|
||||
|
||||
export module lsplant:instrumentation;
|
||||
|
||||
import :art_method;
|
||||
import :common;
|
||||
import hook_helper;
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
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]] {
|
||||
LOGD("Propagate update method code %p for hooked method %p to its backup", quick_code,
|
||||
art_method);
|
||||
return backup;
|
||||
}
|
||||
return 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));
|
||||
};
|
||||
|
||||
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) {
|
||||
if (!IsJavaDebuggable(env)) [[likely]] {
|
||||
return true;
|
||||
}
|
||||
int sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
|
||||
if (!handler(InitializeMethodsCode_, UpdateMethodsCodeToInterpreterEntryPoint_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lsplant::art
|
@ -1,46 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "art_method.hpp"
|
||||
#include "common.hpp"
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
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]] {
|
||||
LOGD("Propagate update method code %p for hooked method %s to its backup", quick_code,
|
||||
art_method->PrettyMethod().c_str());
|
||||
return backup;
|
||||
}
|
||||
return art_method;
|
||||
}
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE",
|
||||
void, UpdateMethodsCodeToInterpreterEntryPoint,
|
||||
(Instrumentation * thiz, ArtMethod *art_method),
|
||||
{ backup(thiz, MaybeUseBackupMethod(art_method, nullptr)); });
|
||||
|
||||
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv",
|
||||
void, InitializeMethodsCode,
|
||||
(Instrumentation * thiz, ArtMethod *art_method, const void* quick_code),
|
||||
{ backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code); });
|
||||
|
||||
public:
|
||||
static bool Init(JNIEnv *env, const HookHandler &handler) {
|
||||
if (!IsJavaDebuggable(env)) [[likely]] {
|
||||
return true;
|
||||
}
|
||||
int sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
|
||||
if (!HookSyms(handler, InitializeMethodsCode, UpdateMethodsCodeToInterpreterEntryPoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace lsplant::art
|
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,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "reflective_reference.hpp"
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
class ArtMethod;
|
||||
class ValueObject {};
|
||||
|
||||
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,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
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
|
177
lsplant/src/main/jni/common.cxx
Normal file
177
lsplant/src/main/jni/common.cxx
Normal file
@ -0,0 +1,177 @@
|
||||
module;
|
||||
|
||||
#include <jni.h>
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <list>
|
||||
#include <shared_mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include "logging.hpp"
|
||||
|
||||
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,
|
||||
kArm64,
|
||||
kX86,
|
||||
kX86_64,
|
||||
kRiscv64,
|
||||
};
|
||||
|
||||
consteval inline Arch GetArch() {
|
||||
#if defined(__i386__)
|
||||
return Arch::kX86;
|
||||
#elif defined(__x86_64__)
|
||||
return Arch::kX86_64;
|
||||
#elif defined(__arm__)
|
||||
return Arch::kArm;
|
||||
#elif defined(__aarch64__)
|
||||
return Arch::kArm64;
|
||||
#elif defined(__riscv)
|
||||
return Arch::kRiscv64;
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
[[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());
|
||||
int base = atoi(prop_value.data());
|
||||
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
|
||||
return base + atoi(prop_value.data());
|
||||
}();
|
||||
return kApiLevel;
|
||||
}
|
||||
|
||||
inline auto IsJavaDebuggable(JNIEnv * env) {
|
||||
static auto kDebuggable = [&env]() {
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int < __ANDROID_API_P__) {
|
||||
return false;
|
||||
}
|
||||
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
|
||||
if (!runtime_class) {
|
||||
LOGE("Failed to find VMRuntime");
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
if (!is_debuggable_method) {
|
||||
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
|
||||
return false;
|
||||
}
|
||||
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
|
||||
if (!runtime) {
|
||||
LOGE("Failed to get VMRuntime");
|
||||
return false;
|
||||
}
|
||||
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
|
||||
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
|
||||
return is_debuggable;
|
||||
}();
|
||||
return kDebuggable;
|
||||
}
|
||||
|
||||
constexpr auto kPointerSize = sizeof(void *);
|
||||
|
||||
SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
|
||||
|
||||
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
|
||||
hooked_classes_;
|
||||
|
||||
SharedHashSet<art::ArtMethod *> deoptimized_methods_set_;
|
||||
|
||||
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
|
||||
deoptimized_classes_;
|
||||
|
||||
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) {
|
||||
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;
|
||||
});
|
||||
return backup;
|
||||
}
|
||||
|
||||
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 backup;
|
||||
}
|
||||
|
||||
inline bool IsDeoptimized(art::ArtMethod * art_method) {
|
||||
return deoptimized_methods_set_.contains(art_method);
|
||||
}
|
||||
|
||||
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
|
||||
std::unique_lock lk(jit_movements_lock_);
|
||||
return std::move(jit_movements_);
|
||||
}
|
||||
|
||||
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); },
|
||||
[&class_def, &target](const auto &ctor) {
|
||||
ctor(class_def, phmap::flat_hash_set<art::ArtMethod *>{target});
|
||||
});
|
||||
hooked_methods_.insert({std::make_pair(target, std::make_pair(reflected_backup, backup)),
|
||||
std::make_pair(backup, std::make_pair(nullptr, target))});
|
||||
}
|
||||
|
||||
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
|
||||
{ deoptimized_classes_[class_def].emplace(art_method); }
|
||||
deoptimized_methods_set_.insert(art_method);
|
||||
}
|
||||
|
||||
inline void RecordJitMovement(art::ArtMethod * target, art::ArtMethod * backup) {
|
||||
std::unique_lock lk(jit_movements_lock_);
|
||||
jit_movements_.emplace_back(target, backup);
|
||||
}
|
||||
} // namespace lsplant
|
@ -1,179 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <absl/container/flat_hash_map.h>
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include <list>
|
||||
#include <shared_mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include "logging.hpp"
|
||||
#include "lsplant.hpp"
|
||||
#include "utils/hook_helper.hpp"
|
||||
|
||||
namespace lsplant {
|
||||
|
||||
enum class Arch {
|
||||
kArm,
|
||||
kArm64,
|
||||
kX86,
|
||||
kX8664,
|
||||
};
|
||||
|
||||
consteval inline Arch GetArch() {
|
||||
#if defined(__i386__)
|
||||
return Arch::kX86;
|
||||
#elif defined(__x86_64__)
|
||||
return Arch::kX8664;
|
||||
#elif defined(__arm__)
|
||||
return Arch::kArm;
|
||||
#elif defined(__aarch64__)
|
||||
return Arch::kArm64;
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static 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() {
|
||||
static auto kApiLevel = []() {
|
||||
std::array<char, PROP_VALUE_MAX> prop_value;
|
||||
__system_property_get("ro.build.version.sdk", prop_value.data());
|
||||
int base = atoi(prop_value.data());
|
||||
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
|
||||
return base + atoi(prop_value.data());
|
||||
}();
|
||||
return kApiLevel;
|
||||
}
|
||||
|
||||
inline auto IsJavaDebuggable(JNIEnv *env) {
|
||||
static auto kDebuggable = [&env]() {
|
||||
auto sdk_int = GetAndroidApiLevel();
|
||||
if (sdk_int < __ANDROID_API_P__) {
|
||||
return false;
|
||||
}
|
||||
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
|
||||
if (!runtime_class) {
|
||||
LOGE("Failed to find VMRuntime");
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
if (!is_debuggable_method) {
|
||||
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
|
||||
return false;
|
||||
}
|
||||
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
|
||||
if (!runtime) {
|
||||
LOGE("Failed to get VMRuntime");
|
||||
return false;
|
||||
}
|
||||
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
|
||||
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
|
||||
return is_debuggable;
|
||||
}();
|
||||
return kDebuggable;
|
||||
}
|
||||
|
||||
inline static constexpr auto kPointerSize = sizeof(void *);
|
||||
|
||||
namespace art {
|
||||
class ArtMethod;
|
||||
namespace dex {
|
||||
class ClassDef;
|
||||
}
|
||||
namespace mirror {
|
||||
class Class;
|
||||
}
|
||||
} // namespace art
|
||||
|
||||
namespace {
|
||||
// target, backup
|
||||
inline absl::flat_hash_map<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
|
||||
inline std::shared_mutex hooked_methods_lock_;
|
||||
|
||||
inline absl::flat_hash_map<const art::dex::ClassDef *, absl::flat_hash_set<art::ArtMethod *>>
|
||||
hooked_classes_;
|
||||
inline std::shared_mutex hooked_classes_lock_;
|
||||
|
||||
inline absl::flat_hash_set<art::ArtMethod *> deoptimized_methods_set_;
|
||||
inline std::shared_mutex deoptimized_methods_lock_;
|
||||
|
||||
inline absl::flat_hash_map<const art::dex::ClassDef *, absl::flat_hash_set<art::ArtMethod *>>
|
||||
deoptimized_classes_;
|
||||
inline std::shared_mutex deoptimized_classes_lock_;
|
||||
|
||||
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
|
||||
inline std::shared_mutex jit_movements_lock_;
|
||||
} // namespace
|
||||
|
||||
inline art::ArtMethod *IsHooked(art::ArtMethod *art_method, bool including_backup = false) {
|
||||
std::shared_lock lk(hooked_methods_lock_);
|
||||
if (auto it = hooked_methods_.find(art_method);
|
||||
it != hooked_methods_.end() && (!including_backup || it->second.first)) {
|
||||
return it->second.second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline art::ArtMethod *IsBackup(art::ArtMethod *art_method) {
|
||||
std::shared_lock lk(hooked_methods_lock_);
|
||||
if (auto it = hooked_methods_.find(art_method);
|
||||
it != hooked_methods_.end() && !it->second.first) {
|
||||
return it->second.second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool IsDeoptimized(art::ArtMethod *art_method) {
|
||||
std::shared_lock lk(deoptimized_methods_lock_);
|
||||
return deoptimized_methods_set_.contains(art_method);
|
||||
}
|
||||
|
||||
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
|
||||
std::unique_lock lk(jit_movements_lock_);
|
||||
return std::move(jit_movements_);
|
||||
}
|
||||
|
||||
inline void RecordHooked(art::ArtMethod *target, const art::dex::ClassDef *class_def,
|
||||
jobject reflected_backup, art::ArtMethod *backup) {
|
||||
{
|
||||
std::unique_lock lk(hooked_classes_lock_);
|
||||
hooked_classes_[class_def].emplace(target);
|
||||
}
|
||||
{
|
||||
std::unique_lock lk(hooked_methods_lock_);
|
||||
hooked_methods_[target] = {reflected_backup, backup};
|
||||
hooked_methods_[backup] = {nullptr, target};
|
||||
}
|
||||
}
|
||||
|
||||
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
|
||||
{
|
||||
std::unique_lock lk(deoptimized_classes_lock_);
|
||||
deoptimized_classes_[class_def].emplace(art_method);
|
||||
}
|
||||
{
|
||||
std::unique_lock lk(deoptimized_methods_lock_);
|
||||
deoptimized_methods_set_.insert(art_method);
|
||||
}
|
||||
}
|
||||
|
||||
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 33c8b50ac7d2498c0db8fe2c6e4273dabc82de75
|
||||
Subproject commit 67077df2a53be0cabb9f16e8a050acdd825f7d69
|
@ -3,8 +3,9 @@
|
||||
#include <jni.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <functional>
|
||||
|
||||
/// \namespace namespace of LSPlant
|
||||
/// \namespace lsplant
|
||||
namespace lsplant {
|
||||
|
||||
inline namespace v2 {
|
||||
|
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>
|
||||
@ -64,11 +75,17 @@ consteval inline auto GetTrampoline() {
|
||||
// NOLINTNEXTLINE
|
||||
uint8_t{56u}, uintptr_t{1u});
|
||||
}
|
||||
if constexpr (kArch == Arch::kX8664) {
|
||||
if constexpr (kArch == Arch::kX86_64) {
|
||||
return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x00\xc3"_uarr,
|
||||
// NOLINTNEXTLINE
|
||||
uint8_t{96u}, uintptr_t{2u});
|
||||
}
|
||||
if constexpr (kArch == Arch::kRiscv64) {
|
||||
return std::make_tuple(
|
||||
"\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{16u});
|
||||
}
|
||||
}
|
||||
|
||||
auto [trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
|
||||
@ -79,20 +96,22 @@ jmethodID class_get_name = nullptr;
|
||||
jmethodID class_get_class_loader = nullptr;
|
||||
jmethodID class_get_declared_constructors = nullptr;
|
||||
jfieldID class_access_flags = nullptr;
|
||||
jclass in_memory_class_loader = nullptr;
|
||||
jmethodID in_memory_class_loader_init = nullptr;
|
||||
jmethodID dex_file_init_with_cl = nullptr;
|
||||
jmethodID dex_file_init = nullptr;
|
||||
jmethodID load_class = nullptr;
|
||||
jmethodID set_accessible = nullptr;
|
||||
jclass executable = nullptr;
|
||||
|
||||
// for proxy method
|
||||
jmethodID method_get_parameter_types = nullptr;
|
||||
jmethodID method_get_return_type = nullptr;
|
||||
// for old platform
|
||||
jclass path_class_loader = nullptr;
|
||||
jmethodID path_class_loader_init = nullptr;
|
||||
|
||||
constexpr auto kInternalMethods = std::make_tuple(
|
||||
&method_get_name, &method_get_declaring_class, &class_get_name, &class_get_class_loader,
|
||||
&class_get_declared_constructors, &in_memory_class_loader_init, &load_class, &set_accessible,
|
||||
&path_class_loader_init);
|
||||
&class_get_declared_constructors, &dex_file_init, &dex_file_init_with_cl, &load_class,
|
||||
&set_accessible, &method_get_parameter_types, &method_get_return_type, &path_class_loader_init);
|
||||
|
||||
std::string generated_class_name;
|
||||
std::string generated_source_name;
|
||||
@ -139,7 +158,20 @@ bool InitJNI(JNIEnv *env) {
|
||||
if (method_get_declaring_class =
|
||||
JNI_GetMethodID(env, executable, "getDeclaringClass", "()Ljava/lang/Class;");
|
||||
!method_get_declaring_class) {
|
||||
LOGE("Failed to find getName method");
|
||||
LOGE("Failed to find getDeclaringClass method");
|
||||
return false;
|
||||
}
|
||||
if (method_get_parameter_types =
|
||||
JNI_GetMethodID(env, executable, "getParameterTypes", "()[Ljava/lang/Class;");
|
||||
!method_get_parameter_types) {
|
||||
LOGE("Failed to find getParameterTypes method");
|
||||
return false;
|
||||
}
|
||||
if (method_get_return_type =
|
||||
JNI_GetMethodID(env, JNI_FindClass(env, "java/lang/reflect/Method"), "getReturnType",
|
||||
"()Ljava/lang/Class;");
|
||||
!method_get_return_type) {
|
||||
LOGE("Failed to find getReturnType method");
|
||||
return false;
|
||||
}
|
||||
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||
@ -172,32 +204,37 @@ bool InitJNI(JNIEnv *env) {
|
||||
LOGE("Failed to find Class.accessFlags");
|
||||
return false;
|
||||
}
|
||||
if (sdk_int >= __ANDROID_API_O__ &&
|
||||
(in_memory_class_loader = JNI_NewGlobalRef(
|
||||
env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader")))) [[likely]] {
|
||||
in_memory_class_loader_init =
|
||||
JNI_GetMethodID(env, in_memory_class_loader, "<init>",
|
||||
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
if (!load_class) {
|
||||
load_class = JNI_GetMethodID(env, in_memory_class_loader, "findClass",
|
||||
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||
}
|
||||
} else if (auto dex_file = JNI_FindClass(env, "dalvik/system/DexFile");
|
||||
dex_file && (path_class_loader = JNI_NewGlobalRef(
|
||||
env, JNI_FindClass(env, "dalvik/system/PathClassLoader")))) {
|
||||
path_class_loader_init = JNI_GetMethodID(env, path_class_loader, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
if (!path_class_loader_init) {
|
||||
LOGE("Failed to find PathClassLoader.<init>");
|
||||
return false;
|
||||
}
|
||||
load_class =
|
||||
JNI_GetMethodID(env, dex_file, "loadClass",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
|
||||
auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
|
||||
if (!path_class_loader) {
|
||||
LOGE("Failed to find PathClassLoader");
|
||||
return false;
|
||||
}
|
||||
if (!load_class) {
|
||||
if (path_class_loader_init = JNI_GetMethodID(env, path_class_loader, "<init>",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
|
||||
!path_class_loader_init) {
|
||||
LOGE("Failed to find PathClassLoader.<init>");
|
||||
return false;
|
||||
}
|
||||
auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile");
|
||||
if (!dex_file_class) {
|
||||
LOGE("Failed to find DexFile");
|
||||
return false;
|
||||
}
|
||||
if (sdk_int >= __ANDROID_API_Q__) {
|
||||
dex_file_init_with_cl = JNI_GetMethodID(
|
||||
env, dex_file_class, "<init>",
|
||||
"([Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;[Ldalvik/system/DexPathList$Element;)V");
|
||||
} else if (sdk_int >= __ANDROID_API_O__) {
|
||||
dex_file_init = JNI_GetMethodID(env, dex_file_class, "<init>", "(Ljava/nio/ByteBuffer;)V");
|
||||
}
|
||||
if (sdk_int >= __ANDROID_API_O__ && !dex_file_init_with_cl && !dex_file_init) {
|
||||
LOGE("Failed to find DexFile.<init>");
|
||||
return false;
|
||||
}
|
||||
if (load_class =
|
||||
JNI_GetMethodID(env, dex_file_class, "loadClass",
|
||||
"(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
|
||||
!load_class) {
|
||||
LOGE("Failed to find a suitable way to load class");
|
||||
return false;
|
||||
}
|
||||
@ -221,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;
|
||||
@ -233,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;
|
||||
@ -253,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;
|
||||
@ -265,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)) {
|
||||
@ -296,7 +334,6 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
|
||||
|
||||
auto parameter_types = std::vector<TypeDescriptor>();
|
||||
parameter_types.reserve(shorty.size() - 1);
|
||||
std::string storage;
|
||||
auto return_type =
|
||||
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]);
|
||||
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
|
||||
@ -316,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()};
|
||||
@ -363,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);
|
||||
}
|
||||
@ -373,17 +409,19 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
|
||||
|
||||
jclass target_class = nullptr;
|
||||
|
||||
if (in_memory_class_loader_init) [[likely]] {
|
||||
auto dex_buffer = JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()),
|
||||
static_cast<jlong>(image.size()));
|
||||
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
|
||||
dex_buffer, class_loader);
|
||||
if (my_cl) {
|
||||
target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
|
||||
env, my_cl, load_class,
|
||||
JNI_NewStringUTF(env, generated_class_name.data())))
|
||||
.release();
|
||||
}
|
||||
ScopedLocalRef<jobject> java_dex_file{nullptr};
|
||||
|
||||
if (auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile"); dex_file_init_with_cl) {
|
||||
java_dex_file = JNI_NewObject(
|
||||
env, dex_file_class, dex_file_init_with_cl,
|
||||
JNI_NewObjectArray(
|
||||
env, 1, JNI_FindClass(env, "java/nio/ByteBuffer"),
|
||||
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size())),
|
||||
nullptr, nullptr);
|
||||
} else if (dex_file_init) {
|
||||
java_dex_file = JNI_NewObject(
|
||||
env, dex_file_class, dex_file_init,
|
||||
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size()));
|
||||
} else {
|
||||
void *target =
|
||||
mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
@ -391,20 +429,24 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
|
||||
mprotect(target, image.size(), PROT_READ);
|
||||
std::string err_msg;
|
||||
const auto *dex = DexFile::OpenMemory(
|
||||
target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name,
|
||||
&err_msg);
|
||||
reinterpret_cast<const uint8_t *>(target), image.size(),
|
||||
generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg);
|
||||
if (!dex) {
|
||||
LOGE("Failed to open memory dex: %s", err_msg.data());
|
||||
} else {
|
||||
java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr);
|
||||
}
|
||||
auto java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr});
|
||||
if (dex && java_dex_file) {
|
||||
auto p = JNI_NewObject(env, path_class_loader, path_class_loader_init,
|
||||
}
|
||||
|
||||
if (auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader");
|
||||
java_dex_file) {
|
||||
auto my_cl = JNI_NewObject(env, path_class_loader, path_class_loader_init,
|
||||
JNI_NewStringUTF(env, ""), class_loader);
|
||||
target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
|
||||
env, java_dex_file, load_class,
|
||||
env->NewStringUTF(generated_class_name.data()), p))
|
||||
.release();
|
||||
}
|
||||
target_class =
|
||||
JNI_Cast<jclass>(
|
||||
JNI_CallObjectMethod(env, java_dex_file, load_class,
|
||||
JNI_NewStringUTF(env, generated_class_name.data()), my_cl))
|
||||
.release();
|
||||
}
|
||||
|
||||
if (target_class) {
|
||||
@ -426,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");
|
||||
@ -435,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)) {
|
||||
@ -462,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();
|
||||
@ -496,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());
|
||||
@ -530,13 +567,79 @@ bool DoUnHook(ArtMethod *target, ArtMethod *backup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
|
||||
const auto return_type = JNI_CallObjectMethod(env, proxy_method, method_get_return_type);
|
||||
const auto parameter_types =
|
||||
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, proxy_method, method_get_parameter_types));
|
||||
auto integer_class = JNI_FindClass(env, "java/lang/Integer");
|
||||
auto long_class = JNI_FindClass(env, "java/lang/Long");
|
||||
auto float_class = JNI_FindClass(env, "java/lang/Float");
|
||||
auto double_class = JNI_FindClass(env, "java/lang/Double");
|
||||
auto boolean_class = JNI_FindClass(env, "java/lang/Boolean");
|
||||
auto byte_class = JNI_FindClass(env, "java/lang/Byte");
|
||||
auto char_class = JNI_FindClass(env, "java/lang/Character");
|
||||
auto short_class = JNI_FindClass(env, "java/lang/Short");
|
||||
auto void_class = JNI_FindClass(env, "java/lang/Void");
|
||||
static auto *kIntTypeField =
|
||||
JNI_GetStaticFieldID(env, integer_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kLongTypeField =
|
||||
JNI_GetStaticFieldID(env, long_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kFloatTypeField =
|
||||
JNI_GetStaticFieldID(env, float_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kDoubleTypeField =
|
||||
JNI_GetStaticFieldID(env, double_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kBooleanTypeField =
|
||||
JNI_GetStaticFieldID(env, boolean_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kByteTypeField =
|
||||
JNI_GetStaticFieldID(env, byte_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kCharTypeField =
|
||||
JNI_GetStaticFieldID(env, char_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kShortTypeField =
|
||||
JNI_GetStaticFieldID(env, short_class, "TYPE", "Ljava/lang/Class;");
|
||||
static auto *kVoidTypeField =
|
||||
JNI_GetStaticFieldID(env, void_class, "TYPE", "Ljava/lang/Class;");
|
||||
|
||||
auto int_type = JNI_GetStaticObjectField(env, integer_class, kIntTypeField);
|
||||
auto long_type = JNI_GetStaticObjectField(env, long_class, kLongTypeField);
|
||||
auto float_type = JNI_GetStaticObjectField(env, float_class, kFloatTypeField);
|
||||
auto double_type = JNI_GetStaticObjectField(env, double_class, kDoubleTypeField);
|
||||
auto boolean_type = JNI_GetStaticObjectField(env, boolean_class, kBooleanTypeField);
|
||||
auto byte_type = JNI_GetStaticObjectField(env, byte_class, kByteTypeField);
|
||||
auto char_type = JNI_GetStaticObjectField(env, char_class, kCharTypeField);
|
||||
auto short_type = JNI_GetStaticObjectField(env, short_class, kShortTypeField);
|
||||
auto void_type = JNI_GetStaticObjectField(env, void_class, kVoidTypeField);
|
||||
|
||||
std::string out;
|
||||
auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) {
|
||||
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);
|
||||
for (const auto ¶m : parameter_types) {
|
||||
out += type_to_shorty(param);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // 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;
|
||||
}
|
||||
@ -589,7 +692,9 @@ using ::lsplant::IsHooked;
|
||||
}
|
||||
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
|
||||
env,
|
||||
BuildDex(env, callback_class_loader, ArtMethod::GetMethodShorty(env, target_method),
|
||||
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(),
|
||||
class_name.get(), callback_method_name.get()));
|
||||
if (!built_class || !hooker_field || !hook_method || !backup_method) {
|
||||
@ -603,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);
|
||||
|
||||
@ -639,28 +744,19 @@ using ::lsplant::IsHooked;
|
||||
auto *target = ArtMethod::FromReflectedMethod(env, target_method);
|
||||
jobject reflected_backup = nullptr;
|
||||
art::ArtMethod *backup = nullptr;
|
||||
{
|
||||
std::unique_lock lk(hooked_methods_lock_);
|
||||
if (auto it = hooked_methods_.find(target); it != hooked_methods_.end()) [[likely]] {
|
||||
std::tie(reflected_backup, backup) = it->second;
|
||||
if (reflected_backup == nullptr) {
|
||||
LOGE("Unable to unhook a method that is not hooked");
|
||||
return false;
|
||||
}
|
||||
hooked_methods_.erase(it->second.second);
|
||||
hooked_methods_.erase(it);
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock lk(hooked_classes_lock_);
|
||||
if (auto it = hooked_classes_.find(target->GetDeclaringClass()->GetClassDef());
|
||||
it != hooked_classes_.end()) {
|
||||
it->second.erase(target);
|
||||
if (it->second.empty()) {
|
||||
hooked_classes_.erase(it);
|
||||
}
|
||||
}
|
||||
if (!hooked_methods_.erase_if(target, [&reflected_backup, &backup](const auto &it) {
|
||||
std::tie(reflected_backup, backup) = it.second;
|
||||
return reflected_backup != nullptr;
|
||||
})) {
|
||||
LOGE("Unable to unhook a method that is not hooked");
|
||||
return false;
|
||||
}
|
||||
// FIXME: not atomic, but should be fine
|
||||
hooked_methods_.erase(backup);
|
||||
hooked_classes_.erase_if(target->GetDeclaringClass()->GetClassDef(), [&target](auto &it) {
|
||||
it.second.erase(target);
|
||||
return it.second.empty();
|
||||
});
|
||||
auto *backup_method = env->FromReflectedMethod(reflected_backup);
|
||||
env->DeleteGlobalRef(reflected_backup);
|
||||
if (DoUnHook(target, backup)) {
|
||||
@ -696,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);
|
||||
@ -734,20 +830,11 @@ using ::lsplant::IsHooked;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool MakeDexFileTrusted(JNIEnv *env, jobject cookie) {
|
||||
struct Guard {
|
||||
Guard() {
|
||||
Runtime::Current()->SetJavaDebuggable(
|
||||
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
|
||||
}
|
||||
~Guard() {
|
||||
Runtime::Current()->SetJavaDebuggable(Runtime::RuntimeDebugState::kNonJavaDebuggable);
|
||||
}
|
||||
} guard;
|
||||
JavaDebuggableGuard guard;
|
||||
if (!cookie) return false;
|
||||
return DexFile::SetTrusted(env, cookie);
|
||||
}
|
||||
}
|
||||
} // namespace v2
|
||||
|
||||
} // namespace lsplant
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
@ -4,13 +4,9 @@ pluginManagement {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
plugins {
|
||||
id("com.android.application") version "7.3.0"
|
||||
id("com.android.library") version "7.3.0"
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -1,5 +1,7 @@
|
||||
import com.android.build.api.dsl.ManagedVirtualDevice
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
alias(libs.plugins.agp.app)
|
||||
}
|
||||
|
||||
val androidTargetSdkVersion: Int by rootProject.extra
|
||||
@ -10,6 +12,7 @@ val androidNdkVersion: String by rootProject.extra
|
||||
val androidCmakeVersion: String by rootProject.extra
|
||||
|
||||
android {
|
||||
namespace = "org.lsposed.lsplant.test"
|
||||
compileSdk = androidCompileSdkVersion
|
||||
ndkVersion = androidNdkVersion
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
@ -33,6 +36,12 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
isDebuggable = false
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path = file("src/main/jni/CMakeLists.txt")
|
||||
@ -44,14 +53,53 @@ android {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
namespace = "org.lsposed.lsplant"
|
||||
|
||||
testOptions {
|
||||
managedDevices {
|
||||
devices {
|
||||
fun createDevice(api: Int, is64: Boolean, target: String = "default") = create<ManagedVirtualDevice>("""avd-$api-${if(is64) "x86_64" else "x86"}-$target""") {
|
||||
device = "Pixel 2"
|
||||
apiLevel = api
|
||||
systemImageSource = target
|
||||
require64Bit = is64
|
||||
}
|
||||
|
||||
createDevice(21, false)
|
||||
createDevice(21, true)
|
||||
createDevice(22, false)
|
||||
createDevice(22, true)
|
||||
createDevice(23, false)
|
||||
createDevice(23, true)
|
||||
createDevice(24, false)
|
||||
createDevice(24, true)
|
||||
createDevice(25, false)
|
||||
createDevice(25, true)
|
||||
createDevice(26, false)
|
||||
createDevice(26, true)
|
||||
createDevice(27, false)
|
||||
createDevice(27, true)
|
||||
createDevice(28, false)
|
||||
createDevice(28, true)
|
||||
createDevice(29, false)
|
||||
createDevice(29, true)
|
||||
createDevice(30, false, "aosp_atd")
|
||||
createDevice(30, true)
|
||||
// createDevice(31, false, "android-tv")
|
||||
createDevice(31, true, "aosp_atd")
|
||||
createDevice(32, true, "google_apis")
|
||||
createDevice(33, true, "google_apis")
|
||||
createDevice(34, true, "google_apis")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lsplant"))
|
||||
implementation("io.github.vvb2060.ndk:dobby:1.2")
|
||||
implementation(libs.dobby)
|
||||
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.3")
|
||||
androidTestImplementation("androidx.test:runner:1.4.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
|
||||
androidTestImplementation(libs.test.ext.junit)
|
||||
androidTestImplementation(libs.test.runner)
|
||||
androidTestImplementation(libs.test.espresso)
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@ -116,4 +117,32 @@ public class UnitTest {
|
||||
Assert.assertFalse((Boolean) callStaticMethod.invoke(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void t06_proxyMethod() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
||||
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] + args[1] + args[2] + args[3] + args[4] + args[5] + args[6] + args[7] + args[8] + args[9];
|
||||
}
|
||||
return method.invoke(proxy1, args);
|
||||
});
|
||||
var abstractMethod = proxy.getClass().getDeclaredMethod("abstractMethod", String.class, boolean.class, byte.class, short.class, int.class, long.class, float.class, double.class, Integer.class, Long.class);
|
||||
var abstractMethodReplacement = Replacement.class.getDeclaredMethod("manyParametersReplacement", Hooker.MethodCallback.class);
|
||||
var a = "test";
|
||||
var b = true;
|
||||
var c = (byte) 114;
|
||||
var d = (short) 514;
|
||||
var e = 19;
|
||||
var f = 19L;
|
||||
var g = 810f;
|
||||
var h = 12345f;
|
||||
var o = a + b + c + d + e + f + g + h + e + f;
|
||||
var r = a + b + c + d + e + f + g + h + e + f + "replace";
|
||||
|
||||
Assert.assertEquals(abstractMethod.invoke(proxy, a, b, c, d, e, f, g, h, e, f), o);
|
||||
Hooker hooker = Hooker.hook(abstractMethod, abstractMethodReplacement, new Replacement());
|
||||
Assert.assertNotNull(hooker);
|
||||
Assert.assertEquals(abstractMethod.invoke(proxy, a, b, c, d, e, f, g, h, e, f), r);
|
||||
Assert.assertEquals(hooker.backup.invoke(proxy, a, b, c, d, e, f, g, h, e, f), o);
|
||||
}
|
||||
}
|
||||
|
@ -50,4 +50,8 @@ public class LSPTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ForProxy {
|
||||
String abstractMethod(String a, boolean b, byte c, short d, int e, long f, float g, double h, Integer i, Long j);
|
||||
}
|
||||
}
|
||||
|
@ -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,7 +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");
|
||||
lsparself::Elf art("/libart.so");
|
||||
lsplant::InitInfo initInfo{
|
||||
.inline_hooker = InlineHooker,
|
||||
.inline_unhooker = InlineUnhooker,
|
||||
@ -63,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