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_init_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread) {
self.vm_init(jni, thread);
}
fn vm_death(&self, _jni: *mut jni::JNIEnv) {}
fn vm_death_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv) {
self.vm_death(jni);
}
fn vm_start(&self, _jni: *mut jni::JNIEnv) {}
fn vm_start_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv) {
self.vm_start(jni);
}
fn thread_start(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn thread_end(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn virtual_thread_start(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {}
fn virtual_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_load_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, klass: jni::jclass) {
self.class_load(jni, thread, klass);
}
fn class_prepare(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _klass: jni::jclass) {}
fn class_prepare_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, klass: jni::jclass) {
self.class_prepare(jni, thread, klass);
}
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) {}
#[allow(clippy::too_many_arguments)]
fn class_file_load_hook_with_jvmti(&self, _jvmti: *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) {
self.class_file_load_hook(jni, class_being_redefined, loader, name, protection_domain, class_data_len, class_data, new_class_data_len, new_class_data);
}
fn method_entry(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID) {}
fn method_entry_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID) {
self.method_entry(jni, thread, method);
}
fn method_exit(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread, _method: jni::jmethodID) {}
fn method_exit_with_jvmti(&self, _jvmti: *mut jvmti::jvmtiEnv, jni: *mut jni::JNIEnv, thread: jni::jthread, method: jni::jmethodID) {
self.method_exit(jni, thread, method);
}
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 data_dump_request(&self) {}
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_with_jvmti(jvmti_env, 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_with_jvmti(jvmti_env, 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_with_jvmti(env, 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_with_jvmti(env, 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_with_jvmti(env, 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_virtual_thread_start(
_env: *mut jvmti::jvmtiEnv,
jni: *mut jni::JNIEnv,
thread: jni::jthread,
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.virtual_thread_start(jni, thread);
}
}
unsafe extern "system" fn trampoline_virtual_thread_end(
_env: *mut jvmti::jvmtiEnv,
jni: *mut jni::JNIEnv,
thread: jni::jthread,
) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.virtual_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_with_jvmti(env, 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_with_jvmti(env, 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_data_dump_request(_env: *mut jvmti::jvmtiEnv) {
if let Some(agent) = GLOBAL_AGENT.get() {
agent.data_dump_request();
}
}
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_with_jvmti(env, 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 {
jvmti::jvmtiEventCallbacks {
VMInit: Some(trampoline_vm_init),
VMDeath: Some(trampoline_vm_death),
ThreadStart: Some(trampoline_thread_start),
ThreadEnd: Some(trampoline_thread_end),
ClassFileLoadHook: Some(trampoline_class_file_load_hook),
ClassLoad: Some(trampoline_class_load),
ClassPrepare: Some(trampoline_class_prepare),
VMStart: Some(trampoline_vm_start),
Exception: Some(trampoline_exception),
ExceptionCatch: Some(trampoline_exception_catch),
SingleStep: Some(trampoline_single_step),
FramePop: Some(trampoline_frame_pop),
Breakpoint: Some(trampoline_breakpoint),
FieldAccess: Some(trampoline_field_access),
FieldModification: Some(trampoline_field_modification),
MethodEntry: Some(trampoline_method_entry),
MethodExit: Some(trampoline_method_exit),
NativeMethodBind: Some(trampoline_native_method_bind),
CompiledMethodLoad: Some(trampoline_compiled_method_load),
CompiledMethodUnload: Some(trampoline_compiled_method_unload),
DynamicCodeGenerated: Some(trampoline_dynamic_code_generated),
DataDumpRequest: Some(trampoline_data_dump_request),
MonitorWait: Some(trampoline_monitor_wait),
MonitorWaited: Some(trampoline_monitor_waited),
MonitorContendedEnter: Some(trampoline_monitor_contended_enter),
MonitorContendedEntered: Some(trampoline_monitor_contended_entered),
ResourceExhausted: Some(trampoline_resource_exhausted),
GarbageCollectionStart: Some(trampoline_garbage_collection_start),
GarbageCollectionFinish: Some(trampoline_garbage_collection_finish),
ObjectFree: Some(trampoline_object_free),
VMObjectAlloc: Some(trampoline_vm_object_alloc),
SampledObjectAlloc: Some(trampoline_sampled_object_alloc),
VirtualThreadStart: Some(trampoline_virtual_thread_start),
VirtualThreadEnd: Some(trampoline_virtual_thread_end),
..Default::default()
}
}
#[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();
}
}
};
}