Compare commits

..

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

67 changed files with 2108 additions and 2085 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

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

View File

@ -5,7 +5,6 @@ on:
branches: ["master"] branches: ["master"]
paths-ignore: paths-ignore:
- 'README.md' - 'README.md'
workflow_dispatch:
pull_request: pull_request:
@ -16,34 +15,25 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-14 ] os: [ ubuntu-latest, windows-latest, macos-13 ]
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 21 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '21' java-version: '17'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1 uses: hendrikmuhs/ccache-action@v1.2
with: with:
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }} restore-keys: ${{ runner.os }}
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Remove Android's cmake
shell: bash
run: rm -rf $ANDROID_HOME/cmake
- name: Build with Gradle - name: Build with Gradle
run: | run: |
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
@ -57,16 +47,15 @@ jobs:
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@v3
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-13
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -141,122 +130,52 @@ jobs:
target: google_apis target: google_apis
arch: x86_64 arch: x86_64
- api-level: 33 - api-level: 33
target: default target: google_apis
arch: x86_64 arch: x86_64
- api-level: 33 - api-level: 33
target: android-tv target: android-tv
arch: x86 arch: x86
- api-level: 34 - api-level: 34
target: default target: google_apis
arch: x86_64 arch: x86_64
- api-level: 34 - api-level: 34
target: android-tv target: android-tv
arch: x86 arch: x86
- api-level: 35
target: default
arch: x86_64
- api-level: Baklava
target: google_apis
arch: x86_64
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: Set up JDK 17
- name: Set up JDK 21 uses: actions/setup-java@v3
uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '21' java-version: '17'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.1
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1 uses: hendrikmuhs/ccache-action@v1.2
with: with:
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }} restore-keys: ${{ runner.os }}
save: false save: false
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests - name: run tests
uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93 uses: yujincheng08/android-emulator-runner@release/apilevel
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
script: | script: |
rm -rf $ANDROID_HOME/cmake
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
ccache -o hash_dir=false ccache -o hash_dir=false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew :test:connectedCheck ./gradlew :test:connectedCheck
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@v3
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,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
submodules: 'recursive' submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0 fetch-depth: 0
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v4 uses: actions/setup-java@v3
with: with:
distribution: 'temurin' distribution: 'zulu'
java-version: '17' java-version: '17'
cache: 'gradle' cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.0
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build with Gradle - name: Build with Gradle
run: | 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

@ -32,7 +32,6 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
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

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

View File

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

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

View File

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

View File

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

Binary file not shown.

View File

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

8
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/HEAD/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/.
@ -86,7 +84,7 @@ done
# shellcheck disable=SC2034 # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -205,7 +203,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command: # Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped. # and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line. # treated as '${Hostname}' itself on the command line.

22
gradlew.bat vendored
View File

@ -13,8 +13,6 @@
@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 ##########################################################################
@ -45,11 +43,11 @@ 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% equ 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 +57,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

View File

@ -1,3 +1,5 @@
import java.nio.file.Paths
plugins { plugins {
alias(libs.plugins.agp.lib) alias(libs.plugins.agp.lib)
alias(libs.plugins.lsplugin.jgit) alias(libs.plugins.lsplugin.jgit)
@ -79,8 +81,8 @@ cmaker {
val flags = arrayOf( val flags = arrayOf(
"-Werror", "-Werror",
"-Wno-gnu-string-literal-operator-template", "-Wno-gnu-string-literal-operator-template",
"-Wno-c++2b-extensions",
) )
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
cppFlags += flags cppFlags += flags
cFlags += flags cFlags += flags
} }
@ -88,15 +90,13 @@ cmaker {
when (it.name) { when (it.name) {
"debug", "release" -> { "debug", "release" -> {
arguments += "-DANDROID_STL=c++_shared" arguments += "-DANDROID_STL=c++_shared"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
} }
"standalone" -> { "standalone" -> {
arguments += "-DANDROID_STL=none" arguments += "-DANDROID_STL=none"
arguments += "-DLSPLANT_STANDALONE=ON" arguments += "-DLSPLANT_STANDALONE=ON"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
} }
} }
arguments += "-DDEBUG_SYMBOLS_PATH=${project.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}" arguments += "-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/${it.name}"
} }
} }
@ -105,17 +105,17 @@ dependencies {
} }
val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") { val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") {
from(project.layout.buildDirectory.file("symbols/release")) from("${project.buildDir.absolutePath}/symbols/release")
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier = "symbols" archiveClassifier.set("symbols")
archiveBaseName = "release" archiveBaseName.set("release")
} }
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") { val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") {
from(project.layout.buildDirectory.file("symbols/standalone")) from("${project.buildDir.absolutePath}/symbols/standalone")
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier = "symbols" archiveClassifier.set("symbols")
archiveBaseName = "standalone" archiveBaseName.set("standalone")
} }
val repo = jgit.repo(true) val repo = jgit.repo(true)
@ -129,24 +129,24 @@ publish {
group = "org.lsposed.lsplant" group = "org.lsposed.lsplant"
version = ver version = ver
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")
} }
} }
} }

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,10 +1,8 @@
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})
@ -15,11 +13,9 @@ if (LSPLANT_STANDALONE)
link_libraries(cxx::cxx) link_libraries(cxx::cxx)
endif() endif()
add_definitions(-std=c++20)
set(SOURCES lsplant.cc) set(SOURCES lsplant.cc)
file(GLOB_RECURSE MODULE_SOURCES CONFIGURE_DEPENDS "*.cxx")
file(GLOB_RECURSE MODULE_INTERFACES CONFIGURE_DEPENDS "*.ixx")
list(FILTER MODULE_SOURCES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
list(FILTER MODULE_INTERFACES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE) set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE)
add_subdirectory(external/dex_builder) add_subdirectory(external/dex_builder)
@ -29,9 +25,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 +40,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,44 +1,47 @@
module; #pragma once
#include <parallel_hashmap/phmap.h> #include "art/runtime/art_method.hpp"
#include "art/runtime/handle.hpp"
#include "common.hpp"
#include "logging.hpp" namespace lsplant::art {
class Thread;
namespace dex {
class ClassDef {};
} // namespace dex
export module lsplant:clazz; namespace mirror {
import :common;
import :art_method;
import :thread;
import :handle;
import hook_helper;
export namespace lsplant::art::mirror {
class Class { class Class {
private: private:
inline static auto GetDescriptor_ = CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>; if (GetDescriptorSym) [[likely]]
return GetDescriptorSym(thiz, storage);
else
return "";
}
inline static auto GetClassDef_ = CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
"_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>; if (GetClassDefSym) [[likely]]
return GetClassDefSym(thiz);
return nullptr;
}
using BackupMethods = phmap::flat_hash_map<art::ArtMethod *, void *>; using BackupMethods = std::list<std::tuple<art::ArtMethod *, void *>>;
inline static phmap::flat_hash_map<const art::Thread *, inline static phmap::flat_hash_map<const art::Thread *,
phmap::flat_hash_map<const dex::ClassDef *, BackupMethods>> phmap::flat_hash_map<const dex::ClassDef *, BackupMethods>>
backup_methods_; backup_methods_;
inline static std::mutex backup_methods_lock_; inline static std::mutex backup_methods_lock_;
inline static uint8_t initialized_status = 0;
static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) { static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods out; std::list<std::tuple<art::ArtMethod *, void *>> out;
if (!class_def) return; if (!class_def) return;
{ {
hooked_classes_.if_contains(class_def, [&out](const auto &it) { hooked_classes_.if_contains(class_def, [&out](const auto &it) {
for (auto method : it.second) { for (auto method : it.second) {
if (method->IsStatic()) { if (method->IsStatic()) {
LOGV("Backup hooked method %p because of initialization", method); LOGV("Backup hooked method %p because of initialization", method);
out.emplace(method, method->GetEntryPoint()); out.emplace_back(method, method->GetEntryPoint());
} }
} }
}); });
@ -48,7 +51,7 @@ private:
for (auto method : it.second) { for (auto method : it.second) {
if (method->IsStatic()) { if (method->IsStatic()) {
LOGV("Backup deoptimized method %p because of initialization", method); LOGV("Backup deoptimized method %p because of initialization", method);
out.emplace(method, method->GetEntryPoint()); out.emplace_back(method, method->GetEntryPoint());
} }
} }
}); });
@ -59,60 +62,62 @@ private:
} }
} }
inline static auto SetClassStatus_ = CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE"_sym.hook->*[] "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE", void,
<Backup auto backup> SetClassStatus, (TrivialHandle<Class> h, uint8_t new_status, Thread *self), {
(TrivialHandle<Class> h, uint8_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) { if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self); BackupClassMethods(h->GetClassDef(), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}; });
inline static auto SetStatus_ = CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[] "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, SetStatus,
<Backup auto backup> (Handle<Class> h, int new_status, Thread *self), {
(Handle<Class> h, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) { if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(h.Get()), self); BackupClassMethods(h->GetClassDef(), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}; });
inline static auto TrivialSetStatus_ = CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[] "_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void,
<Backup auto backup> TrivialSetStatus, (TrivialHandle<Class> h, uint32_t new_status, Thread *self), {
(TrivialHandle<Class> h, uint32_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) { if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self); BackupClassMethods(h->GetClassDef(), self);
} }
return backup(h, new_status, self); return backup(h, new_status, self);
}; });
inline static auto ClassSetStatus_ = CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE", void,
"_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE"_sym.hook->*[] ClassSetStatus, (Class * thiz, int new_status, Thread *self), {
<MemBackup auto backup>
(Class *thiz, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) { if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(thiz), self); BackupClassMethods(thiz->GetClassDef(), self);
} }
return backup(thiz, new_status, self); return backup(thiz, new_status, self);
}; });
inline static uint8_t initialized_status = 0;
public: public:
static bool Init(const HookHandler &handler) { static bool Init(const HookHandler &handler) {
if (!handler(GetDescriptor_) || !handler(GetClassDef_)) { 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 false;
} }
int sdk_int = GetAndroidApiLevel(); int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) { if (sdk_int < __ANDROID_API_O__) {
if (!handler(SetStatus_, ClassSetStatus_)) { if (!HookSyms(handler, SetStatus, ClassSetStatus)) {
return false; return false;
} }
} else { } else {
if (!handler(SetClassStatus_, TrivialSetStatus_)) { if (!HookSyms(handler, SetClassStatus, TrivialSetStatus)) {
return false; return false;
} }
} }
@ -130,14 +135,22 @@ public:
return true; return true;
} }
const char *GetDescriptor(std::string *storage) { return GetDescriptor_(this, storage); } const char *GetDescriptor(std::string *storage) {
if (GetDescriptorSym) {
return GetDescriptor(this, storage);
}
return "";
}
std::string GetDescriptor() { std::string GetDescriptor() {
std::string storage; std::string storage;
return GetDescriptor(&storage); return GetDescriptor(&storage);
} }
const dex::ClassDef *GetClassDef() { return GetClassDef_(this); } const dex::ClassDef *GetClassDef() {
if (GetClassDefSym) return GetClassDef(this);
return nullptr;
}
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) { static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods methods; BackupMethods methods;
@ -171,4 +184,5 @@ public:
} }
}; };
} // namespace lsplant::art::mirror } // 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 *>(
@ -267,7 +221,7 @@ 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,9 @@ public:
LOGE("Failed to find Executable.getName"); LOGE("Failed to find Executable.getName");
return false; return false;
} }
handler(ThrowInvocationTimeError_); RETRIEVE_MEM_FUNC_SYMBOL(ThrowInvocationTimeError, "_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv");
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 +293,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,205 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/runtime/obj_ptr.hpp"
#include "art/runtime/thread.hpp"
#include "common.hpp"
namespace lsplant::art {
class ClassLinker {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, ClassLinker *thiz,
ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreterSym(thiz, art_method);
}
}
CREATE_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv", bool,
ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code), {
if (quick_code != nullptr && IsHooked(art_method)) [[unlikely]] {
return false;
}
return backup(art_method, quick_code);
});
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void *) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void *) {}
inline static art::ArtMethod *MayGetBackup(art::ArtMethod *method) {
if (auto backup = IsHooked(method); backup) [[unlikely]] {
method = backup;
LOGV("propagate native method: %s", method->PrettyMethod(true).data());
}
return method;
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art6mirror9ArtMethod14RegisterNativeEPNS_6ThreadEPKvb", void, RegisterNativeThread,
(art::ArtMethod * method, art::Thread *thread, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), thread, native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror9ArtMethod16UnregisterNativeEPNS_6ThreadE", void,
UnregisterNativeThread,
(art::ArtMethod * method, art::Thread *thread),
{ return backup(MayGetBackup(method), thread); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKvb", void, RegisterNativeFast,
(art::ArtMethod * method, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", void, UnregisterNativeFast,
(art::ArtMethod * method), { return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKv", const void *,
RegisterNative, (art::ArtMethod * method, const void *native_method),
{ return backup(MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", const void *,
UnregisterNative, (art::ArtMethod * method),
{ return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker14RegisterNativeEPNS_6ThreadEPNS_9ArtMethodEPKv", const void *,
RegisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method,
const void *native_method),
{ return backup(thiz, self, MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker16UnregisterNativeEPNS_6ThreadEPNS_9ArtMethodE",
const void *, UnregisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method),
{ return backup(thiz, self, MayGetBackup(method)); });
static auto RestoreBackup(const dex::ClassDef *class_def, art::Thread *self) {
auto methods = mirror::Class::PopBackup(class_def, self);
for (const auto &[art_method, old_trampoline] : methods) {
auto new_trampoline = art_method->GetEntryPoint();
art_method->SetEntryPoint(old_trampoline);
if (IsDeoptimized(art_method)) {
if (new_trampoline != art_quick_to_interpreter_bridge &&
new_trampoline != art_quick_generic_jni_trampoline) {
LOGV("re-deoptimize for %p", art_method);
SetEntryPointsToInterpreter(art_method);
}
continue;
}
if (auto backup_method = IsHooked(art_method); backup_method) [[likely]] {
if (new_trampoline != old_trampoline) [[unlikely]] {
LOGV("propagate entrypoint for %p", backup_method);
backup_method->SetEntryPoint(new_trampoline);
}
}
}
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void,
FixupStaticTrampolines, (ClassLinker * thiz, ObjPtr<mirror::Class> mirror_class), {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
void, FixupStaticTrampolinesWithThread,
(ClassLinker * thiz, art::Thread *self, ObjPtr<mirror::Class> mirror_class), {
backup(thiz, self, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), self);
});
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE",
void, FixupStaticTrampolinesRaw,
(ClassLinker * thiz, mirror::Class *mirror_class), {
backup(thiz, mirror_class);
RestoreBackup(mirror_class->GetClassDef(), nullptr);
});
CREATE_MEM_HOOK_STUB_ENTRY(
LP_SELECT(
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEi",
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEl"),
void, AdjustThreadVisibilityCounter, (void *thiz, art::Thread *self, ssize_t adjustment), {
backup(thiz, self, adjustment);
RestoreBackup(nullptr, self);
});
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker26VisiblyInitializedCallback22MarkVisiblyInitializedEPNS_6ThreadE",
void, MarkVisiblyInitialized, (void *thiz, Thread* self), {
backup(thiz, self);
RestoreBackup(nullptr, self);
});
public:
static bool Init(const HookHandler &handler) {
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_N__ && sdk_int < __ANDROID_API_T__) {
HookSyms(handler, ShouldUseInterpreterEntrypoint);
}
if (!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines,
FixupStaticTrampolinesRaw)) {
return false;
}
if (!HookSyms(handler, RegisterNativeClassLinker, RegisterNative, RegisterNativeFast,
RegisterNativeThread) ||
!HookSyms(handler, UnregisterNativeClassLinker, UnregisterNative, UnregisterNativeFast,
UnregisterNativeThread)) {
return false;
}
if (sdk_int >= __ANDROID_API_R__) {
if constexpr (GetArch() != Arch::kX86 && GetArch() != Arch::kX86_64) {
// fixup static trampoline may have been inlined
HookSyms(handler, AdjustThreadVisibilityCounter, MarkVisiblyInitialized);
}
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(
SetEntryPointsToInterpreter,
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE"))
[[unlikely]] {
if (!RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge,
"art_quick_to_interpreter_bridge")) [[unlikely]] {
return false;
}
if (!RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline,
"art_quick_generic_jni_trampoline")) [[unlikely]] {
return false;
}
LOGD("art_quick_to_interpreter_bridge = %p", art_quick_to_interpreter_bridgeSym);
LOGD("art_quick_generic_jni_trampoline = %p", art_quick_generic_jni_trampolineSym);
}
return true;
}
[[gnu::always_inline]] static bool SetEntryPointsToInterpreter(ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreter(nullptr, art_method);
return true;
}
// Android 13
if (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
if (art_method->GetAccessFlags() & ArtMethod::kAccNative) [[unlikely]] {
LOGV("deoptimize native method %s from %p to %p",
art_method->PrettyMethod(true).data(), art_method->GetEntryPoint(),
art_quick_generic_jni_trampolineSym);
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
} else {
LOGV("deoptimize method %s from %p to %p", art_method->PrettyMethod(true).data(),
art_method->GetEntryPoint(), art_quick_to_interpreter_bridgeSym);
art_method->SetEntryPoint(
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
}
return true;
}
return false;
}
};
} // namespace lsplant::art

View File

@ -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)) {
LOGW("DexFile.setTrusted not found, MakeDexFileTrusted will not work.");
}
}
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
return true;
}
if (!RETRIEVE_FUNC_SYMBOL(
OpenMemory,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryRaw,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryWithoutOdex,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"))) [[unlikely]] {
LOGE("Failed to find OpenMemory");
return false;
}
dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile"));
if (!dex_file_class) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_M__) [[unlikely]] {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;");
} else {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J");
dex_file_start_index = -1;
}
if (!cookie_field) [[unlikely]] {
return false;
}
file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;");
if (!file_name_field) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
internal_cookie_field =
JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;");
if (!internal_cookie_field) [[unlikely]] {
return false;
}
dex_file_start_index = 1u;
}
return true;
}
private:
inline static jclass dex_file_class = nullptr;
inline static jfieldID cookie_field = nullptr;
inline static jfieldID file_name_field = nullptr;
inline static jfieldID internal_cookie_field = nullptr;
inline static size_t oat_file_index = 0u;
inline static size_t dex_file_start_index = 0u;
};
} // namespace lsplant::art

View File

@ -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

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

View File

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

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

View File

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

View File

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

View File

@ -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

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

View File

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

@ -1 +1 @@
Subproject commit 67077df2a53be0cabb9f16e8a050acdd825f7d69 Subproject commit b5a00f2ea94ad4c3b92054fd53896dbb429298f9

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 {};
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: template <char... as, char... bs>
using ThisType = std::conditional_t<std::is_same_v<This, void>, Function, This>; inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) {
inline static union { return {};
Ret (ThisType::*function_)(Args...) const; }
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 { struct {
void *raw_function_ = nullptr; decltype(func) p;
[[maybe_unused]] std::ptrdiff_t adj = 0; std::ptrdiff_t adj;
}; } data;
} inner_; } u{.data = {func, 0}};
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_) + sizeof(inner_.adj)); return u.f;
};
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 <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; }
}; };
template <FixedString, FuncType> // 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; struct Hooker;
template <FixedString Sym, typename Ret, typename... Args> template <typename Ret, typename... Args, char... cs>
struct Hooker<Sym, Ret(Args...)> : Function<Sym, Ret(Args...)> { struct Hooker<Ret(Args...), tstring<cs...>> {
[[gnu::always_inline]] Hooker &operator=(void *function) { inline static Ret (*backup)(Args...) = nullptr;
Function<Sym, Ret(Args...)>::operator=(function);
return *this; inline static constexpr std::string_view sym = tstring<cs...>{};
}
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> template <typename, typename>
struct Hooker<Sym, Ret(This::*)(Args...)> : Function<Sym, Ret(This::*)(Args...)> { struct MemHooker;
[[gnu::always_inline]] Hooker &operator=(void *function) { template <typename Ret, typename This, typename... Args, char... cs>
Function<Sym, Ret(This::*)(Args...)>::operator=(function); struct MemHooker<Ret(This, Args...), tstring<cs...>> {
return *this; inline static MemberFunction<Ret(Args...)> backup;
} inline static constexpr std::string_view sym = tstring<cs...>{};
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> template <typename T>
[[gnu::always_inline]] bool operator()(T &&arg) const { concept HookerType = requires(T a) {
return handle(std::forward<T>(arg), false); a.backup;
} a.replace;
};
template <typename T1, typename T2, typename... U> template <HookerType T>
[[gnu::always_inline]] bool operator()(T1 &&arg1, T2 &&arg2, U &&...args) const { inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
if constexpr(std::is_same_v<T2, bool>) if (original) {
return handle(std::forward<T1>(arg1), std::forward<T2>(arg2)) || this->operator()(std::forward<U>(args)...); if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
else void *backup = handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace));
return handle(std::forward<T1>(arg1), false) || this->operator()(std::forward<T2>(arg2), std::forward<U>(args)...); 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;
private: } else {
[[gnu::always_inline]] bool operator()() const {
return false; 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> template <HookerType T>
requires(requires { T<Sym, Us...>::replace_; }) inline static bool HookSym(const HookHandler &handler, T &arg) {
[[gnu::always_inline]] bool handle(T<Sym, Us...> &hooker, bool match_prefix) const { auto original = handler.art_symbol_resolver(arg.sym);
return hooker = hook(dlsym<Sym>(match_prefix), reinterpret_cast<void *>(hooker.replace_)); return HookSymNoHandle(handler, original, arg);
} }
template <FixedString Sym> template <HookerType T, HookerType... Args>
[[gnu::always_inline]] void *dlsym(bool match_prefix = false) const { inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
if (auto match = info_.art_symbol_resolver(Sym.data); match) { if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
return match; __android_log_print(ANDROID_LOG_ERROR,
} #ifdef LOG_TAG
if (match_prefix && info_.art_symbol_prefix_resolver) [[likely]] { LOG_TAG,
return info_.art_symbol_prefix_resolver(Sym.data);
}
return nullptr;
}
[[gnu::always_inline]] void *hook(void *original, void *replace) const {
if (original) [[likely]] {
return info_.inline_hooker(original, replace);
}
return nullptr;
}
};
template<typename F>
concept Backup = std::is_function_v<std::remove_pointer_t<F>>;
template<typename F>
concept MemBackup = std::is_member_function_pointer_v<std::remove_pointer_t<F>> || Backup<F>;
template<FixedString S>
struct Symbol {
template<typename T>
inline static decltype([]{
if constexpr (FuncType<T>) {
return Function<S, T>{};
} else {
return Field<S, T>{};
}
}()) as{};
[[no_unique_address]] struct Hook {
template<typename F>
auto operator->*(F&&) const {
using Signature = decltype(F::template operator()<&decltype([] static {})::operator()>);
if constexpr (requires { F::template operator()<&decltype([] {})::operator()>; }) {
using HookerType = Hooker<S, decltype([]<class This, typename Ret, typename... Args>(Ret(*)(This*, Args...)) -> Ret(This::*)(Args...) {
return {};
}.template operator()(std::declval<Signature>()))>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
} else {
using HookerType = Hooker<S, Signature>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
}
};
} hook;
};
template <FixedString S> constexpr Symbol<S> operator""_sym() {
return {};
}
template<FixedString S, FixedString P>
consteval auto operator|([[maybe_unused]] Symbol<S> a, [[maybe_unused]] Symbol<P> b) {
#if defined(__LP64__)
return b;
#else #else
return a; "HookHelper",
#endif #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>>;
@ -58,6 +65,8 @@ public:
return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_)); return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_));
} }
operator T() const { return local_ref_; }
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept { ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release()); reset(s.release());
env_ = s.env_; env_ = s.env_;
@ -109,11 +118,11 @@ 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_same_v<std::decay_t<T>, JObjectArrayElement> && std::is_convertible_v<jobject, U>); &&std::is_convertible_v<jobject, U>);
template <typename T> template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>; concept ScopeOrClass = ScopeOrRaw<T, jclass>;
@ -124,11 +133,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};
} }
@ -178,7 +186,8 @@ public:
JUTFString(const ScopedLocalRef<jstring> &jstr) JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {} : JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) { 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
@ -505,13 +514,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
@ -673,14 +675,15 @@ template <ScopeOrClass Class, typename... Args>
// non-virtual methods // non-virtual methods
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_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),
@ -688,7 +691,7 @@ template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_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),
@ -696,49 +699,55 @@ template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
} }
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args> template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_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),
@ -769,12 +778,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,9 +785,8 @@ 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));
} }
@ -902,7 +904,9 @@ public:
reset(local_ref); reset(local_ref);
} }
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); } ScopedLocalRef(ScopedLocalRef &&s) noexcept {
*this = std::move(s);
}
template <JObject U> template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
@ -953,6 +957,8 @@ public:
T get() const { return local_ref_; } T get() const { return local_ref_; }
explicit operator T() const { return local_ref_; }
JArrayUnderlyingType<T> &operator[](size_t index) { JArrayUnderlyingType<T> &operator[](size_t index) {
modified_ = true; modified_ = true;
return elements_[index]; return elements_[index];
@ -1030,8 +1036,9 @@ class JObjectArrayElement {
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_); return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
} }
explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i, size_t size) explicit JObjectArrayElement(JNIEnv * env, jobjectArray array, int i, size_t size) :
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {} env_(env), array_(array), i_(i), size_(size),
item_(obtain()) {}
JObjectArrayElement &operator++() { JObjectArrayElement &operator++() {
++i_; ++i_;
@ -1045,17 +1052,24 @@ class JObjectArrayElement {
return *this; return *this;
} }
JObjectArrayElement operator++(int) { return JObjectArrayElement(env_, array_, i_ + 1, size_); } JObjectArrayElement operator++(int) {
return JObjectArrayElement(env_, array_, i_ + 1, size_);
}
JObjectArrayElement operator--(int) { return JObjectArrayElement(env_, array_, i_ - 1, size_); } JObjectArrayElement operator--(int) {
return JObjectArrayElement(env_, array_, i_ - 1, size_);
}
public: public:
JObjectArrayElement(JObjectArrayElement &&s) JObjectArrayElement(JObjectArrayElement&& s): env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {}
: 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 item_;
}
operator ScopedLocalRef<jobject> &&() && { return std::move(item_); } operator ScopedLocalRef<jobject>&& () && {
return std::move(item_);
}
JObjectArrayElement& operator=(JObjectArrayElement&& s) { JObjectArrayElement& operator=(JObjectArrayElement&& s) {
reset(s.item_.release()); reset(s.item_.release());
@ -1067,18 +1081,6 @@ public:
return *this; return *this;
} }
template<JObject T>
JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {
reset(s.release());
return *this;
}
template<JObject T>
JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {
reset(s.clone());
return *this;
}
JObjectArrayElement& operator=(jobject s) { JObjectArrayElement& operator=(jobject s) {
reset(env_->NewLocalRef(s)); reset(env_->NewLocalRef(s));
return *this; return *this;
@ -1089,7 +1091,13 @@ public:
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_); JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_);
} }
ScopedLocalRef<jobject> clone() const { return item_.clone(); } ScopedLocalRef<jobject> clone() const {
return item_.clone();
}
operator jobject() const {
return item_;
}
jobject get() const { return item_.get(); } jobject get() const { return item_.get(); }
@ -1098,7 +1106,6 @@ public:
jobject operator->() const { return item_.get(); } jobject operator->() const { return item_.get(); }
jobject operator*() const { return item_.get(); } jobject operator*() const { return item_.get(); }
private: private:
JNIEnv *env_; JNIEnv *env_;
jobjectArray array_; jobjectArray array_;
@ -1116,7 +1123,6 @@ public:
Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {} Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}
Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {} Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {}
public: public:
auto &operator*() { return e_; } auto &operator*() { return e_; }
@ -1132,14 +1138,17 @@ public:
return *this; return *this;
} }
Iterator operator++(int) { return Iterator(e_++); } Iterator operator++(int) {
return Iterator(e_++);
}
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_; }
bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; } bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; }
private: private:
JObjectArrayElement e_; JObjectArrayElement e_;
}; };
@ -1152,9 +1161,7 @@ public:
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_); return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
} }
ConstIterator(JNIEnv *env, jobjectArray array, int i, int size) ConstIterator(JNIEnv * env, jobjectArray array, int i, int size) : env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
public: public:
auto &operator*() { return item_; } auto &operator*() { return item_; }
@ -1172,14 +1179,17 @@ public:
return *this; return *this;
} }
ConstIterator operator++(int) { return ConstIterator(env_, array_, i_ + 1, size_); } ConstIterator operator++(int) {
return ConstIterator(env_, array_, i_ + 1, size_);
}
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_; }
bool operator!=(const ConstIterator &other) const { return other.i_ != i_; } bool operator!=(const ConstIterator &other) const { return other.i_ != i_; }
private: private:
JNIEnv* env_; JNIEnv* env_;
jobjectArray array_; jobjectArray array_;
@ -1204,11 +1214,12 @@ public:
reset(local_ref); reset(local_ref);
} }
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); } ScopedLocalRef(ScopedLocalRef &&s) noexcept {
*this = std::move(s);
}
template<JObject U> template<JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (jobjectArray) s.release()) {}
: ScopedLocalRef(s.env_, (jobjectArray)s.release()) {}
explicit ScopedLocalRef(JNIEnv *env) noexcept: ScopedLocalRef(env, jobjectArray {nullptr}) {} explicit ScopedLocalRef(JNIEnv *env) noexcept: ScopedLocalRef(env, jobjectArray {nullptr}) {}
@ -1235,7 +1246,7 @@ public:
jobjectArray get() const { return local_ref_; } jobjectArray get() const { return local_ref_; }
JObjectArrayElement operator[](size_t index) { JObjectArrayElement operator[](size_t index) {
return JObjectArrayElement(env_, local_ref_, index, size_); return JObjectArrayElement(env_, local_ref_, index, JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, index));
} }
const ScopedLocalRef<jobject> operator[](size_t index) const { const ScopedLocalRef<jobject> operator[](size_t index) const {

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

@ -17,14 +17,14 @@
#ifndef NDEBUG #ifndef NDEBUG
#define LOGD(fmt, ...) \ #define LOGD(fmt, ...) \
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \
"%s:%d" \ "%s:%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>
@ -82,9 +71,9 @@ consteval inline auto GetTrampoline() {
} }
if constexpr (kArch == Arch::kRiscv64) { if constexpr (kArch == Arch::kRiscv64) {
return std::make_tuple( return std::make_tuple(
"\x17\x05\x00\x00\x03\x35\x05\x01\x83\x3f\x05\x00\x67\x80\x0f\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr, "\x17\x05\x00\x00\x03\x35\xc5\x00\x67\x00\x05\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// NOLINTNEXTLINE // NOLINTNEXTLINE
uint8_t{84u}, uintptr_t{16u}); uint8_t{84u}, uintptr_t{12u});
} }
} }
@ -258,6 +247,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 +259,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 +279,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 +291,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)) {
@ -317,6 +305,58 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
return true; return true;
} }
struct JavaDebuggableGuard {
JavaDebuggableGuard() {
while (true) {
size_t expected = 0;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
count.fetch_add(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected + 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
~JavaDebuggableGuard() {
while (true) {
size_t expected = 2;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kNonJavaDebuggable);
count.fetch_sub(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected - 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
private:
inline static std::atomic_size_t count{0};
static_assert(std::atomic_size_t::is_always_lock_free, "Unsupported architecture");
static_assert(std::is_same_v<std::atomic_size_t::value_type, size_t>,
"Unsupported architecture");
};
std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader, std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static, std::string_view shorty, bool is_static,
std::string_view method_name, std::string_view method_name,
@ -353,7 +393,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 +440,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);
} }
@ -429,12 +470,12 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
mprotect(target, image.size(), PROT_READ); mprotect(target, image.size(), PROT_READ);
std::string err_msg; std::string err_msg;
const auto *dex = DexFile::OpenMemory( const auto *dex = DexFile::OpenMemory(
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 { } else {
java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr); java_dex_file = WrapScope(env, dex ? dex->ToJavaDexFile(env) : jobject{nullptr});
} }
} }
@ -468,8 +509,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 +518,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 +545,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 +579,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());
@ -611,15 +657,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
std::string out; std::string out;
auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) { auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) {
if (JNI_IsSameObject(env, type, int_type)) return 'I'; if (env->IsSameObject(type, int_type)) return 'I';
if (JNI_IsSameObject(env, type, long_type)) return 'J'; if (env->IsSameObject(type, long_type)) return 'J';
if (JNI_IsSameObject(env, type, float_type)) return 'F'; if (env->IsSameObject(type, float_type)) return 'F';
if (JNI_IsSameObject(env, type, double_type)) return 'D'; if (env->IsSameObject(type, double_type)) return 'D';
if (JNI_IsSameObject(env, type, boolean_type)) return 'Z'; if (env->IsSameObject(type, boolean_type)) return 'Z';
if (JNI_IsSameObject(env, type, byte_type)) return 'B'; if (env->IsSameObject(type, byte_type)) return 'B';
if (JNI_IsSameObject(env, type, char_type)) return 'C'; if (env->IsSameObject(type, char_type)) return 'C';
if (JNI_IsSameObject(env, type, short_type)) return 'S'; if (env->IsSameObject(type, short_type)) return 'S';
if (JNI_IsSameObject(env, type, void_type)) return 'V'; if (env->IsSameObject(type, void_type)) return 'V';
return 'L'; return 'L';
}; };
out += type_to_shorty(return_type); out += type_to_shorty(return_type);
@ -631,15 +677,10 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
} // namespace } // namespace
inline namespace v2 { inline namespace v2 {
extern "C++" {
using ::lsplant::IsHooked; using ::lsplant::IsHooked;
[[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) { [[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) {
if (!info.inline_hooker || !info.inline_unhooker || !info.art_symbol_resolver ||
!info.art_symbol_prefix_resolver) {
return false;
}
bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info); bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info);
return kInit; return kInit;
} }
@ -692,7 +733,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,
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method) __builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method), : ArtMethod::GetMethodShorty(env, target_method),
is_static, target->IsConstructor() ? "constructor" : target_method_name.get(), is_static, target->IsConstructor() ? "constructor" : target_method_name.get(),
@ -708,8 +749,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);
@ -792,7 +833,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);
@ -834,7 +875,8 @@ using ::lsplant::IsHooked;
if (!cookie) return false; if (!cookie) return false;
return DexFile::SetTrusted(env, cookie); return DexFile::SetTrusted(env, cookie);
} }
}
} // namespace v2 } // namespace v2
} // namespace lsplant } // namespace lsplant
#pragma clang diagnostic pop

View File

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

View File

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

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,10 @@ 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");
#if !defined(__i386__)
dobby_enable_near_branch_trampoline();
#endif
lsplant::InitInfo initInfo{ lsplant::InitInfo initInfo{
.inline_hooker = InlineHooker, .inline_hooker = InlineHooker,
.inline_unhooker = InlineUnhooker, .inline_unhooker = InlineUnhooker,
@ -66,7 +66,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);