From f958e57af64dc5da792b7196cbd4320d76b0366d Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Fri, 3 Nov 2023 18:12:50 +0800 Subject: [PATCH 1/6] Get rid of binder-rs & Refine watchdog --- module/src/service.sh | 7 ++ zygiskd/Cargo.toml | 1 - zygiskd/src/constants.rs | 3 + zygiskd/src/utils.rs | 27 ++++++- zygiskd/src/watchdog.rs | 148 ++++++++++++++++++++++++++------------- 5 files changed, 134 insertions(+), 52 deletions(-) diff --git a/module/src/service.sh b/module/src/service.sh index 012a291..7f18a55 100644 --- a/module/src/service.sh +++ b/module/src/service.sh @@ -9,6 +9,13 @@ fi cd "$MODDIR" +# temporary fix AVD 11 magisk +if [ -f /dev/zygisk_service ];then + log -p i -t "zygisk-sh" "service called twice"; + exit; +fi +touch /dev/zygisk_service + if [ "$(which magisk)" ]; then for file in ../*; do if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index 8d7877d..8c6a6ed 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -23,7 +23,6 @@ proc-maps = "0.3" rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] } tokio = { version = "1.28", features = ["full"] } -binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" } fuser = { git = "https://github.com/Dr-TSNG/fuser", default-features = false } ptrace-do = { git = "https://github.com/5ec1cff/ptrace-do" } diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index f367e90..06242c0 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -40,6 +40,9 @@ pub const STATUS_ROOT_IMPL_TOO_OLD: &str = "❌ Root implementation version too pub const STATUS_ROOT_IMPL_ABNORMAL: &str = "❌ Abnormal root implementation version"; pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations installed"; +pub const MAX_RESTART_COUNT: i32 = 5; +pub const ZYGOTE_SERVICE_PROP: &str = "init.svc.zygote"; + #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum DaemonSocketAction { diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs index 481d36f..92280fc 100644 --- a/zygiskd/src/utils.rs +++ b/zygiskd/src/utils.rs @@ -1,6 +1,6 @@ use anyhow::Result; use std::{fs, io::{Read, Write}, os::unix::net::UnixStream}; -use std::ffi::{c_char, CStr, CString}; +use std::ffi::{c_char, c_void, CStr, CString}; use std::os::fd::AsFd; use std::os::unix::net::UnixListener; use std::process::Command; @@ -96,6 +96,28 @@ pub fn set_property(name: &str, value: &str) -> Result<()> { Ok(()) } +pub fn wait_property(name: &str, serial: u32) -> Result { + let name = CString::new(name)?; + let info = unsafe { + __system_property_find(name.as_ptr()) + }; + let mut serial = serial; + unsafe { + __system_property_wait(info, serial, &mut serial, std::ptr::null()); + } + Ok(serial) +} + +pub fn get_property_serial(name: &str) -> Result { + let name = CString::new(name)?; + let info = unsafe { + __system_property_find(name.as_ptr()) + }; + Ok(unsafe { + __system_property_serial(info) + }) +} + pub fn switch_mount_namespace(pid: i32) -> Result<()> { let cwd = std::env::current_dir()?; let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?; @@ -177,4 +199,7 @@ extern "C" { fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32; fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32; fn __system_property_set(name: *const c_char, value: *const c_char) -> u32; + fn __system_property_find(name: *const c_char) -> *const c_void; + fn __system_property_wait(info: *const c_void, old_serial: u32, new_serial: *mut u32, timeout: *const libc::timespec) -> bool; + fn __system_property_serial(info: *const c_void) -> u32; } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index cd2f38e..dff6532 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -1,18 +1,17 @@ use crate::{constants, root_impl, utils}; use anyhow::{bail, Result}; use std::fs; -use std::future::Future; use std::io::{BufRead, BufReader, Write}; -use std::pin::Pin; use std::time::Duration; -use binder::IBinder; -use futures::stream::FuturesUnordered; -use futures::StreamExt; +use futures::{FutureExt, join, pin_mut}; +use futures::future::{Fuse, FusedFuture}; use log::{debug, error, info}; use rustix::mount::mount_bind; -use rustix::process::{getgid, getuid, kill_process, Pid, Signal}; +use rustix::process::{getgid, getuid}; use tokio::process::{Child, Command}; -use crate::utils::LateInit; +use tokio::{select, task}; +use tokio::time::Instant; +use crate::utils::{get_property, get_property_serial, LateInit, wait_property}; static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new(); @@ -108,62 +107,111 @@ fn check_and_set_hint() -> Result { } async fn spawn_daemon() -> Result<()> { - let mut lives = 5; + let mut lives = constants::MAX_RESTART_COUNT; + let mut last_restart_time = Instant::now(); + let (sender, mut receiver) = tokio::sync::mpsc::channel(1); + task::spawn_blocking(move || { + let mut serial = 0u32; + let mut last_state = "running".to_string(); + info!("zygote property monitor started"); + loop { + let old_serial = serial; + serial = wait_property(constants::ZYGOTE_SERVICE_PROP, serial).expect("failed to wait on property"); + let new_state = get_property(constants::ZYGOTE_SERVICE_PROP).expect("failed to get property"); + if last_state == "running" && new_state != "running" { + info!("new zygote state: {} serial {} -> {}", new_state, old_serial, serial); + sender.blocking_send(old_serial).expect("failed to notify"); + } + last_state = new_state + } + }); + let mut restart_serial = 0u32; loop { - let mut futures = FuturesUnordered::>>>>::new(); - let mut child_ids = vec![]; let daemon32 = Command::new(constants::PATH_CP_BIN32).arg("daemon").spawn(); let daemon64 = Command::new(constants::PATH_CP_BIN64).arg("daemon").spawn(); - async fn spawn_daemon(mut daemon: Child) -> Result<()> { - let result = daemon.wait().await?; - log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result); - Ok(()) - } - if let Ok(it) = daemon32 { - child_ids.push(it.id().unwrap()); - futures.push(Box::pin(spawn_daemon(it))); - } - if let Ok(it) = daemon64 { - child_ids.push(it.id().unwrap()); - futures.push(Box::pin(spawn_daemon(it))); - } - - async fn binder_listener() -> Result<()> { - let mut binder = loop { - match binder::get_service("activity") { - Some(binder) => break binder, - None => { - log::trace!("System server not ready, wait for 1s..."); - tokio::time::sleep(Duration::from_secs(1)).await; - } - }; - }; - - info!("System server ready"); - - loop { - if binder.ping_binder().is_err() { break; } - tokio::time::sleep(Duration::from_secs(1)).await; + async fn spawn_daemon(mut daemon: Child, mut killer: tokio::sync::watch::Receiver<()>) { + let id = daemon.id().unwrap(); + select! { + result = daemon.wait() => { + log::error!("Daemon process {} died: {}", id, result + .expect("failed to get daemon process exit reason") + ); + }, + _ = killer.changed() => { + log::warn!("Kill daemon process {}", id); + daemon.kill().await.expect("failed to kill"); + } } - bail!("System server died"); } - futures.push(Box::pin(binder_listener())); + let (tx, rx) = tokio::sync::watch::channel(()); + let wait32 = match daemon32 { + Ok(child) => { + spawn_daemon(child, rx.clone()).fuse() + } + Err(..) => { + Fuse::terminated() + } + }; + let wait64 = match daemon64 { + Ok(child) => { + spawn_daemon(child, rx.clone()).fuse() + } + Err(..) => { + Fuse::terminated() + } + }; - if let Err(e) = futures.next().await.unwrap() { - error!("{}", e); + pin_mut!(wait32, wait64); + + let mut restart_zygote = true; + + select! { + _ = &mut wait32 => {}, + _ = &mut wait64 => {}, + _ = async { + // we expect a serial different from last restart + loop { + if restart_serial != receiver.recv().await.expect("no serial received") { + break; + } + } + } => { + restart_zygote = false; + } } - for child in child_ids { - debug!("Killing child process {}", child); - let _ = kill_process(Pid::from_raw(child as i32).unwrap(), Signal::Kill); + // kill all remain daemons + tx.send(())?; + + // wait for all daemons + loop { + futures::select! { + _ = wait32 => {}, + _ = wait64 => {}, + complete => { break; } + } } - lives -= 1; + let current = Instant::now(); + if current - last_restart_time >= Duration::new(30, 0) { + lives = constants::MAX_RESTART_COUNT; + log::warn!("reset live count to {}", lives); + } else { + lives -= 1; + log::warn!("remain live count {}", lives); + } if lives == 0 { bail!("Too many crashes, abort"); } + last_restart_time = current; - error!("Restarting zygote..."); - utils::set_property(constants::PROP_CTL_RESTART, "zygote")?; + error!("Daemons are going to restart ..."); + + if restart_zygote { + error!("Restarting zygote..."); + restart_serial = get_property_serial(constants::ZYGOTE_SERVICE_PROP)?; + debug!("serial before restart {}", restart_serial); + utils::set_property(constants::PROP_CTL_RESTART, "zygote")?; + } } } From 5e2a211ca461bcad8be0a0418ae931a7d8a0386c Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Fri, 3 Nov 2023 18:19:04 +0800 Subject: [PATCH 2/6] min api is 26 --- build.gradle.kts | 2 +- module/src/customize.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 28fbafa..e98b9c1 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,7 +28,7 @@ val minKsudVersion by extra(10942) val maxKsuVersion by extra(20000) val minMagiskVersion by extra(26300) -val androidMinSdkVersion by extra(29) +val androidMinSdkVersion by extra(26) val androidTargetSdkVersion by extra(34) val androidCompileSdkVersion by extra(34) val androidBuildToolsVersion by extra("34.0.0") diff --git a/module/src/customize.sh b/module/src/customize.sh index fb341ed..8e65f56 100644 --- a/module/src/customize.sh +++ b/module/src/customize.sh @@ -53,9 +53,9 @@ VERSION=$(grep_prop version "${TMPDIR}/module.prop") ui_print "- Installing Zygisk Next $VERSION" # check android -if [ "$API" -lt 29 ]; then +if [ "$API" -lt 26 ]; then ui_print "! Unsupported sdk: $API" - abort "! Minimal supported sdk is 29 (Android 10)" + abort "! Minimal supported sdk is 26 (Android 8.0)" else ui_print "- Device sdk: $API" fi From 1c79932cae9c04598a8a329961023b0a761ca8e4 Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Fri, 3 Nov 2023 18:20:52 +0800 Subject: [PATCH 3/6] comment out useless code --- module/src/service.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/module/src/service.sh b/module/src/service.sh index 7f18a55..5623d52 100644 --- a/module/src/service.sh +++ b/module/src/service.sh @@ -10,11 +10,11 @@ fi cd "$MODDIR" # temporary fix AVD 11 magisk -if [ -f /dev/zygisk_service ];then - log -p i -t "zygisk-sh" "service called twice"; - exit; -fi -touch /dev/zygisk_service +# if [ -f /dev/zygisk_service ];then +# log -p i -t "zygisk-sh" "service called twice"; +# exit; +# fi +# touch /dev/zygisk_service if [ "$(which magisk)" ]; then for file in ../*; do From ac53ef11a353176ed902df71d608e437f8895bdf Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Sat, 4 Nov 2023 16:39:10 +0800 Subject: [PATCH 4/6] refactor jni hooks --- .gitmodules | 3 - loader/src/Android.mk | 2 +- loader/src/external/Android.mk | 7 - loader/src/external/parallel-hashmap | 1 - loader/src/injector/art_method.hpp | 82 ++ loader/src/injector/gen_jni_hooks.py | 270 +++++++ loader/src/injector/hook.cpp | 218 +++-- loader/src/injector/jni_helper.hpp | 1116 ++++++++++++++++++++++++++ loader/src/injector/jni_hooks.hpp | 97 +-- loader/src/injector/memory.cpp | 31 - loader/src/injector/memory.hpp | 44 - 11 files changed, 1610 insertions(+), 261 deletions(-) delete mode 160000 loader/src/external/parallel-hashmap create mode 100644 loader/src/injector/art_method.hpp create mode 100644 loader/src/injector/gen_jni_hooks.py create mode 100644 loader/src/injector/jni_helper.hpp delete mode 100644 loader/src/injector/memory.cpp delete mode 100644 loader/src/injector/memory.hpp diff --git a/.gitmodules b/.gitmodules index c226d7f..340c732 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "loader/src/external/lsplt"] path = loader/src/external/lsplt url = https://github.com/LSPosed/lsplt -[submodule "loader/src/external/parallel-hashmap"] - path = loader/src/external/parallel-hashmap - url = https://github.com/greg7mdp/parallel-hashmap diff --git a/loader/src/Android.mk b/loader/src/Android.mk index 2846d1f..2ceb3f6 100644 --- a/loader/src/Android.mk +++ b/loader/src/Android.mk @@ -17,7 +17,7 @@ LOCAL_MODULE := zygisk LOCAL_C_INCLUDES := $(LOCAL_PATH)/include FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector)) LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) -LOCAL_STATIC_LIBRARIES := cxx common liblsplt libphmap +LOCAL_STATIC_LIBRARIES := cxx common liblsplt LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY) diff --git a/loader/src/external/Android.mk b/loader/src/external/Android.mk index 43e76d1..f69d5f8 100644 --- a/loader/src/external/Android.mk +++ b/loader/src/external/Android.mk @@ -12,10 +12,3 @@ LOCAL_SRC_FILES := \ lsplt/lsplt/src/main/jni/elf_util.cc \ lsplt/lsplt/src/main/jni/lsplt.cc include $(BUILD_STATIC_LIBRARY) - -# Header only library -include $(CLEAR_VARS) -LOCAL_MODULE:= libphmap -LOCAL_CFLAGS := -Wno-unused-value -LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap -include $(BUILD_STATIC_LIBRARY) diff --git a/loader/src/external/parallel-hashmap b/loader/src/external/parallel-hashmap deleted file mode 160000 index 55725db..0000000 --- a/loader/src/external/parallel-hashmap +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 55725dbe7047d54e3cbeccf7c4fb27f6603e0f2e diff --git a/loader/src/injector/art_method.hpp b/loader/src/injector/art_method.hpp new file mode 100644 index 0000000..461d9dc --- /dev/null +++ b/loader/src/injector/art_method.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "jni_helper.hpp" + +template +constexpr inline auto RoundUpTo(T v, size_t size) { + return v + size - 1 - ((v + size - 1) & (size - 1)); +} + +inline static constexpr auto kPointerSize = sizeof(void *); + +namespace lsplant::art { + + class ArtMethod { + + public: + void *GetData() { + return *reinterpret_cast(reinterpret_cast(this) + data_offset); + } + + static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { + if (art_method_field) [[likely]] { + return reinterpret_cast( + JNI_GetLongField(env, method, art_method_field)); + } else { + return reinterpret_cast(env->FromReflectedMethod(method)); + } + } + + static bool Init(JNIEnv *env) { + ScopedLocalRef executable{env, nullptr}; + 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); + jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors", + "()[Ljava/lang/reflect/Constructor;"); + const auto constructors = + JNI_Cast(JNI_CallObjectMethod(env, throwable, get_declared_constructors)); + if (constructors.size() < 2) { + LOGE("Throwable has less than 2 constructors"); + return false; + } + auto &first_ctor = constructors[0]; + auto &second_ctor = constructors[1]; + auto *first = FromReflectedMethod(env, first_ctor.get()); + auto *second = FromReflectedMethod(env, second_ctor.get()); + art_method_size = reinterpret_cast(second) - reinterpret_cast(first); + LOGD("ArtMethod size: %zu", art_method_size); + if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] { + LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); + } + entry_point_offset = art_method_size - kPointerSize; + data_offset = entry_point_offset - kPointerSize; + LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); + LOGD("ArtMethod::data offset: %zu", data_offset); + return true; + } + + 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; + }; + +} // namespace lsplant::art diff --git a/loader/src/injector/gen_jni_hooks.py b/loader/src/injector/gen_jni_hooks.py new file mode 100644 index 0000000..c15ea32 --- /dev/null +++ b/loader/src/injector/gen_jni_hooks.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 + +primitives = ['jint', 'jboolean', 'jlong'] + +class JType: + def __init__(self, cpp, jni): + self.cpp = cpp + self.jni = jni + + +class JArray(JType): + def __init__(self, type): + if type.cpp in primitives: + name = type.cpp + 'Array' + else: + name = 'jobjectArray' + super().__init__(name, '[' + type.jni) + + +class Argument: + def __init__(self, name, type, set_arg = False): + self.name = name + self.type = type + self.set_arg = set_arg + + def cpp(self): + return f'{self.type.cpp} {self.name}' + +# Args we don't care, give it an auto generated name +class Anon(Argument): + cnt = 0 + def __init__(self, type): + super().__init__(f'_{Anon.cnt}', type) + Anon.cnt += 1 + +class Return: + def __init__(self, value, type): + self.value = value + self.type = type + +class Method: + def __init__(self, name, ret, args): + self.name = name + self.ret = ret + self.args = args + + def cpp(self): + return ', '.join(map(lambda x: x.cpp(), self.args)) + + def name_list(self): + return ', '.join(map(lambda x: x.name, self.args)) + + def jni(self): + args = ''.join(map(lambda x: x.type.jni, self.args)) + return f'({args}){self.ret.type.jni}' + + def body(self): + return '' + +class JNIHook(Method): + def __init__(self, ver, ret, args): + name = f'{self.base_name()}_{ver}' + super().__init__(name, ret, args) + + def base_name(self): + return '' + + def orig_method(self): + return f'reinterpret_cast({self.base_name()}_orig)' + +def ind(i): + return '\n' + ' ' * i + +# Common types +jint = JType('jint', 'I') +jintArray = JArray(jint) +jstring = JType('jstring', 'Ljava/lang/String;') +jboolean = JType('jboolean', 'Z') +jlong = JType('jlong', 'J') +void = JType('void', 'V') + +class ForkAndSpec(JNIHook): + def __init__(self, ver, args): + super().__init__(ver, Return('ctx.pid', jint), args) + + def base_name(self): + return 'nativeForkAndSpecialize' + + def init_args(self): + return 'AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);' + + def body(self): + decl = '' + decl += ind(1) + self.init_args() + for a in self.args: + if a.set_arg: + decl += ind(1) + f'args.{a.name} = &{a.name};' + decl += ind(1) + 'HookContext ctx(env, &args);' + decl += ind(1) + f'ctx.{self.base_name()}_pre();' + decl += ind(1) + self.orig_method() + '(' + decl += ind(2) + f'env, clazz, {self.name_list()}' + decl += ind(1) + ');' + decl += ind(1) + f'ctx.{self.base_name()}_post();' + return decl + +class SpecApp(ForkAndSpec): + def __init__(self, ver, args): + super().__init__(ver, args) + self.ret = Return('', void) + + def base_name(self): + return 'nativeSpecializeAppProcess' + +class ForkServer(ForkAndSpec): + def base_name(self): + return 'nativeForkSystemServer' + + def init_args(self): + return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' + +# Common args +uid = Argument('uid', jint) +gid = Argument('gid', jint) +gids = Argument('gids', jintArray) +runtime_flags = Argument('runtime_flags', jint) +rlimits = Argument('rlimits', JArray(jintArray)) +mount_external = Argument('mount_external', jint) +se_info = Argument('se_info', jstring) +nice_name = Argument('nice_name', jstring) +fds_to_close = Argument('fds_to_close', jintArray) +instruction_set = Argument('instruction_set', jstring) +app_data_dir = Argument('app_data_dir', jstring) + +# o +fds_to_ignore = Argument('fds_to_ignore', jintArray, True) + +# p +is_child_zygote = Argument('is_child_zygote', jboolean, True) + +# q_alt +is_top_app = Argument('is_top_app', jboolean, True) + +# r +pkg_data_info_list = Argument('pkg_data_info_list', JArray(jstring), True) +whitelisted_data_info_list = Argument('whitelisted_data_info_list', JArray(jstring), True) +mount_data_dirs = Argument('mount_data_dirs', jboolean, True) +mount_storage_dirs = Argument('mount_storage_dirs', jboolean, True) + +# server +permitted_capabilities = Argument('permitted_capabilities', jlong) +effective_capabilities = Argument('effective_capabilities', jlong) + +# Method definitions +fas_l = ForkAndSpec('l', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, nice_name, fds_to_close, instruction_set, app_data_dir]) + +fas_o = ForkAndSpec('o', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) + +fas_p = ForkAndSpec('p', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir]) + +fas_q_alt = ForkAndSpec('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app]) + +fas_r = ForkAndSpec('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, + pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs]) + +fas_samsung_m = ForkAndSpec('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir]) + +fas_samsung_n = ForkAndSpec('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir, Anon(jint)]) + +fas_samsung_o = ForkAndSpec('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) + +fas_samsung_p = ForkAndSpec('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, is_child_zygote, + instruction_set, app_data_dir]) + +spec_q = SpecApp('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, is_child_zygote, instruction_set, app_data_dir]) + +spec_q_alt = SpecApp('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app]) + +spec_r = SpecApp('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, + is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, + whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs]) + +spec_samsung_q = SpecApp('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, is_child_zygote, instruction_set, app_data_dir]) + +server_l = ForkServer('l', [uid, gid, gids, runtime_flags, rlimits, + permitted_capabilities, effective_capabilities]) + +server_samsung_q = ForkServer('samsung_q', [uid, gid, gids, runtime_flags, Anon(jint), Anon(jint), rlimits, + permitted_capabilities, effective_capabilities]) + +hook_map = {} + +def gen_jni_def(clz, methods): + if clz not in hook_map: + hook_map[clz] = [] + + decl = '' + for m in methods: + decl += ind(0) + f'[[clang::no_stack_protector]] {m.ret.type.cpp} {m.name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{' + decl += m.body() + if m.ret.value: + decl += ind(1) + f'return {m.ret.value};' + decl += ind(0) + '}' + + decl += ind(0) + f'std::array {m.base_name()}_methods {{' + for m in methods: + decl += ind(1) + 'JNINativeMethod {' + decl += ind(2) + f'"{m.base_name()}",' + decl += ind(2) + f'"{m.jni()}",' + decl += ind(2) + f'(void *) &{m.name}' + decl += ind(1) + '},' + decl += ind(0) + '};' + decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl + decl += ind(0) + + hook_map[clz].append(m.base_name()) + + return decl + +def gen_jni_hook(): + decl = '' + decl += ind(0) + 'static void do_hook_zygote(JNIEnv *env) {' + decl += ind(1) + 'vector hooks;' + decl += ind(1) + 'const char *clz;' + for clz, methods in hook_map.items(): + decl += ind(1) + f'clz = "{clz}";' + for m in methods: + decl += ind(1) + f'hookJniNativeMethods(env, clz, {m}_methods.data(), {m}_methods.size());' + decl += ind(1) + f'for (auto &method : {m}_methods) {{' + decl += ind(2) + f'if (method.fnPtr) {{' + decl += ind(3) + f'{m}_orig = method.fnPtr;' + decl += ind(3) + f'hooks.emplace_back(method);' + decl += ind(3) + f'break;' + decl += ind(2) + f'}}' + decl += ind(1) + f'}}' + decl += ind(1) + f'jni_hook_list->emplace(clz, std::move(hooks));' + + decl += ind(0) + '}' + return decl + +with open('jni_hooks.hpp', 'w') as f: + f.write('// Generated by gen_jni_hooks.py\n') + f.write('\nnamespace {\n') + + zygote = 'com/android/internal/os/Zygote' + + methods = [fas_l, fas_o, fas_p, fas_q_alt, fas_r, fas_samsung_m, fas_samsung_n, fas_samsung_o, fas_samsung_p] + f.write(gen_jni_def(zygote, methods)) + + methods = [spec_q, spec_q_alt, spec_r, spec_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + methods = [server_l, server_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + f.write('\n} // namespace\n') + + f.write(gen_jni_hook()) + f.write('\n') \ No newline at end of file diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index 382dbf9..c4e90ea 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -4,29 +4,30 @@ #include #include #include +#include +#include #include #include #include #include +#include #include #include "dl.h" #include "daemon.h" #include "zygisk.hpp" -#include "memory.hpp" #include "module.hpp" #include "files.hpp" +#include "misc.hpp" + +#include "art_method.hpp" using namespace std; -using jni_hook::hash_map; -using jni_hook::tree_map; -using xstring = jni_hook::string; static void hook_unloader(); static void unhook_functions(); -static void restore_jni_env(JNIEnv *env); namespace { @@ -88,11 +89,6 @@ struct HookContext { HookContext(JNIEnv *env, void *args) : env(env), args{args}, process(nullptr), pid(-1), info_flags(0), hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { - static bool restored_env = false; - if (!restored_env) { - restore_jni_env(env); - restored_env = true; - } g_ctx = this; } ~HookContext(); @@ -123,98 +119,12 @@ struct HookContext { // Global variables vector> *plt_hook_list; map, StringCmp> *jni_hook_list; -hash_map>> *jni_method_map; bool should_unmap_zygisk = false; -const JNINativeInterface *old_functions = nullptr; -JNINativeInterface *new_functions = nullptr; - } // namespace -#define HOOK_JNI(method) \ -if (methods[i].name == #method##sv) { \ - int j = 0; \ - for (; j < method##_methods_num; ++j) { \ - if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \ - jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \ - method##_orig = methods[i].fnPtr; \ - newMethods[i] = method##_methods[j]; \ - LOGI("replaced %s#" #method "\n", className); \ - --hook_cnt; \ - break; \ - } \ - } \ - if (j == method##_methods_num) { \ - LOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \ - } \ - continue; \ -} - -// JNI method hook definitions, auto generated -#include "jni_hooks.hpp" - -#undef HOOK_JNI - namespace { -string get_class_name(JNIEnv *env, jclass clazz) { - static auto class_getName = env->GetMethodID(env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); - auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); - const char *name = env->GetStringUTFChars(nameRef, nullptr); - string className(name); - env->ReleaseStringUTFChars(nameRef, name); - std::replace(className.begin(), className.end(), '.', '/'); - return className; -} - -jint env_RegisterNatives( - JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { - auto className = get_class_name(env, clazz); - LOGV("JNIEnv->RegisterNatives [%s]\n", className.data()); - auto newMethods = hookAndSaveJNIMethods(className.data(), methods, numMethods); - return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); -} - -void replace_jni_methods() { - auto get_created_java_vms = reinterpret_cast( - dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); - if (!get_created_java_vms) { - for (auto &map: lsplt::MapInfo::Scan()) { - if (!map.path.ends_with("/libnativehelper.so")) continue; - void *h = dlopen(map.path.data(), RTLD_LAZY); - if (!h) { - LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); - break; - } - get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); - dlclose(h); - break; - } - if (!get_created_java_vms) { - LOGW("JNI_GetCreatedJavaVMs not found\n"); - return; - } - } - JavaVM *vm = nullptr; - jsize num = 0; - jint res = get_created_java_vms(&vm, 1, &num); - if (res != JNI_OK || vm == nullptr) return; - JNIEnv *env = nullptr; - res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); - if (res != JNI_OK || env == nullptr) return; - default_new(new_functions); - memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &env_RegisterNatives; - - // Replace the function table in JNIEnv to hook RegisterNatives - old_functions = env->functions; - env->functions = new_functions; - - // Re-run register_com_android_internal_os_Zygote to hook JNI methods - auto register_zygote = dlsym(RTLD_DEFAULT, "_ZN7android39register_com_android_internal_os_ZygoteEP7_JNIEnv"); - reinterpret_cast(register_zygote)(env); -} - #define DCL_HOOK_FUNC(ret, func, ...) \ ret (*old_##func)(__VA_ARGS__); \ ret new_##func(__VA_ARGS__) @@ -288,40 +198,103 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { // ----------------------------------------------------------------- +static bool can_hook_jni = false; +static jint MODIFIER_NATIVE = 0; +static jmethodID member_getModifiers = nullptr; + void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { - auto class_map = jni_method_map->find(clz); - if (class_map == jni_method_map->end()) { - for (int i = 0; i < numMethods; ++i) { + if (!can_hook_jni) return; + auto clazz = env->FindClass(clz); + if (clazz == nullptr) { + env->ExceptionClear(); + for (int i = 0; i < numMethods; i++) { methods[i].fnPtr = nullptr; } return; } vector hooks; - for (int i = 0; i < numMethods; ++i) { - auto method_map = class_map->second.find(methods[i].name); - if (method_map != class_map->second.end()) { - auto it = method_map->second.find(methods[i].signature); - if (it != method_map->second.end()) { - // Copy the JNINativeMethod - hooks.push_back(methods[i]); - // Save the original function pointer - methods[i].fnPtr = it->second; - // Do not allow double hook, remove method from map - method_map->second.erase(it); - continue; - } + for (int i = 0; i < numMethods; i++) { + auto &nm = methods[i]; + auto mid = env->GetMethodID(clazz, nm.name, nm.signature); + bool is_static = false; + if (mid == nullptr) { + env->ExceptionClear(); + mid = env->GetStaticMethodID(clazz, nm.name, nm.signature); + is_static = true; } - // No matching method found, set fnPtr to null - methods[i].fnPtr = nullptr; + if (mid == nullptr) { + env->ExceptionClear(); + nm.fnPtr = nullptr; + continue; + } + auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static); + auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers); + if ((modifier & MODIFIER_NATIVE) == 0) { + nm.fnPtr = nullptr; + continue; + } + auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method); + hooks.push_back(nm); + auto orig = artMethod->GetData(); + LOGD("replaced %s %s orig %p", clz, nm.name, orig); + nm.fnPtr = orig; } - if (hooks.empty()) - return; - - old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(), static_cast(hooks.size())); + if (hooks.empty()) return; + env->RegisterNatives(clazz, hooks.data(), hooks.size()); } +// JNI method hook definitions, auto generated +#include "jni_hooks.hpp" + +void initialize_jni_hook() { + auto get_created_java_vms = reinterpret_cast( + dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); + if (!get_created_java_vms) { + for (auto &map: lsplt::MapInfo::Scan()) { + if (!map.path.ends_with("/libnativehelper.so")) continue; + void *h = dlopen(map.path.data(), RTLD_LAZY); + if (!h) { + LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); + break; + } + get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); + dlclose(h); + break; + } + if (!get_created_java_vms) { + LOGW("JNI_GetCreatedJavaVMs not found\n"); + return; + } + } + JavaVM *vm = nullptr; + jsize num = 0; + jint res = get_created_java_vms(&vm, 1, &num); + if (res != JNI_OK || vm == nullptr) return; + JNIEnv *env = nullptr; + res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (res != JNI_OK || env == nullptr) return; + + auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member"); + if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I"); + auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier"); + if (classModifier != nullptr) { + auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I"); + if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId); + } + if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return; + if (!lsplant::art::ArtMethod::Init(env)) { + LOGE("failed to init ArtMethod"); + return; + } + + can_hook_jni = true; + do_hook_zygote(env); +} + +// ----------------------------------------------------------------- + ZygiskModule::ZygiskModule(int id, void *handle, void *entry) : id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { // Make sure all pointers are null @@ -716,12 +689,6 @@ HookContext::~HookContext() { } // namespace -static void restore_jni_env(JNIEnv *env) { - env->functions = old_functions; - delete new_functions; - new_functions = nullptr; -} - static bool hook_commit() { if (lsplt::CommitHook()) { return true; @@ -748,7 +715,6 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ void hook_functions() { default_new(plt_hook_list); default_new(jni_hook_list); - default_new(jni_method_map); ino_t android_runtime_inode = 0; dev_t android_runtime_dev = 0; @@ -771,7 +737,7 @@ void hook_functions() { [](auto &t) { return *std::get<3>(t) == nullptr;}), plt_hook_list->end()); - replace_jni_methods(); + initialize_jni_hook(); } static void hook_unloader() { diff --git a/loader/src/injector/jni_helper.hpp b/loader/src/injector/jni_helper.hpp new file mode 100644 index 0000000..fef32b9 --- /dev/null +++ b/loader/src/injector/jni_helper.hpp @@ -0,0 +1,1116 @@ +#pragma once + +#include +#include + +#include +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-partial-specialization" +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma ide diagnostic ignored "OCUnusedGlobalDeclarationInspection" + +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName &) = delete; \ + void operator=(const TypeName &) = delete + +namespace lsplant { + template class> + struct is_instance : public std::false_type {}; + + template class U> + struct is_instance, U> : public std::true_type {}; + + template class U> + inline constexpr bool is_instance_v = is_instance::value; + + template + concept JObject = std::is_base_of_v, std::remove_pointer_t>; + + template + class ScopedLocalRef { + public: + using BaseType [[maybe_unused]] = T; + + ScopedLocalRef(JNIEnv *env, T local_ref) : env_(env), local_ref_(nullptr) { reset(local_ref); } + + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, s.release()) {} + + template + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} + + explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{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 + friend class ScopedLocalRef; + + friend class JUTFString; + + private: + JNIEnv *env_; + T local_ref_; + DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); + }; + + template + concept JArray = std::is_base_of_v, std::remove_pointer_t>; + + template + class ScopedLocalRef; + + class JNIScopeFrame { + JNIEnv *env_; + + DISALLOW_COPY_AND_ASSIGN(JNIScopeFrame); + + public: + JNIScopeFrame(JNIEnv *env, jint size) : env_(env) { env_->PushLocalFrame(size); } + + ~JNIScopeFrame() { env_->PopLocalFrame(nullptr); } + }; + + class JNIMonitor { + JNIEnv *env_; + jobject obj_; + + DISALLOW_COPY_AND_ASSIGN(JNIMonitor); + + public: + JNIMonitor(JNIEnv *env, jobject obj) : env_(env), obj_(obj) { env_->MonitorEnter(obj_); } + + ~JNIMonitor() { env_->MonitorExit(obj_); } + }; + + template + concept ScopeOrRaw = std::is_convertible_v || + (is_instance_v, ScopedLocalRef> + &&std::is_convertible_v::BaseType, U>); + + template + concept ScopeOrClass = ScopeOrRaw; + + template + concept ScopeOrObject = ScopeOrRaw; + + inline ScopedLocalRef 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 + [[maybe_unused]] inline auto UnwrapScope(T &&x) { + if constexpr (std::is_same_v, std::string_view>) + return x.data(); + else if constexpr (is_instance_v, ScopedLocalRef>) + return x.get(); + else + return std::forward(x); + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, T &&x) { + if constexpr (std::is_convertible_v) { + return ScopedLocalRef(env, std::forward(x)); + } else + return x; + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x, + std::index_sequence) { + return std::make_tuple(WrapScope(env, std::forward(std::get(x)))...); + } + + template + [[maybe_unused]] inline auto WrapScope(JNIEnv *env, std::tuple &&x) { + return WrapScope(env, std::forward>(x), + std::make_index_sequence()); + } + + 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 &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 + requires(std::is_function_v) + [[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_)) { + __android_log_print(ANDROID_LOG_ERROR, +#ifdef LOG_TAG + LOG_TAG, +#else + "JNIHelper", +#endif + "%s", JUTFString(env_, exception.get()).get()); + } + } + + JNIEnv *env_; + } _(env); + + if constexpr (!std::is_same_v(args)))...>>) + return WrapScope(env, (env->*f)(UnwrapScope(std::forward(args))...)); + else + (env->*f)(UnwrapScope(std::forward(args))...); + } + +// functions to class + + [[maybe_unused]] inline auto JNI_FindClass(JNIEnv *env, std::string_view name) { + return JNI_SafeInvoke(env, &JNIEnv::FindClass, name); + } + + template + [[maybe_unused]] inline auto JNI_GetObjectClass(JNIEnv *env, const Object &obj) { + return JNI_SafeInvoke(env, &JNIEnv::GetObjectClass, obj); + } + +// functions to field + + template + [[maybe_unused]] inline auto JNI_GetFieldID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetFieldID, std::forward(clazz), name, sig); + } + +// getters + + template + [[maybe_unused]] inline auto JNI_GetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetObjectField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetBooleanField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetByteField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetCharField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetShortField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetIntField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetLongField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetFloatField, std::forward(obj), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetDoubleField, std::forward(obj), fieldId); + } + +// setters + + template + [[maybe_unused]] inline auto JNI_SetObjectField(JNIEnv *env, Object &&obj, jfieldID fieldId, + const Value &value) { + return JNI_SafeInvoke(env, &JNIEnv::SetObjectField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetBooleanField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jboolean value) { + return JNI_SafeInvoke(env, &JNIEnv::SetBooleanField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetByteField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jbyte value) { + return JNI_SafeInvoke(env, &JNIEnv::SetByteField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetCharField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jchar value) { + return JNI_SafeInvoke(env, &JNIEnv::SetCharField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetShortField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jshort value) { + return JNI_SafeInvoke(env, &JNIEnv::SetShortField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetIntField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jint value) { + return JNI_SafeInvoke(env, &JNIEnv::SetIntField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetLongField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jlong value) { + return JNI_SafeInvoke(env, &JNIEnv::SetLongField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetFloatField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jfloat value) { + return JNI_SafeInvoke(env, &JNIEnv::SetFloatField, std::forward(obj), fieldId, value); + } + + template + [[maybe_unused]] inline auto JNI_SetDoubleField(JNIEnv *env, Object &&obj, jfieldID fieldId, + jdouble value) { + return JNI_SafeInvoke(env, &JNIEnv::SetDoubleField, std::forward(obj), fieldId, value); + } + +// functions to static field + + template + [[maybe_unused]] inline auto JNI_GetStaticFieldID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticFieldID, std::forward(clazz), name, sig); + } + +// getters + + template + [[maybe_unused]] inline auto JNI_GetStaticObjectField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticObjectField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticBooleanField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticBooleanField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticByteField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticCharField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticShortField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticIntField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticLongField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticFloatField, std::forward(clazz), fieldId); + } + + template + [[maybe_unused]] inline auto JNI_GetStaticDoubleField(JNIEnv *env, Class &&clazz, + jfieldID fieldId) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticDoubleField, std::forward(clazz), fieldId); + } + +// setters + + template + [[maybe_unused]] inline auto JNI_SetStaticObjectField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + const Object &value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticObjectField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticBooleanField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jboolean value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticBooleanField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticByteField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jbyte value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticByteField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticCharField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jchar value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticCharField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticShortField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jshort value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticShortField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticIntField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jint value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticIntField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticLongField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jlong value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticLongField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticFloatField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jfloat value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticFloatField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_SetStaticDoubleField(JNIEnv *env, Class &&clazz, jfieldID fieldId, + jdouble value) { + return JNI_SafeInvoke(env, &JNIEnv::SetStaticDoubleField, std::forward(clazz), fieldId, + value); + } + + template + [[maybe_unused]] inline auto JNI_ToReflectedMethod(JNIEnv *env, Class &&clazz, jmethodID method, + jboolean isStatic = JNI_FALSE) { + return JNI_SafeInvoke(env, &JNIEnv::ToReflectedMethod, std::forward(clazz), method, + isStatic); + } + +// functions to method + +// virtual methods + + template + [[maybe_unused]] inline auto JNI_GetMethodID(JNIEnv *env, Class &&clazz, std::string_view name, + std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetMethodID, std::forward(clazz), name, sig); + } + + template + [[maybe_unused]] inline auto JNI_CallVoidMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallVoidMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallObjectMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallObjectMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallBooleanMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallBooleanMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallByteMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallByteMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCharMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallCharMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallShortMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallShortMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallIntMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallIntMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallLongMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallLongMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallFloatMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallFloatMethod, std::forward(obj), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallDoubleMethod(JNIEnv *env, Object &&obj, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallDoubleMethod, std::forward(obj), method, + std::forward(args)...); + } + +// static methods + + template + [[maybe_unused]] inline auto JNI_GetStaticMethodID(JNIEnv *env, Class &&clazz, + std::string_view name, std::string_view sig) { + return JNI_SafeInvoke(env, &JNIEnv::GetStaticMethodID, std::forward(clazz), name, sig); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticVoidMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticVoidMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticObjectMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticObjectMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticBooleanMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticBooleanMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticByteMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticByteMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticCharMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticCharMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticShortMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticShortMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticIntMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticIntMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticLongMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticLongMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticFloatMethod(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticFloatMethod, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallStaticDoubleMethod(JNIEnv *env, Class &&clazz, + jmethodID method, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallStaticDoubleMethod, std::forward(clazz), method, + std::forward(args)...); + } + +// non-vritual methods + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualVoidMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualVoidMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualObjectMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualObjectMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualBooleanMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualBooleanMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualByteMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualByteMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualCharMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualCharMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualShortMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualShortMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualIntMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualIntMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualLongMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualLongMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualFloatMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualFloatMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_CallCallNonvirtualDoubleMethod(JNIEnv *env, Object &&obj, + Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::CallNonvirtualDoubleMethod, std::forward(obj), + std::forward(clazz), method, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_NewObject(JNIEnv *env, Class &&clazz, jmethodID method, + Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::NewObject, std::forward(clazz), method, + std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, Args &&...args) { + return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, std::forward(args)...); + } + + template + [[maybe_unused]] inline auto JNI_RegisterNatives(JNIEnv *env, Class &&clazz, + const JNINativeMethod *methods, jint size) { + return JNI_SafeInvoke(env, &JNIEnv::RegisterNatives, std::forward(clazz), methods, size); + } + + template + [[maybe_unused]] inline auto JNI_IsInstanceOf(JNIEnv *env, Object &&obj, Class &&clazz) { + return JNI_SafeInvoke(env, &JNIEnv::IsInstanceOf, std::forward(obj), + std::forward(clazz)); + } + + template + [[maybe_unused]] inline auto JNI_NewGlobalRef(JNIEnv *env, Object &&x) { + return (decltype(UnwrapScope(std::forward(x))))env->NewGlobalRef( + UnwrapScope(std::forward(x))); + } + + template + [[maybe_unused]] inline auto JNI_Cast(ScopedLocalRef &&x) requires( + std::is_convertible_v) { + return ScopedLocalRef(std::move(x)); + } + + [[maybe_unused]] inline auto JNI_NewDirectByteBuffer(JNIEnv *env, void *address, jlong capacity) { + return JNI_SafeInvoke(env, &JNIEnv::NewDirectByteBuffer, address, capacity); + } + + template + struct JArrayUnderlyingTypeHelper; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = ScopedLocalRef; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jboolean; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jbyte; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jchar; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jshort; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jint; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jlong; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jfloat; + }; + + template <> + struct JArrayUnderlyingTypeHelper { + using Type = jdouble; + }; + + template + using JArrayUnderlyingType = typename JArrayUnderlyingTypeHelper::Type; + + template + class ScopedLocalRef { + ScopedLocalRef(JNIEnv *env, T local_ref, size_t size, JArrayUnderlyingType *elements, + bool modified) noexcept + : env_(env), local_ref_(local_ref), size_(size), elements_(elements), modified_(modified) {} + + public: + class Iterator { + friend class ScopedLocalRef; + Iterator(JArrayUnderlyingType *e) : e_(e) {} + JArrayUnderlyingType *e_; + + public: + auto &operator*() { return *e_; } + auto *operator->() { return e_; } + Iterator &operator++() { return ++e_, *this; } + Iterator &operator--() { return --e_, *this; } + Iterator operator++(int) { return Iterator(e_++); } + Iterator operator--(int) { return Iterator(e_--); } + bool operator==(const Iterator &other) const { return other.e_ == e_; } + bool operator!=(const Iterator &other) const { return other.e_ != e_; } + }; + + class ConstIterator { + friend class ScopedLocalRef; + ConstIterator(const JArrayUnderlyingType *e) : e_(e) {} + const JArrayUnderlyingType *e_; + + public: + const auto &operator*() { return *e_; } + const auto *operator->() { return e_; } + ConstIterator &operator++() { return ++e_, *this; } + ConstIterator &operator--() { return --e_, *this; } + ConstIterator operator++(int) { return ConstIterator(e_++); } + ConstIterator operator--(int) { return ConstIterator(e_--); } + bool operator==(const ConstIterator &other) const { return other.e_ == e_; } + bool operator!=(const ConstIterator &other) const { return other.e_ != e_; } + }; + + auto begin() { + modified_ = true; + return Iterator(elements_); + } + + auto end() { + modified_ = true; + return Iterator(elements_ + size_); + } + + auto begin() const { return ConstIterator(elements_); } + + auto end() const { return ConstIterator(elements_ + size_); } + + auto cbegin() const { return ConstIterator(elements_); } + + auto cend() const { return ConstIterator(elements_ + size_); } + + using BaseType [[maybe_unused]] = T; + + ScopedLocalRef(JNIEnv *env, T local_ref) noexcept : env_(env), local_ref_(nullptr) { + reset(local_ref); + } + + ScopedLocalRef(ScopedLocalRef &&s) noexcept + : ScopedLocalRef(s.env_, s.local_ref_, s.size_, s.elements_, s.modified_) { + s.local_ref_ = nullptr; + s.size_ = 0; + s.elements_ = nullptr; + s.modified_ = false; + } + + template + ScopedLocalRef(ScopedLocalRef &&s) noexcept : ScopedLocalRef(s.env_, (T)s.release()) {} + + explicit ScopedLocalRef(JNIEnv *env) noexcept : ScopedLocalRef(env, T{nullptr}) {} + + ~ScopedLocalRef() { env_->DeleteLocalRef(release()); } + + void reset(T ptr = nullptr) { + if (ptr != local_ref_) { + if (local_ref_ != nullptr) { + ReleaseElements(modified_ ? 0 : JNI_ABORT); + env_->DeleteLocalRef(local_ref_); + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + elements_[i].~ScopedLocalRef(); + } + operator delete[](elements_); + } + elements_ = nullptr; + } + local_ref_ = ptr; + size_ = local_ref_ ? env_->GetArrayLength(local_ref_) : 0; + if (!local_ref_) return; + if constexpr (std::is_same_v) { + elements_ = static_cast *>(operator new[]( + sizeof(ScopedLocalRef) * size_)); + for (size_t i = 0; i < size_; ++i) { + new (&elements_[i]) ScopedLocalRef( + JNI_SafeInvoke(env_, &JNIEnv::GetObjectArrayElement, local_ref_, i)); + } + } else if constexpr (std::is_same_v) { + elements_ = env_->GetBooleanArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetByteArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetCharArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetShortArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetIntArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetLongArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetFloatArrayElements(local_ref_, nullptr); + } else if constexpr (std::is_same_v) { + elements_ = env_->GetDoubleArrayElements(local_ref_, nullptr); + } + } + } + + [[nodiscard]] T release() { + T localRef = local_ref_; + size_ = 0; + local_ref_ = nullptr; + ReleaseElements(modified_ ? 0 : JNI_ABORT); + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + elements_[i].~ScopedLocalRef(); + } + operator delete[](elements_); + } + elements_ = nullptr; + return localRef; + } + + T get() const { return local_ref_; } + + explicit operator T() const { return local_ref_; } + + JArrayUnderlyingType &operator[](size_t index) { + modified_ = true; + return elements_[index]; + } + + const JArrayUnderlyingType &operator[](size_t index) const { return elements_[index]; } + + void commit() { + ReleaseElements(JNI_COMMIT); + modified_ = false; + } + + // We do not expose an empty constructor as it can easily lead to errors + // using common idioms, e.g.: + // ScopedLocalRef<...> ref; + // ref.reset(...); + // Move assignment operator. + ScopedLocalRef &operator=(ScopedLocalRef &&s) noexcept { + env_ = s.env_; + local_ref_ = s.local_ref_; + size_ = s.size_; + elements_ = s.elements_; + modified_ = s.modified_; + s.elements_ = nullptr; + s.size_ = 0; + s.modified_ = false; + s.local_ref_ = nullptr; + return *this; + } + + size_t size() const { return size_; } + + operator bool() const { return local_ref_; } + + template + friend class ScopedLocalRef; + + friend class JUTFString; + + private: + void ReleaseElements(jint mode) { + if (!local_ref_ || !elements_) return; + if constexpr (std::is_same_v) { + for (size_t i = 0; i < size_; ++i) { + JNI_SafeInvoke(env_, &JNIEnv::SetObjectArrayElement, local_ref_, i, elements_[i]); + } + } else if constexpr (std::is_same_v) { + env_->ReleaseBooleanArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseByteArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseCharArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseShortArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseIntArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseLongArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseFloatArrayElements(local_ref_, elements_, mode); + } else if constexpr (std::is_same_v) { + env_->ReleaseDoubleArrayElements(local_ref_, elements_, mode); + } + } + + JNIEnv *env_; + T local_ref_; + size_t size_; + JArrayUnderlyingType *elements_{nullptr}; + bool modified_ = false; + DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); + }; + +// functions to array + + template Array> + [[maybe_unused]] inline auto JNI_GetArrayLength(JNIEnv *env, const Array &array) { + return JNI_SafeInvoke(env, &JNIEnv::GetArrayLength, array); + } + +// newers + + template + [[maybe_unused]] inline auto JNI_NewObjectArray(JNIEnv *env, jsize len, Class &&clazz, + const Object &init) { + return JNI_SafeInvoke(env, &JNIEnv::NewObjectArray, len, std::forward(clazz), init); + } + + [[maybe_unused]] inline auto JNI_NewBooleanArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewBooleanArray, len); + } + + [[maybe_unused]] inline auto JNI_NewByteArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewByteArray, len); + } + + [[maybe_unused]] inline auto JNI_NewCharArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewCharArray, len); + } + + [[maybe_unused]] inline auto JNI_NewShortArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewShortArray, len); + } + + [[maybe_unused]] inline auto JNI_NewIntArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewIntArray, len); + } + + [[maybe_unused]] inline auto JNI_NewLongArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewLongArray, len); + } + + [[maybe_unused]] inline auto JNI_NewFloatArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewFloatArray, len); + } + + [[maybe_unused]] inline auto JNI_NewDoubleArray(JNIEnv *env, jsize len) { + return JNI_SafeInvoke(env, &JNIEnv::NewDoubleArray, len); + } + + template + [[maybe_unused]] inline auto JNI_GetObjectFieldOf(JNIEnv *env, Object &&object, + std::string_view field_name, + std::string_view field_class) { + auto &&o = std::forward(object); + return JNI_GetObjectField( + env, o, JNI_GetFieldID(env, JNI_GetObjectClass(env, o), field_name, field_class)); + } + +} // namespace lsplant + +#undef DISALLOW_COPY_AND_ASSIGN + +#pragma clang diagnostic pop diff --git a/loader/src/injector/jni_hooks.hpp b/loader/src/injector/jni_hooks.hpp index 6ec6a95..ea18140 100644 --- a/loader/src/injector/jni_hooks.hpp +++ b/loader/src/injector/jni_hooks.hpp @@ -109,54 +109,53 @@ void *nativeForkAndSpecialize_orig = nullptr; ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -const JNINativeMethod nativeForkAndSpecialize_methods[] = { - { +std::array nativeForkAndSpecialize_methods { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_l }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_p }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I", (void *) &nativeForkAndSpecialize_q_alt }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void *) &nativeForkAndSpecialize_r }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_m }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I", (void *) &nativeForkAndSpecialize_samsung_n }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_p }, }; -constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods); void *nativeSpecializeAppProcess_orig = nullptr; [[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { @@ -205,29 +204,28 @@ void *nativeSpecializeAppProcess_orig = nullptr; ); ctx.nativeSpecializeAppProcess_post(); } -const JNINativeMethod nativeSpecializeAppProcess_methods[] = { - { +std::array nativeSpecializeAppProcess_methods { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_q }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V", (void *) &nativeSpecializeAppProcess_q_alt }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", (void *) &nativeSpecializeAppProcess_r }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_samsung_q }, }; -constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods); void *nativeForkSystemServer_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { @@ -250,45 +248,48 @@ void *nativeForkSystemServer_orig = nullptr; ctx.nativeForkSystemServer_post(); return ctx.pid; } -const JNINativeMethod nativeForkSystemServer_methods[] = { - { +std::array nativeForkSystemServer_methods { + JNINativeMethod { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) &nativeForkSystemServer_l }, - { + JNINativeMethod { "nativeForkSystemServer", "(II[IIII[[IJJ)I", (void *) &nativeForkSystemServer_samsung_q }, }; -constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods); - -unique_ptr hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { - unique_ptr newMethods; - int clz_id = -1; - int hook_cnt = 0; - do { - if (className == "com/android/internal/os/Zygote"sv) { - clz_id = 0; - hook_cnt = 3; - break; - } - } while (false); - if (hook_cnt) { - newMethods = make_unique(numMethods); - memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods); - } - auto &class_map = (*jni_method_map)[className]; - for (int i = 0; i < numMethods; ++i) { - if (hook_cnt && clz_id == 0) { - HOOK_JNI(nativeForkAndSpecialize) - HOOK_JNI(nativeSpecializeAppProcess) - HOOK_JNI(nativeForkSystemServer) - } - class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; - } - return newMethods; -} } // namespace + +static void do_hook_zygote(JNIEnv *env) { + vector hooks; + const char *clz; + clz = "com/android/internal/os/Zygote"; + hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size()); + for (auto &method : nativeForkAndSpecialize_methods) { + if (method.fnPtr) { + nativeForkAndSpecialize_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size()); + for (auto &method : nativeSpecializeAppProcess_methods) { + if (method.fnPtr) { + nativeSpecializeAppProcess_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size()); + for (auto &method : nativeForkSystemServer_methods) { + if (method.fnPtr) { + nativeForkSystemServer_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + jni_hook_list->emplace(clz, std::move(hooks)); +} diff --git a/loader/src/injector/memory.cpp b/loader/src/injector/memory.cpp deleted file mode 100644 index 1ebcf52..0000000 --- a/loader/src/injector/memory.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include "memory.hpp" - -namespace jni_hook { - -// We know our minimum alignment is WORD size (size of pointer) -static constexpr size_t ALIGN = sizeof(long); - -// 4MB is more than enough -static constexpr size_t CAPACITY = (1 << 22); - -// No need to be thread safe as the initial mmap always happens on the main thread -static uint8_t *_area = nullptr; - -static std::atomic _curr = nullptr; - -void *memory_block::allocate(size_t sz) { - if (!_area) { - // Memory will not actually be allocated because physical pages are mapped in on-demand - _area = static_cast(mmap( - nullptr, CAPACITY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - _curr = _area; - } - return _curr.fetch_add(align_to(sz, ALIGN)); -} - -void memory_block::release() { - if (_area) - munmap(_area, CAPACITY); -} - -} // namespace jni_hook diff --git a/loader/src/injector/memory.hpp b/loader/src/injector/memory.hpp deleted file mode 100644 index a48c44a..0000000 --- a/loader/src/injector/memory.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include -#include - -#pragma clang diagnostic push -#include -#pragma clang diagnostic pop - -#include "misc.hpp" - -namespace jni_hook { - -struct memory_block { - static void *allocate(size_t sz); - static void deallocate(void *, size_t) { /* Monotonic increase */ } - static void release(); -}; - -template -using allocator = stateless_allocator; - -using string = std::basic_string, allocator>; - -// Use node_hash_map since it will use less memory because we are using a monotonic allocator -template -using hash_map = phmap::node_hash_map, - phmap::priv::hash_default_eq, - allocator> ->; - -template -using tree_map = std::map, - allocator> ->; - -} // namespace jni_hook - -// Provide heterogeneous lookup for jni_hook::string -namespace phmap::priv { -template <> struct HashEq : StringHashEqT {}; -} // namespace phmap::priv From add1c65626c368311896b7fc90489297f543a09d Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Sat, 4 Nov 2023 16:39:21 +0800 Subject: [PATCH 5/6] make compiler happy --- loader/src/common/socket_utils.cpp | 8 ++++---- loader/src/include/socket_utils.h | 2 +- zygiskd/src/root_impl/kernelsu.rs | 3 ++- zygiskd/src/watchdog.rs | 4 ++-- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/loader/src/common/socket_utils.cpp b/loader/src/common/socket_utils.cpp index 73dd1d2..d4a9f1d 100644 --- a/loader/src/common/socket_utils.cpp +++ b/loader/src/common/socket_utils.cpp @@ -19,12 +19,12 @@ namespace socket_utils { read_sz += ret; } while (read_sz != count && ret != 0); if (read_sz != count) { - PLOGE("read (%d != %d)", count, read_sz); + PLOGE("read (%zu != %zu)", count, read_sz); } return read_sz; } - ssize_t xwrite(int fd, const void* buf, size_t count) { + size_t xwrite(int fd, const void* buf, size_t count) { size_t write_sz = 0; ssize_t ret; do { @@ -32,12 +32,12 @@ namespace socket_utils { if (ret < 0) { if (errno == EINTR) continue; PLOGE("write"); - return ret; + return write_sz; } write_sz += ret; } while (write_sz != count && ret != 0); if (write_sz != count) { - PLOGE("write (%d != %d)", count, write_sz); + PLOGE("write (%zu != %zu)", count, write_sz); } return write_sz; } diff --git a/loader/src/include/socket_utils.h b/loader/src/include/socket_utils.h index b2775d7..09330de 100644 --- a/loader/src/include/socket_utils.h +++ b/loader/src/include/socket_utils.h @@ -9,7 +9,7 @@ namespace socket_utils { ssize_t xread(int fd, void *buf, size_t count); - ssize_t xwrite(int fd, const void *buf, size_t count); + size_t xwrite(int fd, const void *buf, size_t count); uint8_t read_u8(int fd); diff --git a/zygiskd/src/root_impl/kernelsu.rs b/zygiskd/src/root_impl/kernelsu.rs index e92163c..d629b20 100644 --- a/zygiskd/src/root_impl/kernelsu.rs +++ b/zygiskd/src/root_impl/kernelsu.rs @@ -23,10 +23,11 @@ pub fn get_kernel_su() -> Option { 0, ) }; + const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1; match version { 0 => None, MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported), - 1..=MIN_KSU_VERSION => Some(Version::TooOld), + 1..=MAX_OLD_VERSION => Some(Version::TooOld), _ => Some(Version::Abnormal), } } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index dff6532..c0ef32a 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -3,8 +3,8 @@ use anyhow::{bail, Result}; use std::fs; use std::io::{BufRead, BufReader, Write}; use std::time::Duration; -use futures::{FutureExt, join, pin_mut}; -use futures::future::{Fuse, FusedFuture}; +use futures::{FutureExt, pin_mut}; +use futures::future::{Fuse}; use log::{debug, error, info}; use rustix::mount::mount_bind; use rustix::process::{getgid, getuid}; From 070aa5f1a1e4807bbde115bfc319d99adc695ae0 Mon Sep 17 00:00:00 2001 From: 5ec1cff Date: Mon, 6 Nov 2023 10:05:41 +0800 Subject: [PATCH 6/6] fix pure 64 bit support --- module/src/customize.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/src/customize.sh b/module/src/customize.sh index 8e65f56..5cf56f9 100644 --- a/module/src/customize.sh +++ b/module/src/customize.sh @@ -101,7 +101,7 @@ extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" extract "$ZIPFILE" 'service.sh' "$MODPATH" mv "$TMPDIR/sepolicy.rule" "$MODPATH" -HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true +HAS32BIT=false && [ $(getprop ro.product.cpu.abilist32) ] && HAS32BIT=true mkdir "$MODPATH/bin" mkdir "$MODPATH/system" @@ -148,8 +148,8 @@ fi ui_print "- Setting permissions" set_perm_recursive "$MODPATH/bin" 0 0 0755 0755 -set_perm_recursive "$MODPATH/system/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 set_perm_recursive "$MODPATH/system/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0 +[ "$HAS32BIT" = true ] && set_perm_recursive "$MODPATH/system/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 # If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable)