Compare commits

..

160 Commits
v5.2 ... master

Author SHA1 Message Date
5ec1cff
e91129897c
Update build.yml 2025-03-31 18:54:27 +08:00
LoveSy
ff815781ba
Update hook_helper.hpp 2025-03-07 09:10:12 +08:00
LoveSy
0110cf7e6e upgrade to ndk 29 an use module partition 2025-03-06 11:53:03 +08:00
LoveSy
e2a35a4fab Setup Android SDK 2025-03-06 11:52:43 +08:00
dependabot[bot]
53070cff33
Bump the maven-dependencies group across 1 directory with 2 updates (#143) 2025-03-05 16:59:16 +08:00
LoveSy
c657d08dcf Upgrade gradle 2025-03-05 16:58:31 +08:00
LoveSy
ee6124f21e Simplify hookhandler usage 2025-03-05 15:36:13 +08:00
LoveSy
c54fb307f2
Refactor symbol define and use static operator() (#142) 2025-03-05 14:49:47 +08:00
LoveSy
4a2293e222
We only need to deoptimize non-native methods
Fix #140, close #141
2025-03-03 21:44:33 +08:00
LoveSy
4a18848a86
Use GetRuntimeQuickGenericJniStub for art_quick_generic_jni_trampoline 2025-03-01 20:45:22 +08:00
LoveSy
e9a016836b
Fix unit test 2025-02-26 21:30:33 +08:00
LoveSy
ce08314ca3
Fix art_quick_* not found
Close #136
2025-02-26 20:58:24 +08:00
烂泥扶上墙
8c3c6e6b2c
Fix the SIGSEGV caused by premature initialization of ClassLinker (#137)
ClassLinker should be initialized after Class because FixupStaticTrampolines references Class::GetClassDef, causing a SIGSEGV.
2025-02-26 19:29:45 +08:00
南宫雪珊
90d8d433d4
add cuttlefish test (#133) 2025-02-12 17:44:18 +08:00
dependabot[bot]
08b65e436f
Bump the maven-dependencies group with 2 updates (#116) 2024-11-06 23:04:27 +08:00
dependabot[bot]
a60403bd54
Bump AGP (#114) 2024-10-15 10:09:13 +08:00
dependabot[bot]
f5c423fd20
Bump AGP to 8.6.0 (#108) 2024-08-30 06:29:05 +08:00
dependabot[bot]
8ebc00ec8a
Bump androidx.test:runner from 1.6.1 to 1.6.2 in the maven-dependencies group (#106) 2024-08-18 19:30:28 +08:00
LoveSy
f8120339d2
Upgrade gradle 2024-08-16 23:00:18 +08:00
LoveSy
16c6820b86
Fix cmake 2024-08-15 01:14:59 +08:00
LoveSy
05289b548a
Fix IsBackup 2024-08-12 00:22:14 +08:00
LoveSy
ae4bc68b12
Fix including backup 2024-08-12 00:07:48 +08:00
dependabot[bot]
0b1e5f8b7a
Bump the maven-dependencies group with 2 updates (#105) 2024-08-10 09:28:40 +08:00
LoveSy
56a9cf7e53
Avoid hook called with unsupported arguments 2024-08-09 11:00:15 +08:00
LoveSy
897c70b74a
Make backup logic as a function 2024-08-06 23:39:03 +08:00
LoveSy
68f8a63e8f
Make member function const 2024-08-06 10:26:51 +08:00
LoveSy
14b753e1dc
Modularize hook helper 2024-08-06 00:33:24 +08:00
LoveSy
39334baed3
No need to setup android sdk 2024-08-02 01:38:11 +08:00
LoveSy
c9bf82a36c
Replace tstring with fixedstring 2024-08-02 01:10:32 +08:00
LoveSy
a7b0b022c2
Propagate hotness counter for backup method to target to avoid crash
Fix #99

See https://5ec1cff.github.io/my-blog/2024/06/27/lsp-crash-analysis/
2024-08-02 00:54:16 +08:00
LoveSy
0d9faca38d
Fix GarbageCollectCache got removed
Co-authored-by: JingMatrix <jingmatrix@gmail.com>
2024-07-24 00:39:03 +08:00
LoveSy
5121a21064
Remove dangerous implicit cast 2024-07-23 11:40:35 +08:00
LoveSy
bd64ecbc16
Fix array manipulation 2024-07-23 03:01:05 +08:00
LoveSy
85ab3c8c11
Upgrade deps 2024-07-17 11:50:46 +08:00
dependabot[bot]
87d2caede7
Bump the maven-dependencies group with 2 updates (#96) 2024-07-12 15:41:40 +08:00
LoveSy
3d9257fc11
Group dependencies 2024-07-12 15:39:27 +08:00
LoveSy
9d63f3bb50
Use kotlint dsl simple property assignment 2024-07-10 22:42:06 +08:00
dependabot[bot]
8fe8c6da1f
Bump androidx.test:runner from 1.6.0 to 1.6.1 (#92) 2024-06-28 07:03:27 +08:00
dependabot[bot]
368fed7c4f
Bump androidx.test.espresso:espresso-core from 3.6.0 to 3.6.1 (#93) 2024-06-28 07:02:31 +08:00
dependabot[bot]
172d11641a
Bump androidx.test.ext:junit from 1.2.0 to 1.2.1 (#94) 2024-06-28 07:02:01 +08:00
dependabot[bot]
5c8cdbd985
Bump androidx.test:runner from 1.5.2 to 1.6.0 (#90) 2024-06-25 16:37:59 +08:00
dependabot[bot]
6c08583233
Bump androidx.test.ext:junit from 1.1.5 to 1.2.0 (#91) 2024-06-25 16:37:59 +08:00
dependabot[bot]
b96a512319
Bump androidx.test.espresso:espresso-core from 3.5.1 to 3.6.0 (#89) 2024-06-25 16:37:59 +08:00
dependabot[bot]
95e3790500
Bump agp from 8.4.2 to 8.5.0 (#87) 2024-06-25 16:37:59 +08:00
dependabot[bot]
b1fe35c0c4
Bump agp from 8.4.1 to 8.4.2 (#86) 2024-06-25 16:37:59 +08:00
LoveSy
ac41dc6c18
Dont log function name to avoid too verbose log 2024-06-25 16:37:49 +08:00
LoveSy
da6b276f4d
Update actions 2024-06-05 11:32:19 +08:00
LoveSy
c35a50a60f
Test use modularized lsparself 2024-06-05 10:29:23 +08:00
LoveSy
b1e6d18494
Upgrade gradle & ndk 2024-06-05 10:04:30 +08:00
LoveSy
b858504dec
Upgrade deps 2024-06-05 10:04:30 +08:00
LoveSy
4ec6de77ad
No dobby_enable_near_branch_trampoline which may crash on Android 15 2024-06-05 10:04:30 +08:00
LoveSy
2d8204774b
Fix test build 2024-06-05 10:04:30 +08:00
LoveSy
1e1b221dcb
Use c++ 23 2024-06-05 10:04:30 +08:00
LoveSy
1744bde5ac
Fix compare 2024-06-05 10:04:30 +08:00
LoveSy
d3ed8751db
Use module for dex_builder 2024-06-05 10:04:30 +08:00
LoveSy
11aa507da9
Use module for test 2024-06-05 10:04:30 +08:00
LoveSy
29f124c576
Modularize interfaces 2024-06-05 10:04:30 +08:00
LoveSy
2db8b66b29
Modularize codes 2024-06-05 10:04:29 +08:00
LoveSy
386200f0c9
Run CI on Apple silicon 2024-05-23 16:35:48 +08:00
LoveSy
94e6e6236b
[skip ci] Announce support for Android 15 Beta2 2024-05-23 16:34:28 +08:00
LoveSy
01d26b03c5
[skip ci] Create FUNDING.yml 2024-05-23 16:32:52 +08:00
dependabot[bot]
3e97d85b0e
Bump agp from 8.4.0 to 8.4.1 (#84) 2024-05-23 16:31:55 +08:00
残页
68360babc5
Remove useless register in backup of non-wide methods (#81) 2024-05-03 18:32:45 +08:00
LoveSy
fb753a7daf
Upgrade AGP 2024-05-01 01:41:32 +08:00
LoveSy
c935b27b9a
[skip ci] Update README.md 2024-04-18 22:14:11 +08:00
LoveSy
7217ac6f41
Enable riscv64 for standalone build as well 2024-04-18 21:33:47 +08:00
LoveSy
c774d42b76
Use android-actions/setup-android@v3 to accept license 2024-04-18 10:32:53 +08:00
LoveSy
33504c455c
Upgrade ndk and enable riscv64 abi 2024-04-18 10:25:34 +08:00
LoveSy
62533694c5
Upgrade deps 2024-04-11 21:20:16 +08:00
dependabot[bot]
bd4b53c331
Bump agp from 8.3.1 to 8.3.2 (#77) 2024-04-11 08:48:26 +08:00
LoveSy
2cccfae8c1
Fix riscv trampoline 2024-03-27 02:04:47 +08:00
LoveSy
34b41249fa
Try support 16k page size 2024-03-25 23:03:12 +08:00
dependabot[bot]
1e36d0ab2f
Bump agp from 8.3.0 to 8.3.1 (#74) 2024-03-19 08:05:21 +08:00
LoveSy
6bb4abdae0
Upgrade SDK 2024-03-18 22:22:53 +08:00
LoveSy
e203bfdf0e
[skip ci] Update README.md 2024-03-18 21:49:06 +08:00
dependabot[bot]
460c5aec1d
Bump agp from 8.2.2 to 8.3.0 (#71) 2024-03-08 16:12:53 +08:00
LoveSy
5dbc9ff657
Update build script 2024-02-18 19:02:02 +08:00
LoveSy
1726816a98
Upgrade deps 2024-02-18 13:07:49 +08:00
5ec1cff
7ebe6b476a
Fix static method hook lost if it is deoptimized before class initialization 2024-02-18 01:09:09 +08:00
LoveSy
e0e8cc11dc
Fix Android 15 test 2024-02-17 17:30:58 +08:00
LoveSy
7d4987c80f
Use ssh-key for checkout 2024-02-17 16:53:13 +08:00
LoveSy
f240269c5a
Use VanillaIceCream runner 2024-02-17 16:38:16 +08:00
LoveSy
f82b5bf9e9
Add Android 15 to unit test 2024-02-17 16:09:23 +08:00
LoveSy
0de159c0b9
Upgrade gradle 2024-02-17 16:06:44 +08:00
dependabot[bot]
56ce2ff076
Bump agp from 8.2.1 to 8.2.2 (#66) 2024-01-25 19:36:02 +08:00
LoveSy
0305a18d7f
Run tests on Linux 2024-01-23 13:48:16 +08:00
LoveSy
3b9ec2b02e
Fix ThrowInvocationTimeError initialization
Fix #65

Co-authored-by: canyie <a1364259@163.com>
2024-01-17 15:01:10 +08:00
LoveSy
4ec86d14f4
Update pages.yml 2024-01-06 21:31:00 +08:00
dependabot[bot]
7af00b8aa7
Bump agp from 8.2.0 to 8.2.1 (#62) 2024-01-05 16:06:08 +08:00
LoveSy
92a6a727e2 Allow set null 2023-12-07 11:12:45 +08:00
Ylarod
4c62b5d558 fix size 2023-12-07 11:12:45 +08:00
LoveSy
dafa21cc38 Fix size 2023-12-07 11:12:45 +08:00
LoveSy
467d57a376 Fix size 2023-12-07 11:12:45 +08:00
LoveSy
d675c45d3f upgrade gradle 2023-12-07 11:12:45 +08:00
LoveSy
3ce7b1d1a0 Missing return 2023-12-07 11:12:45 +08:00
LoveSy
0d144a0881 fix infinity loop 2023-12-07 11:12:45 +08:00
LoveSy
c5b2c550d3 Update build.yml 2023-12-07 11:12:45 +08:00
LoveSy
a0389a2429 Upgrade ndk 2023-12-07 11:12:45 +08:00
LoveSy
4ff1786610 Add clone and fix assign of jObjArrEle 2023-12-07 11:12:45 +08:00
LoveSy
b435731080 Remove redundant inline 2023-12-07 11:12:45 +08:00
LoveSy
a1948df0dc Fix call of JObjectArrayElement 2023-12-07 11:12:45 +08:00
LoveSy
0f0f9f7bcd 1 2023-12-07 11:12:45 +08:00
Ylarod
713bb300c4 update 2023-12-07 11:12:45 +08:00
Ylarod
df7d10b1d8 update jni_helper.hpp 2023-12-07 11:12:45 +08:00
dependabot[bot]
4e95078c79
Bump agp from 8.1.4 to 8.2.0 (#60) 2023-12-06 15:26:02 +08:00
葫芦娃
439f38f48c
fixed logic bug for re-deoptimize condition in RestoreBackup (#59) 2023-11-22 17:57:39 +08:00
dependabot[bot]
7c732a7215
Bump agp from 8.1.3 to 8.1.4 (#58) 2023-11-17 05:20:17 +08:00
Ylarod
896f985017
Fix typo (#57) 2023-11-14 17:18:44 +08:00
dependabot[bot]
554136c1bb
Bump agp from 8.1.2 to 8.1.3 (#56) 2023-11-08 08:51:38 +08:00
LoveSy
c6d6773536
Fix missing PLOGE when disabled log
Fix #51
2023-10-23 18:05:39 +08:00
LoveSy
a612522188
Avoid using too much java debuggable guard 2023-10-11 13:54:25 +08:00
LoveSy
899766f2bd
[skip ci] Update maven.yml 2023-10-11 00:07:20 +08:00
LoveSy
cb005a88bc
[skip ci] Update maven.yml 2023-10-11 00:03:51 +08:00
LoveSy
e48d78c6ad
Fix old SDK 2023-10-10 13:19:59 +08:00
LoveSy
a4f6979c28
Fix AdjustThreadVisibilityCounter inline 2023-10-10 11:24:32 +08:00
LoveSy
780cbe09a3 Fix class verification of hooker crash for sdk 34 2023-10-10 02:29:53 +08:00
LoveSy
79f2f13517 Fix crash when logging uninitialized methods 2023-10-10 02:24:31 +08:00
LoveSy
d55d96929a
Upgrade gradle 2023-10-09 20:24:45 +08:00
LoveSy
c18f276177
Use phmap 2023-10-07 21:35:45 +08:00
Wang Han
7a6940e722
Don't hook ShouldUseInterpreterEntrypoint on T+ (#49) 2023-10-06 14:34:28 +08:00
LoveSy
cd7c81128b
Upgrade gradle 2023-10-04 23:53:30 +08:00
LoveSy
bbabd25043
Add functional header 2023-10-04 20:45:06 +08:00
dependabot[bot]
c907c3b402
Bump agp from 8.1.0 to 8.1.2 (#48) 2023-09-29 05:36:08 +08:00
LoveSy
ab5830a020
Hook if ShouldUseInterpreterEntrypoint
This will not be inlined
2023-07-26 15:56:26 +08:00
LoveSy
78e4dac54a
[skip ci] Upgrade gradle 2023-07-26 11:32:40 +08:00
LoveSy
8169d387b7
Set debuggable=false for unit test
LSPlant does not fully support debuggable process
2023-07-26 10:59:12 +08:00
dependabot[bot]
a49aecfb74
Bump agp from 8.0.2 to 8.1.0 (#46) 2023-07-26 08:44:54 +08:00
LoveSy
9c33903c87 Avoid using PrettyMethod during backup 2023-06-29 01:51:45 +08:00
LoveSy
20853b0d7d
Use LSPlugin 2023-06-27 19:08:49 +08:00
dependabot[bot]
909e91b8f2
Bump org.eclipse.jgit:org.eclipse.jgit (#44) 2023-06-27 15:53:42 +08:00
LoveSy
5f475c56b7
Use version category 2023-06-27 15:52:48 +08:00
LoveSy
254b21edd4
Upgrade deps 2023-06-27 15:38:44 +08:00
LoveSy
35612201f9
Lock-free JavaDebuggable guard 2023-06-27 15:31:44 +08:00
LoveSy
8c92a2d5a1 Make JavaDebuggable Guard thread free 2023-06-25 22:19:05 +08:00
LoveSy
261e2c2503
Update pages.yml 2023-06-17 01:21:20 +08:00
Katana
7766263eeb
Fix initialization failed on specific devices (#43)
Co-authored-by: LoveSy <shana@zju.edu.cn>
2023-06-17 01:02:49 +08:00
LoveSy
03517ca86d Upgrade deps 2023-06-08 12:02:41 +08:00
Howard Wu
d48c639849
CI: Stop Gradle daemon after build
Allow saving cache in Windows host
2023-04-12 14:02:11 +08:00
LoveSy
c4b670709b
Fix build 2023-04-12 11:16:44 +08:00
LoveSy
25fff8a2a8
Preliminary riscv64 support 2023-04-12 10:19:18 +08:00
LoveSy
77d02b29ec
Upgrade deps 2023-04-12 10:03:42 +08:00
LoveSy
25c617d1fd
No duplicate gradle cache 2023-02-09 17:01:31 +08:00
LoveSy
28f17fee3e
No duplicate gradle cache 2023-02-09 16:57:11 +08:00
LoveSy
b77771ffc9
Fix unit test 2023-02-09 16:53:23 +08:00
LoveSy
70a68d9fb4
Unit Test on Android U 2023-02-09 11:17:52 +08:00
LoveSy
9ad8f22da2
Upgrade AGP 2023-01-15 17:49:08 +08:00
LoveSy
89cdf4cd2d
Update deps 2023-01-08 06:31:55 +08:00
LoveSy
56ab000184
Fix build 2023-01-06 23:52:27 +08:00
LoveSy
bdc1e6f8a7
x86 and x86_64 do not have AdjustThreadVisibilityCounter 2023-01-06 22:01:30 +08:00
LoveSy
845ec5dc40
Add Android 13 32bit unit test 2022-12-15 12:34:03 +08:00
LoveSy
9e437ffcd6
Fix 32bit symbol 2022-12-07 09:46:29 +08:00
LoveSy
d5a057b025
Redeoptimize when class initialized 2022-12-02 23:22:13 +08:00
LoveSy
65a276ff91
Use latest tag for publication 2022-11-29 16:40:53 +08:00
LoveSy
a2b5d87159
Update deps 2022-11-27 17:08:32 +08:00
LoveSy
492a20bab9
Revert "Use MVD for unit test again"
This reverts commit f7e3a0cef653d10c351098d8d84b840d0fd57ebc.
2022-11-27 17:04:47 +08:00
LoveSy
1a5932a119
Revert "Use gradle to run all tests"
This reverts commit 6e0ef3c855a966af3a7dd8b8c29cbd7df7996482.
2022-11-27 17:04:07 +08:00
LoveSy
2b784e114f
Use one inst hook 2022-11-20 01:52:39 +08:00
LoveSy
a15d854930
No hook instrument when non-debuggable 2022-11-20 01:09:16 +08:00
LoveSy
6e0ef3c855
Use gradle to run all tests 2022-11-11 01:21:08 +08:00
LoveSy
f7e3a0cef6
Use MVD for unit test again 2022-11-11 00:59:15 +08:00
71 changed files with 2779 additions and 2510 deletions

3
.github/FUNDING.yml vendored Normal file
View File

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

94
.github/cuttlefish.sh vendored Executable file
View File

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

12
.github/dependabot.yml vendored Normal file
View File

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

View File

@ -5,6 +5,7 @@ on:
branches: ["master"] branches: ["master"]
paths-ignore: paths-ignore:
- 'README.md' - 'README.md'
workflow_dispatch:
pull_request: pull_request:
@ -15,33 +16,34 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macOS-latest ] os: [ ubuntu-latest, windows-latest, macos-14 ]
steps: steps:
- name: Check out - name: Check out
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Set up JDK 17 ssh-key: ${{ secrets.SSH_KEY }}
uses: actions/setup-java@v3 fetch-depth: 0
- name: Set up JDK 21
uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '21'
cache: 'gradle' cache: 'gradle'
- name: Cache Gradle Build - uses: seanmiddleditch/gha-setup-ninja@master
uses: actions/cache@v3
with: with:
path: | version: 1.12.1
~/.gradle/caches/build-cache-*
~/.gradle/buildOutputCleanup/cache.properties
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds-${{ runner.os }}
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1.2 uses: hendrikmuhs/ccache-action@v1
with: with:
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }} restore-keys: ${{ runner.os }}
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Remove Android's cmake
shell: bash
run: rm -rf $ANDROID_HOME/cmake
- name: Build with Gradle - name: Build with Gradle
run: | run: |
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
@ -50,19 +52,21 @@ jobs:
ccache -p ccache -p
echo 'android.native.buildOutput=verbose' >> gradle.properties echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew :lsplant:publishToMavenLocal :lsplant:prefabDebugPackage ./gradlew :lsplant:publishToMavenLocal :lsplant:prefabDebugPackage
./gradlew --stop
env: env:
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }}
- name: Upload library - name: Upload library
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: ${{ matrix.os }}-library name: ${{ matrix.os }}-library
path: ~/.m2 path: ~/.m2
include-hidden-files: true
test: test:
needs: build needs: build
name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }} name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }}
runs-on: macos-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -137,52 +141,122 @@ jobs:
target: google_apis target: google_apis
arch: x86_64 arch: x86_64
- api-level: 33 - api-level: 33
target: default
arch: x86_64
- api-level: 33
target: android-tv
arch: x86
- api-level: 34
target: default
arch: x86_64
- api-level: 34
target: android-tv
arch: x86
- api-level: 35
target: default
arch: x86_64
- api-level: Baklava
target: google_apis target: google_apis
arch: x86_64 arch: x86_64
steps: steps:
- name: checkout - name: checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
submodules: 'recursive' submodules: 'recursive'
- name: Set up JDK 17 ssh-key: ${{ secrets.SSH_KEY }}
uses: actions/setup-java@v3 - name: Set up JDK 21
uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '21'
cache: 'gradle' cache: 'gradle'
- name: Cache Gradle Build - uses: seanmiddleditch/gha-setup-ninja@master
uses: actions/cache@v3
with: with:
path: | version: 1.12.1
~/.gradle/caches/build-cache-*
~/.gradle/buildOutputCleanup/cache.properties
key: gradle-builds-core-${{ github.sha }}
restore-keys: |
gradle-builds-${{ runner.os }}
- name: ccache - name: ccache
uses: hendrikmuhs/ccache-action@v1.2 uses: hendrikmuhs/ccache-action@v1
with: with:
key: ${{ runner.os }}-${{ github.sha }} key: ${{ runner.os }}-${{ github.sha }}
restore-keys: ${{ runner.os }} restore-keys: ${{ runner.os }}
save: false save: false
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: run tests - name: run tests
uses: reactivecircus/android-emulator-runner@v2 uses: reactivecircus/android-emulator-runner@b683a061eaff4aac4d0b585bfd0cf714a40aee93
with: with:
api-level: ${{ matrix.api-level }} api-level: ${{ matrix.api-level }}
arch: ${{ matrix.arch }} arch: ${{ matrix.arch }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
script: | script: |
rm -rf $ANDROID_HOME/cmake
ccache -o cache_dir=${{ github.workspace }}/.ccache ccache -o cache_dir=${{ github.workspace }}/.ccache
ccache -o hash_dir=false ccache -o hash_dir=false
ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion'
echo 'android.native.buildOutput=verbose' >> gradle.properties
./gradlew :test:connectedCheck ./gradlew :test:connectedCheck
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true disable-animations: true
avd-name: ${{ matrix.api-level }}_${{ matrix.arch }} avd-name: ${{ matrix.api-level }}_${{ matrix.arch }}
- name: Upload outputs - name: Upload outputs
if: always() if: always()
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: test-outputs name: test-outputs-${{ matrix.arch }}-${{ matrix.target }}-${{ matrix.api-level }}
path: test/build/outputs 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

View File

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

View File

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

6
.gitmodules vendored
View File

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

View File

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

View File

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

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

View File

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

17
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,17 @@
[versions]
agp = "8.9.0"
[plugins]
agp-app = { id = "com.android.application", version.ref = "agp" }
agp-lib = { id = "com.android.library", version.ref = "agp" }
lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" }
lsplugin-publish = { id = "org.lsposed.lsplugin.publish", version = "1.1" }
lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version = "1.2" }
[libraries]
cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" }
dobby = { module = "io.github.vvb2060.ndk:dobby", version = "1.2" }
test-ext-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
test-espresso = { module = "androidx.test.espresso:espresso-core", version = "3.6.1" }

Binary file not shown.

View File

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

43
gradlew vendored
View File

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

37
gradlew.bat vendored
View File

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

View File

@ -1,9 +1,10 @@
import java.nio.file.Paths
plugins { plugins {
id("com.android.library") alias(libs.plugins.agp.lib)
id("maven-publish") alias(libs.plugins.lsplugin.jgit)
id("signing") alias(libs.plugins.lsplugin.publish)
alias(libs.plugins.lsplugin.cmaker)
`maven-publish`
signing
} }
val androidTargetSdkVersion: Int by rootProject.extra val androidTargetSdkVersion: Int by rootProject.extra
@ -13,16 +14,6 @@ val androidCompileSdkVersion: Int by rootProject.extra
val androidNdkVersion: String by rootProject.extra val androidNdkVersion: String by rootProject.extra
val androidCmakeVersion: String by rootProject.extra val androidCmakeVersion: String by rootProject.extra
fun findInPath(executable: String): String? {
val pathEnv = System.getenv("PATH")
return pathEnv.split(File.pathSeparator).map { folder ->
Paths.get("${folder}${File.separator}${executable}${if (org.gradle.internal.os.OperatingSystem.current().isWindows) ".exe" else ""}")
.toFile()
}.firstOrNull { path ->
path.exists()
}?.absolutePath
}
android { android {
compileSdk = androidCompileSdkVersion compileSdk = androidCompileSdkVersion
ndkVersion = androidNdkVersion ndkVersion = androidNdkVersion
@ -35,7 +26,7 @@ android {
prefab = true prefab = true
} }
packagingOptions { packaging {
jniLibs { jniLibs {
excludes += "**.so" excludes += "**.so"
} }
@ -49,90 +40,12 @@ android {
defaultConfig { defaultConfig {
minSdk = androidMinSdkVersion minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
} }
buildTypes { buildTypes {
all {
externalNativeBuild {
cmake {
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
val flags = arrayOf(
"-Wall",
"-Werror",
"-Qunused-arguments",
"-Wno-gnu-string-literal-operator-template",
"-fno-rtti",
"-fvisibility=hidden",
"-fvisibility-inlines-hidden",
"-fno-exceptions",
"-fno-stack-protector",
"-fomit-frame-pointer",
"-Wno-builtin-macro-redefined",
"-ffunction-sections",
"-fdata-sections",
"-Wno-unused-value",
"-D__FILE__=__FILE_NAME__",
"-Wl,--exclude-libs,ALL",
)
cppFlags("-std=c++20", *flags)
cFlags("-std=c18", *flags)
val configFlags = arrayOf(
"-Oz",
"-DNDEBUG"
).joinToString(" ")
arguments(
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
"-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/$name",
)
findInPath("ccache")?.let {
println("Using ccache $it")
arguments += "-DANDROID_CCACHE=$it"
}
}
}
}
release {
externalNativeBuild {
val flags = arrayOf(
"-Wl,--gc-sections",
"-flto",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
)
cmake {
cppFlags += flags
cFlags += flags
arguments += "-DANDROID_STL=c++_shared"
arguments += "-DCMAKE_BUILD_TYPE=Release"
}
}
}
debug {
externalNativeBuild {
cmake {
arguments += "-DANDROID_STL=c++_shared"
}
}
}
create("standalone") { create("standalone") {
initWith(getByName("release")) initWith(getByName("release"))
externalNativeBuild { matchingFallbacks += "release"
val flags = arrayOf(
"-Wl,--gc-sections",
"-flto",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
)
cmake {
cppFlags += flags
cFlags += flags
arguments += "-DANDROID_STL=none"
arguments += "-DCMAKE_BUILD_TYPE=Release"
arguments += "-DLSPLANT_STANDALONE=ON"
}
}
} }
} }
@ -161,42 +74,79 @@ android {
} }
} }
cmaker {
default {
val flags = arrayOf(
"-Werror",
"-Wno-gnu-string-literal-operator-template",
)
abiFilters("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
cppFlags += flags
cFlags += flags
}
buildTypes {
when (it.name) {
"debug", "release" -> {
arguments += "-DANDROID_STL=c++_shared"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
"standalone" -> {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLANT_STANDALONE=ON"
arguments += "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
}
}
arguments += "-DDEBUG_SYMBOLS_PATH=${project.layout.buildDirectory.file("symbols/${it.name}").get().asFile.absolutePath}"
}
}
dependencies {
"standaloneCompileOnly"(libs.cxx)
}
val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") { val symbolsReleaseTask = tasks.register<Jar>("generateReleaseSymbolsJar") {
from("${project.buildDir.absolutePath}/symbols/release") from(project.layout.buildDirectory.file("symbols/release"))
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier.set("symbols") archiveClassifier = "symbols"
archiveBaseName = "release"
} }
val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") { val symbolsStandaloneTask = tasks.register<Jar>("generateStandaloneSymbolsJar") {
from("${project.buildDir.absolutePath}/symbols/standalone") from(project.layout.buildDirectory.file("symbols/standalone"))
exclude("**/dex_builder") exclude("**/dex_builder")
archiveClassifier.set("symbols") archiveClassifier = "symbols"
archiveBaseName = "standalone"
} }
publishing { val repo = jgit.repo(true)
val ver = repo?.latestTag?.removePrefix("v") ?: "0.0"
println("${rootProject.name} version: $ver")
publish {
githubRepo = "LSPosed/LSPlant"
publications { publications {
fun MavenPublication.setup() { fun MavenPublication.setup() {
group = "org.lsposed.lsplant" group = "org.lsposed.lsplant"
version = "5.2" version = ver
pom { pom {
name.set("LSPlant") name = "LSPlant"
description.set("A hook framework for Android Runtime (ART)") description = "A hook framework for Android Runtime (ART)"
url.set("https://github.com/LSPosed/LSPlant") url = "https://github.com/LSPosed/LSPlant"
licenses { licenses {
license { license {
name.set("GNU Lesser General Public License v3.0") name = "GNU Lesser General Public License v3.0"
url.set("https://github.com/LSPosed/LSPlant/blob/master/LICENSE") url = "https://github.com/LSPosed/LSPlant/blob/master/LICENSE"
} }
} }
developers { developers {
developer { developer {
name.set("Lsposed") name = "Lsposed"
url.set("https://lsposed.org") url = "https://lsposed.org"
} }
} }
scm { scm {
connection.set("scm:git:https://github.com/LSPosed/LSPlant.git") connection = "scm:git:https://github.com/LSPosed/LSPlant.git"
url.set("https://github.com/LSPosed/LSPlant") url = "https://github.com/LSPosed/LSPlant"
} }
} }
} }
@ -217,31 +167,4 @@ publishing {
setup() setup()
} }
} }
repositories {
maven {
name = "ossrh"
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials(PasswordCredentials::class)
}
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/LSPosed/LSPlant")
credentials {
username = System.getenv("GITHUB_ACTOR")
password = System.getenv("GITHUB_TOKEN")
}
}
}
dependencies {
"standaloneCompileOnly"("dev.rikka.ndk.thirdparty:cxx:1.2.0")
}
}
signing {
val signingKey = findProperty("signingKey") as String?
val signingPassword = findProperty("signingPassword") as String?
if (signingKey != null && signingPassword != null) {
useInMemoryPgpKeys(signingKey, signingPassword)
}
sign(publishing.publications)
} }

View File

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

View File

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

View File

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

View File

@ -0,0 +1,174 @@
module;
#include <parallel_hashmap/phmap.h>
#include "logging.hpp"
export module lsplant:clazz;
import :common;
import :art_method;
import :thread;
import :handle;
import hook_helper;
export namespace lsplant::art::mirror {
class Class {
private:
inline static auto GetDescriptor_ =
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE"_sym.as<const char *(Class::*)(std::string *)>;
inline static auto GetClassDef_ =
"_ZN3art6mirror5Class11GetClassDefEv"_sym.as<const dex::ClassDef *(Class::*)()>;
using BackupMethods = phmap::flat_hash_map<art::ArtMethod *, void *>;
inline static phmap::flat_hash_map<const art::Thread *,
phmap::flat_hash_map<const dex::ClassDef *, BackupMethods>>
backup_methods_;
inline static std::mutex backup_methods_lock_;
inline static uint8_t initialized_status = 0;
static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods out;
if (!class_def) return;
{
hooked_classes_.if_contains(class_def, [&out](const auto &it) {
for (auto method : it.second) {
if (method->IsStatic()) {
LOGV("Backup hooked method %p because of initialization", method);
out.emplace(method, method->GetEntryPoint());
}
}
});
}
{
deoptimized_classes_.if_contains(class_def, [&out](const auto &it) {
for (auto method : it.second) {
if (method->IsStatic()) {
LOGV("Backup deoptimized method %p because of initialization", method);
out.emplace(method, method->GetEntryPoint());
}
}
});
}
if (!out.empty()) [[unlikely]] {
std::unique_lock lk(backup_methods_lock_);
backup_methods_[self].emplace(class_def, std::move(out));
}
}
inline static auto SetClassStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS_11ClassStatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint8_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
};
inline static auto SetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(Handle<Class> h, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
};
inline static auto TrivialSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<Backup auto backup>
(TrivialHandle<Class> h, uint32_t new_status, Thread *self) static -> void {
if (new_status == initialized_status) {
BackupClassMethods(GetClassDef_(h.Get()), self);
}
return backup(h, new_status, self);
};
inline static auto ClassSetStatus_ =
"_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE"_sym.hook->*[]
<MemBackup auto backup>
(Class *thiz, int new_status, Thread *self) static -> void {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(GetClassDef_(thiz), self);
}
return backup(thiz, new_status, self);
};
public:
static bool Init(const HookHandler &handler) {
if (!handler(GetDescriptor_) || !handler(GetClassDef_)) {
return false;
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) {
if (!handler(SetStatus_, ClassSetStatus_)) {
return false;
}
} else {
if (!handler(SetClassStatus_, TrivialSetStatus_)) {
return false;
}
}
if (sdk_int >= __ANDROID_API_R__) {
initialized_status = 15;
} else if (sdk_int >= __ANDROID_API_P__) {
initialized_status = 14;
} else if (sdk_int == __ANDROID_API_O_MR1__) {
initialized_status = 11;
} else {
initialized_status = 10;
}
return true;
}
const char *GetDescriptor(std::string *storage) { return GetDescriptor_(this, storage); }
std::string GetDescriptor() {
std::string storage;
return GetDescriptor(&storage);
}
const dex::ClassDef *GetClassDef() { return GetClassDef_(this); }
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods methods;
if (!backup_methods_.size()) [[likely]] {
return methods;
}
if (class_def) {
std::unique_lock lk(backup_methods_lock_);
for (auto it = backup_methods_.begin(); it != backup_methods_.end();) {
if (auto found = it->second.find(class_def); found != it->second.end()) {
methods.merge(std::move(found->second));
it->second.erase(found);
}
if (it->second.empty()) {
backup_methods_.erase(it++);
} else {
it++;
}
}
} else if (self) {
std::unique_lock lk(backup_methods_lock_);
if (auto found = backup_methods_.find(self); found != backup_methods_.end()) {
for (auto it = found->second.begin(); it != found->second.end();) {
methods.merge(std::move(it->second));
found->second.erase(it++);
}
backup_methods_.erase(found);
}
}
return methods;
}
};
} // namespace lsplant::art::mirror

View File

@ -1,192 +0,0 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/runtime/handle.hpp"
#include "common.hpp"
namespace lsplant::art {
class Thread;
namespace dex {
class ClassDef {};
} // namespace dex
namespace mirror {
class Class {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
if (GetDescriptorSym) [[likely]]
return GetDescriptorSym(thiz, storage);
else
return "";
}
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
if (GetClassDefSym) [[likely]]
return GetClassDefSym(thiz);
return nullptr;
}
using BackupMethods = std::list<std::tuple<art::ArtMethod *, void *>>;
inline static absl::flat_hash_map<const art::Thread *,
absl::flat_hash_map<const dex::ClassDef *, BackupMethods>>
backup_methods_;
inline static std::mutex backup_methods_lock_;
static void BackupClassMethods(const dex::ClassDef *class_def, art::Thread *self) {
std::list<std::tuple<art::ArtMethod *, void *>> out;
if (!class_def) return;
{
std::shared_lock lk(hooked_classes_lock_);
if (auto found = hooked_classes_.find(class_def); found != hooked_classes_.end())
[[unlikely]] {
for (auto method : found->second) {
if (method->IsStatic()) {
LOGV("Backup hooked method %p because of initialization", method);
out.emplace_back(method, method->GetEntryPoint());
}
}
}
}
{
std::shared_lock lk(deoptimized_methods_lock_);
if (auto found = deoptimized_classes_.find(class_def);
found != deoptimized_classes_.end()) [[unlikely]] {
for (auto method : found->second) {
if (method->IsStatic()) {
LOGV("Backup deoptimized method %p because of initialization", method);
out.emplace_back(method, method->GetEntryPoint());
}
}
}
}
if (!out.empty()) [[unlikely]] {
std::unique_lock lk(backup_methods_lock_);
backup_methods_[self].emplace(class_def, std::move(out));
}
}
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(h->GetClassDef(), self);
}
return backup(h, new_status, self);
});
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void, SetStatus,
(Handle<Class> h, int new_status, Thread *self), {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(h->GetClassDef(), self);
}
return backup(h, new_status, self);
});
CREATE_HOOK_STUB_ENTRY(
"_ZN3art6mirror5Class9SetStatusENS_6HandleIS1_EENS1_6StatusEPNS_6ThreadE", void,
TrivialSetStatus, (TrivialHandle<Class> h, uint32_t new_status, Thread *self), {
if (new_status == initialized_status) {
BackupClassMethods(h->GetClassDef(), self);
}
return backup(h, new_status, self);
});
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror5Class9SetStatusENS1_6StatusEPNS_6ThreadE", void,
ClassSetStatus, (Class * thiz, int new_status, Thread *self), {
if (new_status == static_cast<int>(initialized_status)) {
BackupClassMethods(thiz->GetClassDef(), self);
}
return backup(thiz, new_status, self);
});
inline static uint8_t initialized_status = 0;
public:
static bool Init(const HookHandler &handler) {
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetDescriptor,
"_ZN3art6mirror5Class13GetDescriptorEPNSt3__112"
"basic_stringIcNS2_11char_traitsIcEENS2_9allocatorIcEEEE")) {
return false;
}
if (!RETRIEVE_MEM_FUNC_SYMBOL(GetClassDef, "_ZN3art6mirror5Class11GetClassDefEv")) {
return false;
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_O__) {
if (!HookSyms(handler, SetStatus, ClassSetStatus)) {
return false;
}
} else {
if (!HookSyms(handler, SetClassStatus, TrivialSetStatus)) {
return false;
}
}
if (sdk_int >= __ANDROID_API_R__) {
initialized_status = 15;
} else if (sdk_int >= __ANDROID_API_P__) {
initialized_status = 14;
} else if (sdk_int == __ANDROID_API_O_MR1__) {
initialized_status = 11;
} else {
initialized_status = 10;
}
return true;
}
const char *GetDescriptor(std::string *storage) {
if (GetDescriptorSym) {
return GetDescriptor(this, storage);
}
return "";
}
std::string GetDescriptor() {
std::string storage;
return GetDescriptor(&storage);
}
const dex::ClassDef *GetClassDef() {
if (GetClassDefSym) return GetClassDef(this);
return nullptr;
}
static auto PopBackup(const dex::ClassDef *class_def, art::Thread *self) {
BackupMethods methods;
if (!backup_methods_.size()) [[likely]] {
return methods;
}
if (class_def) {
std::unique_lock lk(backup_methods_lock_);
for (auto it = backup_methods_.begin(); it != backup_methods_.end();) {
if (auto found = it->second.find(class_def); found != it->second.end()) {
methods.merge(std::move(found->second));
it->second.erase(found);
}
if (it->second.empty()) {
backup_methods_.erase(it++);
} else {
it++;
}
}
} else if (self) {
std::unique_lock lk(backup_methods_lock_);
if (auto found = backup_methods_.find(self); found != backup_methods_.end()) {
for (auto it = found->second.begin(); it != found->second.end();) {
methods.merge(std::move(it->second));
found->second.erase(it++);
}
backup_methods_.erase(found);
}
}
return methods;
}
};
} // namespace mirror
} // namespace lsplant::art

View File

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

View File

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

View File

@ -1,180 +0,0 @@
#pragma once
#include "art/runtime/art_method.hpp"
#include "art/runtime/obj_ptr.hpp"
#include "art/runtime/thread.hpp"
#include "common.hpp"
namespace lsplant::art {
class ClassLinker {
private:
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetEntryPointsToInterpreter, ClassLinker *thiz,
ArtMethod *art_method) {
if (SetEntryPointsToInterpreterSym) [[likely]] {
SetEntryPointsToInterpreterSym(thiz, art_method);
}
}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void *) {}
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void *) {}
inline static art::ArtMethod *MayGetBackup(art::ArtMethod *method) {
if (auto backup = IsHooked(method); backup) [[unlikely]] {
method = backup;
LOGV("propagate native method: %s", method->PrettyMethod(true).data());
}
return method;
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art6mirror9ArtMethod14RegisterNativeEPNS_6ThreadEPKvb", void, RegisterNativeThread,
(art::ArtMethod * method, art::Thread *thread, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), thread, native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art6mirror9ArtMethod16UnregisterNativeEPNS_6ThreadE", void,
UnregisterNativeThread,
(art::ArtMethod * method, art::Thread *thread),
{ return backup(MayGetBackup(method), thread); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKvb", void, RegisterNativeFast,
(art::ArtMethod * method, const void *native_method, bool is_fast),
{ return backup(MayGetBackup(method), native_method, is_fast); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", void, UnregisterNativeFast,
(art::ArtMethod * method), { return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod14RegisterNativeEPKv", const void *,
RegisterNative, (art::ArtMethod * method, const void *native_method),
{ return backup(MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art9ArtMethod16UnregisterNativeEv", const void *,
UnregisterNative, (art::ArtMethod * method),
{ return backup(MayGetBackup(method)); });
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker14RegisterNativeEPNS_6ThreadEPNS_9ArtMethodEPKv", const void *,
RegisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method,
const void *native_method),
{ return backup(thiz, self, MayGetBackup(method), native_method); });
CREATE_MEM_HOOK_STUB_ENTRY("_ZN3art11ClassLinker16UnregisterNativeEPNS_6ThreadEPNS_9ArtMethodE",
const void *, UnregisterNativeClassLinker,
(art::ClassLinker * thiz, art::Thread *self, art::ArtMethod *method),
{ return backup(thiz, self, MayGetBackup(method)); });
static auto 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 != old_trampoline) [[unlikely]] {
LOGV("prevent deoptimized method %s from being overwritten",
art_method->PrettyMethod(true).data());
}
continue;
}
if (auto backup_method = IsHooked(art_method); backup_method) [[likely]] {
if (new_trampoline != old_trampoline) [[unlikely]] {
LOGV("propagate entrypoint for %s", backup_method->PrettyMethod(true).data());
backup_method->SetEntryPoint(new_trampoline);
}
}
}
}
CREATE_MEM_HOOK_STUB_ENTRY(
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE", void,
FixupStaticTrampolines, (ClassLinker * thiz, ObjPtr<mirror::Class> mirror_class), {
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(
"_ZN3art11ClassLinker26VisiblyInitializedCallback29AdjustThreadVisibilityCounterEPNS_6ThreadEl",
void, AdjustThreadVisibilityCounter, (void *thiz, art::Thread *self, ssize_t adjustment), {
backup(thiz, self, adjustment);
RestoreBackup(nullptr, self);
});
public:
static bool Init(const HookHandler &handler) {
if (!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines,
FixupStaticTrampolinesRaw)) {
return false;
}
if (!HookSyms(handler, RegisterNativeClassLinker, RegisterNative, RegisterNativeFast,
RegisterNativeThread) ||
!HookSyms(handler, UnregisterNativeClassLinker, UnregisterNative, UnregisterNativeFast,
UnregisterNativeThread)) {
return false;
}
int sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_R__) {
// fixup static trampoline may have been inlined
HookSyms(handler, AdjustThreadVisibilityCounter);
}
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

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

View File

@ -1,172 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#include "common.hpp"
namespace lsplant::art {
class DexFile {
struct Header {
[[maybe_unused]] uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_
};
CREATE_FUNC_SYMBOL_ENTRY(std::unique_ptr<DexFile>, OpenMemory, const uint8_t* dex_file,
size_t size, const std::string& location, uint32_t location_checksum,
void* mem_map, const void* oat_dex_file, std::string* error_msg) {
if (OpenMemorySym) [[likely]] {
return OpenMemorySym(dex_file, size, location, location_checksum, mem_map, oat_dex_file,
error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryRaw, const uint8_t* dex_file, size_t size,
const std::string& location, uint32_t location_checksum, void* mem_map,
const void* oat_dex_file, std::string* error_msg) {
if (OpenMemoryRawSym) [[likely]] {
return OpenMemoryRawSym(dex_file, size, location, location_checksum, mem_map,
oat_dex_file, error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(const DexFile*, OpenMemoryWithoutOdex, const uint8_t* dex_file,
size_t size, const std::string& location, uint32_t location_checksum,
void* mem_map, std::string* error_msg) {
if (OpenMemoryWithoutOdexSym) [[likely]] {
return OpenMemoryWithoutOdexSym(dex_file, size, location, location_checksum, mem_map,
error_msg);
}
if (error_msg) *error_msg = "null sym";
return nullptr;
}
CREATE_FUNC_SYMBOL_ENTRY(void, DexFile_setTrusted, JNIEnv* env, jclass clazz,
jobject j_cookie) {
if (DexFile_setTrustedSym != nullptr) [[likely]] {
DexFile_setTrustedSym(env, clazz, j_cookie);
}
}
public:
static const DexFile* OpenMemory(const void* dex_file, size_t size, std::string location,
std::string* error_msg) {
if (OpenMemorySym) [[likely]] {
return OpenMemory(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg)
.release();
} else if (OpenMemoryRawSym) [[likely]] {
return OpenMemoryRaw(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_, nullptr,
nullptr, error_msg);
} else if (OpenMemoryWithoutOdexSym) [[likely]] {
return OpenMemoryWithoutOdex(reinterpret_cast<const uint8_t*>(dex_file), size, location,
reinterpret_cast<const Header*>(dex_file)->checksum_,
nullptr, error_msg);
} else {
if (error_msg) *error_msg = "no sym";
return nullptr;
}
}
jobject ToJavaDexFile(JNIEnv* env) const {
auto java_dex_file = env->AllocObject(dex_file_class);
auto cookie = JNI_NewLongArray(env, dex_file_start_index + 1);
if (dex_file_start_index != size_t(-1)) [[likely]] {
cookie[oat_file_index] = 0;
cookie[dex_file_start_index] = reinterpret_cast<jlong>(this);
cookie.commit();
JNI_SetObjectField(env, java_dex_file, cookie_field, cookie);
if (internal_cookie_field) {
JNI_SetObjectField(env, java_dex_file, internal_cookie_field, cookie);
}
} else {
JNI_SetLongField(
env, java_dex_file, cookie_field,
static_cast<jlong>(reinterpret_cast<uintptr_t>(new std::vector{this})));
}
JNI_SetObjectField(env, java_dex_file, file_name_field, JNI_NewStringUTF(env, ""));
return java_dex_file;
}
static bool SetTrusted(JNIEnv* env, jobject cookie) {
if (!DexFile_setTrustedSym) return false;
DexFile_setTrusted(env, nullptr, cookie);
return true;
}
static bool Init(JNIEnv* env, const HookHandler& handler) {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int >= __ANDROID_API_P__) [[likely]] {
if (!RETRIEVE_FUNC_SYMBOL(DexFile_setTrusted,
"_ZN3artL18DexFile_setTrustedEP7_JNIEnvP7_jclassP8_jobject",
true)) {
return false;
}
}
if (sdk_int >= __ANDROID_API_O__) [[likely]] {
return true;
}
if (!RETRIEVE_FUNC_SYMBOL(
OpenMemory,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryRaw,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_")) &&
!RETRIEVE_FUNC_SYMBOL(
OpenMemoryWithoutOdex,
LP_SELECT("_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_",
"_ZN3art7DexFile10OpenMemoryEPKhmRKNSt3__112basic_stringIcNS3_11char_"
"traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPS9_"))) [[unlikely]] {
LOGE("Failed to find OpenMemory");
return false;
}
dex_file_class = JNI_NewGlobalRef(env, JNI_FindClass(env, "dalvik/system/DexFile"));
if (!dex_file_class) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_M__) [[unlikely]] {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "Ljava/lang/Object;");
} else {
cookie_field = JNI_GetFieldID(env, dex_file_class, "mCookie", "J");
dex_file_start_index = -1;
}
if (!cookie_field) [[unlikely]] {
return false;
}
file_name_field = JNI_GetFieldID(env, dex_file_class, "mFileName", "Ljava/lang/String;");
if (!file_name_field) [[unlikely]] {
return false;
}
if (sdk_int >= __ANDROID_API_N__) [[likely]] {
internal_cookie_field =
JNI_GetFieldID(env, dex_file_class, "mInternalCookie", "Ljava/lang/Object;");
if (!internal_cookie_field) [[unlikely]] {
return false;
}
dex_file_start_index = 1u;
}
return true;
}
private:
inline static jclass dex_file_class = nullptr;
inline static jfieldID cookie_field = nullptr;
inline static jfieldID file_name_field = nullptr;
inline static jfieldID internal_cookie_field = nullptr;
inline static size_t oat_file_index = 0u;
inline static size_t dex_file_start_index = 0u;
};
} // namespace lsplant::art

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,177 @@
module;
#include <jni.h>
#include <parallel_hashmap/phmap.h>
#include <sys/system_properties.h>
#include <list>
#include <shared_mutex>
#include <string_view>
#include "logging.hpp"
export module lsplant:common;
export import jni_helper;
export import hook_helper;
export namespace lsplant {
namespace art {
class ArtMethod;
namespace mirror {
class Class;
}
namespace dex {
class ClassDef {};
} // namespace dex
} // namespace art
enum class Arch {
kArm,
kArm64,
kX86,
kX86_64,
kRiscv64,
};
consteval inline Arch GetArch() {
#if defined(__i386__)
return Arch::kX86;
#elif defined(__x86_64__)
return Arch::kX86_64;
#elif defined(__arm__)
return Arch::kArm;
#elif defined(__aarch64__)
return Arch::kArm64;
#elif defined(__riscv)
return Arch::kRiscv64;
#else
#error "unsupported architecture"
#endif
}
template <class K, class V, class Hash = phmap::priv::hash_default_hash<K>,
class Eq = phmap::priv::hash_default_eq<K>,
class Alloc = phmap::priv::Allocator<phmap::priv::Pair<const K, V>>, size_t N = 4>
using SharedHashMap = phmap::parallel_flat_hash_map<K, V, Hash, Eq, Alloc, N, std::shared_mutex>;
template <class T, class Hash = phmap::priv::hash_default_hash<T>,
class Eq = phmap::priv::hash_default_eq<T>, class Alloc = phmap::priv::Allocator<T>,
size_t N = 4>
using SharedHashSet = phmap::parallel_flat_hash_set<T, Hash, Eq, Alloc, N, std::shared_mutex>;
constexpr auto kArch = GetArch();
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
[[gnu::const]] inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
int base = atoi(prop_value.data());
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
return base + atoi(prop_value.data());
}();
return kApiLevel;
}
inline auto IsJavaDebuggable(JNIEnv * env) {
static auto kDebuggable = [&env]() {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_P__) {
return false;
}
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
if (!runtime_class) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method = JNI_GetStaticMethodID(env, runtime_class, "getRuntime",
"()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method =
JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
}
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
if (!runtime) {
LOGE("Failed to get VMRuntime");
return false;
}
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
return is_debuggable;
}();
return kDebuggable;
}
constexpr auto kPointerSize = sizeof(void *);
SharedHashMap<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
hooked_classes_;
SharedHashSet<art::ArtMethod *> deoptimized_methods_set_;
SharedHashMap<const art::dex::ClassDef *, phmap::flat_hash_set<art::ArtMethod *>>
deoptimized_classes_;
std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
std::shared_mutex jit_movements_lock_;
inline art::ArtMethod *IsHooked(art::ArtMethod * art_method, bool including_backup = false) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup, &including_backup](const auto &it) {
if (including_backup || it.second.first) backup = it.second.second;
});
return backup;
}
inline art::ArtMethod *IsBackup(art::ArtMethod * art_method) {
art::ArtMethod *backup = nullptr;
hooked_methods_.if_contains(art_method, [&backup](const auto &it) {
if (!it.second.first) backup = it.second.second;
});
return backup;
}
inline bool IsDeoptimized(art::ArtMethod * art_method) {
return deoptimized_methods_set_.contains(art_method);
}
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
std::unique_lock lk(jit_movements_lock_);
return std::move(jit_movements_);
}
inline void RecordHooked(art::ArtMethod * target, const art::dex::ClassDef *class_def,
jobject reflected_backup, art::ArtMethod *backup) {
hooked_classes_.lazy_emplace_l(
class_def, [&target](auto &it) { it.second.emplace(target); },
[&class_def, &target](const auto &ctor) {
ctor(class_def, phmap::flat_hash_set<art::ArtMethod *>{target});
});
hooked_methods_.insert({std::make_pair(target, std::make_pair(reflected_backup, backup)),
std::make_pair(backup, std::make_pair(nullptr, target))});
}
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
{ deoptimized_classes_[class_def].emplace(art_method); }
deoptimized_methods_set_.insert(art_method);
}
inline void RecordJitMovement(art::ArtMethod * target, art::ArtMethod * backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
} // namespace lsplant

View File

@ -1,179 +0,0 @@
#pragma once
#include <absl/container/flat_hash_map.h>
#include <absl/container/flat_hash_set.h>
#include <sys/system_properties.h>
#include <list>
#include <shared_mutex>
#include <string_view>
#include "logging.hpp"
#include "lsplant.hpp"
#include "utils/hook_helper.hpp"
namespace lsplant {
enum class Arch {
kArm,
kArm64,
kX86,
kX8664,
};
consteval inline Arch GetArch() {
#if defined(__i386__)
return Arch::kX86;
#elif defined(__x86_64__)
return Arch::kX8664;
#elif defined(__arm__)
return Arch::kArm;
#elif defined(__aarch64__)
return Arch::kArm64;
#else
#error "unsupported architecture"
#endif
}
inline static constexpr auto kArch = GetArch();
template <typename T>
constexpr inline auto RoundUpTo(T v, size_t size) {
return v + size - 1 - ((v + size - 1) & (size - 1));
}
inline auto GetAndroidApiLevel() {
static auto kApiLevel = []() {
std::array<char, PROP_VALUE_MAX> prop_value;
__system_property_get("ro.build.version.sdk", prop_value.data());
int base = atoi(prop_value.data());
__system_property_get("ro.build.version.preview_sdk", prop_value.data());
return base + atoi(prop_value.data());
}();
return kApiLevel;
}
inline auto IsJavaDebuggable(JNIEnv *env) {
static auto kDebuggable = [&env]() {
auto sdk_int = GetAndroidApiLevel();
if (sdk_int < __ANDROID_API_P__) {
return false;
}
auto runtime_class = JNI_FindClass(env, "dalvik/system/VMRuntime");
if (!runtime_class) {
LOGE("Failed to find VMRuntime");
return false;
}
auto get_runtime_method =
JNI_GetStaticMethodID(env, runtime_class, "getRuntime", "()Ldalvik/system/VMRuntime;");
if (!get_runtime_method) {
LOGE("Failed to find VMRuntime.getRuntime()");
return false;
}
auto is_debuggable_method = JNI_GetMethodID(env, runtime_class, "isJavaDebuggable", "()Z");
if (!is_debuggable_method) {
LOGE("Failed to find VMRuntime.isJavaDebuggable()");
return false;
}
auto runtime = JNI_CallStaticObjectMethod(env, runtime_class, get_runtime_method);
if (!runtime) {
LOGE("Failed to get VMRuntime");
return false;
}
bool is_debuggable = JNI_CallBooleanMethod(env, runtime, is_debuggable_method);
LOGD("java runtime debuggable %s", is_debuggable ? "true" : "false");
return is_debuggable;
}();
return kDebuggable;
}
inline static constexpr auto kPointerSize = sizeof(void *);
namespace art {
class ArtMethod;
namespace dex {
class ClassDef;
}
namespace mirror {
class Class;
}
} // namespace art
namespace {
// target, backup
inline absl::flat_hash_map<art::ArtMethod *, std::pair<jobject, art::ArtMethod *>> hooked_methods_;
inline std::shared_mutex hooked_methods_lock_;
inline absl::flat_hash_map<const art::dex::ClassDef *, absl::flat_hash_set<art::ArtMethod *>>
hooked_classes_;
inline std::shared_mutex hooked_classes_lock_;
inline absl::flat_hash_set<art::ArtMethod *> deoptimized_methods_set_;
inline std::shared_mutex deoptimized_methods_lock_;
inline absl::flat_hash_map<const art::dex::ClassDef *, absl::flat_hash_set<art::ArtMethod *>>
deoptimized_classes_;
inline std::shared_mutex deoptimized_classes_lock_;
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
inline std::shared_mutex jit_movements_lock_;
} // namespace
inline art::ArtMethod *IsHooked(art::ArtMethod *art_method, bool including_backup = false) {
std::shared_lock lk(hooked_methods_lock_);
if (auto it = hooked_methods_.find(art_method);
it != hooked_methods_.end() && (!including_backup || it->second.first)) {
return it->second.second;
}
return nullptr;
}
inline art::ArtMethod *IsBackup(art::ArtMethod *art_method) {
std::shared_lock lk(hooked_methods_lock_);
if (auto it = hooked_methods_.find(art_method);
it != hooked_methods_.end() && !it->second.first) {
return it->second.second;
}
return nullptr;
}
inline bool IsDeoptimized(art::ArtMethod *art_method) {
std::shared_lock lk(deoptimized_methods_lock_);
return deoptimized_methods_set_.contains(art_method);
}
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> GetJitMovements() {
std::unique_lock lk(jit_movements_lock_);
return std::move(jit_movements_);
}
inline void RecordHooked(art::ArtMethod *target, const art::dex::ClassDef *class_def,
jobject reflected_backup, art::ArtMethod *backup) {
{
std::unique_lock lk(hooked_classes_lock_);
hooked_classes_[class_def].emplace(target);
}
{
std::unique_lock lk(hooked_methods_lock_);
hooked_methods_[target] = {reflected_backup, backup};
hooked_methods_[backup] = {nullptr, target};
}
}
inline void RecordDeoptimized(const art::dex::ClassDef *class_def, art::ArtMethod *art_method) {
{
std::unique_lock lk(deoptimized_classes_lock_);
deoptimized_classes_[class_def].emplace(art_method);
}
{
std::unique_lock lk(deoptimized_methods_lock_);
deoptimized_methods_set_.insert(art_method);
}
}
inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) {
std::unique_lock lk(jit_movements_lock_);
jit_movements_.emplace_back(target, backup);
}
} // namespace lsplant

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
import com.android.build.api.dsl.ManagedVirtualDevice import com.android.build.api.dsl.ManagedVirtualDevice
plugins { plugins {
id("com.android.application") alias(libs.plugins.agp.app)
} }
val androidTargetSdkVersion: Int by rootProject.extra val androidTargetSdkVersion: Int by rootProject.extra
@ -36,6 +36,12 @@ android {
} }
} }
buildTypes {
debug {
isDebuggable = false
}
}
externalNativeBuild { externalNativeBuild {
cmake { cmake {
path = file("src/main/jni/CMakeLists.txt") path = file("src/main/jni/CMakeLists.txt")
@ -58,30 +64,31 @@ android {
require64Bit = is64 require64Bit = is64
} }
// createDevice(21, false) createDevice(21, false)
// createDevice(21, true) createDevice(21, true)
// createDevice(22, false) createDevice(22, false)
// createDevice(22, true) createDevice(22, true)
// createDevice(23, false) createDevice(23, false)
// createDevice(23, true) createDevice(23, true)
// createDevice(24, false) createDevice(24, false)
// createDevice(24, true) createDevice(24, true)
// createDevice(25, false) createDevice(25, false)
// createDevice(25, true) createDevice(25, true)
// createDevice(26, false) createDevice(26, false)
// createDevice(26, true) createDevice(26, true)
createDevice(27, false) createDevice(27, false)
createDevice(27, true) createDevice(27, true)
createDevice(28, false) createDevice(28, false)
createDevice(28, true) createDevice(28, true)
createDevice(29, false) createDevice(29, false)
createDevice(29, true) createDevice(29, true)
createDevice(30, false) createDevice(30, false, "aosp_atd")
createDevice(30, true) createDevice(30, true)
// createDevice(31, false, "android-tv") // createDevice(31, false, "android-tv")
createDevice(31, true, "aosp_atd") createDevice(31, true, "aosp_atd")
createDevice(32, true, "google_apis") createDevice(32, true, "google_apis")
createDevice(33, true, "google_apis") createDevice(33, true, "google_apis")
createDevice(34, true, "google_apis")
} }
} }
} }
@ -89,9 +96,10 @@ android {
dependencies { dependencies {
implementation(project(":lsplant")) implementation(project(":lsplant"))
implementation("io.github.vvb2060.ndk:dobby:1.2") implementation(libs.dobby)
androidTestImplementation("androidx.test.ext:junit:1.1.3") androidTestImplementation(libs.test.ext.junit)
androidTestImplementation("androidx.test:runner:1.4.0") androidTestImplementation(libs.test.runner)
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") androidTestImplementation(libs.test.espresso)
} }

View File

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

View File

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

View File

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

View File

@ -1,144 +0,0 @@
/*
* This file is part of LSPosed.
*
* LSPosed is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSPosed is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
*
* Copyright (C) 2019 Swift Gan
* Copyright (C) 2021 LSPosed Contributors
*/
#ifndef SANDHOOK_ELF_UTIL_H
#define SANDHOOK_ELF_UTIL_H
#include <string_view>
#include <map>
#include <linux/elf.h>
#include <sys/types.h>
#include <string>
#include <link.h>
#define SHT_GNU_HASH 0x6ffffff6
namespace SandHook {
class ElfImg {
public:
ElfImg(std::string_view elf);
template<typename T = void*>
requires(std::is_pointer_v<T>)
constexpr const T getSymbAddress(std::string_view name) const {
auto offset = getSymbOffset(name, GnuHash(name), ElfHash(name));
if (offset > 0 && base != nullptr) {
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
} else {
return nullptr;
}
}
template<typename T = void*>
requires(std::is_pointer_v<T>)
constexpr const T getSymbPrefixFirstOffset(std::string_view prefix) const {
auto offset = PrefixLookupFirst(prefix);
if (offset > 0 && base != nullptr) {
return reinterpret_cast<T>(static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias));
} else {
return nullptr;
}
}
bool isValid() const {
return base != nullptr;
}
const std::string name() const {
return elf;
}
~ElfImg();
private:
ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const;
ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const;
ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const;
ElfW(Addr) LinearLookup(std::string_view name) const;
ElfW(Addr) PrefixLookupFirst(std::string_view prefix) const;
constexpr static uint32_t ElfHash(std::string_view name);
constexpr static uint32_t GnuHash(std::string_view name);
bool findModuleBase();
void MayInitLinearMap() const;
std::string elf;
void *base = nullptr;
char *buffer = nullptr;
off_t size = 0;
off_t bias = -4396;
ElfW(Ehdr) *header = nullptr;
ElfW(Shdr) *section_header = nullptr;
ElfW(Shdr) *symtab = nullptr;
ElfW(Shdr) *strtab = nullptr;
ElfW(Shdr) *dynsym = nullptr;
ElfW(Sym) *symtab_start = nullptr;
ElfW(Sym) *dynsym_start = nullptr;
ElfW(Sym) *strtab_start = nullptr;
ElfW(Off) symtab_count = 0;
ElfW(Off) symstr_offset = 0;
ElfW(Off) symstr_offset_for_symtab = 0;
ElfW(Off) symtab_offset = 0;
ElfW(Off) dynsym_offset = 0;
ElfW(Off) symtab_size = 0;
uint32_t nbucket_{};
uint32_t *bucket_ = nullptr;
uint32_t *chain_ = nullptr;
uint32_t gnu_nbucket_{};
uint32_t gnu_symndx_{};
uint32_t gnu_bloom_size_;
uint32_t gnu_shift2_;
uintptr_t *gnu_bloom_filter_;
uint32_t *gnu_bucket_;
uint32_t *gnu_chain_;
mutable std::map<std::string_view, ElfW(Sym) *> symtabs_;
};
constexpr uint32_t ElfImg::ElfHash(std::string_view name) {
uint32_t h = 0, g;
for (unsigned char p: name) {
h = (h << 4) + p;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
constexpr uint32_t ElfImg::GnuHash(std::string_view name) {
uint32_t h = 5381;
for (unsigned char p: name) {
h += (h << 5) + p;
}
return h;
}
}
#endif //SANDHOOK_ELF_UTIL_H

@ -0,0 +1 @@
Subproject commit 73b146d780339e0ec3fc91368828c5f4441761c4

1
test/src/main/jni/external/lsprism vendored Submodule

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

View File

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