mirror of
https://github.com/LSPosed/LSPlant.git
synced 2025-05-04 20:42:02 +08:00
Initial commit
This commit is contained in:
commit
6febb83834
12
.gitattributes
vendored
Normal file
12
.gitattributes
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
# Declare files that will always have CRLF line endings on checkout.
|
||||||
|
*.cmd text eol=crlf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
|
||||||
|
# Denote all files that are truly binary and should not be modified.
|
||||||
|
*.so binary
|
||||||
|
*.dex binary
|
||||||
|
*.jar binary
|
||||||
|
*.png binary
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[submodule "library/jni/external/dex_builder"]
|
||||||
|
path = library/jni/external/dex_builder
|
||||||
|
url = https://github.com/LSPosed/DexBuilder.git
|
||||||
|
branch = master
|
165
LICENSE
Normal file
165
LICENSE
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2022 LSPosed <https://lsposed.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
42
build.gradle.kts
Normal file
42
build.gradle.kts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||||
|
|
||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
val agpVersion by extra("7.1.1")
|
||||||
|
dependencies {
|
||||||
|
classpath("com.android.tools.build:gradle:$agpVersion")
|
||||||
|
classpath("org.eclipse.jgit:org.eclipse.jgit:6.0.0.202111291000-r")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val androidTargetSdkVersion by extra(32)
|
||||||
|
val androidMinSdkVersion by extra(27)
|
||||||
|
val androidBuildToolsVersion by extra("32.0.0")
|
||||||
|
val androidCompileSdkVersion by extra(32)
|
||||||
|
val androidCompileNdkVersion by extra("23.1.7779620")
|
||||||
|
|
||||||
|
tasks.register("Delete", Delete::class) {
|
||||||
|
delete(rootProject.buildDir)
|
||||||
|
}
|
13
gradle.properties
Normal file
13
gradle.properties
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
org.gradle.parallel=true
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
234
gradlew
vendored
Normal file
234
gradlew
vendored
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
89
gradlew.bat
vendored
Normal file
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
5
library/.gitignore
vendored
Normal file
5
library/.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/build
|
||||||
|
/libs
|
||||||
|
/obj
|
||||||
|
/release
|
||||||
|
|
65
library/build.gradle.kts
Normal file
65
library/build.gradle.kts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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) 2021 LSPosed Contributors
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
}
|
||||||
|
|
||||||
|
val androidTargetSdkVersion: Int by rootProject.extra
|
||||||
|
val androidMinSdkVersion: Int by rootProject.extra
|
||||||
|
val androidBuildToolsVersion: String by rootProject.extra
|
||||||
|
val androidCompileSdkVersion: Int by rootProject.extra
|
||||||
|
val androidCompileNdkVersion: String by rootProject.extra
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdk = androidCompileSdkVersion
|
||||||
|
ndkVersion = androidCompileNdkVersion
|
||||||
|
buildToolsVersion = androidBuildToolsVersion
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
prefab = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = androidMinSdkVersion
|
||||||
|
targetSdk = androidTargetSdkVersion
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
arguments += "-j${Runtime.getRuntime().availableProcessors()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lint {
|
||||||
|
abortOnError = true
|
||||||
|
checkReleaseBuilds = false
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
ndkBuild {
|
||||||
|
path("jni/Android.mk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||||
|
}
|
63
library/jni/.clang-tidy
Normal file
63
library/jni/.clang-tidy
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
Checks: >
|
||||||
|
-*,
|
||||||
|
bugprone-*,
|
||||||
|
google-*,
|
||||||
|
misc-*,
|
||||||
|
modernize-*,
|
||||||
|
performance-*,
|
||||||
|
portability-*,
|
||||||
|
readability-*,
|
||||||
|
clang-analyzer-*,
|
||||||
|
llvm-include-order,
|
||||||
|
-modernize-use-trailing-return-type,
|
||||||
|
-readability-implicit-bool-conversion,
|
||||||
|
-performance-no-int-to-ptr,
|
||||||
|
|
||||||
|
CheckOptions:
|
||||||
|
- key: readability-identifier-naming.ClassCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.ClassMemberCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.EnumCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.EnumConstantCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.EnumConstantPrefix
|
||||||
|
value: k
|
||||||
|
- key: readability-identifier-naming.FunctionCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.GlobalConstantCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.GlobalConstantPrefix
|
||||||
|
value: k
|
||||||
|
- key: readability-identifier-naming.StaticConstantCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.StaticConstantPrefix
|
||||||
|
value: k
|
||||||
|
- key: readability-identifier-naming.StaticVariableCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.StaticVariablePrefix
|
||||||
|
value: k
|
||||||
|
- key: readability-identifier-naming.MacroDefinitionCase
|
||||||
|
value: UPPER_CASE
|
||||||
|
- key: readability-identifier-naming.MemberCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||||
|
value: _
|
||||||
|
- key: readability-identifier-naming.ProtectedMemberSuffix
|
||||||
|
value: _
|
||||||
|
- key: readability-identifier-naming.NamespaceCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.ParameterCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.TypeAliasCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.TypedefCase
|
||||||
|
value: CamelCase
|
||||||
|
- key: readability-identifier-naming.VariableCase
|
||||||
|
value: lower_case
|
||||||
|
- key: readability-identifier-naming.IgnoreMainLikeFunctions
|
||||||
|
value: 1
|
||||||
|
- key: readability-braces-around-statements.ShortStatementLines
|
||||||
|
value: 1
|
15
library/jni/Android.mk
Normal file
15
library/jni/Android.mk
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE := lsplant
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||||
|
LOCAL_SRC_FILES := lsplant.cc
|
||||||
|
LOCAL_EXPORT_C_INCLUDES:= $(LOCAL_PATH)/include
|
||||||
|
LOCAL_STATIC_LIBRARIES := cxx dex_builder
|
||||||
|
LOCAL_LDLIBS := -llog
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
$(call import-module,prefab/cxx)
|
||||||
|
include jni/external/dex_builder/Android.mk
|
||||||
|
|
14
library/jni/Application.mk
Normal file
14
library/jni/Application.mk
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
APP_CFLAGS := -Wall -Wextra
|
||||||
|
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
|
||||||
|
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__ -Wno-gnu-string-literal-operator-template
|
||||||
|
APP_CPPFLAGS := -std=c++20
|
||||||
|
APP_CONLYFLAGS := -std=c18
|
||||||
|
APP_STL := none
|
||||||
|
|
||||||
|
ifneq ($(NDK_DEBUG),1)
|
||||||
|
APP_CFLAGS += -Oz -flto
|
||||||
|
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 -flto -Wl,--gc-sections -Wl,--strip-all
|
||||||
|
endif
|
31
library/jni/art/instrumentation.hpp
Normal file
31
library/jni/art/instrumentation.hpp
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include "runtime/art_method.hpp"
|
||||||
|
|
||||||
|
namespace lsplant::art {
|
||||||
|
|
||||||
|
class Instrumentation {
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art15instrumentation15Instrumentation21UpdateMethodsCodeImplEPNS_9ArtMethodEPKv",
|
||||||
|
void, UpdateMethodsCode,
|
||||||
|
(Instrumentation * thiz, ArtMethod * art_method, const void *quick_code), {
|
||||||
|
if (IsHooked(art_method)) [[unlikely]] {
|
||||||
|
LOGD("Skip update method code for hooked method %s",
|
||||||
|
art_method->PrettyMethod().c_str());
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
backup(thiz, art_method, quick_code);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool Init(const HookHandler &handler) {
|
||||||
|
if (!HookSyms(handler, UpdateMethodsCode)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
98
library/jni/art/mirror/class.hpp
Normal file
98
library/jni/art/mirror/class.hpp
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
|
||||||
|
namespace lsplant::art {
|
||||||
|
|
||||||
|
namespace dex {
|
||||||
|
class ClassDef {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace mirror {
|
||||||
|
|
||||||
|
class Class {
|
||||||
|
private:
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(const char *, GetDescriptor, Class *thiz, std::string *storage) {
|
||||||
|
if (GetDescriptorSym) [[likely]]
|
||||||
|
return GetDescriptor(thiz, storage);
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(const dex::ClassDef *, GetClassDef, Class *thiz) {
|
||||||
|
if (GetClassDefSym) [[likely]]
|
||||||
|
return GetClassDefSym(thiz);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool Init(JNIEnv *env, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||||
|
if (!clazz) {
|
||||||
|
LOGE("Failed to find Class");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_status = JNI_GetFieldID(env, clazz, "status", "I"); !class_status) {
|
||||||
|
LOGE("Failed to find status");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sdk_int = GetAndroidApiLevel();
|
||||||
|
|
||||||
|
if (sdk_int >= __ANDROID_API_P__) {
|
||||||
|
is_unsigned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *GetDescriptor(std::string *storage) {
|
||||||
|
if (GetDescriptorSym) {
|
||||||
|
return GetDescriptor(storage);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GetDescriptor() {
|
||||||
|
std::string storage;
|
||||||
|
return GetDescriptor(&storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const dex::ClassDef *GetClassDef() {
|
||||||
|
if (GetClassDefSym)
|
||||||
|
return GetClassDef(this);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int GetStatus(JNIEnv *env, jclass clazz) {
|
||||||
|
int status = JNI_GetLongField(env, clazz, class_status);
|
||||||
|
return is_unsigned ? static_cast<uint32_t>(status) >> (32 - 4) : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsInitialized(JNIEnv *env, jclass clazz) {
|
||||||
|
return is_unsigned ? GetStatus(env, clazz) >= 14 : GetStatus(env, clazz) == 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Class *FromReflectedClass(JNIEnv *, jclass clazz) {
|
||||||
|
return reinterpret_cast<Class *>(art::Thread::Current()->DecodeJObject(clazz));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline static jfieldID class_status = nullptr;
|
||||||
|
inline static bool is_unsigned = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
40
library/jni/art/runtime.hpp
Normal file
40
library/jni/art/runtime.hpp
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
|
||||||
|
namespace lsplant::art {
|
||||||
|
|
||||||
|
class Runtime {
|
||||||
|
private:
|
||||||
|
inline static Runtime *instance_;
|
||||||
|
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, SetJavaDebuggable, Runtime *thiz, bool value) {
|
||||||
|
if (SetJavaDebuggableSym) [[likely]] {
|
||||||
|
SetJavaDebuggableSym(thiz, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
inline static Runtime *Current() {
|
||||||
|
return instance_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetJavaDebuggable(bool value) {
|
||||||
|
SetJavaDebuggable(this, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Init(const HookHandler &handler) {
|
||||||
|
Runtime *instance = nullptr;
|
||||||
|
if (!RETRIEVE_FIELD_SYMBOL(instance, "_ZN3art7Runtime9instance_E")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
instance_ = *reinterpret_cast<Runtime **>(instance);
|
||||||
|
LOGD("_ZN3art7Runtime9instance_E = %p", instance_);
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(SetJavaDebuggable, "_ZN3art7Runtime17SetJavaDebuggableEb")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
180
library/jni/art/runtime/art_method.hpp
Normal file
180
library/jni/art/runtime/art_method.hpp
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
#include "art/mirror/class.hpp"
|
||||||
|
|
||||||
|
namespace lsplant::art {
|
||||||
|
|
||||||
|
class ArtMethod {
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(std::string, PrettyMethod, ArtMethod *thiz, bool with_signature) {
|
||||||
|
if (thiz == nullptr) [[unlikely]] return "null";
|
||||||
|
else if (PrettyMethodSym) [[likely]] return PrettyMethodSym(thiz, with_signature);
|
||||||
|
else return "null sym";
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void SetNonCompilable() {
|
||||||
|
auto access_flags = GetAccessFlags();
|
||||||
|
access_flags |= kAccCompileDontBother;
|
||||||
|
access_flags &= ~kAccPreCompiled;
|
||||||
|
SetAccessFlags(access_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetNonIntrinsic() {
|
||||||
|
auto access_flags = GetAccessFlags();
|
||||||
|
access_flags &= ~kAccFastInterpreterToInterpreterInvoke;
|
||||||
|
SetAccessFlags(access_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPrivate() {
|
||||||
|
auto access_flags = GetAccessFlags();
|
||||||
|
if (!(access_flags & kAccStatic)) {
|
||||||
|
access_flags |= kAccPrivate;
|
||||||
|
access_flags &= ~kAccProtected;
|
||||||
|
access_flags &= ~kAccPublic;
|
||||||
|
SetAccessFlags(access_flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsStatic() {
|
||||||
|
return GetAccessFlags() & kAccStatic;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CopyFrom(const ArtMethod *other) {
|
||||||
|
memcpy(this, other, art_method_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetEntryPoint(void *entry_point) {
|
||||||
|
*reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
|
||||||
|
entry_point_offset) = entry_point;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *GetEntryPoint() {
|
||||||
|
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
|
||||||
|
entry_point_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void *GetData() {
|
||||||
|
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) +
|
||||||
|
data_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetAccessFlags() {
|
||||||
|
return (reinterpret_cast<const std::atomic<uint32_t> *>(
|
||||||
|
reinterpret_cast<uintptr_t>(this) + access_flags_offset))->load(
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAccessFlags(uint32_t flags) {
|
||||||
|
return (reinterpret_cast<std::atomic<uint32_t> *>(
|
||||||
|
reinterpret_cast<uintptr_t>(this) + access_flags_offset))->store(
|
||||||
|
flags, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string PrettyMethod(bool with_signature = true) {
|
||||||
|
return PrettyMethod(this, with_signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
mirror::Class *GetDeclaringClass() {
|
||||||
|
return *reinterpret_cast<mirror::Class **>(reinterpret_cast<uintptr_t>(this) +
|
||||||
|
declaring_class_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
|
||||||
|
return reinterpret_cast<art::ArtMethod *>(JNI_GetLongField(env, method, art_method_field));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Init(JNIEnv *env, const lsplant::InitInfo info) {
|
||||||
|
auto executable = JNI_FindClass(env, "java/lang/reflect/Executable");
|
||||||
|
if (!executable) {
|
||||||
|
LOGE("Failed to found Executable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod",
|
||||||
|
"J"); !art_method_field) {
|
||||||
|
LOGE("Failed to find artMethod field");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto throwable = JNI_FindClass(env, "java/lang/Throwable");
|
||||||
|
if (!throwable) {
|
||||||
|
LOGE("Failed to found Executable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||||
|
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
|
||||||
|
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
|
||||||
|
"()[Ljava/lang/reflect/Constructor;");
|
||||||
|
auto constructors = JNI_Cast<jobjectArray>(
|
||||||
|
JNI_CallObjectMethod(env, clazz, get_declared_constructors));
|
||||||
|
auto length = JNI_GetArrayLength(env, constructors);
|
||||||
|
if (length < 2) {
|
||||||
|
LOGE("Throwable has less than 2 constructors");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto *first = FromReflectedMethod(env,
|
||||||
|
JNI_GetObjectArrayElement(env, constructors, 0).get());
|
||||||
|
auto *second = FromReflectedMethod(env,
|
||||||
|
JNI_GetObjectArrayElement(env, constructors, 0).get());
|
||||||
|
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
|
||||||
|
LOGD("ArtMethod size: %zu", art_method_size);
|
||||||
|
|
||||||
|
if (RoundUpTo(4 * 4 + 2 * 2, kPointerSize) + kPointerSize * 3 < art_method_size) {
|
||||||
|
LOGW("ArtMethod size exceeds maximum assume. There may be something wrong.");
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_point_offset = art_method_size - sizeof(void *);
|
||||||
|
LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset);
|
||||||
|
|
||||||
|
data_offset = entry_point_offset - sizeof(void *);
|
||||||
|
LOGD("ArtMethod::data offset: %zu", data_offset);
|
||||||
|
|
||||||
|
declaring_class_offset = 0U;
|
||||||
|
LOGD("ArtMethod::declaring_class offset: %zu", declaring_class_offset);
|
||||||
|
|
||||||
|
access_flags_offset = 4U;
|
||||||
|
LOGD("ArtMethod::access_flags offset: %zu", access_flags_offset);
|
||||||
|
auto sdk_int = GetAndroidApiLevel();
|
||||||
|
|
||||||
|
if (sdk_int < __ANDROID_API_R__) kAccPreCompiled = 0;
|
||||||
|
if (sdk_int < __ANDROID_API_Q__) kAccFastInterpreterToInterpreterInvoke = 0;
|
||||||
|
|
||||||
|
get_method_shorty_symbol = GetArtSymbol<decltype(get_method_shorty_symbol)>(
|
||||||
|
info.art_symbol_resolver,
|
||||||
|
"_ZN3artL15GetMethodShortyEP7_JNIEnvP10_jmethodID");
|
||||||
|
if (!get_method_shorty_symbol) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char *GetMethodShorty(_JNIEnv *env, _jmethodID *method) {
|
||||||
|
if (get_method_shorty_symbol) [[likely]] return get_method_shorty_symbol(env, method);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetEntryPointOffset() {
|
||||||
|
return entry_point_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr static uint32_t kAccPublic = 0x0001; // class, field, method, ic
|
||||||
|
constexpr static uint32_t kAccPrivate = 0x0002; // field, method, ic
|
||||||
|
constexpr static uint32_t kAccProtected = 0x0004; // field, method, ic
|
||||||
|
constexpr static uint32_t kAccStatic = 0x0008; // field, method, ic
|
||||||
|
constexpr static uint32_t kAccNative = 0x0100; // method
|
||||||
|
|
||||||
|
private:
|
||||||
|
inline static jfieldID art_method_field = nullptr;
|
||||||
|
inline static size_t art_method_size = 0;
|
||||||
|
inline static size_t entry_point_offset = 0;
|
||||||
|
inline static size_t data_offset = 0;
|
||||||
|
inline static size_t declaring_class_offset = 0;
|
||||||
|
inline static size_t access_flags_offset = 0;
|
||||||
|
inline static uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000;
|
||||||
|
inline static uint32_t kAccPreCompiled = 0x00200000;
|
||||||
|
inline static uint32_t kAccCompileDontBother = 0x02000000;
|
||||||
|
|
||||||
|
inline static const char *
|
||||||
|
(*get_method_shorty_symbol)(_JNIEnv *env, _jmethodID *method) = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
146
library/jni/art/runtime/class_linker.hpp
Normal file
146
library/jni/art/runtime/class_linker.hpp
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "art/runtime/art_method.hpp"
|
||||||
|
#include "art/mirror/class.hpp"
|
||||||
|
#include "art/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
static void MaybeDelayHook(mirror::Class *clazz) {
|
||||||
|
const auto *class_def = clazz->GetClassDef();
|
||||||
|
bool should_intercept = class_def && IsPending(class_def);
|
||||||
|
if (should_intercept) [[unlikely]] {
|
||||||
|
LOGD("Pending hook for %p (%s)", clazz, clazz->GetDescriptor().c_str());
|
||||||
|
OnPending(class_def);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE",
|
||||||
|
void, FixupStaticTrampolines, (ClassLinker * thiz, mirror::Class * clazz), {
|
||||||
|
backup(thiz, clazz);
|
||||||
|
MaybeDelayHook(clazz);
|
||||||
|
});
|
||||||
|
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6ThreadENS_6ObjPtrINS_6mirror5ClassEEE",
|
||||||
|
void, FixupStaticTrampolinesWithThread,
|
||||||
|
(ClassLinker * thiz, Thread * self, mirror::Class * clazz), {
|
||||||
|
backup(thiz, self, clazz);
|
||||||
|
MaybeDelayHook(clazz);
|
||||||
|
});
|
||||||
|
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art11ClassLinker20MarkClassInitializedEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEE",
|
||||||
|
void*, MarkClassInitialized, (ClassLinker * thiz, Thread * self, uint32_t * clazz_ptr),
|
||||||
|
{
|
||||||
|
void *result = backup(thiz, self, clazz_ptr);
|
||||||
|
auto clazz = reinterpret_cast<mirror::Class *>(*clazz_ptr);
|
||||||
|
MaybeDelayHook(clazz);
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv",
|
||||||
|
bool, ShouldUseInterpreterEntrypoint, (ArtMethod * art_method, const void *quick_code),
|
||||||
|
{
|
||||||
|
if (quick_code != nullptr &&
|
||||||
|
(IsHooked(art_method) || IsPending(art_method))) [[unlikely]] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return backup(art_method, quick_code);
|
||||||
|
});
|
||||||
|
|
||||||
|
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_to_interpreter_bridge, void*) {}
|
||||||
|
|
||||||
|
CREATE_FUNC_SYMBOL_ENTRY(void, art_quick_generic_jni_trampoline, void*) {}
|
||||||
|
|
||||||
|
CREATE_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art11interpreter29ShouldStayInSwitchInterpreterEPNS_9ArtMethodE",
|
||||||
|
bool, ShouldStayInSwitchInterpreter, (ArtMethod * art_method), {
|
||||||
|
if (IsHooked(art_method) || IsPending(art_method)) [[unlikely]] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return backup(art_method);
|
||||||
|
});
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool Init(const HookHandler &handler) {
|
||||||
|
int api_level = GetAndroidApiLevel();
|
||||||
|
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(SetEntryPointsToInterpreter,
|
||||||
|
"_ZNK3art11ClassLinker27SetEntryPointsToInterpreterEPNS_9ArtMethodE")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HookSyms(handler, ShouldUseInterpreterEntrypoint, ShouldStayInSwitchInterpreter)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api_level >= __ANDROID_API_R__) {
|
||||||
|
// In android R, FixupStaticTrampolines won't be called unless it's marking it as
|
||||||
|
// visiblyInitialized.
|
||||||
|
// So we miss some calls between initialized and visiblyInitialized.
|
||||||
|
// Therefore we hook the new introduced MarkClassInitialized instead
|
||||||
|
// This only happens on non-x86 devices
|
||||||
|
if (!HookSyms(handler, MarkClassInitialized) ||
|
||||||
|
!HookSyms(handler, FixupStaticTrampolinesWithThread, FixupStaticTrampolines)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!HookSyms(handler, FixupStaticTrampolines)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeInitializedClassesVisiblyInitialized will cause deadlock
|
||||||
|
// IsQuickToInterpreterBridge is inlined
|
||||||
|
// So we use GetSavedEntryPointOfPreCompiledMethod instead
|
||||||
|
|
||||||
|
if (!RETRIEVE_FUNC_SYMBOL(art_quick_to_interpreter_bridge,
|
||||||
|
"art_quick_to_interpreter_bridge")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!RETRIEVE_FUNC_SYMBOL(art_quick_generic_jni_trampoline,
|
||||||
|
"art_quick_generic_jni_trampoline")) {
|
||||||
|
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 (art_quick_to_interpreter_bridgeSym && art_quick_generic_jni_trampolineSym) [[likely]] {
|
||||||
|
if (art_method->GetAccessFlags() & ArtMethod::kAccNative) [[unlikely]] {
|
||||||
|
art_method->SetEntryPoint(
|
||||||
|
reinterpret_cast<void *>(art_quick_generic_jni_trampolineSym));
|
||||||
|
} else {
|
||||||
|
art_method->SetEntryPoint(
|
||||||
|
reinterpret_cast<void *>(art_quick_to_interpreter_bridgeSym));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (SetEntryPointsToInterpreterSym) [[likely]] {
|
||||||
|
SetEntryPointsToInterpreter(nullptr, art_method);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
42
library/jni/art/runtime/gc/collector_type.hpp
Normal file
42
library/jni/art/runtime/gc/collector_type.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace lsplant::art::gc {
|
||||||
|
// Which types of collections are able to be performed.
|
||||||
|
enum CollectorType {
|
||||||
|
// No collector selected.
|
||||||
|
kCollectorTypeNone,
|
||||||
|
// Non concurrent mark-sweep.
|
||||||
|
kCollectorTypeMS,
|
||||||
|
// Concurrent mark-sweep.
|
||||||
|
kCollectorTypeCMS,
|
||||||
|
// Semi-space / mark-sweep hybrid, enables compaction.
|
||||||
|
kCollectorTypeSS,
|
||||||
|
// Heap trimming collector, doesn't do any actual collecting.
|
||||||
|
kCollectorTypeHeapTrim,
|
||||||
|
// A (mostly) concurrent copying collector.
|
||||||
|
kCollectorTypeCC,
|
||||||
|
// The background compaction of the concurrent copying collector.
|
||||||
|
kCollectorTypeCCBackground,
|
||||||
|
// Instrumentation critical section fake collector.
|
||||||
|
kCollectorTypeInstrumentation,
|
||||||
|
// Fake collector for adding or removing application image spaces.
|
||||||
|
kCollectorTypeAddRemoveAppImageSpace,
|
||||||
|
// Fake collector used to implement exclusion between GC and debugger.
|
||||||
|
kCollectorTypeDebugger,
|
||||||
|
// A homogeneous space compaction collector used in background transition
|
||||||
|
// when both foreground and background collector are CMS.
|
||||||
|
kCollectorTypeHomogeneousSpaceCompact,
|
||||||
|
// Class linker fake collector.
|
||||||
|
kCollectorTypeClassLinker,
|
||||||
|
// JIT Code cache fake collector.
|
||||||
|
kCollectorTypeJitCodeCache,
|
||||||
|
// Hprof fake collector.
|
||||||
|
kCollectorTypeHprof,
|
||||||
|
// Fake collector for installing/removing a system-weak holder.
|
||||||
|
kCollectorTypeAddRemoveSystemWeakHolder,
|
||||||
|
// Fake collector type for GetObjectsAllocated
|
||||||
|
kCollectorTypeGetObjectsAllocated,
|
||||||
|
// Fake collector type for ScopedGCCriticalSection
|
||||||
|
kCollectorTypeCriticalSection,
|
||||||
|
};
|
||||||
|
} // namespace gc
|
44
library/jni/art/runtime/gc/gc_cause.hpp
Normal file
44
library/jni/art/runtime/gc/gc_cause.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#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 art
|
55
library/jni/art/runtime/gc/scoped_gc_critical_section.hpp
Normal file
55
library/jni/art/runtime/gc/scoped_gc_critical_section.hpp
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "gc_cause.hpp"
|
||||||
|
#include "collector_type.hpp"
|
||||||
|
#include "art/thread.hpp"
|
||||||
|
#include "common.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 == nullptr) [[unlikely]] return;
|
||||||
|
if (constructorSym) [[likely]]
|
||||||
|
return constructorSym(thiz, self, cause, collector_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedGCCriticalSection *thiz) {
|
||||||
|
if (thiz == nullptr) [[unlikely]] return;
|
||||||
|
if (destructorSym) [[likely]]
|
||||||
|
return 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) {
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(constructor,
|
||||||
|
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art2gc23ScopedGCCriticalSectionD2Ev")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[maybe_unused]] GCCriticalSection critical_section_;
|
||||||
|
[[maybe_unused]] const char *old_no_suspend_reason_;
|
||||||
|
};
|
||||||
|
}
|
35
library/jni/art/runtime/jit/jit_code_cache.hpp
Normal file
35
library/jni/art/runtime/jit/jit_code_cache.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#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);
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_HOOK_STUB_ENTRY(
|
||||||
|
"_ZN3art3jit12JitCodeCache19GarbageCollectCacheEPNS_6ThreadE",
|
||||||
|
void, GarbageCollectCache, (JitCodeCache * thiz, Thread * self), {
|
||||||
|
LOGD("Before jit cache gc, moving hooked methods");
|
||||||
|
for (auto[target, backup] : GetJitMovements()) {
|
||||||
|
MoveObsoleteMethod(thiz, target, backup);
|
||||||
|
}
|
||||||
|
backup(thiz, self);
|
||||||
|
});
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool Init(const HookHandler &handler) {
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(MoveObsoleteMethod,
|
||||||
|
"_ZN3art3jit12JitCodeCache18MoveObsoleteMethodEPNS_9ArtMethodES3_")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!HookSyms(handler, GarbageCollectCache)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
50
library/jni/art/thread.hpp
Normal file
50
library/jni/art/thread.hpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.hpp"
|
||||||
|
|
||||||
|
namespace lsplant::art {
|
||||||
|
|
||||||
|
class Thread {
|
||||||
|
struct ObjPtr {
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(ObjPtr, DecodeJObject, Thread *thiz, jobject obj) {
|
||||||
|
if (DecodeJObjectSym)
|
||||||
|
return DecodeJObjectSym(thiz, obj);
|
||||||
|
else
|
||||||
|
return { .data=nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
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_MEM_FUNC_SYMBOL(DecodeJObject,
|
||||||
|
"_ZNK3art6Thread13DecodeJObjectEP8_jobject")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!RETRIEVE_FUNC_SYMBOL(CurrentFromGdb,
|
||||||
|
"_ZN3art6Thread14CurrentFromGdbEv")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *DecodeJObject(jobject obj) {
|
||||||
|
if (DecodeJObjectSym) [[likely]] {
|
||||||
|
return DecodeJObject(this, obj).data;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
42
library/jni/art/thread_list.hpp
Normal file
42
library/jni/art/thread_list.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#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 == nullptr) [[unlikely]] return;
|
||||||
|
if (constructorSym) [[likely]]
|
||||||
|
return constructorSym(thiz, cause, long_suspend);
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_MEM_FUNC_SYMBOL_ENTRY(void, destructor, ScopedSuspendAll *thiz) {
|
||||||
|
if (thiz == nullptr) [[unlikely]] return;
|
||||||
|
if (destructorSym) [[likely]]
|
||||||
|
return destructorSym(thiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!RETRIEVE_MEM_FUNC_SYMBOL(destructor, "_ZN3art16ScopedSuspendAllD2Ev")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
154
library/jni/common.hpp
Normal file
154
library/jni/common.hpp
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <list>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
#include "logging.hpp"
|
||||||
|
#include "utils/hook_helper.hpp"
|
||||||
|
#include "lsplant.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 static constexpr auto kPointerSize = sizeof(void *);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline T GetArtSymbol(const std::function<void *(std::string_view)> &resolver,
|
||||||
|
std::string_view symbol) requires(std::is_pointer_v<T>) {
|
||||||
|
if (auto *result = resolver(symbol); result) {
|
||||||
|
return reinterpret_cast<T>(result);
|
||||||
|
} else {
|
||||||
|
LOGW("Failed to find symbol %*s", static_cast<int>(symbol.length()), symbol.data());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace art {
|
||||||
|
class ArtMethod;
|
||||||
|
namespace dex {
|
||||||
|
class ClassDef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// target, backup
|
||||||
|
inline std::unordered_map<art::ArtMethod *, art::ArtMethod *> hooked_methods_;
|
||||||
|
inline std::shared_mutex hooked_methods_lock_;
|
||||||
|
|
||||||
|
inline std::list<std::pair<art::ArtMethod *, art::ArtMethod *>> jit_movements_;
|
||||||
|
inline std::shared_mutex jit_movements_lock_;
|
||||||
|
|
||||||
|
inline std::unordered_map<const art::dex::ClassDef *, std::list<std::tuple<art::ArtMethod *, art::ArtMethod *, art::ArtMethod *>>> pending_classes_;
|
||||||
|
inline std::shared_mutex pending_classes_lock_;
|
||||||
|
|
||||||
|
inline std::unordered_set<const art::ArtMethod *> pending_methods_;
|
||||||
|
inline std::shared_mutex pending_methods_lock_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsHooked(art::ArtMethod *art_method) {
|
||||||
|
std::shared_lock lk(hooked_methods_lock_);
|
||||||
|
return hooked_methods_.contains(art_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsPending(art::ArtMethod *art_method) {
|
||||||
|
std::shared_lock lk(pending_methods_lock_);
|
||||||
|
return pending_methods_.contains(art_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsPending(const art::dex::ClassDef *class_def) {
|
||||||
|
std::shared_lock lk(pending_classes_lock_);
|
||||||
|
return pending_classes_.contains(class_def);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, art::ArtMethod *backup) {
|
||||||
|
std::unique_lock lk(hooked_methods_lock_);
|
||||||
|
hooked_methods_.emplace(target, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void RecordJitMovement(art::ArtMethod *target, art::ArtMethod *backup) {
|
||||||
|
std::unique_lock lk(jit_movements_lock_);
|
||||||
|
jit_movements_.emplace_back(target, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
RecordPending(const art::dex::ClassDef *class_def, art::ArtMethod *target, art::ArtMethod *hook,
|
||||||
|
art::ArtMethod *backup) {
|
||||||
|
{
|
||||||
|
std::unique_lock lk(pending_methods_lock_);
|
||||||
|
pending_methods_.emplace(target);
|
||||||
|
}
|
||||||
|
std::unique_lock lk(pending_classes_lock_);
|
||||||
|
pending_classes_[class_def].emplace_back(std::make_tuple(target, hook, backup));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnPending(art::ArtMethod *target, art::ArtMethod *hook, art::ArtMethod *backup);
|
||||||
|
|
||||||
|
inline void OnPending(const art::dex::ClassDef *class_def) {
|
||||||
|
{
|
||||||
|
std::shared_lock lk(pending_classes_lock_);
|
||||||
|
if (!pending_classes_.contains(class_def)) return;
|
||||||
|
}
|
||||||
|
typename decltype(pending_classes_)::value_type::second_type set;
|
||||||
|
{
|
||||||
|
std::unique_lock lk(pending_classes_lock_);
|
||||||
|
auto it = pending_classes_.find(class_def);
|
||||||
|
if (it == pending_classes_.end()) return;
|
||||||
|
set = std::move(it->second);
|
||||||
|
pending_classes_.erase(it);
|
||||||
|
}
|
||||||
|
for (auto&[target, hook, backup]: set) {
|
||||||
|
{
|
||||||
|
std::unique_lock mlk(pending_methods_lock_);
|
||||||
|
pending_methods_.erase(target);
|
||||||
|
}
|
||||||
|
OnPending(target, hook, backup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
library/jni/external/dex_builder
vendored
Submodule
1
library/jni/external/dex_builder
vendored
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 9d202f44c90489ef4759328a3f55a56effdc9105
|
37
library/jni/include/lsplant.hpp
Normal file
37
library/jni/include/lsplant.hpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace lsplant {
|
||||||
|
|
||||||
|
struct InitInfo {
|
||||||
|
using InlineHookFunType = std::function<void *(void *target, void *hooker)>;
|
||||||
|
using InlineUnhookFunType = std::function<bool(void *func)>;
|
||||||
|
using ArtSymbolResolver = std::function<void *(std::string_view symbol_name)>;
|
||||||
|
|
||||||
|
InlineHookFunType inline_hooker;
|
||||||
|
InlineUnhookFunType inline_unhooker;
|
||||||
|
ArtSymbolResolver art_symbol_resolver;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
bool Init(JNIEnv *env, const InitInfo &info);
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
jmethodID
|
||||||
|
Hook(JNIEnv *env, jmethodID target_method, jobject hooker_object, jmethodID callback_method);
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
bool UnHook(JNIEnv *env, jmethodID target_method);
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
bool IsHooked(JNIEnv *env, jmethodID method);
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
bool Deoptimize(JNIEnv *env, jmethodID method);
|
||||||
|
|
||||||
|
[[nodiscard]] [[maybe_unused]] [[gnu::visibility("default")]]
|
||||||
|
void *GetNativeFunction(JNIEnv *env, jmethodID method);
|
||||||
|
|
||||||
|
} // namespace lsplant
|
197
library/jni/include/utils/hook_helper.hpp
Normal file
197
library/jni/include/utils/hook_helper.hpp
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "logging.hpp"
|
||||||
|
#include "jni_helper.hpp"
|
||||||
|
#include "hook_helper.hpp"
|
||||||
|
#include <concepts>
|
||||||
|
|
||||||
|
#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 {
|
||||||
|
|
||||||
|
using HookHandler = InitInfo;
|
||||||
|
|
||||||
|
template<char... chars>
|
||||||
|
struct tstring : public std::integer_sequence<char, chars...> {
|
||||||
|
inline constexpr static const char *c_str() {
|
||||||
|
return str_;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr operator std::string_view() const {
|
||||||
|
return { c_str(), sizeof...(chars) };
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr static char str_[]{ chars..., '\0' };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, T... chars>
|
||||||
|
inline constexpr tstring<chars...> operator ""_tstr() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<char... as, char... bs>
|
||||||
|
inline constexpr tstring<as..., bs...>
|
||||||
|
operator+(const tstring<as...> &, const tstring<bs...> &) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<char... as>
|
||||||
|
inline constexpr auto operator+(const std::string &a, const tstring<as...> &) {
|
||||||
|
char b[]{ as..., '\0' };
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<char... as>
|
||||||
|
inline constexpr auto operator+(const tstring<as...> &, const std::string &b) {
|
||||||
|
char a[]{ as..., '\0' };
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void *Dlsym(const HookHandler &handle, const char *name) {
|
||||||
|
return handle.art_symbol_resolver(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Class, typename Return, typename T, typename... Args>
|
||||||
|
requires (std::is_same_v<T, void> || std::is_same_v<Class, T>)
|
||||||
|
inline static auto memfun_cast(Return (*func)(T *, Args...)) {
|
||||||
|
union {
|
||||||
|
Return (Class::*f)(Args...);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
decltype(func) p;
|
||||||
|
std::ptrdiff_t adj;
|
||||||
|
} data;
|
||||||
|
} u{ .data = { func, 0 }};
|
||||||
|
static_assert(sizeof(u.f) == sizeof(u.data), "Try different T");
|
||||||
|
return u.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<std::same_as<void> T, typename Return, typename... Args>
|
||||||
|
inline auto memfun_cast(Return (*func)(T *, Args...)) {
|
||||||
|
return memfun_cast<T>(func);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename, typename=void>
|
||||||
|
class MemberFunction;
|
||||||
|
|
||||||
|
template<typename This, typename Return, typename ... Args>
|
||||||
|
class MemberFunction<Return(Args...), This> {
|
||||||
|
using SelfType = MemberFunction<Return(This *, Args...), This>;
|
||||||
|
using ThisType = std::conditional_t<std::is_same_v<This, void>, SelfType, This>;
|
||||||
|
using MemFunType = Return(ThisType::*)(Args...);
|
||||||
|
public:
|
||||||
|
using FunType = Return (*)(This *, Args...);
|
||||||
|
private:
|
||||||
|
MemFunType f_ = nullptr;
|
||||||
|
public:
|
||||||
|
MemberFunction() = default;
|
||||||
|
|
||||||
|
MemberFunction(FunType f) : f_(memfun_cast<ThisType>(f)) {}
|
||||||
|
|
||||||
|
MemberFunction(MemFunType f) : f_(f) {}
|
||||||
|
|
||||||
|
Return operator()(This *thiz, Args... args) {
|
||||||
|
return (reinterpret_cast<ThisType *>(thiz)->*f_)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline operator bool() {
|
||||||
|
return f_ != nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// deduction guide
|
||||||
|
template<typename This, typename Return, typename...Args>
|
||||||
|
MemberFunction(Return(*f)(This *, Args...)) -> MemberFunction<Return(Args...), This>;
|
||||||
|
|
||||||
|
template<typename This, typename Return, typename...Args>
|
||||||
|
MemberFunction(Return(This::*f)(Args...)) -> MemberFunction<Return(Args...), This>;
|
||||||
|
|
||||||
|
template<typename, typename>
|
||||||
|
struct Hooker;
|
||||||
|
|
||||||
|
template<typename Ret, typename... Args, char... cs>
|
||||||
|
struct Hooker<Ret(Args...), tstring<cs...>> {
|
||||||
|
inline static Ret (*backup)(Args...) = nullptr;
|
||||||
|
|
||||||
|
inline static constexpr std::string_view sym = tstring<cs...>{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename, typename>
|
||||||
|
struct MemHooker;
|
||||||
|
template<typename Ret, typename This, typename... Args, char... cs>
|
||||||
|
struct MemHooker<Ret(This, Args...), tstring<cs...>> {
|
||||||
|
inline static MemberFunction<Ret(Args...)> backup;
|
||||||
|
inline static constexpr std::string_view sym = tstring<cs...>{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept HookerType = requires(T a) {
|
||||||
|
a.backup;
|
||||||
|
a.replace;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<HookerType T>
|
||||||
|
inline static bool HookSymNoHandle(const HookHandler &handler, void *original, T &arg) {
|
||||||
|
if (original) {
|
||||||
|
if constexpr(is_instance<decltype(arg.backup), MemberFunction>::value) {
|
||||||
|
void *backup = handler.inline_hooker(original, reinterpret_cast<void *>(arg.replace));
|
||||||
|
arg.backup = reinterpret_cast<typename decltype(arg.backup)::FunType>(backup);
|
||||||
|
} else {
|
||||||
|
arg.backup = reinterpret_cast<decltype(arg.backup)>(handler.inline_hooker(original,
|
||||||
|
reinterpret_cast<void *>(arg.replace)));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<HookerType T>
|
||||||
|
inline static bool HookSym(const HookHandler &handler, T &arg) {
|
||||||
|
auto original = handler.art_symbol_resolver(arg.sym);
|
||||||
|
return HookSymNoHandle(handler, original, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<HookerType T, HookerType...Args>
|
||||||
|
inline static bool HookSyms(const HookHandler &handle, T &first, Args &...rest) {
|
||||||
|
if (!(HookSym(handle, first) || ... || HookSym(handle, rest))) {
|
||||||
|
LOGW("Hook Fails: %*s", static_cast<int>(first.sym.size()), first.sym.data());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
424
library/jni/include/utils/jni_helper.hpp
Normal file
424
library/jni/include/utils/jni_helper.hpp
Normal file
@ -0,0 +1,424 @@
|
|||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||||
|
#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection"
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <string>
|
||||||
|
#include "logging.hpp"
|
||||||
|
|
||||||
|
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||||
|
TypeName(const TypeName&) = delete; \
|
||||||
|
void operator=(const TypeName&) = delete
|
||||||
|
|
||||||
|
namespace lsplant {
|
||||||
|
template<class, template<class, class...> class>
|
||||||
|
struct is_instance : public std::false_type {
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class...Ts, template<class, class...> class U>
|
||||||
|
struct is_instance<U<Ts...>, U> : public std::true_type {
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept JObject = std::is_base_of_v<std::remove_pointer_t<_jobject>, std::remove_pointer_t<T>>;
|
||||||
|
|
||||||
|
template<JObject T>
|
||||||
|
class ScopedLocalRef {
|
||||||
|
public:
|
||||||
|
using BaseType [[maybe_unused]] = T;
|
||||||
|
|
||||||
|
ScopedLocalRef(JNIEnv *env, T localRef) : env_(env), local_ref_(localRef) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedLocalRef(ScopedLocalRef &&s) noexcept: env_(s.env_), local_ref_(s.release()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template<JObject U>
|
||||||
|
ScopedLocalRef(ScopedLocalRef<U> &&s) noexcept: env_(s.env_), local_ref_((T) s.release()) {
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit ScopedLocalRef(JNIEnv *env) noexcept: env_(env), local_ref_(nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedLocalRef() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset(T ptr = nullptr) {
|
||||||
|
if (ptr != local_ref_) {
|
||||||
|
if (local_ref_ != nullptr) {
|
||||||
|
env_->DeleteLocalRef(local_ref_);
|
||||||
|
}
|
||||||
|
local_ref_ = ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] T release() {
|
||||||
|
T localRef = local_ref_;
|
||||||
|
local_ref_ = nullptr;
|
||||||
|
return localRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
T get() const {
|
||||||
|
return local_ref_;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator T() const {
|
||||||
|
return local_ref_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not expose an empty constructor as it can easily lead to errors
|
||||||
|
// using common idioms, e.g.:
|
||||||
|
// ScopedLocalRef<...> ref;
|
||||||
|
// ref.reset(...);
|
||||||
|
// Move assignment operator.
|
||||||
|
ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept {
|
||||||
|
reset(s.release());
|
||||||
|
env_ = s.env_;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return local_ref_;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<JObject U>
|
||||||
|
friend
|
||||||
|
class ScopedLocalRef;
|
||||||
|
|
||||||
|
friend
|
||||||
|
class JUTFString;
|
||||||
|
|
||||||
|
private:
|
||||||
|
JNIEnv *env_;
|
||||||
|
T local_ref_;
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class JNIScopeFrame {
|
||||||
|
JNIEnv *env_;
|
||||||
|
public:
|
||||||
|
JNIScopeFrame(JNIEnv *env, jint size) : env_(env) {
|
||||||
|
env_->PushLocalFrame(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
~JNIScopeFrame() {
|
||||||
|
env_->PopLocalFrame(nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
concept ScopeOrRaw = std::is_convertible_v<T, U> ||
|
||||||
|
(is_instance<std::decay_t<T>, ScopedLocalRef>::value &&
|
||||||
|
std::is_convertible_v<typename std::decay_t<T>::BaseType, U>);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept ScopeOrClass = ScopeOrRaw<T, jclass>;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept ScopeOrObject = ScopeOrRaw<T, jobject>;
|
||||||
|
|
||||||
|
inline ScopedLocalRef<jstring> ClearException(JNIEnv *env) {
|
||||||
|
if (auto exception = env->ExceptionOccurred()) {
|
||||||
|
env->ExceptionClear();
|
||||||
|
static jclass log = (jclass) env->NewGlobalRef(env->FindClass("android/util/Log"));
|
||||||
|
static jmethodID toString = env->GetStaticMethodID(log, "getStackTraceString",
|
||||||
|
"(Ljava/lang/Throwable;)Ljava/lang/String;");
|
||||||
|
auto str = (jstring) env->CallStaticObjectMethod(log, toString, exception);
|
||||||
|
env->DeleteLocalRef(exception);
|
||||||
|
return { env, str };
|
||||||
|
}
|
||||||
|
return { env, nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto UnwrapScope(T &&x) {
|
||||||
|
if constexpr(std::is_same_v<std::decay_t<T>, std::string_view>)
|
||||||
|
return x.data();
|
||||||
|
else if constexpr(is_instance<std::decay_t<T>, ScopedLocalRef>::value)
|
||||||
|
return x.get();
|
||||||
|
else return std::forward<T>(x);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto WrapScope(JNIEnv *env, T &&x) {
|
||||||
|
if constexpr(std::is_convertible_v<T, _jobject *>) {
|
||||||
|
return ScopedLocalRef(env, std::forward<T>(x));
|
||||||
|
} else return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ...T, size_t ...I>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x, std::index_sequence<I...>) {
|
||||||
|
return std::make_tuple(WrapScope(env, std::forward<T>(std::get<I>(x)))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename ...T>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto WrapScope(JNIEnv *env, std::tuple<T...> &&x) {
|
||||||
|
return WrapScope(env, std::forward<std::tuple<T...>>(x),
|
||||||
|
std::make_index_sequence<sizeof...(T)>());
|
||||||
|
}
|
||||||
|
|
||||||
|
inline auto JNI_NewStringUTF(JNIEnv *env, std::string_view sv) {
|
||||||
|
return ScopedLocalRef(env, env->NewStringUTF(sv.data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
class JUTFString {
|
||||||
|
public:
|
||||||
|
inline JUTFString(JNIEnv *env, jstring jstr) : JUTFString(env, jstr, nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JUTFString(const ScopedLocalRef<jstring> &jstr) : JUTFString(jstr.env_, jstr.local_ref_,
|
||||||
|
nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
|
inline JUTFString(JNIEnv *env, jstring jstr, const char *default_cstr) : env_(env),
|
||||||
|
jstr_(jstr) {
|
||||||
|
if (env_ && jstr_) cstr_ = env_->GetStringUTFChars(jstr, nullptr);
|
||||||
|
else cstr_ = default_cstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline operator const char *() const { return cstr_; }
|
||||||
|
|
||||||
|
inline operator const std::string() const { return cstr_; }
|
||||||
|
|
||||||
|
inline operator const bool() const { return cstr_ != nullptr; }
|
||||||
|
|
||||||
|
inline auto get() const { return cstr_; }
|
||||||
|
|
||||||
|
inline ~JUTFString() {
|
||||||
|
if (env_ && jstr_) env_->ReleaseStringUTFChars(jstr_, cstr_);
|
||||||
|
}
|
||||||
|
|
||||||
|
JUTFString(JUTFString &&other)
|
||||||
|
: env_(std::move(other.env_)), jstr_(std::move(other.jstr_)),
|
||||||
|
cstr_(std::move(other.cstr_)) {
|
||||||
|
other.cstr_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
JUTFString &
|
||||||
|
operator=(JUTFString &&other) {
|
||||||
|
if (&other != this) {
|
||||||
|
env_ = std::move(other.env_);
|
||||||
|
jstr_ = std::move(other.jstr_);
|
||||||
|
cstr_ = std::move(other.cstr_);
|
||||||
|
other.cstr_ = nullptr;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
JNIEnv *env_;
|
||||||
|
jstring jstr_;
|
||||||
|
const char *cstr_;
|
||||||
|
|
||||||
|
JUTFString(const JUTFString &) = delete;
|
||||||
|
|
||||||
|
JUTFString &operator=(const JUTFString &) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<typename Func, typename ...Args>
|
||||||
|
requires(std::is_function_v<Func>)
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_SafeInvoke(JNIEnv *env, Func JNIEnv::*f, Args &&... args) {
|
||||||
|
struct finally {
|
||||||
|
finally(JNIEnv *env) : env_(env) {}
|
||||||
|
|
||||||
|
~finally() {
|
||||||
|
if (auto exception = ClearException(env_)) {
|
||||||
|
LOGE("%s", JUTFString(env_, exception.get()).get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv *env_;
|
||||||
|
} _(env);
|
||||||
|
|
||||||
|
if constexpr(!std::is_same_v<void, std::invoke_result_t<Func, decltype(UnwrapScope(
|
||||||
|
std::forward<Args>(args)))... >>)
|
||||||
|
return WrapScope(env, (env->*f)(UnwrapScope(std::forward<Args>(args))...));
|
||||||
|
else (env->*f)(UnwrapScope(std::forward<Args>(args))...);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_FindClass(JNIEnv *env, std::string_view name) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::FindClass, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_GetFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, clazz, name, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_ToReflectedMethod(JNIEnv *env, const Class &clazz, jmethodID method, jboolean isStatic) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::ToReflectedMethod, clazz, method, isStatic);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetObjectField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, obj, fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetLongField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetLongField, obj, fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetIntField(JNIEnv *env, const Object &obj, jfieldID fieldId) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetIntField, obj, fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_GetMethodID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, clazz, name, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_CallObjectMethod(JNIEnv *env, const Object &obj, jmethodID method, Args &&... args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, obj, method, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallIntMethod(JNIEnv *env, const Object &obj, jmethodID method, Args &&... args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, obj, method, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallLongMethod(JNIEnv *env, const Object &obj, Args &&... args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, obj, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallVoidMethod(JNIEnv *env, const Object &obj, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, obj, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallBooleanMethod(JNIEnv *env, const Object &obj, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, obj, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_GetStaticFieldID(JNIEnv *env, const Class &clazz, std::string_view name, std::string_view sig) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, clazz, name, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetStaticObjectField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, clazz, fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetStaticIntField(JNIEnv *env, const Class &clazz, jfieldID fieldId) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, clazz, fieldId);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_GetStaticMethodID(JNIEnv *env, const Class &clazz, std::string_view name,
|
||||||
|
std::string_view sig) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, clazz, name, sig);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallStaticVoidMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, clazz, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallStaticObjectMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, clazz, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallStaticIntMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, clazz, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, const Class &clazz, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, clazz,
|
||||||
|
std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrRaw<jarray> Array>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrRaw<jobjectArray> Array>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_GetObjectArrayElement(JNIEnv *env, const Array &array, jsize idx) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::GetObjectArrayElement, array, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class, typename ...Args>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_NewObject(JNIEnv *env, const Class &clazz, Args &&...args) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::NewObject, clazz, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrClass Class>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_RegisterNatives(JNIEnv *env, const Class &clazz, const JNINativeMethod *methods, jint size) {
|
||||||
|
return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, clazz, methods, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<ScopeOrObject Object>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) {
|
||||||
|
return (decltype(UnwrapScope(std::forward<Object>(x)))) env->NewGlobalRef(
|
||||||
|
UnwrapScope(std::forward<Object>(x)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename U, typename T>
|
||||||
|
[[maybe_unused]]
|
||||||
|
inline auto
|
||||||
|
JNI_Cast(ScopedLocalRef<T> &&x)requires(std::is_convertible_v<T, _jobject *>) {
|
||||||
|
return ScopedLocalRef<U>(std::move(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#undef DISALLOW_COPY_AND_ASSIGN
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
28
library/jni/logging.hpp
Normal file
28
library/jni/logging.hpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#ifndef LOG_TAG
|
||||||
|
#define LOG_TAG "LSPlant"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef LOG_DISABLED
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#define LOGI(...)
|
||||||
|
#define LOGW(...)
|
||||||
|
#define LOGE(...)
|
||||||
|
#else
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
|
||||||
|
#define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#endif
|
||||||
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||||
|
#endif
|
566
library/jni/lsplant.cc
Normal file
566
library/jni/lsplant.cc
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
#include "lsplant.hpp"
|
||||||
|
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <android/api-level.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <array>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
#include "utils/jni_helper.hpp"
|
||||||
|
#include "art/runtime/gc/scoped_gc_critical_section.hpp"
|
||||||
|
#include "art/runtime.hpp"
|
||||||
|
#include "art/thread.hpp"
|
||||||
|
#include "art/instrumentation.hpp"
|
||||||
|
#include "art/runtime/jit/jit_code_cache.hpp"
|
||||||
|
#include "art/runtime/art_method.hpp"
|
||||||
|
#include "art/thread_list.hpp"
|
||||||
|
#include "art/runtime/class_linker.hpp"
|
||||||
|
#include "external/dex_builder/dex_builder.h"
|
||||||
|
#include "common.hpp"
|
||||||
|
|
||||||
|
#pragma clang diagnostic push
|
||||||
|
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||||
|
#pragma ide diagnostic ignored "ConstantConditionsOC"
|
||||||
|
#pragma ide diagnostic ignored "UnreachableCode"
|
||||||
|
namespace lsplant {
|
||||||
|
|
||||||
|
using art::ArtMethod;
|
||||||
|
using art::thread_list::ScopedSuspendAll;
|
||||||
|
using art::ClassLinker;
|
||||||
|
using art::mirror::Class;
|
||||||
|
using art::Runtime;
|
||||||
|
using art::Thread;
|
||||||
|
using art::Instrumentation;
|
||||||
|
using art::gc::ScopedGCCriticalSection;
|
||||||
|
using art::jit::JitCodeCache;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T, T... chars>
|
||||||
|
inline consteval auto operator ""_arr() {
|
||||||
|
return std::array<T, sizeof...(chars)>{ chars... };
|
||||||
|
}
|
||||||
|
|
||||||
|
consteval inline auto GetTrampoline() {
|
||||||
|
if constexpr(kArch == Arch::kArm) {
|
||||||
|
return std::make_tuple(
|
||||||
|
"\x00\x00\x9f\xe5\x20\xf0\x90\xe5\x78\x56\x34\x12"_arr,
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
uint8_t{ 4u }, uintptr_t{ 8u });
|
||||||
|
}
|
||||||
|
if constexpr(kArch == Arch::kArm64) {
|
||||||
|
return std::make_tuple(
|
||||||
|
"\x60\x00\x00\x58\x10\x00\x40\xf8\x00\x02\x1f\xd6\x78\x56\x34\x12\x78\x56\x34\x12"_arr,
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
uint16_t{ 5u }, uintptr_t{ 12u });
|
||||||
|
}
|
||||||
|
if constexpr(kArch == Arch::kX86) {
|
||||||
|
return std::make_tuple(
|
||||||
|
"\xb8\x78\x56\x34\x12\xff\x70\x20\xc3"_arr,
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
uint8_t{ 7u }, uintptr_t{ 1u });
|
||||||
|
}
|
||||||
|
if constexpr(kArch == Arch::kX8664) {
|
||||||
|
return std::make_tuple(
|
||||||
|
"\x48\xbf\x78\x56\x34\x12\x78\x56\x34\x12\xff\x77\x20\xc3"_arr,
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
uint8_t{ 12u }, uintptr_t{ 2u });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto[trampoline, entry_point_offset, art_method_offset] = GetTrampoline();
|
||||||
|
|
||||||
|
jmethodID method_get_name = nullptr;
|
||||||
|
jmethodID method_get_declaring_class = nullptr;
|
||||||
|
jmethodID class_get_canonical_name = nullptr;
|
||||||
|
jmethodID class_get_class_loader = nullptr;
|
||||||
|
jmethodID class_is_proxy = nullptr;
|
||||||
|
jclass in_memory_class_loader = nullptr;
|
||||||
|
jmethodID in_memory_class_loader_init = nullptr;
|
||||||
|
jmethodID load_class = nullptr;
|
||||||
|
|
||||||
|
bool InitJNI(JNIEnv *env) {
|
||||||
|
auto executable = JNI_FindClass(env, "java/lang/reflect/Executable");
|
||||||
|
if (!executable) {
|
||||||
|
LOGE("Failed to found Executable");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_get_name = JNI_GetMethodID(env, executable, "getName",
|
||||||
|
"()Ljava/lang/String;"); !method_get_name) {
|
||||||
|
LOGE("Failed to find getName method");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (method_get_declaring_class = JNI_GetMethodID(env, executable, "getDeclaringClass",
|
||||||
|
"()Ljava/lang/Class;"); !method_get_declaring_class) {
|
||||||
|
LOGE("Failed to find getName method");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||||
|
if (!clazz) {
|
||||||
|
LOGE("Failed to find Class");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_get_class_loader = JNI_GetMethodID(env, clazz, "getClassLoader",
|
||||||
|
"()Ljava/lang/ClassLoader;");
|
||||||
|
!class_get_class_loader) {
|
||||||
|
LOGE("Failed to find getClassLoader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_get_canonical_name = JNI_GetMethodID(env, clazz, "getCanonicalName",
|
||||||
|
"()Ljava/lang/String;");
|
||||||
|
!class_get_canonical_name) {
|
||||||
|
LOGE("Failed to find getCanonicalName");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_is_proxy = JNI_GetMethodID(env, clazz, "isProxy", "()Z"); !class_is_proxy) {
|
||||||
|
LOGE("Failed to find isProxy");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
in_memory_class_loader = JNI_NewGlobalRef(env, JNI_FindClass(env,
|
||||||
|
"dalvik/system/InMemoryDexClassLoader"));
|
||||||
|
in_memory_class_loader_init = JNI_GetMethodID(env, in_memory_class_loader, "<init>",
|
||||||
|
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
|
||||||
|
|
||||||
|
load_class = JNI_GetMethodID(env, in_memory_class_loader, "loadClass",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||||
|
if (!load_class) {
|
||||||
|
load_class = JNI_GetMethodID(env, in_memory_class_loader, "findClass",
|
||||||
|
"(Ljava/lang/String;)Ljava/lang/Class;");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void UpdateTrampoline(decltype(entry_point_offset) offset) {
|
||||||
|
*reinterpret_cast<decltype(entry_point_offset) *>(trampoline.data() +
|
||||||
|
entry_point_offset) = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InitNative(JNIEnv *env, const HookHandler &handler) {
|
||||||
|
if (!handler.inline_hooker || !handler.inline_unhooker || !handler.art_symbol_resolver)
|
||||||
|
return false;
|
||||||
|
if (!Runtime::Init(handler)) {
|
||||||
|
LOGE("Failed to init runtime");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ArtMethod::Init(env, handler)) {
|
||||||
|
LOGE("Failed to init art method");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
UpdateTrampoline(ArtMethod::GetEntryPointOffset());
|
||||||
|
if (!Thread::Init(handler)) {
|
||||||
|
LOGE("Failed to init thread");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ClassLinker::Init(handler)) {
|
||||||
|
LOGE("Failed to init class linker");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Class::Init(env, handler)) {
|
||||||
|
LOGE("failed to init mirror class");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Instrumentation::Init(handler)) {
|
||||||
|
LOGE("failed to init intrumentation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ScopedSuspendAll::Init(handler)) {
|
||||||
|
LOGE("failed to init scoped suspend all");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!ScopedGCCriticalSection::Init(handler)) {
|
||||||
|
LOGE("failed to init scoped gc critical section");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!JitCodeCache::Init(handler)) {
|
||||||
|
LOGE("failed to init jit code cache");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::tuple<jclass, jfieldID, jmethodID, jmethodID>
|
||||||
|
BuildDex(JNIEnv *env, jobject class_loader,
|
||||||
|
std::string_view shorty, bool is_static, std::string_view method_name,
|
||||||
|
std::string_view hooker_class, std::string_view callback_name) {
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
using namespace startop::dex;
|
||||||
|
|
||||||
|
if (shorty.empty()) {
|
||||||
|
LOGE("Invalid shorty");
|
||||||
|
return { nullptr, nullptr, nullptr, nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
DexBuilder dex_file;
|
||||||
|
|
||||||
|
auto parameter_types = std::vector<TypeDescriptor>();
|
||||||
|
parameter_types.reserve(shorty.size() - 1);
|
||||||
|
std::string storage;
|
||||||
|
auto return_type =
|
||||||
|
shorty[0] == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
|
||||||
|
shorty[0]);
|
||||||
|
if (!is_static) parameter_types.push_back(TypeDescriptor::Object); // this object
|
||||||
|
for (const char ¶m: shorty.substr(1)) {
|
||||||
|
parameter_types.push_back(
|
||||||
|
param == 'L' ? TypeDescriptor::Object : TypeDescriptor::FromDescriptor(
|
||||||
|
static_cast<char>(param)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(yujincheng08): customize it
|
||||||
|
ClassBuilder cbuilder{ dex_file.MakeClass("LspHooker_") };
|
||||||
|
// TODO(yujincheng08): customize it
|
||||||
|
cbuilder.set_source_file("LSP");
|
||||||
|
|
||||||
|
auto hooker_type = TypeDescriptor::FromClassname(hooker_class.data());
|
||||||
|
|
||||||
|
// TODO(yujincheng08): customize it
|
||||||
|
auto *hooker_field = cbuilder.CreateField("hooker", hooker_type)
|
||||||
|
.access_flags(dex::kAccStatic)
|
||||||
|
.Encode();
|
||||||
|
|
||||||
|
auto hook_builder{ cbuilder.CreateMethod(
|
||||||
|
method_name.data(), Prototype{ return_type, parameter_types }) };
|
||||||
|
// allocate tmp first because of wide
|
||||||
|
auto tmp{ hook_builder.AllocRegister() };
|
||||||
|
hook_builder.BuildConst(tmp, static_cast<int>(parameter_types.size()));
|
||||||
|
auto hook_params_array{ hook_builder.AllocRegister() };
|
||||||
|
hook_builder.BuildNewArray(hook_params_array, TypeDescriptor::Object, tmp);
|
||||||
|
for (size_t i = 0U, j = 0U; i < parameter_types.size(); ++i, ++j) {
|
||||||
|
hook_builder.BuildBoxIfPrimitive(Value::Parameter(j), parameter_types[i],
|
||||||
|
Value::Parameter(j));
|
||||||
|
hook_builder.BuildConst(tmp, static_cast<int>(i));
|
||||||
|
hook_builder.BuildAput(Instruction::Op::kAputObject, hook_params_array,
|
||||||
|
Value::Parameter(j), tmp);
|
||||||
|
if (parameter_types[i].is_wide()) ++j;
|
||||||
|
}
|
||||||
|
auto handle_hook_method{ dex_file.GetOrDeclareMethod(
|
||||||
|
hooker_type, callback_name.data(),
|
||||||
|
Prototype{ TypeDescriptor::Object, TypeDescriptor::Object.ToArray() }) };
|
||||||
|
hook_builder.AddInstruction(
|
||||||
|
Instruction::GetStaticObjectField(hooker_field->decl->orig_index, tmp));
|
||||||
|
hook_builder.AddInstruction(Instruction::InvokeVirtualObject(
|
||||||
|
handle_hook_method.id, tmp, tmp, hook_params_array));
|
||||||
|
if (return_type == TypeDescriptor::Void) {
|
||||||
|
hook_builder.BuildReturn();
|
||||||
|
} else if (return_type.is_primitive()) {
|
||||||
|
auto box_type{ return_type.ToBoxType() };
|
||||||
|
const ir::Type *type_def = dex_file.GetOrAddType(box_type);
|
||||||
|
hook_builder.AddInstruction(
|
||||||
|
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
|
||||||
|
hook_builder.BuildUnBoxIfPrimitive(tmp, box_type, tmp);
|
||||||
|
hook_builder.BuildReturn(tmp, false, return_type.is_wide());
|
||||||
|
} else {
|
||||||
|
const ir::Type *type_def = dex_file.GetOrAddType(return_type);
|
||||||
|
hook_builder.AddInstruction(
|
||||||
|
Instruction::Cast(tmp, Value::Type(type_def->orig_index)));
|
||||||
|
hook_builder.BuildReturn(tmp, true);
|
||||||
|
}
|
||||||
|
auto *hook_method = hook_builder.Encode();
|
||||||
|
|
||||||
|
auto backup_builder{
|
||||||
|
cbuilder.CreateMethod("backup", Prototype{ return_type, parameter_types }) };
|
||||||
|
if (return_type == TypeDescriptor::Void) {
|
||||||
|
backup_builder.BuildReturn();
|
||||||
|
} else if (return_type.is_wide()) {
|
||||||
|
LiveRegister zero = backup_builder.AllocRegister();
|
||||||
|
LiveRegister zero_wide = backup_builder.AllocRegister();
|
||||||
|
backup_builder.BuildConstWide(zero, 0);
|
||||||
|
backup_builder.BuildReturn(zero, /*is_object=*/false, true);
|
||||||
|
} else {
|
||||||
|
LiveRegister zero = backup_builder.AllocRegister();
|
||||||
|
LiveRegister zero_wide = backup_builder.AllocRegister();
|
||||||
|
backup_builder.BuildConst(zero, 0);
|
||||||
|
backup_builder.BuildReturn(zero, /*is_object=*/!return_type.is_primitive(), false);
|
||||||
|
}
|
||||||
|
auto *backup_method = backup_builder.Encode();
|
||||||
|
|
||||||
|
slicer::MemView image{ dex_file.CreateImage() };
|
||||||
|
|
||||||
|
auto *dex_buffer = env->NewDirectByteBuffer(const_cast<void *>(image.ptr()), image.size());
|
||||||
|
auto my_cl = JNI_NewObject(env, in_memory_class_loader, in_memory_class_loader_init,
|
||||||
|
dex_buffer, class_loader);
|
||||||
|
env->DeleteLocalRef(dex_buffer);
|
||||||
|
|
||||||
|
if (my_cl) {
|
||||||
|
auto target_class = JNI_Cast<jclass>(JNI_CallObjectMethod(env, my_cl, load_class,
|
||||||
|
JNI_NewStringUTF(env,
|
||||||
|
"LspHooker_")));
|
||||||
|
return {
|
||||||
|
target_class.release(),
|
||||||
|
JNI_GetStaticFieldID(env, target_class, hooker_field->decl->name->c_str(),
|
||||||
|
hooker_field->decl->type->Decl().data()),
|
||||||
|
JNI_GetStaticMethodID(env, target_class, hook_method->decl->name->c_str(),
|
||||||
|
hook_method->decl->prototype->Signature().data()),
|
||||||
|
JNI_GetStaticMethodID(env, target_class, backup_method->decl->name->c_str(),
|
||||||
|
backup_method->decl->prototype->Signature().data()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { nullptr, nullptr, nullptr, nullptr };
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(std::endian::native == std::endian::little, "Unsupported architecture");
|
||||||
|
|
||||||
|
union Trampoline {
|
||||||
|
public:
|
||||||
|
uintptr_t address;
|
||||||
|
unsigned count: 12;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Trampoline) == sizeof(uintptr_t), "Unsupported architecture");
|
||||||
|
static_assert(std::atomic_uintptr_t::is_always_lock_free, "Unsupported architecture");
|
||||||
|
|
||||||
|
std::atomic_uintptr_t trampoline_pool{ 0 };
|
||||||
|
std::atomic_flag trampoline_lock{ false };
|
||||||
|
constexpr size_t kTrampolineSize = RoundUpTo(sizeof(trampoline), kPointerSize);
|
||||||
|
constexpr size_t kPageSize = 4096; // assume
|
||||||
|
constexpr size_t kTrampolineNumPerPage = kPageSize / kTrampolineSize;
|
||||||
|
constexpr uintptr_t kAddressMask = 0xFFFU;
|
||||||
|
|
||||||
|
void *GenerateTrampolineFor(art::ArtMethod *hook) {
|
||||||
|
unsigned count;
|
||||||
|
uintptr_t address;
|
||||||
|
while (true) {
|
||||||
|
auto tl = Trampoline{ .address = trampoline_pool.fetch_add(1, std::memory_order_release) };
|
||||||
|
count = tl.count;
|
||||||
|
address = tl.address & ~kAddressMask;
|
||||||
|
if (address == 0 || count >= kTrampolineNumPerPage) {
|
||||||
|
if (trampoline_lock.test_and_set(std::memory_order_acq_rel)) {
|
||||||
|
trampoline_lock.wait(true, std::memory_order_acquire);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
address = reinterpret_cast<uintptr_t>(mmap(nullptr, kPageSize,
|
||||||
|
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||||
|
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0));
|
||||||
|
if (address == reinterpret_cast<uintptr_t>(MAP_FAILED)) {
|
||||||
|
PLOGE("mmap trampoline");
|
||||||
|
trampoline_lock.clear(std::memory_order_release);
|
||||||
|
trampoline_lock.notify_all();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
count = 0;
|
||||||
|
tl.address = address;
|
||||||
|
tl.count = count + 1;
|
||||||
|
trampoline_pool.store(tl.address, std::memory_order_release);
|
||||||
|
trampoline_lock.clear(std::memory_order_release);
|
||||||
|
trampoline_lock.notify_all();
|
||||||
|
|
||||||
|
}
|
||||||
|
LOGV("trampoline: count = %u, address = %zx, target = %zx", count, address,
|
||||||
|
address + count * kTrampolineSize);
|
||||||
|
address = address + count * kTrampolineSize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto *address_ptr = reinterpret_cast<char *>(address);
|
||||||
|
std::memcpy(address_ptr, trampoline.data(), trampoline.size());
|
||||||
|
|
||||||
|
*reinterpret_cast<art::ArtMethod **>(address_ptr + art_method_offset) = hook;
|
||||||
|
|
||||||
|
__builtin___clear_cache(address_ptr, reinterpret_cast<char *>(address + trampoline.size()));
|
||||||
|
|
||||||
|
return address_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoHook(ArtMethod *target, ArtMethod *hook, ArtMethod *backup) {
|
||||||
|
ScopedGCCriticalSection section(art::Thread::Current(),
|
||||||
|
art::gc::kGcCauseDebugger,
|
||||||
|
art::gc::kCollectorTypeDebugger);
|
||||||
|
ScopedSuspendAll suspend("Yahfa Hook", false);
|
||||||
|
LOGD("target = %p, hook = %p, backup = %p", target, hook, backup);
|
||||||
|
|
||||||
|
if (auto *trampoline = GenerateTrampolineFor(hook); !trampoline) {
|
||||||
|
LOGE("Failed to generate trampoline");
|
||||||
|
return false;
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
} else {
|
||||||
|
LOGV("Generated trampoline %p", trampoline);
|
||||||
|
|
||||||
|
target->SetNonCompilable();
|
||||||
|
hook->SetNonCompilable();
|
||||||
|
|
||||||
|
// copy after setNonCompilable
|
||||||
|
backup->CopyFrom(target);
|
||||||
|
|
||||||
|
target->SetNonIntrinsic();
|
||||||
|
|
||||||
|
target->SetEntryPoint(trampoline);
|
||||||
|
|
||||||
|
backup->SetPrivate();
|
||||||
|
|
||||||
|
LOGD("done hook");
|
||||||
|
LOGV("target(%p:0x%x) -> %p; backup(%p:0x%x) -> %p; hook(%p:0x%x) -> %p",
|
||||||
|
target, target->GetAccessFlags(), target->GetEntryPoint(),
|
||||||
|
backup, backup->GetAccessFlags(), backup->GetEntryPoint(),
|
||||||
|
hook, hook->GetAccessFlags(), hook->GetEntryPoint());
|
||||||
|
|
||||||
|
RecordHooked(target, backup);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DoUnHook(ArtMethod *target, ArtMethod *backup) {
|
||||||
|
ScopedGCCriticalSection section(art::Thread::Current(),
|
||||||
|
art::gc::kGcCauseDebugger,
|
||||||
|
art::gc::kCollectorTypeDebugger);
|
||||||
|
ScopedSuspendAll suspend("Yahfa Hook", false);
|
||||||
|
auto access_flags = target->GetAccessFlags();
|
||||||
|
target->CopyFrom(backup);
|
||||||
|
target->SetAccessFlags(access_flags);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void OnPending(art::ArtMethod *target, art::ArtMethod *hook, art::ArtMethod *backup) {
|
||||||
|
LOGD("On pending hook");
|
||||||
|
if (!DoHook(target, hook, backup)) {
|
||||||
|
LOGE("Pending hook failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
bool Init(JNIEnv *env, const InitInfo &info) {
|
||||||
|
bool static kInit = InitJNI(env) && InitNative(env, info);
|
||||||
|
return kInit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: sync? does not record hook immediately
|
||||||
|
[[maybe_unused]]
|
||||||
|
jmethodID
|
||||||
|
Hook(JNIEnv *env, jmethodID target_method, jobject hooker_object, jmethodID callback_method) {
|
||||||
|
auto reflected_target = JNI_ToReflectedMethod(env, jclass{ nullptr }, target_method, false);
|
||||||
|
auto reflected_callback = JNI_ToReflectedMethod(env, jclass{ nullptr }, callback_method, false);
|
||||||
|
jmethodID hook_method = nullptr;
|
||||||
|
jmethodID backup_method = nullptr;
|
||||||
|
jfieldID hooker_field = nullptr;
|
||||||
|
|
||||||
|
auto target_class = JNI_Cast<jclass>(
|
||||||
|
JNI_CallObjectMethod(env, reflected_target, method_get_declaring_class));
|
||||||
|
bool is_proxy = JNI_CallBooleanMethod(env, target_class, class_is_proxy);
|
||||||
|
auto *target = ArtMethod::FromReflectedMethod(env, reflected_target);
|
||||||
|
bool is_static = target->IsStatic();
|
||||||
|
|
||||||
|
if (IsHooked(target) || IsPending(target)) {
|
||||||
|
LOGW("Skip duplicate hook");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedLocalRef<jclass> built_class{ env };
|
||||||
|
{
|
||||||
|
auto callback_name = JNI_Cast<jstring>(
|
||||||
|
JNI_CallObjectMethod(env, reflected_callback, method_get_name));
|
||||||
|
JUTFString method_name(callback_name);
|
||||||
|
auto callback_class = JNI_Cast<jclass>(JNI_CallObjectMethod(env, reflected_callback,
|
||||||
|
method_get_declaring_class));
|
||||||
|
auto callback_class_loader = JNI_CallObjectMethod(env, callback_class,
|
||||||
|
class_get_class_loader);
|
||||||
|
auto callback_class_name = JNI_Cast<jstring>(JNI_CallObjectMethod(env, callback_class,
|
||||||
|
class_get_canonical_name));
|
||||||
|
JUTFString class_name(callback_class_name);
|
||||||
|
if (env->IsInstanceOf(hooker_object, callback_class)) {
|
||||||
|
LOGE("callback_method is not a method of hooker_object");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::tie(built_class, hooker_field, hook_method, backup_method) =
|
||||||
|
WrapScope(env, BuildDex(env, callback_class_loader,
|
||||||
|
ArtMethod::GetMethodShorty(env, target_method),
|
||||||
|
is_static,
|
||||||
|
method_name.get(),
|
||||||
|
class_name.get(),
|
||||||
|
method_name.get()));
|
||||||
|
if (!built_class || !hooker_field || !hook_method || !backup_method) {
|
||||||
|
LOGE("Failed to generate hooker");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto reflected_hook = JNI_ToReflectedMethod(env, jclass{ nullptr }, hook_method, false);
|
||||||
|
auto reflected_backup = JNI_ToReflectedMethod(env, jclass{ nullptr }, backup_method, false);
|
||||||
|
|
||||||
|
auto *hook = ArtMethod::FromReflectedMethod(env, reflected_hook);
|
||||||
|
auto *backup = ArtMethod::FromReflectedMethod(env, reflected_backup);
|
||||||
|
|
||||||
|
env->SetStaticObjectField(built_class.get(), hooker_field, hooker_object);
|
||||||
|
|
||||||
|
if (is_static && !Class::IsInitialized(env, target_class.get())) {
|
||||||
|
auto *miror_class = Class::FromReflectedClass(env, target_class);
|
||||||
|
if (!miror_class) {
|
||||||
|
LOGE("Failed to decode target class");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto class_def = miror_class->GetClassDef();
|
||||||
|
if (!class_def) {
|
||||||
|
LOGE("Failed to get target class def");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
RecordPending(class_def, target, hook, backup);
|
||||||
|
return backup_method;
|
||||||
|
} else if (DoHook(target, hook, backup)) {
|
||||||
|
JNI_NewGlobalRef(env, reflected_hook);
|
||||||
|
JNI_NewGlobalRef(env, reflected_backup);
|
||||||
|
if (!is_proxy) [[likely]] RecordJitMovement(target, backup);
|
||||||
|
return backup_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
bool UnHook(JNIEnv *env, jmethodID target_method) {
|
||||||
|
auto reflected_target = JNI_ToReflectedMethod(env, jclass{ nullptr }, target_method, false);
|
||||||
|
auto *target = ArtMethod::FromReflectedMethod(env, reflected_target);
|
||||||
|
ArtMethod *backup = nullptr;
|
||||||
|
{
|
||||||
|
std::unique_lock lk(pending_methods_lock_);
|
||||||
|
if (auto it = pending_methods_.find(target); it != pending_methods_.end()) {
|
||||||
|
pending_methods_.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::unique_lock lk(hooked_methods_lock_);
|
||||||
|
if (auto it = hooked_methods_.find(target);it != hooked_methods_.end()) {
|
||||||
|
backup = it->second;
|
||||||
|
hooked_methods_.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (backup == nullptr) {
|
||||||
|
LOGE("Unable to unhook a method that is not hooked");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return DoUnHook(target, backup);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
bool IsHooked(JNIEnv *env, jmethodID method) {
|
||||||
|
auto reflected = JNI_ToReflectedMethod(env, jclass{ nullptr }, method, false);
|
||||||
|
auto *art_method = ArtMethod::FromReflectedMethod(env, reflected);
|
||||||
|
|
||||||
|
if (std::shared_lock lk(hooked_methods_lock_); hooked_methods_.contains(art_method)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (std::shared_lock lk(pending_methods_lock_); pending_methods_.contains(art_method)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
bool Deoptimize(JNIEnv *env, jmethodID method) {
|
||||||
|
auto reflected = JNI_ToReflectedMethod(env, jclass{ nullptr }, method, false);
|
||||||
|
auto *art_method = ArtMethod::FromReflectedMethod(env, reflected);
|
||||||
|
return ClassLinker::SetEntryPointsToInterpreter(art_method);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[maybe_unused]]
|
||||||
|
void *GetNativeFunction(JNIEnv *env, jmethodID method) {
|
||||||
|
auto reflected = JNI_ToReflectedMethod(env, jclass{ nullptr }, method, false);
|
||||||
|
auto *art_method = ArtMethod::FromReflectedMethod(env, reflected);
|
||||||
|
return art_method->GetData();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace lsplant
|
||||||
|
|
||||||
|
#pragma clang diagnostic pop
|
2
library/src/main/AndroidManifest.xml
Normal file
2
library/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="org.lsposed.lsplant" />
|
12
settings.gradle.kts
Normal file
12
settings.gradle.kts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
dependencyResolutionManagement {
|
||||||
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "LSPlant"
|
||||||
|
include(
|
||||||
|
":library",
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user