Compare commits

..

No commits in common. "master" and "v4.2" have entirely different histories.
master ... v4.2

70 changed files with 2283 additions and 3027 deletions

3
.github/FUNDING.yml vendored
View File

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

94
.github/cuttlefish.sh vendored
View File

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

View File

@ -1,12 +0,0 @@
version: 2
updates:
- package-ecosystem: gradle
directory: "/"
schedule:
interval: daily
time: "21:00"
target-branch: master
groups:
maven-dependencies:
patterns:
- "*"

View File

@ -2,10 +2,8 @@ name: Build
on: on:
push: push:
branches: ["master"]
paths-ignore: paths-ignore:
- 'README.md' - 'README.md'
workflow_dispatch:
pull_request: pull_request:
@ -16,57 +14,41 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-14 ] os: [ ubuntu-latest, windows-latest, macOS-latest ]
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }} - name: Set up JDK 11
fetch-depth: 0 uses: actions/setup-java@v1
- name: Set up JDK 21
uses: actions/setup-java@v4
with: with:
distribution: 'temurin' java-version: '11'
java-version: '21' - name: Gradle cache
cache: 'gradle' uses: actions/cache@v2
- uses: seanmiddleditch/gha-setup-ninja@master
with: with:
version: 1.12.1 path: |
- name: ccache ~/.gradle/caches
uses: hendrikmuhs/ccache-action@v1 ~/.gradle/wrapper
with: key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
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 - name: Build with Gradle
run: | 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 echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew :lsplant:publishToMavenLocal :lsplant:prefabDebugPackage ./gradlew :lsplant:publishToMavenLocal
./gradlew --stop
env: env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}
- name: Upload library - name: Upload library
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: ${{ matrix.os }}-library name: ${{ matrix.os }}-library
path: ~/.m2 path: ~/.m2
include-hidden-files: true
test: test:
needs: build needs: build
name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }} name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }}
runs-on: ubuntu-latest runs-on: macos-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -141,122 +123,59 @@ jobs:
target: google_apis target: google_apis
arch: x86_64 arch: x86_64
- api-level: 33 - 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 target: google_apis
arch: x86_64 arch: x86_64
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }} - name: Set up JDK 11
- name: Set up JDK 21 uses: actions/setup-java@v1
uses: actions/setup-java@v4
with: with:
distribution: 'temurin' java-version: '11'
java-version: '21' - name: AVD cache
cache: 'gradle' uses: actions/cache@v2
- uses: seanmiddleditch/gha-setup-ninja@master id: avd-cache
with: with:
version: 1.12.1 path: |
- name: ccache ~/.android/avd/*
uses: hendrikmuhs/ccache-action@v1 ~/.android/adb*
key: avd-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }}
- name: Gradle cache
uses: actions/cache@v2
with: with:
key: ${{ runner.os }}-${{ github.sha }} path: |
restore-keys: ${{ runner.os }} ~/.gradle/caches
save: false ~/.gradle/wrapper
- name: Enable KVM group perms key: gradle-${{ runner.os }}-${{ matrix.api-level }}-${{ matrix.arch }}-${{ matrix.target }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/buildSrc/**/*.kt') }}
run: | restore-keys: gradle-${{ runner.os }}-
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - name: create AVD and generate snapshot for caching
sudo udevadm control --reload-rules if: steps.avd-cache.outputs.cache-hit != 'true'
sudo udevadm trigger --name-match=kvm uses: reactivecircus/android-emulator-runner@release/v2
- name: run tests
uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
script: | script: echo "Generated AVD snapshot for caching."
rm -rf $ANDROID_HOME/cmake force-avd-creation: false
ccache -o cache_dir=${{ github.workspace }}/.ccache emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
ccache -o hash_dir=false disable-animations: false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' avd-name: ${{ matrix.api-level }}_${{ matrix.arch }}
./gradlew :test:connectedCheck - name: run tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }}
target: ${{ matrix.target }}
script: ./gradlew :lsplant:prefabDebugConfigurePackage; ./gradlew :test:connectedCheck
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true disable-animations: true
avd-name: ${{ matrix.api-level }}_${{ matrix.arch }} avd-name: ${{ matrix.api-level }}_${{ matrix.arch }}
- name: Upload outputs - name: Upload outputs
if: always() if: always()
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: test-outputs-${{ matrix.arch }}-${{ matrix.target }}-${{ matrix.api-level }} name: test-outputs
path: | path: test/build/outputs
test/build/outputs
!test/build/outputs/apk
cf-test:
name: Test ${{ matrix.device }}
runs-on: ubuntu-24.04
env:
CF_HOME: /home/runner/aosp_cf_phone
strategy:
fail-fast: false
matrix:
include:
- branch: "aosp-main"
device: "aosp_cf_x86_64_phone"
steps:
- name: checkout
uses: actions/checkout@v4
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: ccache
uses: hendrikmuhs/ccache-action@v1
with:
key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }}
save: false
- name: Setup Cuttlefish environment
run: |
.github/cuttlefish.sh setup
.github/cuttlefish.sh download ${{ matrix.branch }} ${{ matrix.device }}
curl -LO https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-linux.zip
unzip ninja-linux.zip -d $ANDROID_HOME/platform-tools
chmod +x $ANDROID_HOME/platform-tools/ninja
rm -rf $ANDROID_HOME/cmake
ccache -o cache_dir=${{ github.workspace }}/.ccache
ccache -o hash_dir=false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
- name: Run Cuttlefish test
timeout-minutes: 10
run: su $USER -c '.github/cuttlefish.sh test'
- name: Upload logs on error
if: ${{ failure() }}
uses: actions/upload-artifact@v4
with:
name: "cvd-logs-${{ matrix.device }}"
path: |
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/logs
/home/runner/aosp_cf_phone/cuttlefish/instances/cvd-1/cuttlefish_config.json

View File

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

View File

@ -9,6 +9,12 @@ on:
# Allows you to run this workflow manually from the Actions tab # Allows you to run this workflow manually from the Actions tab
workflow_dispatch: 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 # Allow one concurrent deployment
concurrency: concurrency:
group: "pages" group: "pages"
@ -17,28 +23,21 @@ concurrency:
jobs: jobs:
# Single deploy job since we're just deploying # Single deploy job since we're just deploying
deploy: deploy:
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
permissions:
contents: read
pages: write
id-token: write
environment: environment:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
- name: Install doxygen - name: Install doxygen
run: sudo apt install -y doxygen run: sudo apt install -y doxygen
- name: Generate doxygen - name: Generate doxygen
run: doxygen docs/doxygen.cfg run: doxygen docs/doxygen.cfg
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v3 uses: actions/upload-pages-artifact@v1
with: with:
# Upload entire repository # Upload entire repository
path: 'docs/docs' path: 'docs/docs'

6
.gitmodules vendored
View File

@ -5,9 +5,3 @@
[submodule "docs/doxygen-awesome-css"] [submodule "docs/doxygen-awesome-css"]
path = docs/doxygen-awesome-css path = docs/doxygen-awesome-css
url = https://github.com/jothepro/doxygen-awesome-css.git url = https://github.com/jothepro/doxygen-awesome-css.git
[submodule "test/src/main/jni/external/lsparself"]
path = test/src/main/jni/external/lsparself
url = git@github.com:LSPosed/lsparself.git
[submodule "test/src/main/jni/external/lsprism"]
path = test/src/main/jni/external/lsprism
url = git@github.com:LSPosed/lsprism.git

View File

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

View File

@ -1,10 +1,6 @@
plugins { val androidTargetSdkVersion by extra(33)
alias(libs.plugins.lsplugin.publish)
}
val androidTargetSdkVersion by extra(35)
val androidMinSdkVersion by extra(21) val androidMinSdkVersion by extra(21)
val androidBuildToolsVersion by extra("35.0.0") val androidBuildToolsVersion by extra("33.0.0")
val androidCompileSdkVersion by extra(35) val androidCompileSdkVersion by extra(33)
val androidNdkVersion by extra("29.0.13113456") val androidNdkVersion by extra("25.1.8937393")
val androidCmakeVersion by extra("3.28.0+") val androidCmakeVersion by extra("3.22.1+")

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

View File

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

View File

@ -1,17 +0,0 @@
[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" }

Binary file not shown.

View File

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

43
gradlew vendored
View File

@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
# #
@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -82,11 +80,13 @@ do
esac esac
done done
# This is normally unused APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# 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 # 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"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -133,29 +133,22 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
if ! command -v java >/dev/null 2>&1 which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
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 Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) 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 ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | 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" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Collect all arguments for the java command;
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# Collect all arguments for the java command: # * put everything else in single quotes, so that it's not re-expanded.
# * 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 -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
@ -216,12 +205,6 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ 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. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

37
gradlew.bat vendored
View File

@ -13,10 +13,8 @@
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off @if "%DEBUG%" == "" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -27,8 +25,7 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -43,13 +40,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute if "%ERRORLEVEL%" == "0" goto execute
echo. 1>&2 echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 echo location of your Java installation.
goto fail goto fail
@ -59,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute if exist "%JAVA_EXE%" goto execute
echo. 1>&2 echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo. 1>&2 echo.
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. 1>&2 echo location of your Java installation.
goto fail goto fail
@ -78,15 +75,13 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd if "%ERRORLEVEL%"=="0" goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL% if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
if %EXIT_CODE% equ 0 set EXIT_CODE=1 exit /b 1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@ -1,10 +1,7 @@
plugins { plugins {
alias(libs.plugins.agp.lib) id("com.android.library")
alias(libs.plugins.lsplugin.jgit) id("maven-publish")
alias(libs.plugins.lsplugin.publish) id("signing")
alias(libs.plugins.lsplugin.cmaker)
`maven-publish`
signing
} }
val androidTargetSdkVersion: Int by rootProject.extra val androidTargetSdkVersion: Int by rootProject.extra
@ -23,10 +20,9 @@ android {
buildConfig = false buildConfig = false
prefabPublishing = true prefabPublishing = true
androidResources = false androidResources = false
prefab = true
} }
packaging { packagingOptions {
jniLibs { jniLibs {
excludes += "**.so" excludes += "**.so"
} }
@ -40,12 +36,49 @@ android {
defaultConfig { defaultConfig {
minSdk = androidMinSdkVersion minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
} }
buildTypes { buildTypes {
create("standalone") { all {
initWith(getByName("release")) externalNativeBuild {
matchingFallbacks += "release" 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",
"-Wl,--gc-sections",
"-D__FILE__=__FILE_NAME__",
"-Wl,--exclude-libs,ALL",
)
cppFlags("-std=c++20", *flags)
cFlags("-std=c18", *flags)
val configFlags = arrayOf(
"-Oz",
"-DNDEBUG"
).joinToString(" ")
arguments(
"-DANDROID_STL=c++_shared",
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
"-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/$name",
)
}
}
} }
} }
@ -67,104 +100,70 @@ android {
withSourcesJar() withSourcesJar()
withJavadocJar() withJavadocJar()
} }
singleVariant("standalone") {
withSourcesJar()
withJavadocJar()
}
} }
} }
cmaker { val symbolsTask = tasks.register<Jar>("generateReleaseSymbolsJar") {
default { from("${project.buildDir.absolutePath}/symbols/release")
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.layout.buildDirectory.file("symbols/release"))
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier = "symbols" archiveClassifier.set("symbols")
archiveBaseName = "release"
} }
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") { publishing {
from(project.layout.buildDirectory.file("symbols/standalone"))
exclude("**/dex_builder")
archiveClassifier = "symbols"
archiveBaseName = "standalone"
}
val repo = jgit.repo(true)
val ver = repo?.latestTag?.removePrefix("v") ?: "0.0"
println("${rootProject.name} version: $ver")
publish {
githubRepo = "LSPosed/LSPlant"
publications { publications {
fun MavenPublication.setup() { register<MavenPublication>("lsplant") {
group = "org.lsposed.lsplant" group = "org.lsposed.lsplant"
version = ver artifactId = "lsplant"
version = "4.2"
afterEvaluate {
from(components.getByName("release"))
artifact(symbolsTask)
}
pom { pom {
name = "LSPlant" name.set("LSPlant")
description = "A hook framework for Android Runtime (ART)" description.set("A hook framework for Android Runtime (ART)")
url = "https://github.com/LSPosed/LSPlant" url.set("https://github.com/LSPosed/LSPlant")
licenses { licenses {
license { license {
name = "GNU Lesser General Public License v3.0" name.set("GNU Lesser General Public License v3.0")
url = "https://github.com/LSPosed/LSPlant/blob/master/LICENSE" url.set("https://github.com/LSPosed/LSPlant/blob/master/LICENSE")
} }
} }
developers { developers {
developer { developer {
name = "Lsposed" name.set("Lsposed")
url = "https://lsposed.org" url.set("https://lsposed.org")
} }
} }
scm { scm {
connection = "scm:git:https://github.com/LSPosed/LSPlant.git" connection.set("scm:git:https://github.com/LSPosed/LSPlant.git")
url = "https://github.com/LSPosed/LSPlant" url.set("https://github.com/LSPosed/LSPlant")
} }
} }
} }
register<MavenPublication>("lsplant") { }
artifactId = "lsplant" repositories {
afterEvaluate { maven {
from(components.getByName("release")) name = "ossrh"
artifact(symbolsReleaseTask) url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
} credentials(PasswordCredentials::class)
setup()
} }
register<MavenPublication>("lsplantStandalone") { maven {
artifactId = "lsplant-standalone" name = "GitHubPackages"
afterEvaluate { url = uri("https://maven.pkg.github.com/LSPosed/LSPlant")
from(components.getByName("standalone")) credentials {
artifact(symbolsStandaloneTask) username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
} }
setup()
} }
} }
} }
signing {
val signingKey = findProperty("signingKey") as String?
val signingPassword = findProperty("signingPassword") as String?
if (signingKey != null && signingPassword != null) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
sign(publishing.publications)
}

View File

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

View File

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

View File

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

View File

@ -1,174 +0,0 @@
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

View File

@ -0,0 +1,60 @@
#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

View File

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

View File

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

View File

@ -0,0 +1,192 @@
#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

View File

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

View File

@ -0,0 +1,172 @@
#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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,63 +0,0 @@
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

View File

@ -0,0 +1,46 @@
#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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
#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

View File

@ -0,0 +1,17 @@
#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

View File

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

View File

@ -0,0 +1,110 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2022 LSPosed Contributors
*/
#pragma once
#include "utils/hook_helper.hpp"
namespace lsplant::art {
class Runtime {
public:
enum class RuntimeDebugState {
// This doesn't support any debug features / method tracing. This is the expected state
// usually.
kNonJavaDebuggable,
// This supports method tracing and a restricted set of debug features (for ex: redefinition
// isn't supported). We transition to this state when method tracing has started or when the
// debugger was attached and transition back to NonDebuggable once the tracing has stopped /
// the debugger agent has detached..
kJavaDebuggable,
// The runtime was started as a debuggable runtime. This allows us to support the extended
// set
// of debug features (for ex: redefinition). We never transition out of this state.
kJavaDebuggableAtInit
};
private:
inline static Runtime *instance_;
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, void *thiz, bool value) {
SetJavaDebuggableSym(thiz, value);
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetRuntimeDebugState, void *thiz, RuntimeDebugState value) {
SetRuntimeDebugStateSym(thiz, value);
}
inline static size_t debug_state_offset = 0U;
public:
inline static Runtime *Current() { return instance_; }
void SetJavaDebuggable(RuntimeDebugState value) {
if (SetJavaDebuggableSym) {
SetJavaDebuggable(this, value != RuntimeDebugState::kNonJavaDebuggable);
} else if (debug_state_offset > 0) {
*reinterpret_cast<RuntimeDebugState *>(reinterpret_cast<uintptr_t>(instance_) +
debug_state_offset) = value;
}
}
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (void **instance; !RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E")) {
return false;
} else if (instance_ = reinterpret_cast<Runtime *>(*instance); !instance_) {
return false;
}
LOGD("runtime instance = %p", instance_);
if (sdk_int >= __ANDROID_API_O__) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable,
"_ZN3art7Runtime17SetJavaDebuggableEb") &&
!RETRIEVE_MEM_FUNC_SYMBOL(
SetRuntimeDebugState,
"_ZN3art7Runtime20SetRuntimeDebugStateENS0_17RuntimeDebugStateE")) {
return false;
}
}
if (SetRuntimeDebugStateSym) {
static constexpr size_t kLargeEnoughSizeForRuntime = 4096;
std::array<uint8_t, kLargeEnoughSizeForRuntime> code;
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggable) != 0);
static_assert(static_cast<int>(RuntimeDebugState::kJavaDebuggableAtInit) != 0);
code.fill(uint8_t{0});
auto *const fake_runtime = reinterpret_cast<Runtime *>(code.data());
SetRuntimeDebugState(fake_runtime, RuntimeDebugState::kJavaDebuggable);
for (size_t i = 0; i < kLargeEnoughSizeForRuntime; ++i) {
if (*reinterpret_cast<RuntimeDebugState *>(
reinterpret_cast<uintptr_t>(fake_runtime) + i) ==
RuntimeDebugState::kJavaDebuggable) {
LOGD("found debug_state at offset %zu", i);
debug_state_offset = i;
break;
}
}
if (debug_state_offset == 0) {
LOGE("failed to find debug_state");
return false;
}
}
return true;
}
};
} // namespace lsplant::art

View File

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

View File

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

View File

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

View File

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

View File

@ -1,177 +0,0 @@
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

View File

@ -0,0 +1,179 @@
#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

@ -1 +1 @@
Subproject commit 67077df2a53be0cabb9f16e8a050acdd825f7d69 Subproject commit 33c8b50ac7d2498c0db8fe2c6e4273dabc82de75

View File

@ -3,9 +3,8 @@
#include <jni.h> #include <jni.h>
#include <string_view> #include <string_view>
#include <functional>
/// \namespace lsplant /// \namespace namespace of LSPlant
namespace lsplant { namespace lsplant {
inline namespace v2 { inline namespace v2 {

View File

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

View File

@ -1,223 +1,207 @@
#pragma once #pragma once
#include <android/log.h>
#include <concepts> #include <concepts>
#include "jni_helper.hpp"
#include "lsplant.hpp" #include "lsplant.hpp"
#include "type_traits.hpp"
#if defined(__LP64__)
#define LP_SELECT(lp32, lp64) lp64
#else
#define LP_SELECT(lp32, lp64) lp32
#endif
#define CONCATENATE(a, b) a##b
#define CREATE_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::Hooker<RET PARAMS, decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define CREATE_MEM_HOOK_STUB_ENTRY(SYM, RET, FUNC, PARAMS, DEF) \
inline static struct : public lsplant::MemHooker<RET PARAMS, \
decltype(CONCATENATE(SYM, _tstr))>{ \
inline static RET replace PARAMS DEF} FUNC
#define RETRIEVE_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_MEM_FUNC_SYMBOL(name, ...) \
(name##Sym = reinterpret_cast<name##Type::FunType>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define RETRIEVE_FIELD_SYMBOL(name, ...) \
(name = reinterpret_cast<decltype(name)>(lsplant::Dlsym(handler, __VA_ARGS__)))
#define CREATE_FUNC_SYMBOL_ENTRY(ret, func, ...) \
typedef ret (*func##Type)(__VA_ARGS__); \
inline static ret (*func##Sym)(__VA_ARGS__); \
inline static ret func(__VA_ARGS__)
#define CREATE_MEM_FUNC_SYMBOL_ENTRY(ret, func, thiz, ...) \
using func##Type = lsplant::MemberFunction<ret(__VA_ARGS__)>; \
inline static func##Type func##Sym; \
inline static ret func(thiz, ##__VA_ARGS__)
namespace lsplant { namespace lsplant {
template <size_t N> using HookHandler = InitInfo;
struct FixedString {
consteval FixedString(const char (&str)[N]) { std::copy_n(str, N, data); }
char data[N] = {};
};
template<typename T> template <char... chars>
concept FuncType = std::is_function_v<T> || std::is_member_function_pointer_v<T>; struct tstring : public std::integer_sequence<char, chars...> {
inline constexpr static const char *c_str() { return str_; }
template <FixedString, FuncType> inline constexpr operator std::string_view() const { return {c_str(), sizeof...(chars)}; }
struct Function;
template <FixedString Sym, typename Ret, typename... Args>
struct Function<Sym, Ret(Args...)> {
[[gnu::always_inline]] static Ret operator()(Args... args) {
return inner_.function_(args...);
}
[[gnu::always_inline]] operator bool() { return inner_.raw_function_; }
[[gnu::always_inline]] auto operator&() const { return inner_.function_; }
[[gnu::always_inline]] Function &operator=(void *function) {
inner_.raw_function_ = function;
return *this;
}
private: private:
inline static union { inline static constexpr char str_[]{chars..., '\0'};
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> template <typename T, T... chars>
struct Function<Sym, Ret(This::*)(Args...)> { inline constexpr tstring<chars...> operator""_tstr() {
[[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 {
void *raw_function_ = nullptr;
[[maybe_unused]] std::ptrdiff_t adj = 0;
};
} inner_;
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_) + sizeof(inner_.adj));
};
template <FixedString, typename T>
struct Field {
[[gnu::always_inline]] T *operator->() { return inner_.field_; }
[[gnu::always_inline]] T &operator*() { return *inner_.field_; }
[[gnu::always_inline]] operator bool() { return inner_.raw_field_ != nullptr; }
[[gnu::always_inline]] Field &operator=(void *field) {
inner_.raw_field_ = field;
return *this;
}
private:
inline static union {
void *raw_field_ = nullptr;
T *field_;
} inner_;
static_assert(sizeof(inner_.field_) == sizeof(inner_.raw_field_));
};
template <FixedString, FuncType>
struct Hooker;
template <FixedString Sym, typename Ret, typename... Args>
struct Hooker<Sym, Ret(Args...)> : Function<Sym, Ret(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(Args...)) {
replace_ = replace;
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(Args...) = nullptr;
};
template <FixedString Sym, class This, typename Ret, typename... Args>
struct Hooker<Sym, Ret(This::*)(Args...)> : Function<Sym, Ret(This::*)(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(This::*)(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(This *, Args...)) {
replace_ = replace;
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(This *, Args...) = nullptr;
};
struct HookHandler {
HookHandler(const InitInfo &info) : info_(info) {}
template <typename T>
[[gnu::always_inline]] bool operator()(T &&arg) const {
return handle(std::forward<T>(arg), false);
}
template <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;
}
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 {}; return {};
} }
template<FixedString S, FixedString P> template <char... as, char... bs>
consteval auto operator|([[maybe_unused]] Symbol<S> a, [[maybe_unused]] Symbol<P> b) { inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) {
#if defined(__LP64__) return {};
return b;
#else
return a;
#endif
} }
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);
}
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...);
struct {
decltype(func) p;
std::ptrdiff_t adj;
} data;
} u{.data = {func, 0}};
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
return u.f;
}
template <std::same_as<void> T, typename Return, typename... Args>
inline auto memfun_cast(Return (*func)(T *, Args...)) {
return memfun_cast<T>(func);
}
template <typename, typename = void>
class MemberFunction;
template <typename This, typename Return, typename... Args>
class MemberFunction<Return(Args...), This> {
using SelfType = MemberFunction<Return(This *, Args...), This>;
using ThisType = std::conditional_t<std::is_same_v<This, void>, SelfType, This>;
using MemFunType = Return (ThisType::*)(Args...);
public:
using FunType = Return (*)(This *, Args...);
private:
MemFunType f_ = nullptr;
public:
MemberFunction() = default;
MemberFunction(FunType f) : f_(memfun_cast<ThisType>(f)) {}
MemberFunction(MemFunType f) : f_(f) {}
Return operator()(This *thiz, Args... args) {
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
}
inline operator bool() { return f_ != nullptr; }
};
// deduction guide
template <typename This, typename Return, typename... Args>
MemberFunction(Return (*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
template <typename This, typename Return, typename... Args>
MemberFunction(Return (This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
template <typename, typename>
struct Hooker;
template <typename Ret, typename... Args, char... cs>
struct Hooker<Ret(Args...), tstring<cs...>> {
inline static Ret (*backup)(Args...) = nullptr;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
template <typename, typename>
struct MemHooker;
template <typename Ret, typename This, typename... Args, char... cs>
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
inline static MemberFunction<Ret(Args...)> backup;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
template <typename T>
concept HookerType = requires(T a) {
a.backup;
a.replace;
};
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)));
}
return true;
} else {
return false;
}
}
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,
#else
"HookHelper",
#endif
"Hook Fails: %*s", static_cast<int>(first.sym.size()),
first.sym.data());
return false;
}
return true;
}
} // namespace lsplant } // namespace lsplant

View File

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

View File

@ -6,8 +6,6 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include "type_traits.hpp"
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-partial-specialization" #pragma clang diagnostic ignored "-Winvalid-partial-specialization"
#pragma clang diagnostic ignored "-Wunknown-pragmas" #pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -18,6 +16,15 @@
void operator=(const TypeName &) = delete void operator=(const TypeName &) = delete
namespace lsplant { namespace lsplant {
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
template <class... Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
template <class T, template <class, class...> class U>
inline constexpr bool is_instance_v = is_instance<T, U>::value;
template <typename T> template <typename T>
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>; concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
@ -54,10 +61,13 @@ public:
T get() const { return local_ref_; } T get() const { return local_ref_; }
ScopedLocalRef<T> clone() const { operator T() const { return local_ref_; }
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 { ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release()); reset(s.release());
env_ = s.env_; env_ = s.env_;
@ -77,8 +87,6 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
}; };
class JObjectArrayElement;
template <typename T> template <typename T>
concept JArray = std::is_base_of_v<std::remove_pointer_t<_jarray>, std::remove_pointer_t<T>>; concept JArray = std::is_base_of_v<std::remove_pointer_t<_jarray>, std::remove_pointer_t<T>>;
@ -109,11 +117,9 @@ public:
}; };
template <typename T, typename U> template <typename T, typename U>
concept ScopeOrRaw = concept ScopeOrRaw = std::is_convertible_v<T, U> ||
std::is_convertible_v<T, U> || (is_instance_v<std::decay_t<T>, ScopedLocalRef>
(is_instance_v<std::decay_t<T>, ScopedLocalRef> && &&std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
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> template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>; concept ScopeOrClass = ScopeOrRaw<T, jclass>;
@ -124,11 +130,10 @@ concept ScopeOrObject = ScopeOrRaw<T, jobject>;
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) { inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
if (auto exception = env->ExceptionOccurred()) { if (auto exception = env->ExceptionOccurred()) {
env->ExceptionClear(); env->ExceptionClear();
jclass log = (jclass)env->FindClass("android/util/Log"); static jclass log = (jclass)env->NewGlobalRef(env->FindClass("android/util/Log"));
static jmethodID toString = env->GetStaticMethodID( static jmethodID toString = env->GetStaticMethodID(
log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;"); log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception); auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
env->DeleteLocalRef(log);
env->DeleteLocalRef(exception); env->DeleteLocalRef(exception);
return {env, str}; return {env, str};
} }
@ -141,8 +146,6 @@ template <typename T>
return x.data(); return x.data();
else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>) else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>)
return x.get(); return x.get();
else if constexpr (std::is_same_v<std::decay_t<T>, JObjectArrayElement>)
return x.get();
else else
return std::forward<T>(x); return std::forward<T>(x);
} }
@ -173,27 +176,28 @@ inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
class JUTFString { class JUTFString {
public: public:
JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {} inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
JUTFString(const ScopedLocalRef<jstring> &jstr) inline JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {} : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) { inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr)
: env_(env), jstr_(jstr) {
if (env_ && jstr_) if (env_ && jstr_)
cstr_ = env_->GetStringUTFChars(jstr, nullptr); cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else else
cstr_ = default_cstr; cstr_ = default_cstr;
} }
operator const char *() const { return cstr_; } inline operator const char *() const { return cstr_; }
operator const std::string() const { return cstr_; } inline operator const std::string() const { return cstr_; }
operator const bool() const { return cstr_ != nullptr; } inline operator const bool() const { return cstr_ != nullptr; }
auto get() const { return cstr_; } inline auto get() const { return cstr_; }
~JUTFString() { inline ~JUTFString() {
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_); if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
} }
@ -225,8 +229,8 @@ private:
}; };
template <typename Func, typename... Args> template <typename Func, typename... Args>
requires(std::is_function_v<Func>) requires(std::is_function_v<Func>)
[[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) { [[maybe_unused]] inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&...args) {
struct finally { struct finally {
finally(JNIEnv *env) : env_(env) {} finally(JNIEnv *env) : env_(env) {}
@ -505,13 +509,6 @@ template <ScopeOrClass Class>
isStatic); isStatic);
} }
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_ToReflectedField(JNIEnv *env, Class &&clazz, jfieldID field,
jboolean isStatic = JNI_FALSE) {
return JNI_SafeInvoke(env, &JNIEnv::ToReflectedField, std::forward<Class>(clazz), field,
isStatic);
}
// functions to method // functions to method
// virtual methods // virtual methods
@ -670,77 +667,84 @@ template <ScopeOrClass Class, typename... Args>
std::forward<Args>(args)...); std::forward<Args>(args)...);
} }
// non-virtual methods // non-vritual methods
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallCallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualByteMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, Class &&clazz, [[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj,
jmethodID method, Args &&...args) { Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj, [[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method, Class &&clazz, jmethodID method,
Args &&...args) { Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj), return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...); std::forward<Class>(clazz), method, std::forward<Args>(args)...);
} }
@ -769,12 +773,6 @@ template <ScopeOrObject Object, ScopeOrClass Class>
std::forward<Class>(clazz)); std::forward<Class>(clazz));
} }
template <ScopeOrObject Object1, ScopeOrObject Object2>
[[maybe_unused]] inline auto JNI_IsSameObject(JNIEnv *env, Object1 &&a, Object2 &&b) {
return JNI_SafeInvoke(env, &JNIEnv::IsSameObject, std::forward<Object1>(a),
std::forward<Object2>(b));
}
template <ScopeOrObject Object> template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) { [[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef( return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(
@ -782,17 +780,11 @@ template <ScopeOrObject Object>
} }
template <typename U, typename T> template <typename U, typename T>
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) [[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) requires(
requires(std::is_convertible_v<T, _jobject *>) std::is_convertible_v<T, _jobject *>) {
{
return ScopedLocalRef<U>(std::move(x)); 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) { [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) {
return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity); return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity);
} }
@ -800,6 +792,11 @@ template <typename U>
template <JArray T> template <JArray T>
struct JArrayUnderlyingTypeHelper; struct JArrayUnderlyingTypeHelper;
template <>
struct JArrayUnderlyingTypeHelper<jobjectArray> {
using Type = ScopedLocalRef<jobject>;
};
template <> template <>
struct JArrayUnderlyingTypeHelper<jbooleanArray> { struct JArrayUnderlyingTypeHelper<jbooleanArray> {
using Type = jboolean; using Type = jboolean;
@ -845,6 +842,10 @@ using JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper<T>::Type;
template <JArray T> template <JArray T>
class ScopedLocalRef<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: public:
class Iterator { class Iterator {
friend class ScopedLocalRef<T>; friend class ScopedLocalRef<T>;
@ -902,7 +903,13 @@ public:
reset(local_ref); reset(local_ref);
} }
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); } 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;
}
template <JObject U> template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
@ -916,13 +923,25 @@ public:
if (local_ref_ != nullptr) { if (local_ref_ != nullptr) {
ReleaseElements(modified_ ? 0 : JNI_ABORT); ReleaseElements(modified_ ? 0 : JNI_ABORT);
env_->DeleteLocalRef(local_ref_); 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; elements_ = nullptr;
} }
local_ref_ = ptr; local_ref_ = ptr;
size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0; size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;
if (!local_ref_) return; if (!local_ref_) return;
static_assert(!std::is_same_v<T, jobjectArray>); if constexpr (std::is_same_v<T, jobjectArray>) {
if constexpr (std::is_same_v<T, jbooleanArray>) { 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>) {
elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr); elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr);
} else if constexpr (std::is_same_v<T, jbyteArray>) { } else if constexpr (std::is_same_v<T, jbyteArray>) {
elements_ = env_->GetByteArrayElements(local_ref_, nullptr); elements_ = env_->GetByteArrayElements(local_ref_, nullptr);
@ -947,12 +966,20 @@ public:
size_ = 0; size_ = 0;
local_ref_ = nullptr; local_ref_ = nullptr;
ReleaseElements(modified_ ? 0 : JNI_ABORT); 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; elements_ = nullptr;
return localRef; return localRef;
} }
T get() const { return local_ref_; } T get() const { return local_ref_; }
explicit operator T() const { return local_ref_; }
JArrayUnderlyingType<T> &operator[](size_t index) { JArrayUnderlyingType<T> &operator[](size_t index) {
modified_ = true; modified_ = true;
return elements_[index]; return elements_[index];
@ -995,7 +1022,11 @@ public:
private: private:
void ReleaseElements(jint mode) { void ReleaseElements(jint mode) {
if (!local_ref_ || !elements_) return; if (!local_ref_ || !elements_) return;
if constexpr (std::is_same_v<T, jbooleanArray>) { 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>) {
env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode); env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode);
} else if constexpr (std::is_same_v<T, jbyteArray>) { } else if constexpr (std::is_same_v<T, jbyteArray>) {
env_->ReleaseByteArrayElements(local_ref_, elements_, mode); env_->ReleaseByteArrayElements(local_ref_, elements_, mode);
@ -1022,255 +1053,6 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); 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 // functions to array
template <ScopeOrRaw<jarray> Array> template <ScopeOrRaw<jarray> Array>

View File

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

View File

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

View File

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

View File

@ -1,39 +1,32 @@
module;
#include "lsplant.hpp" #include "lsplant.hpp"
#include <android/api-level.h> #include <android/api-level.h>
#include <bits/sysconf.h>
#include <jni.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/system_properties.h> #include <sys/system_properties.h>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <bit>
#include <string_view>
#include <tuple>
#include "logging.hpp" #include "art/mirror/class.hpp"
#include "art/runtime/art_method.hpp"
module lsplant; #include "art/runtime/class_linker.hpp"
#include "art/runtime/dex_file.hpp"
import dex_builder; #include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/runtime/instrumentation.hpp"
import :common; #include "art/runtime/jit/jit_code_cache.hpp"
import :art_method; #include "art/runtime/jni/jni_id_manager.h"
import :clazz; #include "art/runtime/runtime.hpp"
import :thread; #include "art/runtime/thread.hpp"
import :instrumentation; #include "art/runtime/thread_list.hpp"
import :runtime; #include "common.hpp"
import :thread_list; #include "dex_builder.h"
import :class_linker; #include "utils/jni_helper.hpp"
import :scope_gc_critical_section;
import :jit_code_cache;
import :jni_id_manager;
import :dex_file;
import :jit;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "ConstantConditionsOC"
#pragma ide diagnostic ignored "Simplify"
#pragma ide diagnostic ignored "UnreachableCode"
namespace lsplant { namespace lsplant {
using art::ArtMethod; using art::ArtMethod;
@ -43,14 +36,10 @@ using art::Instrumentation;
using art::Runtime; using art::Runtime;
using art::Thread; using art::Thread;
using art::gc::ScopedGCCriticalSection; using art::gc::ScopedGCCriticalSection;
using art::jit::Jit;
using art::jit::JitCodeCache; using art::jit::JitCodeCache;
using art::jni::JniIdManager; using art::jni::JniIdManager;
using art::mirror::Class; using art::mirror::Class;
using art::thread_list::ScopedSuspendAll; using art::thread_list::ScopedSuspendAll;
using art::JavaDebuggableGuard;
using namespace std::string_view_literals;
namespace { namespace {
template <typename T, T... chars> template <typename T, T... chars>
@ -75,17 +64,11 @@ consteval inline auto GetTrampoline() {
// NOLINTNEXTLINE // NOLINTNEXTLINE
uint8_t{56u}, uintptr_t{1u}); uint8_t{56u}, uintptr_t{1u});
} }
if constexpr (kArch == Arch::kX86_64) { if constexpr (kArch == Arch::kX8664) {
return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x00\xc3"_uarr, return std::make_tuple("\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x00\xc3"_uarr,
// NOLINTNEXTLINE // NOLINTNEXTLINE
uint8_t{96u}, uintptr_t{2u}); 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(); auto [trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
@ -96,22 +79,20 @@ jmethodID class_get_name = nullptr;
jmethodID class_get_class_loader = nullptr; jmethodID class_get_class_loader = nullptr;
jmethodID class_get_declared_constructors = nullptr; jmethodID class_get_declared_constructors = nullptr;
jfieldID class_access_flags = nullptr; jfieldID class_access_flags = nullptr;
jmethodID dex_file_init_with_cl = nullptr; jclass in_memory_class_loader = nullptr;
jmethodID dex_file_init = nullptr; jmethodID in_memory_class_loader_init = nullptr;
jmethodID load_class = nullptr; jmethodID load_class = nullptr;
jmethodID set_accessible = nullptr; jmethodID set_accessible = nullptr;
jclass executable = nullptr; jclass executable = nullptr;
// for proxy method
jmethodID method_get_parameter_types = nullptr;
jmethodID method_get_return_type = nullptr;
// for old platform // for old platform
jclass path_class_loader = nullptr;
jmethodID path_class_loader_init = nullptr; jmethodID path_class_loader_init = nullptr;
constexpr auto kInternalMethods = std::make_tuple( constexpr auto kInternalMethods = std::make_tuple(
&method_get_name, &method_get_declaring_class, &class_get_name, &class_get_class_loader, &method_get_name, &method_get_declaring_class, &class_get_name, &class_get_class_loader,
&class_get_declared_constructors, &dex_file_init, &dex_file_init_with_cl, &load_class, &class_get_declared_constructors, &in_memory_class_loader_init, &load_class, &set_accessible,
&set_accessible, &method_get_parameter_types, &method_get_return_type, &path_class_loader_init); &path_class_loader_init);
std::string generated_class_name; std::string generated_class_name;
std::string generated_source_name; std::string generated_source_name;
@ -158,20 +139,7 @@ bool InitJNI(JNIEnv *env) {
if (method_get_declaring_class = if (method_get_declaring_class =
JNI_GetMethodID(env, executable, "getDeclaringClass", "()Ljava/lang/Class;"); JNI_GetMethodID(env, executable, "getDeclaringClass", "()Ljava/lang/Class;");
!method_get_declaring_class) { !method_get_declaring_class) {
LOGE("Failed to find getDeclaringClass method"); LOGE("Failed to find getName 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; return false;
} }
auto clazz = JNI_FindClass(env, "java/lang/Class"); auto clazz = JNI_FindClass(env, "java/lang/Class");
@ -204,37 +172,32 @@ bool InitJNI(JNIEnv *env) {
LOGE("Failed to find Class.accessFlags"); LOGE("Failed to find Class.accessFlags");
return false; return false;
} }
auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader"); if (sdk_int >= __ANDROID_API_O__ &&
if (!path_class_loader) { (in_memory_class_loader = JNI_NewGlobalRef(
LOGE("Failed to find PathClassLoader"); env, JNI_FindClass(env, "dalvik/system/InMemoryDexClassLoader")))) [[likely]] {
return false; in_memory_class_loader_init =
} JNI_GetMethodID(env, in_memory_class_loader, "<init>",
if (path_class_loader_init = JNI_GetMethodID(env, path_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"); "(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
!path_class_loader_init) { if (!path_class_loader_init) {
LOGE("Failed to find PathClassLoader.<init>"); LOGE("Failed to find PathClassLoader.<init>");
return false; return false;
} }
auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile"); load_class =
if (!dex_file_class) { JNI_GetMethodID(env, dex_file, "loadClass",
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;"); "(Ljava/lang/String;Ljava/lang/ClassLoader;)Ljava/lang/Class;");
!load_class) { }
if (!load_class) {
LOGE("Failed to find a suitable way to load class"); LOGE("Failed to find a suitable way to load class");
return false; return false;
} }
@ -258,6 +221,9 @@ inline void UpdateTrampoline(uint8_t offset) {
} }
bool InitNative(JNIEnv *env, const HookHandler &handler) { bool InitNative(JNIEnv *env, const HookHandler &handler) {
if (!handler.inline_hooker || !handler.inline_unhooker || !handler.art_symbol_resolver) {
return false;
}
if (!ArtMethod::Init(env, handler)) { if (!ArtMethod::Init(env, handler)) {
LOGE("Failed to init art method"); LOGE("Failed to init art method");
return false; return false;
@ -267,18 +233,14 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init thread"); LOGE("Failed to init thread");
return false; return false;
} }
if (!ClassLinker::Init(handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!Class::Init(handler)) { if (!Class::Init(handler)) {
LOGE("Failed to init mirror class"); LOGE("Failed to init mirror class");
return false; return false;
} }
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
if (!ClassLinker::Init(env, handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!ScopedSuspendAll::Init(handler)) { if (!ScopedSuspendAll::Init(handler)) {
LOGE("Failed to init scoped suspend all"); LOGE("Failed to init scoped suspend all");
return false; return false;
@ -291,10 +253,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jit code cache"); LOGE("Failed to init jit code cache");
return false; return false;
} }
if (!Jit::Init(handler)) {
LOGE("Failed to init jit");
return false;
}
if (!DexFile::Init(env, handler)) { if (!DexFile::Init(env, handler)) {
LOGE("Failed to init dex file"); LOGE("Failed to init dex file");
return false; return false;
@ -307,6 +265,10 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jni id manager"); LOGE("Failed to init jni id manager");
return false; return false;
} }
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
// This should always be the last one // This should always be the last one
if (IsJavaDebuggable(env)) { if (IsJavaDebuggable(env)) {
@ -334,6 +296,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
auto parameter_types = std::vector<TypeDescriptor>(); auto parameter_types = std::vector<TypeDescriptor>();
parameter_types.reserve(shorty.size() - 1); parameter_types.reserve(shorty.size() - 1);
std::string storage;
auto return_type = auto return_type =
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]); shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(shorty[0]);
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
@ -353,7 +316,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
.Encode(); .Encode();
auto hook_builder{cbuilder.CreateMethod( auto hook_builder{cbuilder.CreateMethod(
generated_method_name == "{target}"sv ? method_name.data() : generated_method_name, generated_method_name == "{target}" ? method_name.data() : generated_method_name,
Prototype{return_type, parameter_types})}; Prototype{return_type, parameter_types})};
// allocate tmp first because of wide // allocate tmp first because of wide
auto tmp{hook_builder.AllocRegister()}; auto tmp{hook_builder.AllocRegister()};
@ -400,6 +363,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
backup_builder.BuildReturn(zero, /*is_object=*/false, true); backup_builder.BuildReturn(zero, /*is_object=*/false, true);
} else { } else {
LiveRegister zero = backup_builder.AllocRegister(); LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0); backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false); backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
} }
@ -409,19 +373,17 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
jclass target_class = nullptr; jclass target_class = nullptr;
ScopedLocalRef<jobject> java_dex_file{nullptr}; if (in_memory_class_loader_init) [[likely]] {
auto dex_buffer = JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()),
if (auto dex_file_class = JNI_FindClass(env, "dalvik/system/DexFile"); dex_file_init_with_cl) { static_cast<jlong>(image.size()));
java_dex_file = JNI_NewObject( auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
env, dex_file_class, dex_file_init_with_cl, dex_buffer, class_loader);
JNI_NewObjectArray( if (my_cl) {
env, 1, JNI_FindClass(env, "java/nio/ByteBuffer"), target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size())), env, my_cl, load_class,
nullptr, nullptr); JNI_NewStringUTF(env, generated_class_name.data())))
} else if (dex_file_init) { .release();
java_dex_file = JNI_NewObject( }
env, dex_file_class, dex_file_init,
JNI_NewDirectByteBuffer(env, const_cast<void *>(image.ptr()), image.size()));
} else { } else {
void *target = void *target =
mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); mmap(nullptr, image.size(), PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
@ -429,24 +391,20 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
mprotect(target, image.size(), PROT_READ); mprotect(target, image.size(), PROT_READ);
std::string err_msg; std::string err_msg;
const auto *dex = DexFile::OpenMemory( const auto *dex = DexFile::OpenMemory(
reinterpret_cast<const uint8_t *>(target), image.size(), target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name,
generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg); &err_msg);
if (!dex) { if (!dex) {
LOGE("Failed to open memory dex: %s", err_msg.data()); LOGE("Failed to open memory dex: %s", err_msg.data());
} else {
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) {
if (auto path_class_loader = JNI_FindClass(env, "dalvik/system/PathClassLoader"); auto p = JNI_NewObject(env, path_class_loader, path_class_loader_init,
java_dex_file) {
auto my_cl = JNI_NewObject(env, path_class_loader, path_class_loader_init,
JNI_NewStringUTF(env, ""), class_loader); JNI_NewStringUTF(env, ""), class_loader);
target_class = target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(
JNI_Cast<jclass>( env, java_dex_file, load_class,
JNI_CallObjectMethod(env, java_dex_file, load_class, env->NewStringUTF(generated_class_name.data()), p))
JNI_NewStringUTF(env, generated_class_name.data()), my_cl)) .release();
.release(); }
} }
if (target_class) { if (target_class) {
@ -468,8 +426,7 @@ static_assert(std::endian::native == std::endian::little, "Unsupported architect
union Trampoline { union Trampoline {
public: public:
uintptr_t address; uintptr_t address;
unsigned count4k : 12; unsigned count : 12;
unsigned count16k : 14;
}; };
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture"); static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
@ -478,16 +435,16 @@ static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architect
std::atomic_uintptr_t trampoline_pool{0}; std::atomic_uintptr_t trampoline_pool{0};
std::atomic_flag trampoline_lock{false}; std::atomic_flag trampoline_lock{false};
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize); constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
constexpr uintptr_t kAddressMask = 0xFFFU; constexpr uintptr_t kAddressMask = 0xFFFU;
void *GenerateTrampolineFor(art::ArtMethod *hook) { void *GenerateTrampolineFor(art::ArtMethod *hook) {
static const size_t kPageSize = sysconf(_SC_PAGESIZE); // assume
static const size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
unsigned count; unsigned count;
uintptr_t address; uintptr_t address;
while (true) { while (true) {
auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)}; auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = kPageSize == 16384 ? tl.count16k : tl.count4k; count = tl.count;
address = tl.address & ~kAddressMask; address = tl.address & ~kAddressMask;
if (address == 0 || count >= kTrampolineNumPerPage) { if (address == 0 || count >= kTrampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) { if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
@ -505,7 +462,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
} }
count = 0; count = 0;
tl.address = address; tl.address = address;
kPageSize == 16384 ? tl.count16k = count + 1 : tl.count4k = count + 1; tl.count = count + 1;
trampoline_pool.store(tl.address, std::memory_order_release); trampoline_pool.store(tl.address, std::memory_order_release);
trampoline_lock.clear(std::memory_order_release); trampoline_lock.clear(std::memory_order_release);
trampoline_lock.notify_all(); trampoline_lock.notify_all();
@ -539,12 +496,18 @@ bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
} else { } else {
LOGV("Generated trampoline %p", entrypoint); LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable();
hook->SetNonCompilable(); hook->SetNonCompilable();
target->BackupTo(backup); // copy after setNonCompilable
backup->CopyFrom(target);
target->ClearFastInterpretFlag();
target->SetEntryPoint(entrypoint); target->SetEntryPoint(entrypoint);
if (!backup->IsStatic()) backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target, LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(), target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint()); backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
@ -567,79 +530,13 @@ bool DoUnHook(ArtMethod *target, ArtMethod *backup) {
return true; 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 &param : parameter_types) {
out += type_to_shorty(param);
}
return out;
}
} // namespace } // namespace
inline namespace v2 { inline namespace v2 {
extern "C++" {
using ::lsplant::IsHooked; using ::lsplant::IsHooked;
[[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) { [[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) {
if (!info.inline_hooker || !info.inline_unhooker || !info.art_symbol_resolver ||
!info.art_symbol_prefix_resolver) {
return false;
}
bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info); bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info);
return kInit; return kInit;
} }
@ -692,9 +589,7 @@ using ::lsplant::IsHooked;
} }
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope( std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env, env,
BuildDex(env, callback_class_loader.get(), BuildDex(env, callback_class_loader, ArtMethod::GetMethodShorty(env, target_method),
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(), is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
class_name.get(), callback_method_name.get())); class_name.get(), callback_method_name.get()));
if (!built_class || !hooker_field || !hook_method || !backup_method) { if (!built_class || !hooker_field || !hook_method || !backup_method) {
@ -708,8 +603,8 @@ using ::lsplant::IsHooked;
JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE); JNI_CallVoidMethod(env, reflected_backup, set_accessible, JNI_TRUE);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook.get()); auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup.get()); auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object); JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
@ -744,19 +639,28 @@ using ::lsplant::IsHooked;
auto *target = ArtMethod::FromReflectedMethod(env, target_method); auto *target = ArtMethod::FromReflectedMethod(env, target_method);
jobject reflected_backup = nullptr; jobject reflected_backup = nullptr;
art::ArtMethod *backup = nullptr; art::ArtMethod *backup = nullptr;
if (!hooked_methods_.erase_if(target, [&reflected_backup, &backup](const auto &it) { {
std::tie(reflected_backup, backup) = it.second; std::unique_lock lk(hooked_methods_lock_);
return reflected_backup != nullptr; if (auto it = hooked_methods_.find(target); it != hooked_methods_.end()) [[likely]] {
})) { std::tie(reflected_backup, backup) = it->second;
LOGE("Unable to unhook a method that is not hooked"); if (reflected_backup == nullptr) {
return false; 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);
}
}
} }
// 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); auto *backup_method = env->FromReflectedMethod(reflected_backup);
env->DeleteGlobalRef(reflected_backup); env->DeleteGlobalRef(reflected_backup);
if (DoUnHook(target, backup)) { if (DoUnHook(target, backup)) {
@ -792,7 +696,7 @@ using ::lsplant::IsHooked;
if (auto *backup = IsHooked(art_method); backup) { if (auto *backup = IsHooked(art_method); backup) {
art_method = backup; art_method = backup;
} }
if (!art_method || art_method->IsNative()) { if (!art_method) {
return false; return false;
} }
return ClassLinker::SetEntryPointsToInterpreter(art_method); return ClassLinker::SetEntryPointsToInterpreter(art_method);
@ -830,11 +734,20 @@ using ::lsplant::IsHooked;
} }
[[maybe_unused]] bool MakeDexFileTrusted(JNIEnv *env, jobject cookie) { [[maybe_unused]] bool MakeDexFileTrusted(JNIEnv *env, jobject cookie) {
JavaDebuggableGuard guard; struct Guard {
Guard() {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
}
~Guard() {
Runtime::Current()->SetJavaDebuggable(Runtime::RuntimeDebugState::kNonJavaDebuggable);
}
} guard;
if (!cookie) return false; if (!cookie) return false;
return DexFile::SetTrusted(env, cookie); return DexFile::SetTrusted(env, cookie);
} }
}
} // namespace v2 } // namespace v2
} // namespace lsplant } // namespace lsplant
#pragma clang diagnostic pop

View File

@ -4,9 +4,13 @@ pluginManagement {
google() google()
mavenCentral() mavenCentral()
} }
plugins {
id("com.android.application") version "7.2.2"
id("com.android.library") version "7.2.2"
}
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
google() google()
mavenCentral() mavenCentral()

View File

@ -1,7 +1,5 @@
import com.android.build.api.dsl.ManagedVirtualDevice
plugins { plugins {
alias(libs.plugins.agp.app) id("com.android.application")
} }
val androidTargetSdkVersion: Int by rootProject.extra val androidTargetSdkVersion: Int by rootProject.extra
@ -12,7 +10,6 @@ val androidNdkVersion: String by rootProject.extra
val androidCmakeVersion: String by rootProject.extra val androidCmakeVersion: String by rootProject.extra
android { android {
namespace = "org.lsposed.lsplant.test"
compileSdk = androidCompileSdkVersion compileSdk = androidCompileSdkVersion
ndkVersion = androidNdkVersion ndkVersion = androidNdkVersion
buildToolsVersion = androidBuildToolsVersion buildToolsVersion = androidBuildToolsVersion
@ -36,12 +33,6 @@ android {
} }
} }
buildTypes {
debug {
isDebuggable = false
}
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path = file("src/main/jni/CMakeLists.txt") path = file("src/main/jni/CMakeLists.txt")
@ -53,53 +44,14 @@ android {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = 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 { dependencies {
implementation(project(":lsplant")) implementation(project(":lsplant"))
implementation(libs.dobby) implementation("io.github.vvb2060.ndk:dobby:1.2")
androidTestImplementation(libs.test.ext.junit) androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation(libs.test.runner) androidTestImplementation("androidx.test:runner:1.4.0")
androidTestImplementation(libs.test.espresso) androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
} }

View File

@ -9,7 +9,6 @@ import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters; import org.junit.runners.MethodSorters;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
@ -117,32 +116,4 @@ public class UnitTest {
Assert.assertFalse((Boolean) callStaticMethod.invoke(null)); 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);
}
} }

View File

@ -50,8 +50,4 @@ 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);
}
} }

View File

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

View File

@ -0,0 +1,284 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2019 Swift Gan
* Copyright (C) 2021 LSPosed Contributors
*/
#include <malloc.h>
#include <cstring>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <cassert>
#include <sys/stat.h>
#include "logging.h"
#include "elf_util.h"
using namespace SandHook;
template<typename T>
inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) {
return reinterpret_cast<std::conditional_t<std::is_pointer_v<T>, T, T *>>(
reinterpret_cast<uintptr_t>(head) + off);
}
ElfImg::ElfImg(std::string_view base_name) : elf(base_name) {
if (!findModuleBase()) {
base = nullptr;
return;
}
//load elf
int fd = open(elf.data(), O_RDONLY);
if (fd < 0) {
LOGE("failed to open %s", elf.data());
return;
}
size = lseek(fd, 0, SEEK_END);
if (size <= 0) {
LOGE("lseek() failed for %s", elf.data());
}
header = reinterpret_cast<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0));
close(fd);
section_header = offsetOf<decltype(section_header)>(header, header->e_shoff);
auto shoff = reinterpret_cast<uintptr_t>(section_header);
char *section_str = offsetOf<char *>(header, section_header[header->e_shstrndx].sh_offset);
for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) {
auto *section_h = (ElfW(Shdr) *) shoff;
char *sname = section_h->sh_name + section_str;
auto entsize = section_h->sh_entsize;
switch (section_h->sh_type) {
case SHT_DYNSYM: {
if (bias == -4396) {
dynsym = section_h;
dynsym_offset = section_h->sh_offset;
dynsym_start = offsetOf<decltype(dynsym_start)>(header, dynsym_offset);
}
break;
}
case SHT_SYMTAB: {
if (strcmp(sname, ".symtab") == 0) {
symtab = section_h;
symtab_offset = section_h->sh_offset;
symtab_size = section_h->sh_size;
symtab_count = symtab_size / entsize;
symtab_start = offsetOf<decltype(symtab_start)>(header, symtab_offset);
}
break;
}
case SHT_STRTAB: {
if (bias == -4396) {
strtab = section_h;
symstr_offset = section_h->sh_offset;
strtab_start = offsetOf<decltype(strtab_start)>(header, symstr_offset);
}
if (strcmp(sname, ".strtab") == 0) {
symstr_offset_for_symtab = section_h->sh_offset;
}
break;
}
case SHT_PROGBITS: {
if (strtab == nullptr || dynsym == nullptr) break;
if (bias == -4396) {
bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset;
}
break;
}
case SHT_HASH: {
auto *d_un = offsetOf<ElfW(Word)>(header, section_h->sh_offset);
nbucket_ = d_un[0];
bucket_ = d_un + 2;
chain_ = bucket_ + nbucket_;
break;
}
case SHT_GNU_HASH: {
auto *d_buf = reinterpret_cast<ElfW(Word) *>(((size_t) header) +
section_h->sh_offset);
gnu_nbucket_ = d_buf[0];
gnu_symndx_ = d_buf[1];
gnu_bloom_size_ = d_buf[2];
gnu_shift2_ = d_buf[3];
gnu_bloom_filter_ = reinterpret_cast<decltype(gnu_bloom_filter_)>(d_buf + 4);
gnu_bucket_ = reinterpret_cast<decltype(gnu_bucket_)>(gnu_bloom_filter_ +
gnu_bloom_size_);
gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_;
break;
}
}
}
}
ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const {
if (nbucket_ == 0) return 0;
char *strings = (char *) strtab_start;
for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
auto *sym = dynsym_start + n;
if (name == strings + sym->st_name) {
return sym->st_value;
}
}
return 0;
}
ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const {
static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0;
auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_];
uintptr_t mask = 0
| (uintptr_t) 1 << (hash % bloom_mask_bits)
| (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits);
if ((mask & bloom_word) == mask) {
auto sym_index = gnu_bucket_[hash % gnu_nbucket_];
if (sym_index >= gnu_symndx_) {
char *strings = (char *) strtab_start;
do {
auto *sym = dynsym_start + sym_index;
if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0
&& name == strings + sym->st_name) {
return sym->st_value;
}
} while ((gnu_chain_[sym_index++] & 1) == 0);
}
}
return 0;
}
void ElfImg::MayInitLinearMap() const {
if (symtabs_.empty()) {
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
for (ElfW(Off) i = 0; i < symtab_count; i++) {
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_symtab +
symtab_start[i].st_name);
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) {
symtabs_.emplace(st_name, &symtab_start[i]);
}
}
}
}
}
ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const {
MayInitLinearMap();
if (auto i = symtabs_.find(name); i != symtabs_.end()) {
return i->second->st_value;
} else {
return 0;
}
}
ElfW(Addr) ElfImg::PrefixLookupFirst(std::string_view prefix) const {
MayInitLinearMap();
if (auto i = symtabs_.lower_bound(prefix); i != symtabs_.end() && i->first.starts_with(prefix)) {
LOGD("found prefix %s of %s %p in %s in symtab by linear lookup", prefix.data(),
i->first.data(), reinterpret_cast<void *>(i->second->st_value), elf.data());
return i->second->st_value;
} else {
return 0;
}
}
ElfImg::~ElfImg() {
//open elf file local
if (buffer) {
free(buffer);
buffer = nullptr;
}
//use mmap
if (header) {
munmap(header, size);
}
}
ElfW(Addr)
ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const {
if (auto offset = GnuLookup(name, gnu_hash); offset > 0) {
LOGD("found %s %p in %s in dynsym by gnuhash", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = ElfLookup(name, elf_hash); offset > 0) {
LOGD("found %s %p in %s in dynsym by elfhash", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else if (offset = LinearLookup(name); offset > 0) {
LOGD("found %s %p in %s in symtab by linear lookup", name.data(),
reinterpret_cast<void *>(offset), elf.data());
return offset;
} else {
return 0;
}
}
constexpr inline bool contains(std::string_view a, std::string_view b) {
return a.find(b) != std::string_view::npos;
}
bool ElfImg::findModuleBase() {
off_t load_addr;
bool found = false;
FILE *maps = fopen("/proc/self/maps", "r");
char *buff = nullptr;
size_t len = 0;
ssize_t nread;
while ((nread = getline(&buff, &len, maps)) != -1) {
std::string_view line{buff, static_cast<size_t>(nread)};
if ((contains(line, "r-xp") || contains(line, "r--p")) && contains(line, elf)) {
LOGD("found: %*s", static_cast<int>(line.size()), line.data());
if (auto begin = line.find_last_of(' '); begin != std::string_view::npos &&
line[++begin] == '/') {
found = true;
elf = line.substr(begin);
if (elf.back() == '\n') elf.pop_back();
LOGD("update path: %s", elf.data());
break;
}
}
}
if (!found) {
if (buff) free(buff);
LOGE("failed to read load address for %s", elf.data());
fclose(maps);
return false;
}
if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) {
LOGE("failed to read load address for %s", elf.data());
}
if (buff) free(buff);
fclose(maps);
LOGD("get module base %s: %lx", elf.data(), load_addr);
base = reinterpret_cast<void *>(load_addr);
return true;
}

View File

@ -0,0 +1,144 @@
/*
* 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 +0,0 @@
Subproject commit 73b146d780339e0ec3fc91368828c5f4441761c4

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

View File

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