This commit is contained in:
bbldrizzy committed 2026-10-23 00:28:57 +01:00
1 parent 83aea53e57
commit aa4f6b6ddc
842 files changed
+47284 -91563

No files matched your search

+255
View File
@@ -0,0 +1,255 @@
//! Minimal raw JNI / JVMTI bindings. Function-table indices were checked against the JDK 21 jni.h / jvmti.h.
use std::ffi::{c_char, c_void, CStr, CString};
pub type JObject = *mut c_void;
pub type JClass = JObject;
pub type JMethodId = *mut c_void;
pub type JValue = u64;
pub type JInt = i32;
pub type JBool = u8;
/// JNIEnv*: pointer to a pointer to the function table.
pub type EnvPtr = *mut *const *const c_void;
pub const JNI_OK: JInt = 0;
pub const JNI_VERSION_1_8: JInt = 0x0001_0008;
pub const JVMTI_VERSION_1_2: JInt = 0x3001_0200;
#[repr(C)]
pub struct NativeMethod {
pub name: *const c_char,
pub signature: *const c_char,
pub fn_ptr: *mut c_void,
}
/// Packs a JNI argument into the 8-byte jvalue slot.
pub fn obj(o: JObject) -> JValue {
o as usize as u64
}
pub fn boolean(b: bool) -> JValue {
b as u64
}
pub struct Jni {
pub env: EnvPtr,
}
macro_rules! f {
($self:expr, $idx:expr, $ty:ty) => {
std::mem::transmute::<*const c_void, $ty>(*(*$self.env).add($idx))
};
}
impl Jni {
/// # Safety
/// `env` must be a valid JNIEnv* of the current thread.
pub unsafe fn new(env: EnvPtr) -> Self {
Jni { env }
}
pub unsafe fn find_class(&self, name: &str) -> JClass {
let n = CString::new(name).unwrap();
f!(self, 6, unsafe extern "system" fn(EnvPtr, *const c_char) -> JClass)(self.env, n.as_ptr())
}
pub unsafe fn exception_occurred(&self) -> JObject {
f!(self, 15, unsafe extern "system" fn(EnvPtr) -> JObject)(self.env)
}
pub unsafe fn exception_clear(&self) {
f!(self, 17, unsafe extern "system" fn(EnvPtr))(self.env)
}
pub unsafe fn new_global_ref(&self, o: JObject) -> JObject {
f!(self, 21, unsafe extern "system" fn(EnvPtr, JObject) -> JObject)(self.env, o)
}
pub unsafe fn delete_local_ref(&self, o: JObject) {
f!(self, 23, unsafe extern "system" fn(EnvPtr, JObject))(self.env, o)
}
pub unsafe fn is_same_object(&self, a: JObject, b: JObject) -> bool {
f!(self, 24, unsafe extern "system" fn(EnvPtr, JObject, JObject) -> JBool)(self.env, a, b) != 0
}
pub unsafe fn get_object_class(&self, o: JObject) -> JClass {
f!(self, 31, unsafe extern "system" fn(EnvPtr, JObject) -> JClass)(self.env, o)
}
pub unsafe fn get_method_id(&self, c: JClass, name: &str, sig: &str) -> JMethodId {
let (n, s) = (CString::new(name).unwrap(), CString::new(sig).unwrap());
f!(self, 33, unsafe extern "system" fn(EnvPtr, JClass, *const c_char, *const c_char) -> JMethodId)(self.env, c, n.as_ptr(), s.as_ptr())
}
pub unsafe fn new_object(&self, c: JClass, ctor: JMethodId, args: &[JValue]) -> JObject {
f!(self, 30, unsafe extern "system" fn(EnvPtr, JClass, JMethodId, *const JValue) -> JObject)(self.env, c, ctor, args.as_ptr())
}
pub unsafe fn call_object(&self, o: JObject, m: JMethodId, args: &[JValue]) -> JObject {
f!(self, 36, unsafe extern "system" fn(EnvPtr, JObject, JMethodId, *const JValue) -> JObject)(self.env, o, m, args.as_ptr())
}
pub unsafe fn call_void(&self, o: JObject, m: JMethodId, args: &[JValue]) {
f!(self, 63, unsafe extern "system" fn(EnvPtr, JObject, JMethodId, *const JValue))(self.env, o, m, args.as_ptr())
}
pub unsafe fn get_static_method_id(&self, c: JClass, name: &str, sig: &str) -> JMethodId {
let (n, s) = (CString::new(name).unwrap(), CString::new(sig).unwrap());
f!(self, 113, unsafe extern "system" fn(EnvPtr, JClass, *const c_char, *const c_char) -> JMethodId)(self.env, c, n.as_ptr(), s.as_ptr())
}
pub unsafe fn call_static_object(&self, c: JClass, m: JMethodId, args: &[JValue]) -> JObject {
f!(self, 116, unsafe extern "system" fn(EnvPtr, JClass, JMethodId, *const JValue) -> JObject)(self.env, c, m, args.as_ptr())
}
pub unsafe fn call_static_void(&self, c: JClass, m: JMethodId, args: &[JValue]) {
f!(self, 143, unsafe extern "system" fn(EnvPtr, JClass, JMethodId, *const JValue))(self.env, c, m, args.as_ptr())
}
pub unsafe fn new_string(&self, s: &str) -> JObject {
let c = CString::new(s).unwrap();
f!(self, 167, unsafe extern "system" fn(EnvPtr, *const c_char) -> JObject)(self.env, c.as_ptr())
}
pub unsafe fn get_string_utf(&self, s: JObject) -> String {
let chars = f!(self, 169, unsafe extern "system" fn(EnvPtr, JObject, *mut JBool) -> *const c_char)(self.env, s, std::ptr::null_mut());
if chars.is_null() {
return String::new();
}
let out = CStr::from_ptr(chars).to_string_lossy().into_owned();
f!(self, 170, unsafe extern "system" fn(EnvPtr, JObject, *const c_char))(self.env, s, chars);
out
}
pub unsafe fn get_array_length(&self, a: JObject) -> JInt {
f!(self, 171, unsafe extern "system" fn(EnvPtr, JObject) -> JInt)(self.env, a)
}
pub unsafe fn new_object_array(&self, len: JInt, c: JClass, init: JObject) -> JObject {
f!(self, 172, unsafe extern "system" fn(EnvPtr, JInt, JClass, JObject) -> JObject)(self.env, len, c, init)
}
pub unsafe fn new_byte_array(&self, len: JInt) -> JObject {
f!(self, 176, unsafe extern "system" fn(EnvPtr, JInt) -> JObject)(self.env, len)
}
pub unsafe fn get_byte_array_region(&self, a: JObject, start: JInt, len: JInt, buf: *mut u8) {
f!(self, 200, unsafe extern "system" fn(EnvPtr, JObject, JInt, JInt, *mut u8))(self.env, a, start, len, buf)
}
pub unsafe fn set_byte_array_region(&self, a: JObject, start: JInt, len: JInt, buf: *const u8) {
f!(self, 208, unsafe extern "system" fn(EnvPtr, JObject, JInt, JInt, *const u8))(self.env, a, start, len, buf)
}
pub unsafe fn register_natives(&self, c: JClass, methods: &[NativeMethod]) -> JInt {
f!(self, 215, unsafe extern "system" fn(EnvPtr, JClass, *const NativeMethod, JInt) -> JInt)(self.env, c, methods.as_ptr(), methods.len() as JInt)
}
pub unsafe fn exception_check(&self) -> bool {
f!(self, 228, unsafe extern "system" fn(EnvPtr) -> JBool)(self.env) != 0
}
/// Returns "ExceptionClass: message" of the pending exception and clears it.
pub unsafe fn take_exception(&self) -> Option<String> {
let ex = self.exception_occurred();
if ex.is_null() {
return None;
}
self.exception_clear();
let cls = self.get_object_class(ex);
let to_string = self.get_method_id(cls, "toString", "()Ljava/lang/String;");
let text = if to_string.is_null() {
"<exception>".to_string()
} else {
let s = self.call_object(ex, to_string, &[]);
self.exception_clear();
if s.is_null() { "<exception>".to_string() } else { self.get_string_utf(s) }
};
Some(text)
}
/// Converts a pending Java exception into an Err carrying `context`.
pub unsafe fn check(&self, context: &str) -> Result<(), String> {
match self.take_exception() {
Some(e) => Err(format!("{context}: {e}")),
None => Ok(()),
}
}
}
/// JavaVM*: pointer to a pointer to the invoke interface.
pub type VmPtr = *mut *const *const c_void;
pub unsafe fn attach_current_thread(vm: VmPtr) -> Result<EnvPtr, String> {
let attach = std::mem::transmute::<*const c_void, unsafe extern "system" fn(VmPtr, *mut EnvPtr, *mut c_void) -> JInt>(*(*vm).add(4));
let mut env: EnvPtr = std::ptr::null_mut();
let rc = attach(vm, &mut env, std::ptr::null_mut());
if rc != JNI_OK || env.is_null() {
return Err(format!("AttachCurrentThread failed ({rc})"));
}
Ok(env)
}
pub unsafe fn get_jvmti(vm: VmPtr) -> Result<JvmtiPtr, String> {
let get_env = std::mem::transmute::<*const c_void, unsafe extern "system" fn(VmPtr, *mut *mut c_void, JInt) -> JInt>(*(*vm).add(6));
let mut out: *mut c_void = std::ptr::null_mut();
let rc = get_env(vm, &mut out, JVMTI_VERSION_1_2);
if rc != JNI_OK || out.is_null() {
return Err(format!("GetEnv(JVMTI) failed ({rc})"));
}
Ok(out as JvmtiPtr)
}
/// jvmtiEnv*: pointer to a pointer to the JVMTI function table (entry n of the spec is index n-1).
pub type JvmtiPtr = *mut *const *const c_void;
#[repr(C)]
pub struct ClassDefinition {
pub klass: JClass,
pub class_byte_count: JInt,
pub class_bytes: *const u8,
}
#[repr(C)]
#[derive(Default, Clone, Copy)]
pub struct Capabilities {
pub bits: [u32; 4],
}
impl Capabilities {
pub fn set(&mut self, bit: u32) {
self.bits[(bit / 32) as usize] |= 1 << (bit % 32);
}
}
// capability bit positions, counted in jvmtiCapabilities declaration order
pub const CAN_REDEFINE_CLASSES: u32 = 9;
pub const CAN_REDEFINE_ANY_CLASS: u32 = 21;
pub const CAN_GENERATE_ALL_CLASS_HOOK_EVENTS: u32 = 26;
pub const CAN_RETRANSFORM_CLASSES: u32 = 37;
pub const CAN_RETRANSFORM_ANY_CLASS: u32 = 38;
pub const EVENT_CLASS_FILE_LOAD_HOOK: JInt = 54;
pub type ClassFileLoadHook = unsafe extern "system" fn(
JvmtiPtr,
EnvPtr,
JClass,
JObject,
*const c_char,
JObject,
JInt,
*const u8,
*mut JInt,
*mut *mut u8,
);
pub unsafe fn add_capabilities(j: JvmtiPtr, caps: &Capabilities) -> JInt {
let f = std::mem::transmute::<*const c_void, unsafe extern "system" fn(JvmtiPtr, *const Capabilities) -> JInt>(*(*j).add(141));
f(j, caps)
}
/// Installs the ClassFileLoadHook callback (field 5 of jvmtiEventCallbacks) and enables the event.
pub unsafe fn enable_class_file_load_hook(j: JvmtiPtr, hook: ClassFileLoadHook) -> Result<(), String> {
let mut callbacks = [0usize; 40];
callbacks[4] = hook as usize;
let set_callbacks = std::mem::transmute::<*const c_void, unsafe extern "system" fn(JvmtiPtr, *const usize, JInt) -> JInt>(*(*j).add(121));
let rc = set_callbacks(j, callbacks.as_ptr(), std::mem::size_of_val(&callbacks) as JInt);
if rc != 0 {
return Err(format!("SetEventCallbacks failed ({rc})"));
}
let set_mode = std::mem::transmute::<*const c_void, unsafe extern "C" fn(JvmtiPtr, JInt, JInt, JObject, ...) -> JInt>(*(*j).add(1));
let rc = set_mode(j, 1, EVENT_CLASS_FILE_LOAD_HOOK, std::ptr::null_mut());
if rc != 0 {
return Err(format!("SetEventNotificationMode failed ({rc})"));
}
Ok(())
}
pub unsafe fn retransform_class(j: JvmtiPtr, class: JClass) -> JInt {
let f = std::mem::transmute::<*const c_void, unsafe extern "system" fn(JvmtiPtr, JInt, *const JClass) -> JInt>(*(*j).add(151));
f(j, 1, &class)
}
pub unsafe fn redefine_class(j: JvmtiPtr, def: &ClassDefinition) -> JInt {
let f = std::mem::transmute::<*const c_void, unsafe extern "system" fn(JvmtiPtr, JInt, *const ClassDefinition) -> JInt>(*(*j).add(86));
f(j, 1, def)
}