Compare commits

..

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

67 changed files with 2114 additions and 2322 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:
interval: daily
time: "21:00"
open-pull-requests-limit: 10
target-branch: master
groups:
maven-dependencies:
patterns:
- "*"
registries:
- maven-google
- gralde-plugin
registries:
maven-google:
type: maven-repository
url: "https://dl.google.com/dl/android/maven2/"
gralde-plugin:
type: maven-repository
url: "https://plugins.gradle.org/m2/"

View File

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

View File

@ -9,26 +9,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
submodules: 'recursive'
ssh-key: ${{ secrets.SSH_KEY }}
fetch-depth: 0
- name: Set up JDK 17
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
distribution: 'temurin'
distribution: 'zulu'
java-version: '17'
cache: 'gradle'
- uses: seanmiddleditch/gha-setup-ninja@master
with:
version: 1.12.0
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Build with Gradle
run: |
rm -rf $ANDROID_HOME/cmake
./gradlew :lsplant:publish
run: ./gradlew :lsplant:publish
env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}

View File

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

6
.gitmodules vendored
View File

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

View File

@ -1,7 +1,7 @@
# LSPlant
![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg)
![](https://img.shields.io/badge/Android-5.0%20--%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://github.com/LSPosed/LSPlant/actions/workflows/build.yml/badge.svg?branch=master&event=push)
![](https://img.shields.io/maven-central/v/org.lsposed.lsplant/lsplant.svg)
@ -12,7 +12,7 @@ This project is part of LSPosed framework under GNU Lesser General Public Licens
## Features
+ Support Android 5.0 - 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 customized inline hook framework and ART symbol resolver
@ -34,7 +34,7 @@ android {
}
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
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)
}
val androidTargetSdkVersion by extra(35)
val androidTargetSdkVersion by extra(34)
val androidMinSdkVersion by extra(21)
val androidBuildToolsVersion by extra("35.0.0")
val androidCompileSdkVersion by extra(35)
val androidNdkVersion by extra("29.0.13113456")
val androidCmakeVersion by extra("3.28.0+")
val androidBuildToolsVersion by extra("34.0.0")
val androidCompileSdkVersion by extra(34)
val androidNdkVersion by extra("26.0.10792818")
val androidCmakeVersion by extra("3.22.1+")

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

View File

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

View File

@ -1,5 +1,5 @@
[versions]
agp = "8.9.0"
agp = "8.1.2"
[plugins]
agp-app = { id = "com.android.application", version.ref = "agp" }
@ -9,9 +9,9 @@ lsplugin-publish = { id = "org.lsposed.lsplugin.publish", version = "1.1" }
lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" }
[libraries]
cxx = { module = "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" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.6.1" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.1.5" }
test-runner = { module = "androidx.test:runner", version = "1.5.2" }
test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.5.1" }

Binary file not shown.

View File

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

20
gradlew vendored
View File

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

22
gradlew.bat vendored
View File

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

View File

@ -1,3 +1,5 @@
import java.nio.file.Paths
plugins {
alias(libs.plugins.agp.lib)
alias(libs.plugins.lsplugin.jgit)
@ -79,8 +81,8 @@ cmaker {
val flags = arrayOf(
"-Werror",
"-Wno-gnu-string-literal-operator-template",
"-Wno-c++2b-extensions",
)
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
cppFlags += flags
cFlags += flags
}
@ -88,15 +90,13 @@ cmaker {
when (it.name) {
"debug", "release" -> {
arguments += "-DANDROID_STL=c++_shared"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
"standalone" -> {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLANT_STANDALONE=ON"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
arguments += "-DDEBUG_SYMBOLS_PATH=${project.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") {
from(project.layout.buildDirectory.file("symbols/release"))
from("${project.buildDir.absolutePath}/symbols/release")
exclude("**/dex_builder")
archiveClassifier = "symbols"
archiveBaseName = "release"
archiveClassifier.set("symbols")
archiveBaseName.set("release")
}
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") {
from(project.layout.buildDirectory.file("symbols/standalone"))
from("${project.buildDir.absolutePath}/symbols/standalone")
exclude("**/dex_builder")
archiveClassifier = "symbols"
archiveBaseName = "standalone"
archiveClassifier.set("symbols")
archiveBaseName.set("standalone")
}
val repo = jgit.repo(true)
@ -129,24 +129,24 @@ publish {
group = "org.lsposed.lsplant"
version = ver
pom {
name = "LSPlant"
description = "A hook framework for Android Runtime (ART)"
url = "https://github.com/LSPosed/LSPlant"
name.set("LSPlant")
description.set("A hook framework for Android Runtime (ART)")
url.set("https://github.com/LSPosed/LSPlant")
licenses {
license {
name = "GNU Lesser General Public License v3.0"
url = "https://github.com/LSPosed/LSPlant/blob/master/LICENSE"
name.set("GNU Lesser General Public License v3.0")
url.set("https://github.com/LSPosed/LSPlant/blob/master/LICENSE")
}
}
developers {
developer {
name = "Lsposed"
url = "https://lsposed.org"
name.set("Lsposed")
url.set("https://lsposed.org")
}
}
scm {
connection = "scm:git:https://github.com/LSPosed/LSPlant.git"
url = "https://github.com/LSPosed/LSPlant"
connection.set("scm:git:https://github.com/LSPosed/LSPlant.git")
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)
find_program(CCACHE ccache)
set(CMAKE_CXX_STANDARD 23)
if (CCACHE)
set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE})
set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE})
@ -15,11 +13,9 @@ if (LSPLANT_STANDALONE)
link_libraries(cxx::cxx)
endif()
add_definitions(-std=c++20)
set(SOURCES lsplant.cc)
file(GLOB_RECURSE MODULE_SOURCES CONFIGURE_DEPENDS "*.cxx")
file(GLOB_RECURSE MODULE_INTERFACES CONFIGURE_DEPENDS "*.ixx")
list(FILTER MODULE_SOURCES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
list(FILTER MODULE_INTERFACES EXCLUDE REGEX "${PROJECT_SOURCE_DIR}.external.+")
set(DEX_BUILDER_BUILD_SHARED OFF CACHE INTERNAL "" FORCE)
add_subdirectory(external/dex_builder)
@ -29,9 +25,6 @@ option(LSPLANT_BUILD_SHARED "If ON, lsplant will also build shared library" ON)
if (LSPLANT_BUILD_SHARED)
message(STATUS "Building lsplant as a shared library")
add_library(${PROJECT_NAME} SHARED ${SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME} PRIVATE FILE_SET ms TYPE CXX_MODULES FILES ${MODULE_SOURCES})
target_sources(${PROJECT_NAME} PUBLIC FILE_SET CXX_MODULES FILES ${MODULE_INTERFACES})
target_include_directories(${PROJECT_NAME} PUBLIC include)
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_compile_options(${PROJECT_NAME} PRIVATE -flto)
@ -47,9 +40,6 @@ if (LSPLANT_BUILD_SHARED)
endif()
add_library(${PROJECT_NAME}_static STATIC ${SOURCES})
set_target_properties(${PROJECT_NAME}_static PROPERTIES CXX_SCAN_FOR_MODULES ON)
target_sources(${PROJECT_NAME}_static PRIVATE FILE_SET ms TYPE CXX_MODULES FILES ${MODULE_SOURCES})
target_sources(${PROJECT_NAME}_static PUBLIC FILE_SET CXX_MODULES FILES ${MODULE_INTERFACES})
target_include_directories(${PROJECT_NAME}_static PUBLIC include)
target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})

View File

@ -1,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;
import :common;
import :art_method;
import :thread;
import :handle;
import hook_helper;
export namespace lsplant::art::mirror {
namespace mirror {
class Class {
private:
inline static auto GetDescriptor_ =
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>;
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
if (GetDescriptorSym) [[likely]]
return GetDescriptorSym(thiz, storage);
else
return "";
}
inline static auto GetClassDef_ =
"_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>;
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
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 *,
phmap::flat_hash_map<const dex::ClassDef *, BackupMethods>>
backup_methods_;
inline static std::mutex backup_methods_lock_;
inline static uint8_t initialized_status = 0;
static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods out;
std::list<std::tuple<art::ArtMethod *, void *>> out;
if (!class_def) return;
{
hooked_classes_.if_contains(class_def, [&out](const auto &it) {
for (auto method : it.second) {
if (method->IsStatic()) {
LOGV("Backup hooked method %p because of initialization", method);
out.emplace(method, method->GetEntryPoint());
out.emplace_back(method, method->GetEntryPoint());
}
}
});
@ -48,7 +51,7 @@ private:
for (auto method : it.second) {
if (method->IsStatic()) {
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_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint8_t new_status, Thread *self) static -> void {
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE", void,
SetClassStatus, (TrivialHandle<Class> h, uint8_t new_status, Thread *self), {
if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self);
BackupClassMethods(h->GetClassDef(), self);
}
return backup(h, new_status, self);
};
});
inline static auto SetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(Handle<Class> h, int new_status, Thread *self) static -> void {
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, SetStatus,
(Handle<Class> h, int new_status, Thread *self), {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(h.Get()), self);
BackupClassMethods(h->GetClassDef(), self);
}
return backup(h, new_status, self);
};
});
inline static auto TrivialSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint32_t new_status, Thread *self) static -> void {
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void,
TrivialSetStatus, (TrivialHandle<Class> h, uint32_t new_status, Thread *self), {
if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self);
BackupClassMethods(h->GetClassDef(), self);
}
return backup(h, new_status, self);
};
});
inline static auto ClassSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(Class *thiz, int new_status, Thread *self) static -> void {
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE", void,
ClassSetStatus, (Class * thiz, int new_status, Thread *self), {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(thiz), self);
BackupClassMethods(thiz->GetClassDef(), self);
}
return backup(thiz, new_status, self);
};
});
inline static uint8_t initialized_status = 0;
public:
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;
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) {
if (!handler(SetStatus_, ClassSetStatus_)) {
if (!HookSyms(handler, SetStatus, ClassSetStatus)) {
return false;
}
} else {
if (!handler(SetClassStatus_, TrivialSetStatus_)) {
if (!HookSyms(handler, SetClassStatus, TrivialSetStatus)) {
return false;
}
}
@ -130,14 +135,22 @@ public:
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 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) {
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 <string>
#include <memory>
#include "art/mirror/class.hpp"
#include "common.hpp"
#include "logging.hpp"
namespace lsplant::art {
export module lsplant:art_method;
import :common;
import hook_helper;
export namespace lsplant::art {
class ArtMethod {
inline static auto PrettyMethod_ =
"_ZN3art9ArtMethod12PrettyMethodEPS0_b"_sym.as<std::string(ArtMethod::*)(bool)>;
inline static auto PrettyMethodStatic_ =
"_ZN3art12PrettyMethodEPNS_9ArtMethodEb"_sym.as<std::string(ArtMethod *thiz, bool with_signature)>;
inline static auto PrettyMethodMirror_ =
"_ZN3art12PrettyMethodEPNS_6mirror9ArtMethodEb"_sym.as<std::string(ArtMethod *thiz, bool with_signature)>;
inline static auto GetMethodShortyL_ =
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as<const char *(JNIEnv *env, jmethodID method)>;
inline static auto GetMethodShorty_ =
"_ZN3art15GetMethodShortyEP7_JNIEnvP10_jmethodID"_sym.as<const char *(JNIEnv *env, jmethodID mid)>;
inline static auto ThrowInvocationTimeError_ =
"_ZN3art9ArtMethod24ThrowInvocationTimeErrorEv"_sym.as<void(ArtMethod::*)()>;
inline static auto art_interpreter_to_compiled_code_bridge_ =
"artInterpreterToCompiledCodeBridge"_sym.as<void()>;
inline void ThrowInvocationTimeError() {
if (ThrowInvocationTimeError_) {
[[likely]] ThrowInvocationTimeError_(this);
CREATE_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) {
if (thiz == nullptr) [[unlikely]]
return "null";
else if (PrettyMethodSym) [[likely]]
return PrettyMethodSym(thiz, with_signature);
else
return "null sym";
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, ThrowInvocationTimeError, ArtMethod *thiz) {
if (thiz && ThrowInvocationTimeErrorSym) [[likely]]
return ThrowInvocationTimeErrorSym(thiz);
}
CREATE_FUNC_SYMBOL_ENTRY(const char *, GetMethodShorty, JNIEnv *env, jmethodID mid) {
if (GetMethodShortySym) [[likely]]
return GetMethodShortySym(env, mid);
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(void, art_interpreter_to_compiled_code_bridge) {}
inline void ThrowInvocationTimeError() { ThrowInvocationTimeError(this); }
public:
inline static const char *GetMethodShorty(JNIEnv *env, jobject method) {
if (GetMethodShortyL_) {
return GetMethodShortyL_(env, env->FromReflectedMethod(method));
}
return GetMethodShorty_(env, env->FromReflectedMethod(method));
return GetMethodShorty(env, env->FromReflectedMethod(method));
}
void SetNonCompilable() {
@ -91,18 +78,6 @@ public:
SetAccessFlags(access_flags);
}
void SetNative() {
auto access_flags = GetAccessFlags();
access_flags |= kAccNative;
SetAccessFlags(access_flags);
}
void SetNonNative() {
auto access_flags = GetAccessFlags();
access_flags &= ~kAccNative;
SetAccessFlags(access_flags);
}
bool IsPrivate() { return GetAccessFlags() & kAccPrivate; }
bool IsProtected() { return GetAccessFlags() & kAccProtected; }
bool IsPublic() { return GetAccessFlags() & kAccPublic; }
@ -119,7 +94,7 @@ public:
if (interpreter_entry_point_offset) [[unlikely]] {
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
interpreter_entry_point_offset) =
reinterpret_cast<void *>(&art_interpreter_to_compiled_code_bridge_);
reinterpret_cast<void *>(art_interpreter_to_compiled_code_bridgeSym);
}
}
@ -148,11 +123,7 @@ public:
}
std::string PrettyMethod(bool with_signature = true) {
if (PrettyMethod_) [[likely]]
return PrettyMethod_(this, with_signature);
if (PrettyMethodStatic_) return PrettyMethodStatic_(this, with_signature);
if (PrettyMethodMirror_) return PrettyMethodMirror_(this, with_signature);
return "null sym";
return PrettyMethod(this, with_signature);
}
mirror::Class *GetDeclaringClass() {
@ -160,23 +131,6 @@ public:
reinterpret_cast<uintptr_t>(this) + declaring_class_offset));
}
std::unique_ptr<ArtMethod> Clone() {
auto *method = reinterpret_cast<ArtMethod*>(::operator new(art_method_size));
method->CopyFrom(this);
return std::unique_ptr<ArtMethod>(method);
}
void BackupTo(ArtMethod *backup) {
SetNonCompilable();
// copy after setNonCompilable
backup->CopyFrom(this);
ClearFastInterpretFlag();
if (!backup->IsStatic()) backup->SetPrivate();
}
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
if (art_method_field) [[likely]] {
return reinterpret_cast<art::ArtMethod *>(
@ -224,8 +178,8 @@ public:
LOGE("Throwable has less than 2 constructors");
return false;
}
auto first_ctor = constructors[0];
auto second_ctor = constructors[1];
auto &first_ctor = constructors[0];
auto &second_ctor = constructors[1];
auto *first = FromReflectedMethod(env, first_ctor.get());
auto *second = FromReflectedMethod(env, second_ctor.get());
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
@ -267,7 +221,7 @@ public:
env,
JNI_GetObjectField(
env,
JNI_ToReflectedField(env, executable,
env->ToReflectedField(executable,
JNI_GetFieldID(env, executable, name, sig), false),
art_field_field),
field_offset);
@ -295,12 +249,17 @@ public:
}
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");
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]] {
auto abstract_method_error = JNI_FindClass(env, "java/lang/AbstractMethodError");
@ -315,9 +274,8 @@ public:
LOGE("Failed to find Executable.getName");
return false;
}
handler(ThrowInvocationTimeError_);
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();
abstract_method->SetAccessFlags(access_flags | kAccDefaultConflict);
abstract_method->ThrowInvocationTimeError();
@ -334,7 +292,8 @@ public:
kAccCompileDontBother = 0;
}
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;
}
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"
export module lsplant:instrumentation;
import :art_method;
import :common;
import hook_helper;
#include "art_method.hpp"
#include "common.hpp"
namespace lsplant::art {
export class Instrumentation {
class Instrumentation {
inline static ArtMethod *MaybeUseBackupMethod(ArtMethod *art_method, const void *quick_code) {
if (auto backup = IsHooked(art_method); backup && art_method->GetEntryPoint() != quick_code)
[[unlikely]] {
@ -21,29 +16,29 @@ export class Instrumentation {
return art_method;
}
inline static auto UpdateMethodsCodeToInterpreterEntryPoint_ =
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE"_sym.hook->*[]
<MemBackup auto backup>
(Instrumentation *thiz, ArtMethod *art_method) static -> void {
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art15instrumentation15Instrumentation40UpdateMethodsCodeToInterpreterEntryPointEPNS_9ArtMethodE",
void, UpdateMethodsCodeToInterpreterEntryPoint,
(Instrumentation * thiz, ArtMethod *art_method), {
if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str());
return;
}
backup(thiz, MaybeUseBackupMethod(art_method, nullptr));
};
});
inline static auto InitializeMethodsCode_ =
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv"_sym.hook->*[]
<MemBackup auto backup>
(Instrumentation *thiz, ArtMethod *art_method, const void *quick_code) static -> void {
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art15instrumentation15Instrumentation21InitializeMethodsCodeEPNS_9ArtMethodEPKv", void,
InitializeMethodsCode,
(Instrumentation * thiz, ArtMethod *art_method, const void *quick_code), {
if (IsDeoptimized(art_method)) {
LOGV("skip update entrypoint on deoptimized method %s",
art_method->PrettyMethod(true).c_str());
return;
}
backup(thiz, MaybeUseBackupMethod(art_method, quick_code), quick_code);
};
});
public:
static bool Init(JNIEnv *env, const HookHandler &handler) {
@ -52,7 +47,8 @@ public:
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!handler(InitializeMethodsCode_, UpdateMethodsCodeToInterpreterEntryPoint_)) {
if (!HookSyms(handler, InitializeMethodsCode,
UpdateMethodsCodeToInterpreterEntryPoint)) {
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 <sys/system_properties.h>
@ -9,23 +8,10 @@ module;
#include <string_view>
#include "logging.hpp"
#include "lsplant.hpp"
#include "utils/hook_helper.hpp"
export module lsplant:common;
export import jni_helper;
export import hook_helper;
export namespace lsplant {
namespace art {
class ArtMethod;
namespace mirror {
class Class;
}
namespace dex {
class ClassDef {};
} // namespace dex
} // namespace art
namespace lsplant {
enum class Arch {
kArm,
@ -51,24 +37,14 @@ consteval inline Arch GetArch() {
#endif
}
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
constexpr auto kArch = GetArch();
inline static constexpr auto kArch = GetArch();
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
[[gnu::const]] inline auto GetAndroidApiLevel() {
inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
@ -90,14 +66,13 @@ inline auto IsJavaDebuggable(JNIEnv * env) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method = JNI_GetStaticMethodID(env, runtime_class, "getRuntime",
"()Ldalvik/system/VMRuntime;");
auto get_runtime_method =
JNI_GetStaticMethodID(env, runtime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method =
JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
auto is_debuggable_method = JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
@ -114,25 +89,48 @@ inline auto IsJavaDebuggable(JNIEnv * env) {
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_;
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_;
std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
std::shared_mutex jit_movements_lock_;
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
inline std::shared_mutex jit_movements_lock_;
} // namespace
inline art::ArtMethod *IsHooked(art::ArtMethod *art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (including_backup || it.second.first) backup = it.second.second;
if (!including_backup || it.second.first) backup = it.second.second;
});
return backup;
}
@ -142,7 +140,7 @@ inline art::ArtMethod *IsBackup(art::ArtMethod * art_method) {
hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second;
});
return backup;
return nullptr;
}
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_);
jit_movements_.emplace_back(target, backup);
}
} // 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
#include <android/log.h>
#include <concepts>
#include "jni_helper.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 {
template <size_t N>
struct FixedString {
consteval FixedString(const char (&str)[N]) { std::copy_n(str, N, data); }
char data[N] = {};
};
using HookHandler = InitInfo;
template<typename T>
concept FuncType = std::is_function_v<T> || std::is_member_function_pointer_v<T>;
template <char... chars>
struct tstring : public std::integer_sequence<char, chars...> {
inline constexpr static const char *c_str() { return str_; }
template <FixedString, FuncType>
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;
}
inline constexpr operator std::string_view() const { return {c_str(), sizeof...(chars)}; }
private:
inline static union {
Ret (*function_)(Args...);
void *raw_function_ = nullptr;
} inner_;
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_));
inline static constexpr char str_[]{chars..., '\0'};
};
template <FixedString Sym, class This, typename Ret, typename... Args>
struct Function<Sym, Ret(This::*)(Args...)> {
[[gnu::always_inline]] static Ret operator()(This *thiz, Args... args) {
return (reinterpret_cast<ThisType *>(thiz)->*inner_.function_)(args...);
}
[[gnu::always_inline]] operator bool() { return inner_.raw_function_; }
[[gnu::always_inline]] auto operator&() const { return inner_.function_; }
[[gnu::always_inline]] Function &operator=(void *function) {
inner_.raw_function_ = function;
return *this;
template <typename T, T... chars>
inline constexpr tstring<chars...> operator""_tstr() {
return {};
}
private:
using ThisType = std::conditional_t<std::is_same_v<This, void>, Function, This>;
inline static union {
Ret (ThisType::*function_)(Args...) const;
template <char... as, char... bs>
inline constexpr tstring<as..., bs...> operator+(const tstring<as...> &, const tstring<bs...> &) {
return {};
}
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 {
void *raw_function_ = nullptr;
[[maybe_unused]] std::ptrdiff_t adj = 0;
};
} inner_;
static_assert(sizeof(inner_.function_) == sizeof(inner_.raw_function_) + sizeof(inner_.adj));
};
template <FixedString, typename T>
struct Field {
[[gnu::always_inline]] T *operator->() { return inner_.field_; }
[[gnu::always_inline]] T &operator*() { return *inner_.field_; }
[[gnu::always_inline]] operator bool() { return inner_.raw_field_ != nullptr; }
[[gnu::always_inline]] Field &operator=(void *field) {
inner_.raw_field_ = field;
return *this;
decltype(func) p;
std::ptrdiff_t adj;
} data;
} u{.data = {func, 0}};
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
return u.f;
}
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;
template <FixedString Sym, typename Ret, typename... Args>
struct Hooker<Sym, Ret(Args...)> : Function<Sym, Ret(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(Args...)) {
replace_ = replace;
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(Args...) = nullptr;
template <typename Ret, typename... Args, char... cs>
struct Hooker<Ret(Args...), tstring<cs...>> {
inline static Ret (*backup)(Args...) = nullptr;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
template <FixedString Sym, class This, typename Ret, typename... Args>
struct Hooker<Sym, Ret(This::*)(Args...)> : Function<Sym, Ret(This::*)(Args...)> {
[[gnu::always_inline]] Hooker &operator=(void *function) {
Function<Sym, Ret(This::*)(Args...)>::operator=(function);
return *this;
}
private:
[[gnu::always_inline]] constexpr Hooker(Ret (*replace)(This *, Args...)) {
replace_ = replace;
template <typename, typename>
struct MemHooker;
template <typename Ret, typename This, typename... Args, char... cs>
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
inline static MemberFunction<Ret(Args...)> backup;
inline static constexpr std::string_view sym = tstring<cs...>{};
};
friend struct HookHandler;
template<FixedString S>
friend struct Symbol;
inline static Ret (*replace_)(This *, Args...) = nullptr;
};
struct HookHandler {
HookHandler(const InitInfo &info) : info_(info) {}
template <typename T>
[[gnu::always_inline]] bool operator()(T &&arg) const {
return handle(std::forward<T>(arg), false);
}
concept HookerType = requires(T a) {
a.backup;
a.replace;
};
template <typename T1, typename T2, typename... U>
[[gnu::always_inline]] bool operator()(T1 &&arg1, T2 &&arg2, U &&...args) const {
if constexpr(std::is_same_v<T2, bool>)
return handle(std::forward<T1>(arg1), std::forward<T2>(arg2)) || this->operator()(std::forward<U>(args)...);
else
return handle(std::forward<T1>(arg1), false) || this->operator()(std::forward<T2>(arg2), std::forward<U>(args)...);
template <HookerType T>
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
if (original) {
if constexpr (is_instance_v<decltype(arg.backup), MemberFunction>) {
void *backup = handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace));
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
} else {
arg.backup = reinterpret_cast<decltype(arg.backup)>(
handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace)));
}
private:
[[gnu::always_inline]] bool operator()() const {
return true;
} else {
return false;
}
const InitInfo &info_;
template<FixedString Sym, typename ...Us, template<FixedString, typename...> typename T>
requires(!requires { T<Sym, Us...>::replace_; })
[[gnu::always_inline]] bool handle(T<Sym, Us...> &target, bool match_prefix) const {
return target = dlsym<Sym>(match_prefix);
}
template<FixedString Sym, typename ...Us, template<FixedString, typename...> typename T>
requires(requires { T<Sym, Us...>::replace_; })
[[gnu::always_inline]] bool handle(T<Sym, Us...> &hooker, bool match_prefix) const {
return hooker = hook(dlsym<Sym>(match_prefix), reinterpret_cast<void *>(hooker.replace_));
template <HookerType T>
inline static bool HookSym(const HookHandler &handler, T &arg) {
auto original = handler.art_symbol_resolver(arg.sym);
return HookSymNoHandle(handler, original, arg);
}
template <FixedString Sym>
[[gnu::always_inline]] void *dlsym(bool match_prefix = false) const {
if (auto match = info_.art_symbol_resolver(Sym.data); match) {
return match;
}
if (match_prefix && info_.art_symbol_prefix_resolver) [[likely]] {
return info_.art_symbol_prefix_resolver(Sym.data);
}
return nullptr;
}
[[gnu::always_inline]] void *hook(void *original, void *replace) const {
if (original) [[likely]] {
return info_.inline_hooker(original, replace);
}
return nullptr;
}
};
template<typename F>
concept Backup = std::is_function_v<std::remove_pointer_t<F>>;
template<typename F>
concept MemBackup = std::is_member_function_pointer_v<std::remove_pointer_t<F>> || Backup<F>;
template<FixedString S>
struct Symbol {
template<typename T>
inline static decltype([]{
if constexpr (FuncType<T>) {
return Function<S, T>{};
} else {
return Field<S, T>{};
}
}()) as{};
[[no_unique_address]] struct Hook {
template<typename F>
auto operator->*(F&&) const {
using Signature = decltype(F::template operator()<&decltype([] static {})::operator()>);
if constexpr (requires { F::template operator()<&decltype([] {})::operator()>; }) {
using HookerType = Hooker<S, decltype([]<class This, typename Ret, typename... Args>(Ret(*)(This*, Args...)) -> Ret(This::*)(Args...) {
return {};
}.template operator()(std::declval<Signature>()))>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
} else {
using HookerType = Hooker<S, Signature>;
return HookerType{static_cast<decltype(HookerType::replace_)>(&F::template operator()<HookerType::operator()>)};
}
};
} hook;
};
template <FixedString S> constexpr Symbol<S> operator""_sym() {
return {};
}
template<FixedString S, FixedString P>
consteval auto operator|([[maybe_unused]] Symbol<S> a, [[maybe_unused]] Symbol<P> b) {
#if defined(__LP64__)
return b;
template <HookerType T, HookerType... Args>
inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
__android_log_print(ANDROID_LOG_ERROR,
#ifdef LOG_TAG
LOG_TAG,
#else
return a;
"HookHelper",
#endif
"Hook Fails: %*s", static_cast<int>(first.sym.size()),
first.sym.data());
return false;
}
return true;
}
} // 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_view>
#include "type_traits.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Winvalid-partial-specialization"
#pragma clang diagnostic ignored "-Wunknown-pragmas"
@ -18,6 +16,15 @@
void operator=(const TypeName &) = delete
namespace lsplant {
template <class, template <class, class...> class>
struct is_instance : public std::false_type {};
template <class... Ts, template <class, class...> class U>
struct is_instance<U<Ts...>, U> : public std::true_type {};
template <class T, template <class, class...> class U>
inline constexpr bool is_instance_v = is_instance<T, U>::value;
template <typename T>
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
@ -54,10 +61,13 @@ public:
T get() const { return local_ref_; }
ScopedLocalRef<T> clone() const {
return ScopedLocalRef<T>(env_, (T)env_->NewLocalRef(local_ref_));
}
operator T() const { return local_ref_; }
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
// ScopedLocalRef<...> ref;
// ref.reset(...);
// Move assignment operator.
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
reset(s.release());
env_ = s.env_;
@ -77,8 +87,6 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
class JObjectArrayElement;
template <typename T>
concept JArray = std::is_base_of_v<std::remove_pointer_t<_jarray>, std::remove_pointer_t<T>>;
@ -109,11 +117,9 @@ public:
};
template <typename T, typename U>
concept ScopeOrRaw =
std::is_convertible_v<T, U> ||
(is_instance_v<std::decay_t<T>, ScopedLocalRef> &&
std::is_convertible_v<typename std::decay_t<T>::BaseType, U>) ||
(std::is_same_v<std::decay_t<T>, JObjectArrayElement> && std::is_convertible_v<jobject, U>);
concept ScopeOrRaw = std::is_convertible_v<T, U> ||
(is_instance_v<std::decay_t<T>, ScopedLocalRef>
&&std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
template <typename T>
concept ScopeOrClass = ScopeOrRaw<T, jclass>;
@ -124,11 +130,10 @@ concept ScopeOrObject = ScopeOrRaw<T, jobject>;
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
if (auto exception = env->ExceptionOccurred()) {
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(
log, "getStackTraceString", "(Ljava/lang/Throwable;)Ljava/lang/String;");
auto str = (jstring)env->CallStaticObjectMethod(log, toString, exception);
env->DeleteLocalRef(log);
env->DeleteLocalRef(exception);
return {env, str};
}
@ -141,8 +146,6 @@ template <typename T>
return x.data();
else if constexpr (is_instance_v<std::decay_t<T>, ScopedLocalRef>)
return x.get();
else if constexpr (std::is_same_v<std::decay_t<T>, JObjectArrayElement>)
return x.get();
else
return std::forward<T>(x);
}
@ -173,27 +176,28 @@ inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
class JUTFString {
public:
JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {}
JUTFString(const ScopedLocalRef<jstring> &jstr)
inline JUTFString(const ScopedLocalRef<jstring> &jstr)
: JUTFString(jstr.env_, jstr.local_ref_, nullptr) {}
JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env), jstr_(jstr) {
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr)
: env_(env), jstr_(jstr) {
if (env_ && jstr_)
cstr_ = env_->GetStringUTFChars(jstr, nullptr);
else
cstr_ = default_cstr;
}
operator const char *() const { return cstr_; }
inline operator const char *() const { return cstr_; }
operator const std::string() const { return cstr_; }
inline operator const std::string() const { return cstr_; }
operator const bool() const { return cstr_ != nullptr; }
inline operator const bool() const { return cstr_ != nullptr; }
auto get() const { return cstr_; }
inline auto get() const { return cstr_; }
~JUTFString() {
inline ~JUTFString() {
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
}
@ -505,13 +509,6 @@ template <ScopeOrClass Class>
isStatic);
}
template <ScopeOrClass Class>
[[maybe_unused]] inline auto JNI_ToReflectedField(JNIEnv *env, Class &&clazz, jfieldID field,
jboolean isStatic = JNI_FALSE) {
return JNI_SafeInvoke(env, &JNIEnv::ToReflectedField, std::forward<Class>(clazz), field,
isStatic);
}
// functions to method
// virtual methods
@ -670,17 +667,18 @@ template <ScopeOrClass Class, typename... Args>
std::forward<Args>(args)...);
}
// non-virtual methods
// non-vritual methods
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
[[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward<Object>(obj),
@ -688,7 +686,7 @@ 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,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward<Object>(obj),
@ -696,49 +694,55 @@ 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,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualCharMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualShortMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualIntMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualLongMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, Class &&clazz,
jmethodID method, Args &&...args) {
[[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward<Object>(obj),
std::forward<Class>(clazz), method, std::forward<Args>(args)...);
}
template <ScopeOrObject Object, ScopeOrClass Class, typename... Args>
[[maybe_unused]] inline auto JNI_CallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
[[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj,
Class &&clazz, jmethodID method,
Args &&...args) {
return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward<Object>(obj),
@ -769,12 +773,6 @@ template <ScopeOrObject Object, ScopeOrClass Class>
std::forward<Class>(clazz));
}
template <ScopeOrObject Object1, ScopeOrObject Object2>
[[maybe_unused]] inline auto JNI_IsSameObject(JNIEnv *env, Object1 &&a, Object2 &&b) {
return JNI_SafeInvoke(env, &JNIEnv::IsSameObject, std::forward<Object1>(a),
std::forward<Object2>(b));
}
template <ScopeOrObject Object>
[[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
return (decltype(UnwrapScope(std::forward<Object>(x))))env->NewGlobalRef(
@ -782,17 +780,11 @@ template <ScopeOrObject Object>
}
template <typename U, typename T>
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x)
requires(std::is_convertible_v<T, _jobject *>)
{
[[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef<T> &&x) requires(
std::is_convertible_v<T, _jobject *>) {
return ScopedLocalRef<U>(std::move(x));
}
template <typename U>
[[maybe_unused]] inline auto JNI_Cast(JObjectArrayElement &&x) {
return JNI_Cast<U, jobject>(std::move(x));
}
[[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) {
return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity);
}
@ -800,6 +792,11 @@ template <typename U>
template <JArray T>
struct JArrayUnderlyingTypeHelper;
template <>
struct JArrayUnderlyingTypeHelper<jobjectArray> {
using Type = ScopedLocalRef<jobject>;
};
template <>
struct JArrayUnderlyingTypeHelper<jbooleanArray> {
using Type = jboolean;
@ -845,6 +842,10 @@ using JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper<T>::Type;
template <JArray T>
class ScopedLocalRef<T> {
ScopedLocalRef(JNIEnv *env, T local_ref, size_t size, JArrayUnderlyingType<T> *elements,
bool modified) noexcept
: env_(env), local_ref_(local_ref), size_(size), elements_(elements), modified_(modified) {}
public:
class Iterator {
friend class ScopedLocalRef<T>;
@ -902,7 +903,13 @@ public:
reset(local_ref);
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
ScopedLocalRef(ScopedLocalRef &&s) noexcept
: ScopedLocalRef(s.env_, s.local_ref_, s.size_, s.elements_, s.modified_) {
s.local_ref_ = nullptr;
s.size_ = 0;
s.elements_ = nullptr;
s.modified_ = false;
}
template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {}
@ -916,13 +923,25 @@ public:
if (local_ref_ != nullptr) {
ReleaseElements(modified_ ? 0 : JNI_ABORT);
env_->DeleteLocalRef(local_ref_);
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
elements_[i].~ScopedLocalRef<jobject>();
}
operator delete[](elements_);
}
elements_ = nullptr;
}
local_ref_ = ptr;
size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;
if (!local_ref_) return;
static_assert(!std::is_same_v<T, jobjectArray>);
if constexpr (std::is_same_v<T, jbooleanArray>) {
if constexpr (std::is_same_v<T, jobjectArray>) {
elements_ = static_cast<ScopedLocalRef<jobject> *>(operator new[](
sizeof(ScopedLocalRef<jobject>) * size_));
for (size_t i = 0; i < size_; ++i) {
new (&elements_[i]) ScopedLocalRef<jobject>(
JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, i));
}
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr);
} else if constexpr (std::is_same_v<T, jbyteArray>) {
elements_ = env_->GetByteArrayElements(local_ref_, nullptr);
@ -947,12 +966,20 @@ public:
size_ = 0;
local_ref_ = nullptr;
ReleaseElements(modified_ ? 0 : JNI_ABORT);
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
elements_[i].~ScopedLocalRef<jobject>();
}
operator delete[](elements_);
}
elements_ = nullptr;
return localRef;
}
T get() const { return local_ref_; }
explicit operator T() const { return local_ref_; }
JArrayUnderlyingType<T> &operator[](size_t index) {
modified_ = true;
return elements_[index];
@ -995,7 +1022,11 @@ public:
private:
void ReleaseElements(jint mode) {
if (!local_ref_ || !elements_) return;
if constexpr (std::is_same_v<T, jbooleanArray>) {
if constexpr (std::is_same_v<T, jobjectArray>) {
for (size_t i = 0; i < size_; ++i) {
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, local_ref_, i, elements_[i]);
}
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode);
} else if constexpr (std::is_same_v<T, jbyteArray>) {
env_->ReleaseByteArrayElements(local_ref_, elements_, mode);
@ -1022,255 +1053,6 @@ private:
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
class JObjectArrayElement {
friend class ScopedLocalRef<jobjectArray>;
auto obtain() {
if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
}
explicit JObjectArrayElement(JNIEnv *env, jobjectArray array, int i, size_t size)
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
JObjectArrayElement &operator++() {
++i_;
item_ = obtain();
return *this;
}
JObjectArrayElement &operator--() {
--i_;
item_ = obtain();
return *this;
}
JObjectArrayElement operator++(int) { return JObjectArrayElement(env_, array_, i_ + 1, size_); }
JObjectArrayElement operator--(int) { return JObjectArrayElement(env_, array_, i_ - 1, size_); }
public:
JObjectArrayElement(JObjectArrayElement &&s)
: env_(s.env_), array_(s.array_), i_(s.i_), size_(s.size_), item_(std::move(s.item_)) {}
operator ScopedLocalRef<jobject> &() & { return item_; }
operator ScopedLocalRef<jobject> &&() && { return std::move(item_); }
JObjectArrayElement &operator=(JObjectArrayElement &&s) {
reset(s.item_.release());
return *this;
}
JObjectArrayElement &operator=(const JObjectArrayElement &s) {
reset(env_->NewLocalRef(s.item_.get()));
return *this;
}
template<JObject T>
JObjectArrayElement &operator=(ScopedLocalRef<T> &&s) {
reset(s.release());
return *this;
}
template<JObject T>
JObjectArrayElement &operator=(const ScopedLocalRef<T> &s) {
reset(s.clone());
return *this;
}
JObjectArrayElement &operator=(jobject s) {
reset(env_->NewLocalRef(s));
return *this;
}
void reset(jobject item) {
item_.reset(item);
JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, array_, i_, item_);
}
ScopedLocalRef<jobject> clone() const { return item_.clone(); }
jobject get() const { return item_.get(); }
jobject release() { return item_.release(); }
jobject operator->() const { return item_.get(); }
jobject operator*() const { return item_.get(); }
private:
JNIEnv *env_;
jobjectArray array_;
int i_;
int size_;
ScopedLocalRef<jobject> item_;
JObjectArrayElement(const JObjectArrayElement &) = delete;
};
template <>
class ScopedLocalRef<jobjectArray> {
public:
class Iterator {
friend class ScopedLocalRef<jobjectArray>;
Iterator(JObjectArrayElement &&e) : e_(std::move(e)) {}
Iterator(JNIEnv *env, jobjectArray array, int i, size_t size) : e_(env, array, i, size) {}
public:
auto &operator*() { return e_; }
auto *operator->() { return e_.get(); }
Iterator &operator++() {
++e_;
return *this;
}
Iterator &operator--() {
--e_;
return *this;
}
Iterator operator++(int) { return Iterator(e_++); }
Iterator operator--(int) { return Iterator(e_--); }
bool operator==(const Iterator &other) const { return other.e_.i_ == e_.i_; }
bool operator!=(const Iterator &other) const { return other.e_.i_ != e_.i_; }
private:
JObjectArrayElement e_;
};
class ConstIterator {
friend class ScopedLocalRef<jobjectArray>;
auto obtain() {
if (i_ < 0 || i_ >= size_) return ScopedLocalRef<jobject>{nullptr};
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, array_, i_);
}
ConstIterator(JNIEnv *env, jobjectArray array, int i, int size)
: env_(env), array_(array), i_(i), size_(size), item_(obtain()) {}
public:
auto &operator*() { return item_; }
auto *operator->() { return &item_; }
ConstIterator &operator++() {
++i_;
item_ = obtain();
return *this;
}
ConstIterator &operator--() {
--i_;
item_ = obtain();
return *this;
}
ConstIterator operator++(int) { return ConstIterator(env_, array_, i_ + 1, size_); }
ConstIterator operator--(int) { return ConstIterator(env_, array_, i_ - 1, size_); }
bool operator==(const ConstIterator &other) const { return other.i_ == i_; }
bool operator!=(const ConstIterator &other) const { return other.i_ != i_; }
private:
JNIEnv *env_;
jobjectArray array_;
int i_;
int size_;
ScopedLocalRef<jobject> item_;
};
auto begin() { return Iterator(env_, local_ref_, 0, size_); }
auto end() { return Iterator(env_, local_ref_, size_, size_); }
const auto begin() const { return ConstIterator(env_, local_ref_, 0, size_); }
auto end() const { return ConstIterator(env_, local_ref_, size_, size_); }
const auto cbegin() const { return ConstIterator(env_, local_ref_, 0, size_); }
auto cend() const { return ConstIterator(env_, local_ref_, size_, size_); }
ScopedLocalRef(JNIEnv *env, jobjectArray local_ref) noexcept : env_(env), local_ref_(nullptr) {
reset(local_ref);
}
ScopedLocalRef(ScopedLocalRef &&s) noexcept { *this = std::move(s); }
template <JObject U>
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept
: ScopedLocalRef(s.env_, (jobjectArray)s.release()) {}
explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, jobjectArray{nullptr}) {}
~ScopedLocalRef() { env_->DeleteLocalRef(release()); }
void reset(jobjectArray ptr = nullptr) {
if (ptr != local_ref_) {
if (local_ref_ != nullptr) {
env_->DeleteLocalRef(local_ref_);
}
local_ref_ = ptr;
size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0;
if (!local_ref_) return;
}
}
[[nodiscard]] jobjectArray release() {
jobjectArray localRef = local_ref_;
size_ = 0;
local_ref_ = nullptr;
return localRef;
}
jobjectArray get() const { return local_ref_; }
JObjectArrayElement operator[](size_t index) {
return JObjectArrayElement(env_, local_ref_, index, size_);
}
const ScopedLocalRef<jobject> operator[](size_t index) const {
return JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, index);
}
// We do not expose an empty constructor as it can easily lead to errors
// using common idioms, e.g.:
// ScopedLocalRef<...> ref;
// ref.reset(...);
// Move assignment operator.
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
env_ = s.env_;
local_ref_ = s.local_ref_;
size_ = s.size_;
s.size_ = 0;
s.local_ref_ = nullptr;
return *this;
}
size_t size() const { return size_; }
operator bool() const { return local_ref_; }
template <JObject U>
friend class ScopedLocalRef;
friend class JUTFString;
private:
JNIEnv *env_;
jobjectArray local_ref_;
size_t size_;
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
};
// functions to array
template <ScopeOrRaw<jarray> Array>

View File

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

View File

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

View File

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

View File

@ -1,39 +1,32 @@
module;
#include "lsplant.hpp"
#include <android/api-level.h>
#include <bits/sysconf.h>
#include <jni.h>
#include <sys/mman.h>
#include <sys/system_properties.h>
#include <array>
#include <atomic>
#include <bit>
#include <string_view>
#include <tuple>
#include "logging.hpp"
module lsplant;
import dex_builder;
import :common;
import :art_method;
import :clazz;
import :thread;
import :instrumentation;
import :runtime;
import :thread_list;
import :class_linker;
import :scope_gc_critical_section;
import :jit_code_cache;
import :jni_id_manager;
import :dex_file;
import :jit;
#include "art/mirror/class.hpp"
#include "art/runtime/art_method.hpp"
#include "art/runtime/class_linker.hpp"
#include "art/runtime/dex_file.hpp"
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
#include "art/runtime/instrumentation.hpp"
#include "art/runtime/jit/jit_code_cache.hpp"
#include "art/runtime/jni/jni_id_manager.h"
#include "art/runtime/runtime.hpp"
#include "art/runtime/thread.hpp"
#include "art/runtime/thread_list.hpp"
#include "common.hpp"
#include "dex_builder.h"
#include "utils/jni_helper.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma ide diagnostic ignored "ConstantConditionsOC"
#pragma ide diagnostic ignored "Simplify"
#pragma ide diagnostic ignored "UnreachableCode"
namespace lsplant {
using art::ArtMethod;
@ -43,14 +36,10 @@ using art::Instrumentation;
using art::Runtime;
using art::Thread;
using art::gc::ScopedGCCriticalSection;
using art::jit::Jit;
using art::jit::JitCodeCache;
using art::jni::JniIdManager;
using art::mirror::Class;
using art::thread_list::ScopedSuspendAll;
using art::JavaDebuggableGuard;
using namespace std::string_view_literals;
namespace {
template <typename T, T... chars>
@ -82,9 +71,9 @@ consteval inline auto GetTrampoline() {
}
if constexpr (kArch == Arch::kRiscv64) {
return std::make_tuple(
"\x17\x05\x00\x00\x03\x35\x05\x01\x83\x3f\x05\x00\x67\x80\x0f\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
"\x17\x05\x00\x00\x03\x35\xc5\x00\x67\x00\x05\x00\x78\x56\x34\x12\x78\x56\x34\x12"_uarr,
// 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) {
if (!handler.inline_hooker || !handler.inline_unhooker || !handler.art_symbol_resolver) {
return false;
}
if (!ArtMethod::Init(env, handler)) {
LOGE("Failed to init art method");
return false;
@ -267,18 +259,14 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init thread");
return false;
}
if (!ClassLinker::Init(handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!Class::Init(handler)) {
LOGE("Failed to init mirror class");
return false;
}
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
if (!ClassLinker::Init(env, handler)) {
LOGE("Failed to init class linker");
return false;
}
if (!ScopedSuspendAll::Init(handler)) {
LOGE("Failed to init scoped suspend all");
return false;
@ -291,10 +279,6 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jit code cache");
return false;
}
if (!Jit::Init(handler)) {
LOGE("Failed to init jit");
return false;
}
if (!DexFile::Init(env, handler)) {
LOGE("Failed to init dex file");
return false;
@ -307,6 +291,10 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
LOGE("Failed to init jni id manager");
return false;
}
if (!Runtime::Init(handler)) {
LOGE("Failed to init runtime");
return false;
}
// This should always be the last one
if (IsJavaDebuggable(env)) {
@ -317,6 +305,58 @@ bool InitNative(JNIEnv *env, const HookHandler &handler) {
return true;
}
struct JavaDebuggableGuard {
JavaDebuggableGuard() {
while (true) {
size_t expected = 0;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kJavaDebuggableAtInit);
count.fetch_add(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected + 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
~JavaDebuggableGuard() {
while (true) {
size_t expected = 2;
if (count.compare_exchange_strong(expected, 1, std::memory_order_acq_rel,
std::memory_order_acquire)) {
Runtime::Current()->SetJavaDebuggable(
Runtime::RuntimeDebugState::kNonJavaDebuggable);
count.fetch_sub(1, std::memory_order_release);
count.notify_all();
break;
}
if (expected == 1) {
count.wait(expected, std::memory_order_acquire);
continue;
}
if (count.compare_exchange_strong(expected, expected - 1, std::memory_order_acq_rel,
std::memory_order_relaxed)) {
break;
}
}
}
private:
inline static std::atomic_size_t count{0};
static_assert(std::atomic_size_t::is_always_lock_free, "Unsupported architecture");
static_assert(std::is_same_v<std::atomic_size_t::value_type, size_t>,
"Unsupported architecture");
};
std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject class_loader,
std::string_view shorty, bool is_static,
std::string_view method_name,
@ -353,7 +393,7 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
.Encode();
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})};
// allocate tmp first because of wide
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);
} else {
LiveRegister zero = backup_builder.AllocRegister();
LiveRegister zero_wide = backup_builder.AllocRegister();
backup_builder.BuildConst(zero, 0);
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
}
@ -429,12 +470,12 @@ std::tuple<jclass, jfieldID, jmethodID, jmethodID> BuildDex(JNIEnv *env, jobject
mprotect(target, image.size(), PROT_READ);
std::string err_msg;
const auto *dex = DexFile::OpenMemory(
reinterpret_cast<const uint8_t *>(target), image.size(),
generated_source_name.empty() ? "lsplant" : generated_source_name, &err_msg);
target, image.size(), generated_source_name.empty() ? "lsplant" : generated_source_name,
&err_msg);
if (!dex) {
LOGE("Failed to open memory dex: %s", err_msg.data());
} else {
java_dex_file = ScopedLocalRef(env, dex ? dex->ToJavaDexFile(env) : nullptr);
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 {
public:
uintptr_t address;
unsigned count4k : 12;
unsigned count16k : 14;
unsigned count : 12;
};
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_flag trampoline_lock{false};
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
constexpr size_t kPageSize = 4096; // assume
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
constexpr uintptr_t kAddressMask = 0xFFFU;
void *GenerateTrampolineFor(art::ArtMethod *hook) {
static const size_t kPageSize = sysconf(_SC_PAGESIZE); // assume
static const size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
unsigned count;
uintptr_t address;
while (true) {
auto tl = Trampoline{.address = trampoline_pool.fetch_add(1, std::memory_order_release)};
count = kPageSize == 16384 ? tl.count16k : tl.count4k;
count = tl.count;
address = tl.address & ~kAddressMask;
if (address == 0 || count >= kTrampolineNumPerPage) {
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
@ -505,7 +545,7 @@ void *GenerateTrampolineFor(art::ArtMethod *hook) {
}
count = 0;
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_lock.clear(std::memory_order_release);
trampoline_lock.notify_all();
@ -539,12 +579,18 @@ bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
} else {
LOGV("Generated trampoline %p", entrypoint);
target->SetNonCompilable();
hook->SetNonCompilable();
target->BackupTo(backup);
// copy after setNonCompilable
backup->CopyFrom(target);
target->ClearFastInterpretFlag();
target->SetEntryPoint(entrypoint);
if (!backup->IsStatic()) backup->SetPrivate();
LOGV("Done hook: target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p", target,
target->GetAccessFlags(), target->GetEntryPoint(), backup, backup->GetAccessFlags(),
backup->GetEntryPoint(), hook, hook->GetAccessFlags(), hook->GetEntryPoint());
@ -611,15 +657,15 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
std::string out;
auto type_to_shorty = [&](const ScopedLocalRef<jobject> &type) {
if (JNI_IsSameObject(env, type, int_type)) return 'I';
if (JNI_IsSameObject(env, type, long_type)) return 'J';
if (JNI_IsSameObject(env, type, float_type)) return 'F';
if (JNI_IsSameObject(env, type, double_type)) return 'D';
if (JNI_IsSameObject(env, type, boolean_type)) return 'Z';
if (JNI_IsSameObject(env, type, byte_type)) return 'B';
if (JNI_IsSameObject(env, type, char_type)) return 'C';
if (JNI_IsSameObject(env, type, short_type)) return 'S';
if (JNI_IsSameObject(env, type, void_type)) return 'V';
if (env->IsSameObject(type, int_type)) return 'I';
if (env->IsSameObject(type, long_type)) return 'J';
if (env->IsSameObject(type, float_type)) return 'F';
if (env->IsSameObject(type, double_type)) return 'D';
if (env->IsSameObject(type, boolean_type)) return 'Z';
if (env->IsSameObject(type, byte_type)) return 'B';
if (env->IsSameObject(type, char_type)) return 'C';
if (env->IsSameObject(type, short_type)) return 'S';
if (env->IsSameObject(type, void_type)) return 'V';
return 'L';
};
out += type_to_shorty(return_type);
@ -631,15 +677,10 @@ std::string GetProxyMethodShorty(JNIEnv *env, jobject proxy_method) {
} // namespace
inline namespace v2 {
extern "C++" {
using ::lsplant::IsHooked;
[[maybe_unused]] bool Init(JNIEnv *env, const InitInfo &info) {
if (!info.inline_hooker || !info.inline_unhooker || !info.art_symbol_resolver ||
!info.art_symbol_prefix_resolver) {
return false;
}
bool static kInit = InitConfig(info) && InitJNI(env) && InitNative(env, info);
return kInit;
}
@ -692,7 +733,7 @@ using ::lsplant::IsHooked;
}
std::tie(built_class, hooker_field, hook_method, backup_method) = WrapScope(
env,
BuildDex(env, callback_class_loader.get(),
BuildDex(env, callback_class_loader,
__builtin_expect(is_proxy, 0) ? GetProxyMethodShorty(env, target_method)
: ArtMethod::GetMethodShorty(env, target_method),
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);
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook.get());
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup.get());
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
JNI_SetStaticObjectField(env, built_class, hooker_field, hooker_object);
@ -792,7 +833,7 @@ using ::lsplant::IsHooked;
if (auto *backup = IsHooked(art_method); backup) {
art_method = backup;
}
if (!art_method || art_method->IsNative()) {
if (!art_method) {
return false;
}
return ClassLinker::SetEntryPointsToInterpreter(art_method);
@ -834,7 +875,8 @@ using ::lsplant::IsHooked;
if (!cookie) return false;
return DexFile::SetTrusted(env, cookie);
}
}
} // namespace v2
} // namespace lsplant
#pragma clang diagnostic pop

View File

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

View File

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

View File

@ -1,18 +1,10 @@
cmake_minimum_required(VERSION 3.18.1)
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)
set_target_properties(test PROPERTIES CXX_SCAN_FOR_MODULES ON)
add_library(test SHARED test.cpp elf_util.cpp)
find_package(dobby REQUIRED CONFIG)
find_package(lsplant REQUIRED CONFIG)
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)
target_link_libraries(test log dobby::dobby lsplant::lsplant)

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