Skip to main content

JdwpConnection

Struct JdwpConnection 

Source
pub struct JdwpConnection { /* private fields */ }

Implementations§

Source§

impl JdwpConnection

Source

pub async fn connect(host: &str, port: u16) -> JdwpResult<Self>

Connect to a JVM via JDWP

§Errors

Returns a JdwpError if the TCP connection or JDWP handshake fails.

Source

pub fn set_read_only(&self, read_only: bool)

Refuse every mutation of the debuggee on this connection from now on (and on every clone of it).

This is the enforcement point for read-only debugging, and per ADR-0001 it is the only one: the MCP layer above does not decide what counts as mutation, the wire does. Every primitive that changes the debuggee returns JdwpError::ReadOnly instead of sending its packet — the two invocations, the four writes, a forced early return, and, since SAFE-9, a class redefinition and a frame pop. It deliberately does not restrict reads — fields, locals, arrays and type metadata are all plain JDWP reads and keep working.

“Mutation” here is wider than “runs code”. A class redefinition invokes nothing, writes no field and forces no return, yet replaces the running program — and unlike every other entry on that list it outlives the connection, so it is the one that least tolerates being missed.

A guard against accident, not a security boundary: anyone who can reach the JDWP port can open their own connection without it.

Source

pub fn is_read_only(&self) -> bool

Whether this connection refuses to mutate the debuggee.

Source

pub fn set_invoke_timeout_ms(&self, ms: u64)

Set how long a debuggee invocation may take before it is abandoned. 0 disables the budget.

Source

pub fn invoke_timeout_ms(&self) -> u64

The current invocation budget in milliseconds; 0 means unbounded.

Source

pub async fn send_command( &mut self, packet: CommandPacket, ) -> JdwpResult<ReplyPacket>

Send a command and wait for reply

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub fn round_trips(&self) -> u32

How many round trips this connection has waited for.

The second cost figure, and PERF-1 (#100) is why there are two. A packet count says what was put on the wire; this says how many times the wire was waited on, and until independent reads existed the two were the same number. They are not any more: a wave of sixteen reads is sixteen packets and about one round trip, so on a remote JVM this is the figure that predicts the wait and the packet count is the figure that predicts nothing about it.

Derived from the window, not observed on the socket, and the difference is worth knowing. A single read counts one. A wave of n counts ceil(n / MAX_READS_IN_FLIGHT), because at most a window’s worth can be outstanding at once — so n reads cannot take fewer sequential batches than that, and the sliding window reaches the bound within one. It is therefore a tight lower bound rather than a measurement, which is why every reply that prints it prints it with a ~.

Source

pub async fn read_independently( &self, packets: Vec<CommandPacket>, ) -> Vec<JdwpResult<ReplyPacket>>

Issue independent reads together and return one result per command, in the order given.

The term is CONTEXT.md‘s and it names a licence, not a mechanism: these commands’ requests must not depend on each other’s replies. That is a property of the sequence and has to be established at the call site — nothing here can check it, and this doc comment is not permission. ADR-0038 records what the licence rests on; three real sequences in this server do not have it.

What it buys is round trips, not packets. Every command still gets its own id from the same counter, so packets_sent is unchanged and the packet-bound tests are unaffected by construction. What changes is that n reads cost about one round trip instead of n, and — where the reads happen under a suspension — the suspension is shorter by the difference. On loopback that difference is nearly nothing; it is a remote JVM this exists for.

Every reply is awaited, including after one has failed. There is no first-error-wins arm and that is deliberate: the commands are already on the wire and JDWP has no way to recall one, so abandoning the wait would abandon only the answer while the JVM did the work anyway. A caller wanting to stop at the first failure can do that to the returned Vec at no cost to the wire. A failed command therefore cannot desynchronise its siblings — see InFlight for why it cannot desynchronise the stream either.

Results are positional: result[i] answers packets[i], whether it succeeded, failed at the JVM, or was never written. An error reply is Ok here and carries its error code, exactly as send_command returns it; the caller still owes it a check_error.

Source

pub async fn try_recv_event(&self) -> Option<EventSet>

Try to receive an event without blocking.

Returns None immediately if no events are available in the queue. This is useful for polling events without blocking the current task.

§Example
if let Some(event) = connection.try_recv_event().await {
    // Handle event
}
Source

pub async fn recv_event(&self) -> Option<EventSet>

Wait for the next event (blocking).

This method blocks until an event is available or the event channel is closed. Use this when you want to wait for events like breakpoints or exceptions.

Returns None if the event loop has shut down.

§Example
while let Some(event) = connection.recv_event().await {
    // Process event
}
Source

pub fn next_id(&self) -> u32

Generate next packet ID

Source

pub fn packets_sent(&self) -> u32

How many command packets this connection has issued.

The measurement instrument for anything that claims to cut JVM round trips. Wall-clock is the wrong tool over loopback, where a round trip is sub-millisecond and noise swamps the signal — that mistake is why the type cache first looked like it did nothing (0.98s → 0.95s). Packet count is what actually differs on a remote JVM, and it is deterministic.

Every command takes exactly one id from the same counter, so the difference across an operation is its packet cost. Events are pushed by the JVM and never counted here.

Source§

impl JdwpConnection

Source

pub async fn get_signature( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<String>

ReferenceType.Signature — JNI signature of a type, e.g. “Lbr/com/x/WSReserva;”.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_signatures_independently( &self, ref_type_ids: &[ReferenceTypeId], ) -> Vec<JdwpResult<String>>

The signature of each of ref_type_ids, read as independent reads (PERF-1, #100).

Cache-aware, and that is the whole of its packet story. get_signature is the most frequently asked question in the tool precisely because it is nearly always a TypeCache hit, so a wave that asked the JVM for every id would turn free lookups into packets — the one way this could cost more than the loop it replaces. Only the misses are waved; every answer, hit or miss, comes back in its own position, and the misses are written back to the cache exactly as the single read writes them.

Independent because a loaded type’s signature never changes and no id’s answer is needed to ask about another.

Source

pub async fn get_superclass( &mut self, class_id: ClassId, ) -> JdwpResult<Option<ClassId>>

ClassType.Superclass — direct superclass of a class (None for java.lang.Object).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_this_object( &mut self, thread_id: ThreadId, frame_id: FrameId, ) -> JdwpResult<ObjectId>

StackFrame.ThisObject — the this reference for a frame (0 = static method).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn invoke_method( &mut self, object_id: ObjectId, thread_id: ThreadId, class_id: ClassId, method_id: MethodId, args: Vec<Value>, ) -> JdwpResult<(Value, ObjectId)>

ObjectReference.InvokeMethod — invoke an instance method on a suspended thread. Returns (return value, exception object id) — exception id 0 means no exception. Uses INVOKE_SINGLE_THREADED so only the target thread runs during the call.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed, or JdwpError::ReadOnly if the connection refuses invocation (set_read_only).

Source

pub async fn invoke_static_method( &mut self, class_id: ClassId, thread_id: ThreadId, method_id: MethodId, args: Vec<Value>, ) -> JdwpResult<(Value, ObjectId)>

ClassType.InvokeMethod — invoke a static method on a suspended thread. Returns (return value, exception object id) — exception id 0 means no exception.

class_id must be the class that declares method_id (walk the superclass chain to find it, as ObjectReference.InvokeMethod requires too). Uses INVOKE_SINGLE_THREADED so only the target thread runs during the call.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed, or JdwpError::ReadOnly if the connection refuses invocation (set_read_only).

Source§

impl JdwpConnection

Source

pub async fn set_breakpoint( &mut self, class_id: ReferenceTypeId, method_id: MethodId, bytecode_index: u64, suspend_policy: SuspendPolicy, ) -> JdwpResult<i32>

Set a breakpoint at a specific location (EventRequest.Set command) Returns the request ID for this breakpoint

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_breakpoint(&mut self, request_id: i32) -> JdwpResult<()>

Clear a breakpoint by request ID (EventRequest.Clear command)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_class_prepare( &mut self, class_pattern: &str, suspend_policy: SuspendPolicy, ) -> JdwpResult<i32>

Request notification when a class matching class_pattern is prepared/loaded (EventRequest.Set, eventKind CLASS_PREPARE, with a ClassMatch modifier). The pattern is a dotted class name, optionally with a leading/trailing * wildcard (e.g. br.com.infotravel.service.PontoVendaSrv). Returns the request id. This is the primitive behind deferred (“class not loaded yet”) breakpoints: register it, then arm the real breakpoint when the matching ClassPrepare event arrives.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_class_prepare(&mut self, request_id: i32) -> JdwpResult<()>

Clear a CLASS_PREPARE request by id (EventRequest.Clear command).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_exception_request( &mut self, ref_type: Option<ReferenceTypeId>, caught: bool, uncaught: bool, suspend_policy: SuspendPolicy, ) -> JdwpResult<i32>

Break when an exception is thrown (EventRequest.Set, eventKind EXCEPTION, with an ExceptionOnly modifier). ref_type restricts to a single exception class and its subclasses; pass None (or 0) to catch every exception — noisy, since a live JVM throws and catches exceptions internally all the time, so prefer a concrete type. caught / uncaught select which throws to report (at least one should be true). Returns the request id. This is the primitive behind debug.set_exception_stop.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_exception_request_ex( &mut self, ref_type: Option<ReferenceTypeId>, caught: bool, uncaught: bool, suspend_policy: SuspendPolicy, filters: EventFilters, ) -> JdwpResult<i32>

As set_exception_request, plus optional ThreadOnly (report only throws on one thread — the single biggest noise reduction on a busy app server, FILT-1) and Count.

Count reports only the Nth throw and then the JVM deletes the request — it is not a sampler, so count: 5 gives you throw #5 and nothing before or after it.

It is not what bounds trace mode, and no caller in this workspace passes it: every call site gives None. The trace-hit budget is counted server-side by decrement_trace_budget, because the requirement is “record the first N hits, then stop” and Count cannot express that — it would silently record one trace instead of N. See ADR-0002, which rejected Count for exactly this and notes the JVM-side expiry is attractive enough that it was nearly re-proposed after being turned down once. This doc comment previously claimed the opposite; a maintainer who believed it might remove the server-side counter as redundant.

Count is the right tool for hit_count (“stop on the Nth hit”), which is what it means, and that is where set_breakpoint_ex uses it.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_exception_request( &mut self, request_id: i32, ) -> JdwpResult<()>

Clear an EXCEPTION request by id (EventRequest.Clear command).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_field_watch( &mut self, ref_type: ReferenceTypeId, field_id: FieldId, kind: WatchKind, suspend_policy: SuspendPolicy, ) -> JdwpResult<i32>

Watch one field (EventRequest.Set with a FieldOnly modifier) — the primitive behind debug.set_field_stop, answering “who touches this field?”.

kind picks WatchKind::Modify (FIELD_MODIFICATION — fires before the store commits, so the field still reads as its old value) or WatchKind::Access (FIELD_ACCESS, every read — far noisier). ref_type must be the type that declares the field, and field_id one of its fields; a field id from a subclass is rejected by the JVM. Returns the request id.

The JVM must report canWatchFieldModification / canWatchFieldAccess; HotSpot does, but watchpoints disable JIT optimisation of that field, so expect the debuggee to slow down.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. A JVM without the capability answers NOT_IMPLEMENTED (99).

Source

pub async fn set_field_watch_ex( &mut self, ref_type: ReferenceTypeId, field_id: FieldId, kind: WatchKind, suspend_policy: SuspendPolicy, filters: EventFilters, ) -> JdwpResult<i32>

As set_field_watch, plus optional ThreadOnly (report only touches from one thread, FILT-1) and Count — which reports only the Nth touch before the JVM deletes the request, and which no caller here passes. See set_exception_request_ex for why the trace budget is counted server-side instead (ADR-0002).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. A JVM without the capability answers NOT_IMPLEMENTED (99).

Source

pub async fn set_method_exit_request( &mut self, class_pattern: &str, with_return_value: bool, suspend_policy: SuspendPolicy, count: Option<i32>, thread: Option<ThreadId>, ) -> JdwpResult<i32>

Report every return from a method of a class matching class_pattern (EventRequest.Set with a ClassMatch modifier) — the primitive behind debug.set_method_exit_stop, answering “what did this method actually return?” without having to guess which return statement runs.

with_return_value picks METHOD_EXIT_WITH_RETURN_VALUE (kind 42), which carries the returned value, over a plain METHOD_EXIT (kind 41), which only says a return happened. Kind 42 needs JDWP ≥ 1.6 — ask can_get_method_return_values, because unlike the monitor features this is not a capability bit, so an old JVM answers with a protocol error rather than NOT_IMPLEMENTED.

class_pattern is a dotted class name, optionally with a leading/trailing *. JDWP has no method-name modifier, so a request on a class fires on every method of it; narrowing to one method is the caller’s job. count and thread add the Count and ThreadOnly modifiers, and this event needs them more than any other: a suspending method exit on a hot method is the fastest way to freeze a shared JVM this crate offers.

Returns the request id.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_method_exit_request_ex( &mut self, class_pattern: &str, with_return_value: bool, suspend_policy: SuspendPolicy, exclude: &[String], filters: EventFilters, ) -> JdwpResult<i32>

set_method_exit_request with ClassExclude patterns (STEP-1).

The exclusions are what make a wildcard ClassMatch usable on a framework-heavy JVM: the match itself is done by the JVM, so a broad pattern sweeps in every proxy and interceptor the container generates, and each unwanted exit costs a real event before this side can discard it. An exclusion stops the event being generated at all.

One modifier per pattern, so the count written into the packet has to include all of them. A wrong count is not diagnosed as such: the JVM reads the following bytes as another modifier and answers INTERNAL (113), which says nothing about the cause.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_method_exit_request( &mut self, request_id: i32, with_return_value: bool, ) -> JdwpResult<()>

Clear a method-exit request by id (EventRequest.Clear command). with_return_value must match what the request was armed with — JDWP keys requests by (eventKind, requestID), and kinds 41 and 42 are different keys, so clearing with the wrong one silently leaves the request armed.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn can_get_method_return_values(&mut self) -> JdwpResult<bool>

Whether this JVM can report a method’s return value (METHOD_EXIT_WITH_RETURN_VALUE).

A JDWP version check, not a capability bit: JDI’s canGetMethodReturnValues() is defined as JDWP ≥ 1.6, and neither Capabilities nor CapabilitiesNew carries a flag for it. Getting this wrong means looking for a bit that does not exist and concluding the JVM can’t do it.

§Errors

Returns a JdwpError if the version request fails or the reply cannot be parsed.

Source

pub async fn set_monitor_request( &mut self, kind: MonitorKind, suspend_policy: SuspendPolicy, monitor_class: Option<ReferenceTypeId>, filters: EventFilters, ) -> JdwpResult<i32>

Report lock contention as it happens (EventRequest.Set, one of the four MONITOR_* kinds) — the primitive behind debug.set_monitor_stop, answering “what are these threads blocked on?” without suspending anything (DUMP-7, #96).

The event-driven counterpart to owned_monitors / current_contended_monitor, which can only be asked of a thread that is already suspended. That is the whole point: “requests are hanging on a lock” was the one wedged-app-server question that forced a freeze of a shared instance.

Ask capabilities_new for can_request_monitor_events first. Unlike METHOD_EXIT_WITH_RETURN_VALUE this is a capability bit, so a JVM without it answers NOT_IMPLEMENTED (99) — which is exactly the bare error code the capability rule exists to improve on.

filters.thread (ThreadOnly) is the cheap narrowing and acts inside the JVM. monitor_class adds a ClassOnly, and what it narrows depends on the kind, per the JDWP spec’s own wording for modKind 4: for MonitorKind::Wait and MonitorKind::Waited it tests the class of the monitor object, and for MonitorKind::Blocked and MonitorKind::Acquired it tests the class of the location — the code that blocked, not the lock it blocked on. See mod_kinds::CLASS_ONLY.

filters.count and filters.instance are accepted by the signature because EventFilters is one value, but see mcp-server’s arming path: InstanceOnly is refused there rather than passed through, on the ADR-0027 rule that acceptance is not application.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. NOT_IMPLEMENTED (99) when the JVM lacks canRequestMonitorEvents.

Source

pub async fn clear_monitor_request( &mut self, request_id: i32, kind: MonitorKind, ) -> JdwpResult<()>

Clear a monitor request by id (EventRequest.Clear command). kind must match the one the request was armed with — JDWP keys requests by (eventKind, requestID), and the four monitor kinds are four separate keys, so clearing with the wrong one silently leaves the request armed.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_field_watch( &mut self, request_id: i32, kind: WatchKind, ) -> JdwpResult<()>

Clear a field watch by id (EventRequest.Clear command). kind must match the one the request was created with — JDWP keys requests by (eventKind, requestID).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source§

impl JdwpConnection

Source

pub async fn set_breakpoint_ex( &mut self, class_id: ReferenceTypeId, method_id: MethodId, bytecode_index: u64, suspend_policy: SuspendPolicy, filters: EventFilters, ) -> JdwpResult<i32>

Set a breakpoint with optional Count (stop on Nth hit) and ThreadOnly filters.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_step( &mut self, thread: ThreadId, depth: StepDepth, ) -> JdwpResult<i32>

Set a single-step request (EventRequest.Set, SINGLE_STEP). Returns the request id; clear it with clear_step before resuming again, or stepping will run away.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_step_ex( &mut self, thread: ThreadId, depth: StepDepth, exclude: &[String], only: &[String], ) -> JdwpResult<i32>

Set a single-step request with ClassExclude / ClassOnly filtering (STEP-1).

exclude drops events from classes matching each pattern; only restricts the request to classes matching each pattern. Patterns are JDWP’s own form — an exact class name, or one with a single leading or trailing * (java.*, *.OrderService) — and the JVM matches them against the dotted class name.

One modifier per pattern. JDWP’s ClassExclude carries a single string, so N exclusions occupy N of the request’s modifier slots and the count written into the packet has to include all of them. Getting that count wrong does not produce a complaint about the modifier: the JVM reads the next bytes as another modifier and answers INTERNAL (113), which says nothing about the cause — the same failure the Count/ThreadOnly pair already carries a warning about.

How many the JVM tolerates, measured rather than assumed (Temurin 17, HotSpot): a step request with 5000 ClassExclude modifiers was accepted without complaint, as were 255, 256 and 1000 — so there is no practical cap to defend against and no error path to translate. The count field is an i32 and the packet is length-prefixed; nothing here bounds it before that. Worth measuring because the byte-level failure mode above gives no signal, so a cap discovered in production would have looked like a bug in this function rather than a limit.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_step(&mut self, request_id: i32) -> JdwpResult<()>

Clear a single-step request (EventRequest.Clear, SINGLE_STEP).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn clear_all_breakpoints(&mut self) -> JdwpResult<()>

Clear all breakpoints (EventRequest.ClearAllBreakpoints).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_array_length(&mut self, array_id: ObjectId) -> JdwpResult<i32>

ArrayReference.Length.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_array_values( &mut self, array_id: ObjectId, first: i32, length: i32, ) -> JdwpResult<Vec<Value>>

ArrayReference.GetValues — returns length elements starting at first.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_array_values( &mut self, array_id: ObjectId, first: i32, values: &[Value], ) -> JdwpResult<()>

ArrayReference.SetValues — overwrite values.len() elements starting at first.

Values go on the wire untagged, so each must already be coerced to the array’s component type: writing an int into a long[] with the wrong width corrupts the element rather than failing. The caller reads the component type from the array’s signature and coerces first.

§Errors

Returns a JdwpError if the JDWP request fails, including INVALID_LENGTH when the range runs past the end of the array.

Source

pub async fn create_string(&mut self, s: &str) -> JdwpResult<ObjectId>

VirtualMachine.CreateString — mirror a string into the target VM, returning its id.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_frame_value( &mut self, thread_id: ThreadId, frame_id: FrameId, slot: i32, value: &Value, ) -> JdwpResult<()>

StackFrame.SetValues — set a single local variable slot to value.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source§

impl JdwpConnection

Source

pub async fn get_line_table( &mut self, ref_type_id: ReferenceTypeId, method_id: MethodId, ) -> JdwpResult<LineTable>

Get line table for a method (Method.LineTable command) Maps source code line numbers to bytecode positions

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_line_tables_independently( &self, pairs: &[(ReferenceTypeId, MethodId)], ) -> Vec<JdwpResult<LineTable>>

A line table for each (type, method) pair, read as independent reads (PERF-1, #100).

Independent because a method’s line table is fixed for the loaded method and naming one method tells you nothing you need in order to name another.

Deduplicate before calling this. A recursive stack has the same pair many times over, and this will read it many times over — the licence is about issuing reads together, not about needing fewer of them. dump_frame_method’s cache and stack_method_tables’s dedupe are where that is decided.

Source

pub async fn get_bytecodes( &mut self, ref_type_id: ReferenceTypeId, method_id: MethodId, ) -> JdwpResult<Vec<u8>>

A method’s bytecode, exactly as the JVM holds it (Method.Bytecodes, command 3).

The evidence a line table cannot give (DISC-9, #63): an edit that changes a method’s code without moving any line — < to <=, a changed constant, a swapped operator — leaves the line table identical and the code array different. That is also the commonest edit in a redeploy loop, so it is the case a line-table comparison is quietest about.

Gated on canGetBytecodes (see VmCapabilities); a JVM without it answers NOT_IMPLEMENTED, which is worth reporting as “cannot tell” rather than as a match. An abstract or native method has no code and answers ABSENT_INFORMATION for the same reason.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_variable_table( &mut self, ref_type_id: ReferenceTypeId, method_id: MethodId, ) -> JdwpResult<Vec<Variable>>

Get variable table for a method (Method.VariableTable command) Returns info about local variables (names, types, slots)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_variable_tables_independently( &self, pairs: &[(ReferenceTypeId, MethodId)], ) -> Vec<JdwpResult<Vec<Variable>>>

A variable table for each (type, method) pair, read as independent reads (PERF-1, #100).

Two waves, because the fallback is per pair. get_variable_table prefers VariableTableWithGeneric and falls back to the plain command on NOT_IMPLEMENTED; a wave has to reproduce that or a JVM without the generic command would lose every variable name at once instead of one call at a time. So the generic wave goes out, the pairs that answered NOT_IMPLEMENTED are collected, and those — usually none — go out as a second wave.

ABSENT_INFORMATION is not a fallback case and is passed through per pair, exactly as the single-read path leaves it: a -g:none build has no variable names and that is an answer, not an error to retry.

Deduplicate before calling, for the reason read_line_tables_independently gives.

Source§

impl JdwpConnection

Source

pub async fn get_object_reference_type( &mut self, object_id: ObjectId, ) -> JdwpResult<ReferenceTypeId>

Get the reference type (class) of an object (ObjectReference.ReferenceType command)

§Arguments
  • object_id - The ObjectId of the object
§Returns

The ReferenceTypeId of the object’s class

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_reference_types_independently( &self, object_ids: &[ObjectId], ) -> Vec<JdwpResult<ReferenceTypeId>>

The reference type of each of object_ids, read as independent reads (PERF-1, #100).

The licence is real here and worth naming: an object’s class is fixed for the object’s life, and asking about one object tells you nothing you need in order to ask about another. So this is a wave.

Positional and total — result[i] answers object_ids[i], and one failure does not touch the rest. A collected object answers INVALID_OBJECT in its own slot, which is exactly what it does on the sequential path.

Source

pub async fn is_collected(&mut self, object_id: ObjectId) -> JdwpResult<bool>

Whether the object behind an id has been garbage collected (ObjectReference.IsCollected, set 9 command 9).

The one command that answers “vanished” as a fact rather than as a failure. A JDWP object id is a weak reference — the JVM is free to collect the object while the debugger still holds the number — and every other command answers ERR_INVALID_OBJECT once that happens, which is the same code a typo produces. This one separates the two while the JVM still remembers the id: Ok(true) is “it was here and it is gone”, where an INVALID_OBJECT error from this command means the JVM has no record of the id at all — collected long enough ago that the mapping itself was dropped, or never valid.

Deliberately not paired with DisableCollection / EnableCollection (commands 7 and 8): pinning an object so its id stays readable makes the debugger the reason a live heap cannot be collected. See ADR-0022.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. In particular INVALID_OBJECT (20) for an id this JVM has no record of.

Source

pub async fn get_object_values( &mut self, object_id: ObjectId, field_ids: Vec<FieldId>, ) -> JdwpResult<Vec<Value>>

Get field values from an object (ObjectReference.GetValues command)

§Arguments
  • object_id - The ObjectId of the object
  • field_ids - Vector of FieldIds to retrieve
§Returns

Vector of Values corresponding to the requested fields

§Example
let fields = vec![field_id1, field_id2];
let values = connection.get_object_values(object_id, fields).await?;
§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_object_values_independently( &self, reads: &[(ObjectId, Vec<FieldId>)], ) -> Vec<JdwpResult<Vec<Value>>>

One object’s fields per entry of reads, all read as independent reads (PERF-1, #100).

Independent because each read names its own object and its own field ids, and a field read changes nothing. What is not independent is how reads was built: the field ids for an object come from its type, and the type comes from a read of its own. That prior read cannot join this wave — see project_query_rows in the server, where the two waves are deliberately two.

Positional and total, like read_reference_types_independently.

Source

pub async fn get_reference_values( &mut self, ref_type_id: ReferenceTypeId, field_ids: Vec<FieldId>, ) -> JdwpResult<Vec<Value>>

Get static field values from a reference type (ReferenceType.GetValues command)

Unlike get_object_values (which reads instance fields off an object), this reads static fields directly off a class — no object instance and no suspended thread required. Use it to read things like ConfigDefaultUtils.dsUrlMotor.

§Arguments
  • ref_type_id - The ReferenceTypeId of the class (from classes_by_signature)
  • field_ids - Vector of static FieldIds to retrieve (from get_fields)
§Returns

Vector of Values corresponding to the requested fields

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_reference_values( &mut self, class_id: ReferenceTypeId, updates: Vec<(FieldId, Value)>, ) -> JdwpResult<()>

Write static field(s) on a class (ClassType.SetValues command).

Each value is written untagged — its wire type comes from the field’s declared type — so coerce every value to match its field first (see the mcp-server field-write path). Lets you flip a static like ConfigDefaultUtils.dsInfra on a running JVM.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn set_object_values( &mut self, object_id: ObjectId, updates: Vec<(FieldId, Value)>, ) -> JdwpResult<()>

Write instance field(s) on an object (ObjectReference.SetValues command).

Like set_reference_values, values are untagged and must already be coerced to each field’s declared type.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source§

impl JdwpConnection

Source

pub async fn get_methods( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Vec<MethodInfo>>

Get methods for a reference type (ReferenceType.Methods command)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_class_loader( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Option<ObjectId>>

ReferenceType.ClassLoader — which classloader defined this type (BP-5, #79).

The answer that makes “the class is loaded twice” a statement a caller can act on rather than a warning they can only nod at. classes_by_signature returns one entry per classloader that has loaded a name, and on an app server that is the ordinary case, not the exotic one: WildFly gives every deployment its own module classloader, and a library packed into each war’s WEB-INF/lib is a genuinely different reference type per deployment — different public static state, different endpoint URLs, different mute flags.

Ok(None) means the bootstrap classloader, which is what JDWP’s null objectID encodes and is a real answer (java.lang.String has no loader object). It is not a failure, and rendering it as one would make every JDK type look broken.

Returns the loader’s raw objectID and nothing else on purpose. Naming it means reading its own type — Self::get_object_reference_type plus a signature — which the caller can do when it has a caller to answer; calling toString() on it would need a suspended thread and is exactly the implicit invocation ADR-0001’s posture rules out.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_interfaces( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Vec<ReferenceTypeId>>

ReferenceType.Interfaces — the interfaces this type declares directly.

Direct only, per the JDWP spec: class A implements Runnable reports Runnable, and a class whose superclass implements it reports nothing. Use Self::implements_interface for the question callers actually have.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_modifiers( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<i32>

ReferenceType.Modifiers — the class-level access flags the JVM holds for this type.

The same u16 the class file’s access_flags carries, widened to i32 by the wire format, so it is directly comparable with a parsed .class — which is what DISC-13 needs it for: HotSpot refuses a redefinition whose class modifiers changed (CLASS_MODIFIERS_CHANGE_NOT_IMPLEMENTED), and that is decidable before the attempt.

Not cached. It is one packet, asked once per forecast, and a type’s modifiers are the sort of thing a redefinition is about — a cache here would be a way to answer from before the swap.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn implements_interface( &mut self, type_id: ReferenceTypeId, wanted: &str, ) -> JdwpResult<bool>

Whether type_id implements the interface whose JNI signature is wanted (e.g. "Ljava/lang/Runnable;"), the way instanceof would answer it.

Walks the whole lattice, because JDWP only reports direct superinterfaces: up the superclass chain (a parent’s interfaces are inherited) and across each type’s interfaces transitively (an interface extends interfaces). Every step reads through the type cache, so a repeat question about the same class costs nothing.

§Errors

Returns a JdwpError if a JDWP request fails or a reply cannot be parsed.

Source

pub async fn get_source_file( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<String>

ReferenceType.SourceFile — the file this type was compiled from, e.g. OrderService.java.

A bare file name, never a path: the SourceFile class-file attribute records the name of the compilation unit and nothing about where it lived, so a caller wanting a path has to get the directory part from the type’s own package. An inner or local type reports its enclosing file (Order.java for Order$Line), because it has no compilation unit of its own — which is exactly why resolving source by class name rather than by this cannot work.

Deliberately uncached, unlike Self::get_methods / get_signature: those are read once per frame in a loop, this is read once per debug.source call.

§Errors

Returns JdwpError::JdwpErrorCode carrying ERR_ABSENT_INFORMATION for a class compiled without the attribute (javac -g:none, or a synthetic class the JVM generated). That is an answer about the class, not a transport failure, and callers are expected to report it as one.

Source

pub async fn get_source_debug_extension( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Option<String>>

ReferenceType.SourceDebugExtension — the JSR-45 SMAP that says which original file the bytecode came from when that is not the .java in Self::get_source_file: a JSP, a Kotlin or Groovy unit, anything run through a translating compiler.

Ok(None) is the ordinary answer, not a degraded one, so two error codes are absorbed rather than propagated: ERR_ABSENT_INFORMATION for a class with no SMAP — which is nearly every class — and ERR_NOT_IMPLEMENTED for a VM that lacks the optional canGetSourceDebugExtension capability. Reporting either as an error would make the common case look broken.

§Errors

Returns a JdwpError only for a genuine failure — a transport error, some other JDWP error code, or a reply that will not parse.

Source

pub async fn get_fields( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Vec<FieldInfo>>

Get fields for a reference type (ReferenceType.Fields command)

§Arguments
  • ref_type_id - The ReferenceTypeId to get fields for
§Returns

Vector of FieldInfo containing field IDs, names, signatures, and modifiers

§Example
let fields = connection.get_fields(class_id).await?;
for field in fields {
    println!("Field: {} ({})", field.name, field.signature);
}
§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn instances( &mut self, ref_type_id: ReferenceTypeId, max_instances: i32, ) -> JdwpResult<Vec<Value>>

The live instances of one type (ReferenceType.Instances, command 16).

This stops the world, and JDWP never says so. No suspend is required and this client issues none, yet the JVM holds every application thread for a full live-heap walk. Measured against Temurin 17.0.20: 522 ms of held application threads on a 2,000,000-object heap to answer with 7 objects, against 54 ms on a 20,000-object heap for the same 7 objects. The cost tracks the live heap, not the result. Full method and wire notes in docs/heap-query-measurements.md.

Exact type, not subtype-inclusive. Widget answers 7 with two live SubWidgets in the heap, not 9. On a CDI or EJB codebase the name a caller reaches for is usually the interface or the base class, so this is the semantic most likely to produce a confident 0 about a type with hundreds of live objects — the Loaded trap from CONTEXT.md in a new costume. Anything built on this has to say so rather than let it be discovered.

Only strongly reachable objects are reported. max_instances 0 means all, a positive value clamps, and a negative one is ILLEGAL_ARGUMENT (103) — rejected here before the round trip, since a wire error is a poor way to report an argument this crate can see is wrong.

Each returned Value carries the JVM’s own tag, so a String, an array, a thread and a class object are distinguishable without a follow-up round trip.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. NOT_IMPLEMENTED (99) when the JVM lacks canGetInstanceInfo, and INVALID_OBJECT (20) for a bogus type id — ask capabilities_new first, so a refusal reads as “this JVM cannot answer that”.

Source§

impl JdwpConnection

Source

pub async fn get_frame_values( &mut self, thread_id: ThreadId, frame_id: FrameId, slots: Vec<VariableSlot>, ) -> JdwpResult<Vec<Value>>

Get values for variable slots in a frame (StackFrame.GetValues command)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_frame_values_independently( &self, reads: &[(ThreadId, FrameId, Vec<VariableSlot>)], ) -> Vec<JdwpResult<Vec<Value>>>

The named slots of each (thread, frame, slots) read, issued as independent reads (PERF-1, #100).

The licence here is narrower than it looks, and the narrowing is caller-visible. Reading one frame’s locals does not disturb another’s, so a set of frames on a suspended thread is independent. But a frame id is only valid until a method is invoked on its thread, and JDWP invalidates every id on that thread when one is — so a wave built before any invocation is fine, and a wave built across invocations reads stale ids. debug.get_stack therefore uses this on its shallow path and not on the deep one, where rendering a value may invoke toString(). See render_frame_variables.

Source

pub async fn pop_frames( &mut self, thread_id: ThreadId, frame_id: FrameId, ) -> JdwpResult<()>

Pop frame_id and every frame above it off a suspended thread’s stack (StackFrame.PopFrames, command 4).

The thread resumes at the call site of the popped method with its operand stack restored, so the next resume re-executes the call. That is what makes it the other half of redefine_classes: a frame already on the stack keeps running the bytecode it entered with, and popping it is how the new bytecode gets entered without re-issuing the request that reached the breakpoint.

Requires canPopFrames (see capabilities_new) and a suspended thread. Three refusals are worth telling apart, and the JDWP codes already do: THREAD_NOT_SUSPENDED (13), NO_MORE_FRAMES (31) for the bottom frame of a stack, and OPAQUE_FRAME (32) for a native one.

Side effects are the caller’s problem and cannot be undone: anything the popped invocation wrote to a field, a file or the network stays written. Only the frame is rewound.

§Errors

Returns a JdwpError if the JDWP request fails or the JVM refuses to pop the frame.

Source§

impl JdwpConnection

Source

pub async fn get_string_value( &mut self, string_id: ObjectId, ) -> JdwpResult<String>

Get the string value from a String object (StringReference.Value command)

§Arguments
  • string_id - The ObjectId of the String object
§Returns

The actual string value

§Example
let value = connection.get_string_value(string_object_id).await?;
println!("String value: {}", value);
§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_string_values_independently( &self, string_ids: &[ObjectId], ) -> Vec<JdwpResult<String>>

The contents of each of string_ids, read as independent reads (PERF-2, #129).

The licence is real and narrower than it looks. A java.lang.String is immutable, so reading one tells you nothing you need in order to read another and the order cannot matter — that is what makes this a wave. What it does not license is reading a string the caller has not committed to rendering: the id is still a weak reference, and a string read speculatively is a packet the sequential path would never have sent. Committing is the caller’s job, and CONTEXT.md’s speculative read is the invariant that job protects.

Positional and total — result[i] answers string_ids[i], and one failure does not touch the rest. A collected object answers INVALID_OBJECT in its own slot, exactly as it does read one at a time.

Source§

impl JdwpConnection

Source

pub async fn get_frames( &mut self, thread_id: ThreadId, start_frame: i32, length: i32, ) -> JdwpResult<Vec<Frame>>

Get stack frames for a thread (ThreadReference.Frames command)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn owned_monitors( &mut self, thread_id: ThreadId, ) -> JdwpResult<Vec<Monitor>>

The monitors this thread currently holds (ThreadReference.OwnedMonitors, command 8).

Half of what a deadlock investigation consists of; the other half is current_contended_monitor. Cross-referencing the two across threads — A holds what B waits for, and vice versa — is what makes a lock cycle visible, which is otherwise unanswerable through this tool.

The thread must be suspended. A running thread’s lock set is not a well-defined thing to read, so the JVM answers THREAD_NOT_SUSPENDED (13) rather than a snapshot that was never true. Requires the JVM’s canGetOwnedMonitorInfo (see capabilities); without it the answer is NOT_IMPLEMENTED (99).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn current_contended_monitor( &mut self, thread_id: ThreadId, ) -> JdwpResult<Option<Monitor>>

The monitor this thread is blocked waiting to enter, if any (ThreadReference.CurrentContendedMonitor, command 9).

None means the thread is not contending for a lock — the common case, and not an error. A thread parked in Object.wait() reports the monitor it will re-acquire.

The thread must be suspended, and the JVM must report canGetCurrentContendedMonitor; see owned_monitors for why both hold.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_all_threads(&mut self) -> JdwpResult<Vec<ThreadId>>

Get all threads (VirtualMachine.AllThreads)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn get_thread_name( &mut self, thread_id: ThreadId, ) -> JdwpResult<String>

Get a thread’s name (ThreadReference.Name).

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn read_thread_names_independently( &self, thread_ids: &[ThreadId], ) -> Vec<JdwpResult<String>>

The name of each of thread_ids, read as independent reads (PERF-1, #100).

The widest fan-out in the tool. A dump’s triage asks this of every thread the VM has — 306 on a production-shaped instance — and used to ask one at a time, under the suspension. A thread’s name is nothing to do with any other thread’s, so this is a wave.

Chunk by MAX_READS_IN_FLIGHT if you have a deadline to honour. Passing all 306 ids is correct and bounded, but nothing can interrupt the call once it starts, and a dump’s suspension budget is checked between threads. Chunking hands the budget back every window — which costs it nothing in time, because a window of sixteen takes about as long as one sequential read.

Source

pub async fn read_thread_statuses_independently( &self, thread_ids: &[ThreadId], ) -> Vec<JdwpResult<(i32, i32)>>

The (thread_status, suspend_status) of each of thread_ids, read as independent reads.

The dump’s second per-thread read, and the one that must go out after the name filter rather than beside it: a thread whose name is filtered out never has its status read on the sequential path, so a single wave over both would spend a packet the loop never spent. See triage_dump_threads.

Source

pub async fn get_thread_status( &mut self, thread_id: ThreadId, ) -> JdwpResult<(i32, i32)>

Get a thread’s (thread_status, suspend_status) (ThreadReference.Status). suspend_status != 0 means the thread is currently suspended.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn suspend_count(&mut self, thread_id: ThreadId) -> JdwpResult<i32>

How many times this thread has been suspended (ThreadReference.SuspendCount).

JDWP counts suspends: a thread suspended n times must be resumed n times before it runs again. That makes this the only way to answer “did my resume actually resume it?” — a single resume_all against a count of 2 leaves the thread stopped while every command still succeeds. Verified against a real JVM: two Suspends then one Resume leaves the debuggee stopped.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn suspend_all(&mut self) -> JdwpResult<()>

Suspend all threads (VirtualMachine.Suspend)

Suspends are counted — calling this twice needs two resumes. Callers that mean “make sure it is stopped” should check suspend_count first rather than suspending again, or they will build a depth that a single resume can’t undo.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn resume_all(&mut self) -> JdwpResult<()>

Resume all threads (VirtualMachine.Resume) — one decrement of every thread’s suspend count.

Not the same as “make the VM run”: if anything suspended it twice, this leaves it stopped and still reports success. Use resume_all_fully when the intent is that the application actually continues.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn resume_all_fully( &mut self, probe_thread: ThreadId, max_resumes: u32, ) -> JdwpResult<(u32, i32)>

Resume until the application is actually running, not just once.

Returns (resumes issued, remaining suspend count) — a remaining count of 0 means the VM is genuinely going again. probe_thread is the thread whose count is checked; any live thread works for a VM-wide suspend, since VirtualMachine.Suspend increments all of them.

This exists because “resume” and “is it running” are different questions in JDWP, and a caller whose job is to un-freeze a shared JVM (a watchdog, a panic button) must not report success on the strength of a command that returned OK while the debuggee stayed stopped.

Bounded by max_resumes so a pathological count can’t spin forever; a thread that is also suspended individually (an EventThread-policy event) may legitimately need more than one.

§Errors

Returns a JdwpError if a JDWP request fails or a reply cannot be parsed.

Source

pub async fn suspend_thread(&mut self, thread_id: ThreadId) -> JdwpResult<()>

Suspend one thread (ThreadReference.Suspend, set 11 command 2) — the counterpart to resume_thread, and the cheap alternative to suspend_all on a debuggee other people are using.

This is the only way to obtain an evaluable frame without freezing every in-flight request: VirtualMachine.Suspend and a SuspendPolicy::All stop point both hold the whole VM, and on a shared application server that is a cost nobody agreed to pay.

Counted, exactly like every other suspend here. This increments this thread’s suspend count by one and nothing else’s; a thread already held by a VirtualMachine.Suspend, or parked at an EventThread-policy event, ends up at 2 and needs two decrements before it runs. So a caller must read suspend_count afterwards rather than assume a depth of 1 — which is ADR-0003’s rule arriving at the per-thread door.

What the JVM answers for a thread that is not running. A finished thread (ZOMBIE) can still be named and described while the debugger holds its Thread object, but it cannot be suspended: HotSpot answers INVALID_THREAD (10) — which reads as “you passed a bad id” and is not what happened. A vanished thread, whose id the JVM has already collected, answers INVALID_OBJECT (20). The two are different findings and callers must not collapse them (DUMP-4), so this returns the raw error rather than a sentence.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn resume_thread(&mut self, thread_id: ThreadId) -> JdwpResult<()>

Resume a single thread (ThreadReference.Resume) — decrements just that thread’s suspend count, leaving other suspended threads alone. Used after arming a deferred breakpoint on the thread that a ClassPrepare event suspended, so class init proceeds without disturbing any thread parked at a real breakpoint.

One decrement, not “make this thread run”. The distinction is the same one resume_all draws against resume_all_fully: the JVM acknowledges this command whether or not the thread is left suspended underneath, so a caller whose intent is that the thread proceeds must verify with suspend_count. debug.resume_thread does exactly that, and says so when the count did not reach zero.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn force_early_return( &mut self, thread_id: ThreadId, value: &Value, ) -> JdwpResult<()>

Force the topmost frame of a suspended thread to return value immediately (ThreadReference.ForceEarlyReturn). The thread must be suspended and the value’s tag must be assignable to the method’s declared return type — pass a Void value for a void method. Lets a caller short-circuit a method (e.g. make a rejecting salvar return true) without editing and redeploying code. Requires the JVM’s canForceEarlyReturn capability.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source§

impl JdwpConnection

Source

pub async fn get_version(&mut self) -> JdwpResult<VmVersion>

Get JVM version information (VirtualMachine.Version command)

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn capabilities(&mut self) -> JdwpResult<VmCapabilities>

Ask the JVM which optional capabilities it supports (VirtualMachine.Capabilities, command 12).

Seven booleans, one byte each, in the order the spec lists them. Used to turn “the JVM refused” into “this JVM cannot do that” — see VmCapabilities.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn capabilities_new(&mut self) -> JdwpResult<VmCapabilitiesNew>

Ask the JVM for the newer capability bits (VirtualMachine.CapabilitiesNew, command 17).

The reply repeats capabilities’ seven booleans before the ones that are only here, so the first seven bytes are read past rather than decoded twice — the two commands answer about the same JVM and disagreeing about the overlap is not a state worth representing. Everything past the eighteenth bit is skipped for the reason VmCapabilitiesNew gives, and so are 13-15, which sit between bits that are consulted.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn instance_counts( &mut self, ref_types: &[ReferenceTypeId], ) -> JdwpResult<Vec<i64>>

How many live instances each of ref_types has (VirtualMachine.InstanceCounts, command 21).

This stops the world, and JDWP never says so. It requires no suspend and this client issues none, yet the JVM holds every application thread for a full live-heap walk: measured at 630 ms over a 2,000,000-object heap, with a matching 522 ms pause, against 54 ms on a 20,000-object heap. The cost tracks the live heap, not the answer. See docs/heap-query-measurements.md.

One walk covers the whole batch — three types measured at 604 ms, about the price of one — so this takes a slice rather than being called in a loop, and asking about more types is close to free.

Counts are exact-type, matching instances: a base class does not count its subclasses. A ref_types entry the JVM does not recognise answers 0 rather than erroring, which makes a typo look like an absence — the caller has to resolve names first.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed. NOT_IMPLEMENTED (99) when the JVM lacks canGetInstanceInfo — ask capabilities_new first, so a refusal can be reported as “this JVM cannot answer that” rather than as an error code.

Source

pub async fn redefine_classes( &mut self, defs: &[(ReferenceTypeId, Vec<u8>)], ) -> JdwpResult<()>

Install new bytecode for already-loaded classes (VirtualMachine.RedefineClasses, command 18) — HotSwap, what an IDE calls “reload changed classes”.

All-or-nothing: the JVM either accepts every definition in the batch or changes nothing, which is why this takes a slice rather than being called in a loop. On HotSpot it accepts method body changes only — add or remove a method or a field, change a signature, a modifier or the hierarchy, and it refuses with one of the twelve codes at 60-71 in ERROR_MESSAGES. Translating those into what the caller should do next is the MCP layer’s job; this reports them as they came.

Frames already on the stack keep running the code they entered with. A method suspended at a breakpoint is unaffected by its own redefinition until it is re-entered — see pop_frames, which is how it gets re-entered without re-issuing the request that got there.

On success every redefined type is dropped from the type cache: the cache holds each type’s methods, fields, signature and interfaces, and a redefinition is one of the two events its own documentation names as making those stale. Method ids for changed methods become obsolete rather than invalid, so a cached list would keep naming code the JVM no longer runs.

§Errors

Returns a JdwpError if the JDWP request fails or the JVM refuses the redefinition.

Source

pub async fn dispose(&mut self) -> JdwpResult<()>

Dispose of the debugger connection (VirtualMachine.Dispose command).

The JVM’s own clean exit from a debug session: it clears every event request this connection set and resumes every thread it suspended, then invalidates the connection. That “resume everything, leave no request armed” guarantee is exactly what a safe disconnect needs — a resume_all alone would leave breakpoints armed to re-freeze the next request, and clearing our tracked requests one by one could still miss one the JVM knows about and we don’t.

The connection is unusable afterwards; drop it. Fire-and-forget by design: if the socket is already half-dead (the case a disconnect most needs to handle), there is nothing better to do than try and move on.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn classes_by_signature( &mut self, signature: &str, ) -> JdwpResult<Vec<ClassInfo>>

Find classes by signature (VirtualMachine.ClassesBySignature command) Signature format: “Lcom/example/MyClass;” for classes

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Source

pub async fn all_classes(&mut self) -> JdwpResult<Vec<ClassInfo>>

List every loaded reference type (VirtualMachine.AllClasses command).

Heavier than classes_by_signature (returns thousands of entries), but lets a caller resolve a class by simple name when the full package isn’t known — e.g. match any signature ending in /ConfigDefaultUtils;.

§Errors

Returns a JdwpError if the JDWP request fails or the reply cannot be parsed.

Trait Implementations§

Source§

impl Clone for JdwpConnection

Source§

fn clone(&self) -> JdwpConnection

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for JdwpConnection

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more