pub mod sys;
pub mod env;
pub mod classfile;
pub mod prelude;
#[cfg(feature = "embed")]
pub mod embed;
#[cfg(feature = "advanced")]
pub mod advanced;
#[doc(hidden)]
pub(crate) mod jvmti_wrapper;
#[doc(hidden)]
pub(crate) mod jni_wrapper;
use std::sync::OnceLock;
pub use crate::sys::jni as jni;
use crate::sys::jvmti as jvmti;
pub fn describe_jni_result(code: jni::jint) -> String {
jni::describe_result(code)
}
pub trait Agent: Sync + Send {
fn on_load(&self, vm: *mut jni::JavaVM, options: &str) -> jni::jint;
fn on_attach(&self, _vm: *mut jni::JavaVM, _options: &str) -> jni::jint {
jni::JNI_OK
}
fn on_unload(&self) {}
fn vm_init(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn vm_death(&self, _jni: *mut jni::JNIEnv) {}
fn vm_start(&self, _jni: *mut jni::JNIEnv) {}
fn thread_start(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn thread_end(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn class_load(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _klass: jni::jclass) {}
fn class_prepare(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _klass: jni::jclass) {}
fn class_file_load_hook(&self, _jni: *mut jni::JNIEnv, _class_being_redefined: jni::jclass,
_loader: jni::jobject, _name: *const std::os::raw::c_char,
_protection_domain: jni::jobject, _class_data_len: jni::jint,
_class_data: *const std::os::raw::c_uchar,
_new_class_data_len: *mut jni::jint,
_new_class_data: *mut *mut std::os::raw::c_uchar) {}
fn method_entry(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID) {}
fn method_exit(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID) {}
fn native_method_bind(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID, _address: *mut std::os::raw::c_void, _new_address_ptr: *mut *mut std::os::raw::c_void) {}
fn compiled_method_load(&self, _method: jni::jmethodID, _code_size: jni::jint, _code_addr: *const std::os::raw::c_void, _map_length: jni::jint, _map: *const std::os::raw::c_void, _compile_info: *const std::os::raw::c_void) {}
fn compiled_method_unload(&self, _method: jni::jmethodID, _code_addr: *const std::os::raw::c_void) {}
fn dynamic_code_generated(&self, _name: *const std::os::raw::c_char, _address: *const std::os::raw::c_void, _length: jni::jint) {}
fn exception(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID,
_location: jvmti::jlocation, _exception: jni::jobject,
_catch_method: jni::jmethodID, _catch_location: jvmti::jlocation) {}
fn exception_catch(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID,
_location: jvmti::jlocation, _exception: jni::jobject) {}
fn single_step(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID, _location: jvmti::jlocation) {}
fn breakpoint(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID, _location: jvmti::jlocation) {}
fn frame_pop(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID, _was_popped_by_exception: jni::jboolean) {}
fn monitor_wait(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject, _timeout: jni::jlong) {}
fn monitor_waited(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject, _timed_out: jni::jboolean) {}
fn monitor_contended_enter(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject) {}
fn monitor_contended_entered(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject) {}
fn field_access(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID,
_location: jvmti::jlocation, _field_klass: jni::jclass, _object: jni::jobject, _field: jni::jfieldID) {}
fn field_modification(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID,
_location: jvmti::jlocation, _field_klass: jni::jclass, _object: jni::jobject,
_field: jni::jfieldID, _sig_type: std::os::raw::c_char, _new_value: jni::jvalue) {}
fn garbage_collection_start(&self) {}
fn garbage_collection_finish(&self) {}
fn resource_exhausted(&self, _jni: *mut jni::JNIEnv, _flags: jni::jint, _description: *const std::os::raw::c_char) {}
fn object_free(&self, _tag: jni::jlong) {}
fn vm_object_alloc(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject, _klass: jni::jclass, _size: jni::jlong) {}
fn sampled_object_alloc(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _object: jni::jobject, _klass: jni::jclass, _size: jni::jlong) {}
}
pub static GLOBAL_AGENT: OnceLock<Box<dyn Agent>> = OnceLock::new();
pub fn set_global_agent(agent: Box<dyn Agent>) -> Result<(), ()> {
GLOBAL_AGENT.set(agent).map_err(|_| ())
}
unsafe extern "system" fn trampoline_method_entry(
_jvmti_env: *mut sys::jvmti::jvmtiEnv,
jni_env: *mut jni::JNIEnv,
thread: jni::jthread,
method: jni::jmethodID,
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.method_entry(jni_env, thread, method);
}
}
unsafe extern "system" fn trampoline_method_exit(
_jvmti_env: *mut sys::jvmti::jvmtiEnv,
jni_env: *mut jni::JNIEnv,
thread: jni::jthread,
method: jni::jmethodID,
_was_popped: jni::jboolean,
_ret_val: jni::jvalue,
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.method_exit(jni_env, thread, method);
}
}
unsafe extern "system" fn trampoline_native_method_bind(
_env: *mut sys::jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID,
address: *mut std::os::raw::c_void, new_address_ptr: *mut *mut std::os::raw::c_void
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.native_method_bind(jni, thread, method, address, new_address_ptr); }
}
unsafe extern "system" fn trampoline_vm_init(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.vm_init(jni, thread); }
}
unsafe extern "system" fn trampoline_vm_death(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.vm_death(jni); }
}
unsafe extern "system" fn trampoline_vm_start(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.vm_start(jni); }
}
unsafe extern "system" fn trampoline_thread_start(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.thread_start(jni, thread); }
}
unsafe extern "system" fn trampoline_thread_end(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.thread_end(jni, thread); }
}
unsafe extern "system" fn trampoline_class_load(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, klass: jni::jclass) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.class_load(jni, thread, klass); }
}
unsafe extern "system" fn trampoline_class_prepare(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, klass: jni::jclass) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.class_prepare(jni, thread, klass); }
}
unsafe extern "system" fn trampoline_compiled_method_load(
_env: *mut jvmti::jvmtiEnv, method: jni::jmethodID, code_size: jni::jint, code_addr: *const std::os::raw::c_void,
map_length: jni::jint, map: *const std::os::raw::c_void, compile_info: *const std::os::raw::c_void
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.compiled_method_load(method, code_size, code_addr, map_length, map, compile_info); }
}
unsafe extern "system" fn trampoline_compiled_method_unload(_env: *mut jvmti::jvmtiEnv, method: jni::jmethodID, code_addr: *const std::os::raw::c_void) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.compiled_method_unload(method, code_addr); }
}
unsafe extern "system" fn trampoline_dynamic_code_generated(_env: *mut jvmti::jvmtiEnv, name: *const std::os::raw::c_char, address: *const std::os::raw::c_void, length: jni::jint) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.dynamic_code_generated(name, address, length); }
}
unsafe extern "system" fn trampoline_class_file_load_hook(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv,
class_being_redefined: jni::jclass, loader: jni::jobject, name: *const std::os::raw::c_char,
protection_domain: jni::jobject, class_data_len: jni::jint, class_data: *const std::os::raw::c_uchar,
new_class_data_len: *mut jni::jint, new_class_data: *mut *mut std::os::raw::c_uchar
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.class_file_load_hook(jni, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data);
}
}
unsafe extern "system" fn trampoline_exception(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID,
location: jvmti::jlocation, exception: jni::jobject, catch_method: jni::jmethodID, catch_location: jvmti::jlocation
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.exception(jni, thread, method, location, exception, catch_method, catch_location);
}
}
unsafe extern "system" fn trampoline_exception_catch(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID,
location: jvmti::jlocation, exception: jni::jobject
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.exception_catch(jni, thread, method, location, exception);
}
}
unsafe extern "system" fn trampoline_single_step(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID, location: jvmti::jlocation
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.single_step(jni, thread, method, location); }
}
unsafe extern "system" fn trampoline_breakpoint(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID, location: jvmti::jlocation
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.breakpoint(jni, thread, method, location); }
}
unsafe extern "system" fn trampoline_frame_pop(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID, was_popped: jni::jboolean
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.frame_pop(jni, thread, method, was_popped); }
}
unsafe extern "system" fn trampoline_monitor_wait(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, object: jni::jobject, timeout: jni::jlong) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.monitor_wait(jni, thread, object, timeout); }
}
unsafe extern "system" fn trampoline_monitor_waited(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, object: jni::jobject, timed_out: jni::jboolean) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.monitor_waited(jni, thread, object, timed_out); }
}
unsafe extern "system" fn trampoline_monitor_contended_enter(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, object: jni::jobject) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.monitor_contended_enter(jni, thread, object); }
}
unsafe extern "system" fn trampoline_monitor_contended_entered(_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, object: jni::jobject) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.monitor_contended_entered(jni, thread, object); }
}
unsafe extern "system" fn trampoline_field_access(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID,
location: jvmti::jlocation, field_klass: jni::jclass, object: jni::jobject, field: crate::sys::jni::jfieldID
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.field_access(jni, thread, method, location, field_klass, object, field); }
}
unsafe extern "system" fn trampoline_field_modification(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID,
location: jvmti::jlocation, field_klass: jni::jclass, object: jni::jobject, field: crate::sys::jni::jfieldID,
sig_type: std::os::raw::c_char, new_value: jni::jvalue
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.field_modification(jni, thread, method, location, field_klass, object, field, sig_type, new_value); }
}
unsafe extern "system" fn trampoline_garbage_collection_start(_env: *mut jvmti::jvmtiEnv) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.garbage_collection_start(); }
}
unsafe extern "system" fn trampoline_garbage_collection_finish(_env: *mut jvmti::jvmtiEnv) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.garbage_collection_finish(); }
}
unsafe extern "system" fn trampoline_resource_exhausted(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, flags: jni::jint,
_reserved: *const std::os::raw::c_void, description: *const std::os::raw::c_char
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.resource_exhausted(jni, flags, description); }
}
unsafe extern "system" fn trampoline_object_free(_env: *mut jvmti::jvmtiEnv, tag: jni::jlong) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.object_free(tag); }
}
unsafe extern "system" fn trampoline_vm_object_alloc(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread,
object: jni::jobject, klass: jni::jclass, size: jni::jlong
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.vm_object_alloc(jni, thread, object, klass, size); }
}
unsafe extern "system" fn trampoline_sampled_object_alloc(
_env: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread,
object: jni::jobject, klass: jni::jclass, size: jni::jlong
) {
if let Some(agent) = GLOBAL_AGENT.get() { agent.sampled_object_alloc(jni, thread, object, klass, size); }
}
pub fn get_default_callbacks() -> jvmti::jvmtiEventCallbacks {
let mut callbacks = jvmti::jvmtiEventCallbacks::default();
callbacks.VMInit = Some(trampoline_vm_init);
callbacks.VMDeath = Some(trampoline_vm_death);
callbacks.VMStart = Some(trampoline_vm_start);
callbacks.ThreadStart = Some(trampoline_thread_start);
callbacks.ThreadEnd = Some(trampoline_thread_end);
callbacks.ClassLoad = Some(trampoline_class_load);
callbacks.ClassPrepare = Some(trampoline_class_prepare);
callbacks.ClassFileLoadHook = Some(trampoline_class_file_load_hook);
callbacks.Exception = Some(trampoline_exception);
callbacks.ExceptionCatch = Some(trampoline_exception_catch);
callbacks.SingleStep = Some(trampoline_single_step);
callbacks.Breakpoint = Some(trampoline_breakpoint);
callbacks.FramePop = Some(trampoline_frame_pop);
callbacks.FieldAccess = Some(trampoline_field_access);
callbacks.FieldModification = Some(trampoline_field_modification);
callbacks.MethodEntry = Some(trampoline_method_entry);
callbacks.MethodExit = Some(trampoline_method_exit);
callbacks.NativeMethodBind = Some(trampoline_native_method_bind);
callbacks.CompiledMethodLoad = Some(trampoline_compiled_method_load);
callbacks.CompiledMethodUnload = Some(trampoline_compiled_method_unload);
callbacks.DynamicCodeGenerated = Some(trampoline_dynamic_code_generated);
callbacks.MonitorWait = Some(trampoline_monitor_wait);
callbacks.MonitorWaited = Some(trampoline_monitor_waited);
callbacks.MonitorContendedEnter = Some(trampoline_monitor_contended_enter);
callbacks.MonitorContendedEntered = Some(trampoline_monitor_contended_entered);
callbacks.GarbageCollectionStart = Some(trampoline_garbage_collection_start);
callbacks.GarbageCollectionFinish = Some(trampoline_garbage_collection_finish);
callbacks.ResourceExhausted = Some(trampoline_resource_exhausted);
callbacks.ObjectFree = Some(trampoline_object_free);
callbacks.VMObjectAlloc = Some(trampoline_vm_object_alloc);
callbacks.SampledObjectAlloc = Some(trampoline_sampled_object_alloc);
callbacks
}
#[macro_export]
macro_rules! export_agent {
($agent_type:ty) => {
#[no_mangle]
pub unsafe extern "system" fn Agent_OnLoad(
vm: *mut $crate::sys::jni::JavaVM,
options: *mut std::ffi::c_char,
reserved: *mut std::ffi::c_void,
) -> $crate::sys::jni::jint {
let agent = Box::new(<$agent_type>::default());
if let Err(_) = $crate::set_global_agent(agent) {
return $crate::sys::jni::JNI_ERR;
}
let options_str = if options.is_null() {
""
} else {
std::ffi::CStr::from_ptr(options).to_str().unwrap_or("")
};
if let Some(global_agent) = $crate::GLOBAL_AGENT.get() {
return global_agent.on_load(vm, options_str);
}
$crate::sys::jni::JNI_ERR
}
#[no_mangle]
pub unsafe extern "system" fn Agent_OnAttach(
vm: *mut $crate::sys::jni::JavaVM,
options: *mut std::ffi::c_char,
reserved: *mut std::ffi::c_void,
) -> $crate::sys::jni::jint {
let agent = Box::new(<$agent_type>::default());
if let Err(_) = $crate::set_global_agent(agent) {
return $crate::sys::jni::JNI_ERR;
}
let options_str = if options.is_null() {
""
} else {
std::ffi::CStr::from_ptr(options).to_str().unwrap_or("")
};
if let Some(global_agent) = $crate::GLOBAL_AGENT.get() {
return global_agent.on_attach(vm, options_str);
}
$crate::sys::jni::JNI_ERR
}
#[no_mangle]
pub unsafe extern "system" fn Agent_OnUnload(vm: *mut $crate::sys::jni::JavaVM) {
if let Some(agent) = $crate::GLOBAL_AGENT.get() {
agent.on_unload();
}
}
};
}