Skip to main content

Crate jvmti_bindings

Crate jvmti_bindings 

Source
Expand description

§jvmti

Complete JNI and JVMTI bindings for Rust with zero dependencies.

This crate provides everything you need to build JVM agents in Rust:

  • Low-level FFI bindings to JNI and JVMTI
  • High-level wrappers with ergonomic Rust APIs
  • The Agent trait and export_agent! macro for easy agent creation

§Features

  • Complete Coverage: All 236 JNI functions, all 156 JVMTI functions
  • Zero Dependencies: No external crates required
  • Ergonomic API: High-level wrappers handle strings, arrays, references
  • Type-Safe: Proper Rust types, Result returns, RAII guards
  • JDK 8-27 Compatible: Verified against JDK 27 headers

§Quick Start

Create a minimal agent in 4 steps:

1. Create a new library crate:

cargo new --lib my_agent

2. Configure Cargo.toml:

[lib]
crate-type = ["cdylib"]

[dependencies]
jvmti = "0.1"

3. Implement your agent (src/lib.rs):

use jvmti_bindings::prelude::*;

#[derive(Default)]
struct MyAgent;

impl Agent for MyAgent {
    fn on_load(&self, vm: *mut jni::JavaVM, options: &str) -> jni::jint {
        println!("[MyAgent] Loaded with options: {}", options);
        jni::JNI_OK
    }

    fn vm_init(&self, _jni: *mut jni::JNIEnv, _thread: jni::jthread) {
        println!("[MyAgent] VM initialized!");
    }

    fn vm_death(&self, _jni: *mut jni::JNIEnv) {
        println!("[MyAgent] VM shutting down");
    }
}

export_agent!(MyAgent);

4. Build and run:

cargo build --release
java -agentpath:./target/release/libmy_agent.so=myoptions MyApp

§Architecture

The crate is organized in layers:

┌─────────────────────────────────────────────────────────┐
│                    Your Agent Code                       │
│         impl Agent for MyAgent { ... }                   │
├─────────────────────────────────────────────────────────┤
│                   Agent Trait + Macros                   │
│      Agent, export_agent!, get_default_callbacks()       │
├─────────────────────────────────────────────────────────┤
│              High-Level Wrappers (env module)            │
│   env::Jvmti - JVMTI operations with Result returns      │
│   env::JniEnv - JNI operations with string helpers       │
│   env::LocalRef, GlobalRef - RAII reference guards       │
├─────────────────────────────────────────────────────────┤
│              Raw FFI Bindings (sys module)               │
│   sys::jni - JNI types, vtable (236 functions)           │
│   sys::jvmti - JVMTI types, vtable (156 functions)       │
└─────────────────────────────────────────────────────────┘

§Modules

ModulePurpose
sys::jniRaw JNI types and vtable (for FFI)
sys::jvmtiRaw JVMTI types, vtable, capabilities, events
[env]High-level wrappers - start here for ergonomic APIs
env::JvmtiJVMTI environment wrapper (153 methods)
env::JniEnvJNI environment wrapper (60+ methods)
classfileClass file parser with all Java 8-27 attributes
preludeRecommended imports for agents
advancedFeature-gated advanced helpers (heap graph utilities)

§Enabling JVMTI Events

To receive JVMTI events, you must:

  1. Request the required capabilities
  2. Set up event callbacks
  3. Enable the specific events
use jvmti_bindings::prelude::*;

#[derive(Default)]
struct ClassMonitor;

impl Agent for ClassMonitor {
    fn on_load(&self, vm: *mut jni::JavaVM, _options: &str) -> jni::jint {
        let jvmti_env = Jvmti::new(vm).expect("Failed to get JVMTI");

        // 1. Request capabilities
        let mut caps = jvmti::jvmtiCapabilities::default();
        caps.set_can_generate_all_class_hook_events(true);
        jvmti_env.add_capabilities(&caps).expect("capabilities");

        // 2. Set up callbacks (wires all events to your Agent impl)
        let callbacks = get_default_callbacks();
        jvmti_env.set_event_callbacks(callbacks).expect("callbacks");

        // 3. Enable specific events
        jvmti_env.set_event_notification_mode(
            true,  // enable
            jvmti::JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
            std::ptr::null_mut()  // all threads
        ).expect("enable event");

        jni::JNI_OK
    }

    fn class_file_load_hook(&self, _jni: *mut jni::JNIEnv, /* ... */) {
        // Called for every class load!
    }
}

export_agent!(ClassMonitor);

§Working with JNI

Use env::JniEnv for ergonomic JNI operations:

use jvmti_bindings::prelude::*;

fn vm_init(&self, jni_ptr: *mut jni::JNIEnv, _thread: jni::jthread) {
    let jni = unsafe { JniEnv::from_raw(jni_ptr) };

    // Find a class
    let system_class = jni.find_class("java/lang/System").unwrap();

    // Get a static field
    let out_field = jni.get_static_field_id(system_class, "out", "Ljava/io/PrintStream;").unwrap();
    let out = jni.get_static_object_field(system_class, out_field);

    // Create a Java string
    let message = jni.new_string_utf("Hello from Rust!").unwrap();

    // Call a method
    let print_class = jni.find_class("java/io/PrintStream").unwrap();
    let println_method = jni.get_method_id(print_class, "println", "(Ljava/lang/String;)V").unwrap();
    jni.call_void_method(out, println_method, &[jni::jvalue { l: message }]);

    // Check for exceptions
    if jni.exception_check() {
        jni.exception_describe();
        jni.exception_clear();
    }
}

§Version Compatibility

JDK VersionJNI FunctionsJVMTI FunctionsNotes
8232153Baseline
9233156+GetModule, +Module functions
11233156+SetHeapSamplingInterval
21234156+IsVirtualThread, +Virtual thread support
24/25235156+GetStringUTFLengthAsLong
27235156+ClearAllFramePops (slot 67)

Re-exports§

pub use crate::sys::jni;

Modules§

advanced
Advanced helpers for JVMTI power users.
classfile
Class file parser for Java 8 through 27.
embed
Helpers for embedding a JVM inside a Rust process.
env
High-level environment wrappers for JVMTI and JNI.
prelude
Common imports for building JVMTI agents.
sys

Macros§

export_agent
Exports your agent type as a loadable JVMTI agent library.
jni_call
Helper to call JNI functions through the vtable. env_ptr: *mut JNIEnv = *mut *const JNINativeInterface_ *env_ptr: *const JNINativeInterface_ (vtable pointer) **env_ptr: JNINativeInterface_ (vtable itself) Usage: jni_call!(env, FindClass, b“java/lang/String\0“.as_ptr() as *const c_char)
jvm_call
Helper to call JavaVM functions through the vtable. vm_ptr: *mut JavaVM = *mut *const JNIInvokeInterface_ *vm_ptr: *const JNIInvokeInterface_ (vtable pointer) **vm_ptr: JNIInvokeInterface_ (vtable itself)

Statics§

GLOBAL_AGENT

Traits§

Agent
The core trait for implementing a JVMTI agent.

Functions§

get_default_callbacks
Returns a pre-configured jvmtiEventCallbacks struct with all event trampolines wired up.
set_global_agent
Helper to initialize the global agent (called by the macro)