jvmti-bindings 2.1.0

Complete JNI and JVMTI bindings for Rust with zero dependencies. Build JVM agents in pure Rust.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# jvmti-bindings
[![Crates.io](https://img.shields.io/crates/v/jvmti-bindings.svg)](https://crates.io/crates/jvmti-bindings)
[![Docs.rs](https://docs.rs/jvmti-bindings/badge.svg)](https://docs.rs/jvmti-bindings)
[![CI](https://github.com/JavaPerformance/jvmti/actions/workflows/ci.yml/badge.svg)](https://github.com/JavaPerformance/jvmti/actions/workflows/ci.yml)

Write JVM agents in Rust with explicit safety boundaries and production-grade ergonomics.

Complete JNI and JVMTI bindings plus higher-level abstractions designed for building profilers, tracers, debuggers, and runtime instrumentation — without writing C or C++.

This crate focuses on:
- Making ownership, lifetimes, and error handling explicit
- Reducing common JVMTI footguns
- Keeping unsafe behavior localized and auditable

It is intended for serious native JVM tooling, not just experimentation.

## Why This Exists

JVMTI is powerful — and notoriously easy to misuse.

Typical problems when writing agents:
- Unchecked error codes that silently corrupt state
- Invalid reference lifetimes causing segfaults
- Allocator mismatches leaking memory
- Thread-local `JNIEnv` misuse across callbacks
- Undocumented callback constraints causing deadlocks

Most existing Rust options either:
- Expose raw bindings with little guidance
- Rely on build-time bindgen
- Are incomplete or unmaintained (7+ years)
- Optimize for JNI, not JVMTI agents

This crate was designed around how agents are actually written, not around mirroring C headers.

## Comparison with Alternatives

If you only need JNI to call into Java from Rust applications, crates like `jni` or `jni-simple` are often sufficient. This crate is purpose-built for **JVMTI agents** (profilers, tracers, debuggers, instrumentation) and emphasizes:

1. Full JNI + JVMTI coverage (agent-first focus)
2. Safe, owned return types in the high-level `env` wrappers
3. Class file parsing with all standard Java 8-27 attributes
4. A tiny but explicit public surface (`env`, `sys`, `classfile`, `prelude`)
5. Safety guidance, pitfalls, and compatibility documentation
6. Examples that mirror real JVMTI tooling patterns

## Why Rust for JVMTI?

C++ is the traditional choice, but Rust offers compelling advantages:

- **Memory safety without GC** — JVMTI agents run inside the JVM process; a segfault kills the application
- **Fearless concurrency** — JVMTI callbacks fire from multiple threads simultaneously
- **Zero-cost abstractions** — RAII guards and Result types add safety without runtime overhead
- **No runtime dependencies** — Deploy a single `.so`/`.dylib`/`.dll` with no external libraries
- **Modern tooling** — Cargo, docs.rs, and crates.io beat Makefiles and manual distribution

Java agents (`java.lang.instrument`) are simpler but can't access low-level features like heap iteration, breakpoints, or raw bytecode hooks.

## Design Goals

| Goal | How |
|------|-----|
| **Explicit safety model** | Unsafe operations centralized; APIs return `Result` |
| **Complete surface** | All 236 JNI + 156 JVMTI functions, mapped to Rust types |
| **Agent-first ergonomics** | Structured callbacks, capability management, RAII resources |
| **No hidden dependencies** | No bindgen, no build-time JVM, no global allocators |
| **Long-term compatibility** | Verified against OpenJDK headers, JDK 8 through 27 |

## Safety and FFI

This crate is built around explicit safety boundaries. See `docs/SAFETY.md` and `docs/PITFALLS.md` for the full checklist.

Key rules:
1. Never use `JNIEnv` across threads.
2. Never panic across JNI/JVMTI callbacks.
3. Always deallocate JVMTI buffers with `Deallocate`.
4. Avoid JNI calls in GC callbacks.

## Public API

The supported public surface is intentionally small. For most users:
1. Use `env` for safe wrappers.
2. Use `prelude` for standard imports.
3. Use `sys` only for raw FFI work.

Details: `docs/PUBLIC_API.md`.

## Raw FFI Access

If you need raw JNI/JVMTI functions, use:
1. `jvmti_bindings::sys::jni` and `jvmti_bindings::sys::jvmti` for raw types and vtables.
2. `JniEnv::raw()` and `Jvmti::raw()` to access the underlying raw pointers.

## Attach and Threading Rules

1. `Agent_OnAttach` is supported via the `export_agent!` macro and `Agent::on_attach`.
2. `JNIEnv` is thread-local and must only be used on its originating thread.
3. `GlobalRef` cleanup attaches to the JVM when needed, but you should still manage lifetimes explicitly.

## Compatibility

See `docs/COMPATIBILITY.md` for a full JDK 8-27 matrix.

## Advanced Helpers

Feature-gated helpers live under `advanced`:
1. `heap-graph` for heap tagging and reference edge extraction.

Enable with:

```toml
[dependencies]
jvmti-bindings = { version = "2", features = ["heap-graph"] }
```

## Quick Start

### 1. Create your crate

```bash
cargo new --lib my_agent
cd my_agent
```

### 2. Configure Cargo.toml

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

[dependencies]
jvmti-bindings = "2"
```

### 3. Implement your agent

```rust
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

```bash
cargo build --release

# Linux
java -agentpath:./target/release/libmy_agent.so=myoptions MyApp

# macOS
java -agentpath:./target/release/libmy_agent.dylib=myoptions MyApp

# Windows
java -agentpath:./target/release/my_agent.dll=myoptions MyApp
```

### Dynamic Attach (Optional)

If you want to attach to an already running JVM, implement `Agent::on_attach` and load the agent with the JVM Attach API:

```rust,ignore
use jvmti_bindings::prelude::*;

#[derive(Default)]
struct AttachLogger;

impl Agent for AttachLogger {
    fn on_attach(&self, vm: *mut jni::JavaVM, options: &str) -> jni::jint {
        println!("[AttachLogger] attached with options: {}", options);
        let _jvmti = Jvmti::new(vm).expect("get JVMTI");
        jni::JNI_OK
    }
}

export_agent!(AttachLogger);
```

Attach it with `jcmd` (example):

```bash
jcmd <pid> JVMTI.agent_load /abs/path/to/libattach_logger.so "opt1=val1"
```

`JVMTI.agent_load` expects an **absolute** path to the native agent and an optional option string.

## Class File Parsing

This crate now includes a zero-dependency class file parser that understands all standard attributes from Java 8 through Java 27. Use it inside `ClassFileLoadHook` to inspect or transform class metadata.

```rust
use jvmti_bindings::classfile::ClassFile;

fn parse_class(bytes: &[u8]) {
    let classfile = ClassFile::parse(bytes).expect("valid class file");
    println!("major version = {}", classfile.major_version);
    println!("attributes = {}", classfile.attributes.len());
}
```

Nested attributes are preserved and exposed (method `Code` attributes, record component attributes, and more). You can traverse them like this:

```rust
use jvmti_bindings::classfile::{AttributeInfo, ClassFile, RecordComponent};

fn walk_attributes(attrs: &[AttributeInfo]) {
    for attr in attrs {
        match attr {
            AttributeInfo::Code(code) => walk_attributes(&code.attributes),
            AttributeInfo::Record { components } => {
                for RecordComponent { attributes, .. } in components {
                    walk_attributes(attributes);
                }
            }
            _ => {}
        }
    }
}

fn parse_class(bytes: &[u8]) {
    let classfile = ClassFile::parse(bytes).expect("valid class file");
    walk_attributes(&classfile.attributes);
    for field in &classfile.fields {
        walk_attributes(&field.attributes);
    }
    for method in &classfile.methods {
        walk_attributes(&method.attributes);
    }
}
```

## Embedding A JVM (Optional)

If you want to **embed** a JVM inside a Rust process (not just build an agent), enable the `embed` feature and use `JavaVmBuilder`:

```rust,ignore
use jvmti_bindings::prelude::*;

let builder = JavaVmBuilder::new(jni::JNI_VERSION_1_8)
    .option("-Xms64m")?
    .option("-Xmx256m")?
    .option("-Djava.class.path=./myapp.jar")?;

let vm = builder.create()?; // uses JAVA_HOME or JVM_LIB_PATH
let env = unsafe { vm.creator_env() }; // only valid on the creating thread

// ... call JNI through env ...

vm.destroy()?;
```

This is feature-gated so the crate remains dependency-free by default. See `docs/EMBEDDING.md` and `examples/embed.rs` for details.

## Examples

Included examples (build as `cdylib` agents):
1. `examples/minimal.rs`
2. `examples/class_logger.rs`
3. `examples/profiler.rs`
4. `examples/tracer.rs`
5. `examples/heap_sampler.rs`
6. `examples/attach_logger.rs` (dynamic attach via `Agent_OnAttach`)

Embedding example (binary):
`examples/embed.rs` (run with `cargo run --example embed --features embed`)

## Agent Starter Template

See `templates/agent-starter/` for a ready-to-copy agent crate.

## CI

The repository includes a GitHub Actions workflow that builds and tests on Linux, macOS, and Windows.

## What `export_agent!` Does

The macro generates the native entry points the JVM expects.

**It does:**
- Generate `Agent_OnLoad` / `Agent_OnUnload` / `Agent_OnAttach` entry points
- Create your agent instance and store it globally (must be `Sync + Send`)
- Pass the options string to your `on_load` / `on_attach` implementation

**It does not:**
- Hide undefined JVMTI behavior
- Make callbacks re-entrant or async-safe
- Attach arbitrary native threads automatically
- Obtain the JVMTI environment for you
- Register callbacks or enable events
- Prevent JVM crashes from invalid JVMTI usage

The goal is clarity, not magic.

## Safety Model

This crate enforces the following invariants:

| Invariant | Enforcement |
|-----------|-------------|
| `JNIEnv` is thread-local | `JniEnv` wrapper is not `Send` |
| Local refs don't escape | `LocalRef<'a>` tied to `JniEnv` lifetime |
| Global refs are freed | `GlobalRef` releases on `Drop` |
| JVMTI memory properly freed | High-level JVMTI methods deallocate buffers they allocate |
| Errors are explicit | JVMTI methods return `Result`, JNI helpers use `Option`/`Result` |

### What Remains Unsafe

Some things cannot be made safe by design:

- **Bytecode transformation correctness** — invalid bytecode crashes the JVM
- **Callback timing assumptions** — JVMTI events fire at specific phases
- **Blocking in callbacks** — long operations in GC callbacks deadlock
- **Cross-thread reference sharing** — JNI local refs are thread-local

Rust helps — but JVMTI is still a sharp tool.

## Is This For You?

**Yes, if you are:**
- Building profilers, tracers, debuggers, or instrumentation
- Want Rust's type system around JVMTI's sharp edges
- Need a single crate that works across JDK 8–27
- Comfortable reading JVMTI docs for advanced use cases

**Probably not, if you:**
- Only need basic JNI calls (consider the `jni` crate)
- Are uncomfortable debugging native JVM crashes
- Need dynamic attach (use `Agent::on_attach` / `Agent_OnAttach`)
- Want zero `unsafe` anywhere

## Architecture

```
┌─────────────────────────────────────────────────────────┐
│                    Your Agent Code                       │
│         impl Agent for MyAgent { ... }                   │
├─────────────────────────────────────────────────────────┤
│                   Agent Trait + Macros                   │
│      Agent, export_agent!, get_default_callbacks()       │
├─────────────────────────────────────────────────────────┤
│              High-Level Wrappers (env module)            │
│   Jvmti      - JVMTI operations (150+ methods)           │
│   JniEnv     - JNI operations (60+ methods)              │
│   LocalRef   - RAII guard, prevented from escaping       │
│   GlobalRef  - RAII guard, releases on drop              │
├─────────────────────────────────────────────────────────┤
│              Class File Parser (classfile)               │
│   ClassFile  - All standard Java 8-27 attributes         │
├─────────────────────────────────────────────────────────┤
│              Convenience Imports (prelude)               │
│   prelude::* - Agent, env, sys, helpers                  │
├─────────────────────────────────────────────────────────┤
│              Raw FFI Bindings (sys module)               │
│   sys::jni   - Complete JNI vtable (236 functions)       │
│   sys::jvmti - Complete JVMTI vtable (156 functions)     │
└─────────────────────────────────────────────────────────┘
```

## Enabling Events

Events require three steps — capabilities, callbacks, then enable:

```rust
use jvmti_bindings::prelude::*;

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 (must happen in on_load)
    let mut caps = jvmti::jvmtiCapabilities::default();
    caps.set_can_generate_all_class_hook_events(true);
    jvmti_env.add_capabilities(&caps).expect("capabilities");

    // 2. Wire callbacks to your Agent impl
    let callbacks = get_default_callbacks();
    jvmti_env.set_event_callbacks(callbacks).expect("callbacks");

    // 3. Enable specific events
    jvmti_env.enable_event(
        jvmti::JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
        std::ptr::null_mut(),
    ).expect("enable");

    jni::JNI_OK
}
```

## Capabilities Reference

| Capability | Required For |
|------------|--------------|
| `can_generate_all_class_hook_events` | `class_file_load_hook` |
| `can_generate_method_entry_events` | `method_entry` |
| `can_generate_method_exit_events` | `method_exit` |
| `can_generate_exception_events` | `exception`, `exception_catch` |
| `can_tag_objects` | Object tagging, heap iteration |
| `can_retransform_classes` | `retransform_classes()` |
| `can_redefine_classes` | `redefine_classes()` |
| `can_get_bytecodes` | `get_bytecodes()` |
| `can_get_line_numbers` | `get_line_number_table()` |
| `can_access_local_variables` | `get_local_*()`, `set_local_*()` |

## JDK Compatibility

| JDK | Status | Notable Additions |
|-----|--------|-------------------|
| 8   | ✅ Tested | Baseline |
| 11  | ✅ Tested | `SetHeapSamplingInterval` |
| 17  | ✅ Tested ||
| 21  | ✅ Tested | Virtual thread support |
| 27  | ✅ Verified | `ClearAllFramePops` |

Bindings generated from JDK 27 headers, backwards compatible to JDK 8.

## Project Status

| Aspect | Status |
|--------|--------|
| API stability | Pre-1.0, breaking changes possible |
| JVMTI coverage | 156/156 (100%) |
| JNI coverage | 236/236 (100%) |
| Dependencies | Zero |
| Testing | Header verification, example agents |

## Examples

```bash
# Minimal agent — lifecycle events only
cargo build --release --example minimal

# Method counter — counts all method entries/exits
cargo build --release --example method_counter

# Class logger — logs every class load
cargo build --release --example class_logger
```

## Documentation

- [**Your First Production Agent**]docs/FIRST_AGENT.md — Step-by-step guide with production hardening
- [**Public API Surface**]docs/PUBLIC_API.md — What is stable and supported
- [**API Stability Checklist**]docs/API_STABILITY.md — Pre-1.0 stability rules
- [**Contributor Style Guide**]docs/STYLE_GUIDE.md — Prelude-first and API consistency
- [**Public API Report**]docs/PUBLIC_API_REPORT.md — Snapshot of the public surface
- [**API Report Script**]scripts/public_api_report.sh — Regenerate the report with rustdoc JSON
- [**Changelog**]CHANGELOG.md — Release notes and breaking changes
- [**Comparison With Alternatives**]docs/COMPARISON.md — Feature parity and positioning
- [**Benchmarks**]docs/BENCHMARKS.md — How to run and view Criterion reports
- [**Embedding A JVM**]docs/EMBEDDING.md — Start a JVM from Rust and attach threads
- [**Dynamic Attach**]docs/ATTACH.md — Agent_OnAttach example and notes
- [**Safety and FFI Checklist**]docs/SAFETY.md — Safety rules and audit checklist
- [**Pitfalls and Footguns**]docs/PITFALLS.md — Common JVMTI/JNI traps
- [**Compatibility Matrix**]docs/COMPATIBILITY.md — JDK 8-27 coverage
- [**Versioning Policy**]docs/VERSIONING.md — API stability and SemVer plan
- [**API Reference**]https://docs.rs/jvmti-bindings — Complete API documentation on docs.rs

## License

MIT OR Apache-2.0