pub struct JdwpConnection { /* private fields */ }Implementations§
Source§impl JdwpConnection
impl JdwpConnection
Sourcepub async fn connect(host: &str, port: u16) -> JdwpResult<Self>
pub async fn connect(host: &str, port: u16) -> JdwpResult<Self>
Sourcepub fn set_read_only(&self, read_only: bool)
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.
Sourcepub fn is_read_only(&self) -> bool
pub fn is_read_only(&self) -> bool
Whether this connection refuses to mutate the debuggee.
Sourcepub fn set_invoke_timeout_ms(&self, ms: u64)
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.
Sourcepub fn invoke_timeout_ms(&self) -> u64
pub fn invoke_timeout_ms(&self) -> u64
The current invocation budget in milliseconds; 0 means unbounded.
Sourcepub async fn send_command(
&mut self,
packet: CommandPacket,
) -> JdwpResult<ReplyPacket>
pub async fn send_command( &mut self, packet: CommandPacket, ) -> JdwpResult<ReplyPacket>
Sourcepub fn round_trips(&self) -> u32
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 ~.
Sourcepub async fn read_independently(
&self,
packets: Vec<CommandPacket>,
) -> Vec<JdwpResult<ReplyPacket>> ⓘ
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.
Sourcepub async fn try_recv_event(&self) -> Option<EventSet>
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
}Sourcepub async fn recv_event(&self) -> Option<EventSet>
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
}Sourcepub fn packets_sent(&self) -> u32
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
impl JdwpConnection
Sourcepub async fn get_signature(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<String>
pub async fn get_signature( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<String>
Sourcepub async fn read_signatures_independently(
&self,
ref_type_ids: &[ReferenceTypeId],
) -> Vec<JdwpResult<String>> ⓘ
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.
Sourcepub async fn get_superclass(
&mut self,
class_id: ClassId,
) -> JdwpResult<Option<ClassId>>
pub async fn get_superclass( &mut self, class_id: ClassId, ) -> JdwpResult<Option<ClassId>>
Sourcepub async fn get_this_object(
&mut self,
thread_id: ThreadId,
frame_id: FrameId,
) -> JdwpResult<ObjectId>
pub async fn get_this_object( &mut self, thread_id: ThreadId, frame_id: FrameId, ) -> JdwpResult<ObjectId>
Sourcepub async fn invoke_method(
&mut self,
object_id: ObjectId,
thread_id: ThreadId,
class_id: ClassId,
method_id: MethodId,
args: Vec<Value>,
) -> JdwpResult<(Value, ObjectId)>
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).
Sourcepub async fn invoke_static_method(
&mut self,
class_id: ClassId,
thread_id: ThreadId,
method_id: MethodId,
args: Vec<Value>,
) -> JdwpResult<(Value, ObjectId)>
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
impl JdwpConnection
Sourcepub async fn set_breakpoint(
&mut self,
class_id: ReferenceTypeId,
method_id: MethodId,
bytecode_index: u64,
suspend_policy: SuspendPolicy,
) -> JdwpResult<i32>
pub async fn set_breakpoint( &mut self, class_id: ReferenceTypeId, method_id: MethodId, bytecode_index: u64, suspend_policy: SuspendPolicy, ) -> JdwpResult<i32>
Sourcepub async fn clear_breakpoint(&mut self, request_id: i32) -> JdwpResult<()>
pub async fn clear_breakpoint(&mut self, request_id: i32) -> JdwpResult<()>
Sourcepub async fn set_class_prepare(
&mut self,
class_pattern: &str,
suspend_policy: SuspendPolicy,
) -> JdwpResult<i32>
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.
Sourcepub async fn clear_class_prepare(&mut self, request_id: i32) -> JdwpResult<()>
pub async fn clear_class_prepare(&mut self, request_id: i32) -> JdwpResult<()>
Sourcepub async fn set_exception_request(
&mut self,
ref_type: Option<ReferenceTypeId>,
caught: bool,
uncaught: bool,
suspend_policy: SuspendPolicy,
) -> JdwpResult<i32>
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.
Sourcepub async fn set_exception_request_ex(
&mut self,
ref_type: Option<ReferenceTypeId>,
caught: bool,
uncaught: bool,
suspend_policy: SuspendPolicy,
filters: EventFilters,
) -> JdwpResult<i32>
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.
Sourcepub async fn clear_exception_request(
&mut self,
request_id: i32,
) -> JdwpResult<()>
pub async fn clear_exception_request( &mut self, request_id: i32, ) -> JdwpResult<()>
Sourcepub async fn set_field_watch(
&mut self,
ref_type: ReferenceTypeId,
field_id: FieldId,
kind: WatchKind,
suspend_policy: SuspendPolicy,
) -> JdwpResult<i32>
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).
Sourcepub async fn set_field_watch_ex(
&mut self,
ref_type: ReferenceTypeId,
field_id: FieldId,
kind: WatchKind,
suspend_policy: SuspendPolicy,
filters: EventFilters,
) -> JdwpResult<i32>
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).
Sourcepub 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>
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.
Sourcepub 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>
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.
Sourcepub async fn clear_method_exit_request(
&mut self,
request_id: i32,
with_return_value: bool,
) -> JdwpResult<()>
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.
Sourcepub async fn can_get_method_return_values(&mut self) -> JdwpResult<bool>
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.
Sourcepub async fn set_monitor_request(
&mut self,
kind: MonitorKind,
suspend_policy: SuspendPolicy,
monitor_class: Option<ReferenceTypeId>,
filters: EventFilters,
) -> JdwpResult<i32>
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.
Sourcepub async fn clear_monitor_request(
&mut self,
request_id: i32,
kind: MonitorKind,
) -> JdwpResult<()>
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.
Sourcepub async fn clear_field_watch(
&mut self,
request_id: i32,
kind: WatchKind,
) -> JdwpResult<()>
pub async fn clear_field_watch( &mut self, request_id: i32, kind: WatchKind, ) -> JdwpResult<()>
Source§impl JdwpConnection
impl JdwpConnection
Sourcepub async fn set_breakpoint_ex(
&mut self,
class_id: ReferenceTypeId,
method_id: MethodId,
bytecode_index: u64,
suspend_policy: SuspendPolicy,
filters: EventFilters,
) -> JdwpResult<i32>
pub async fn set_breakpoint_ex( &mut self, class_id: ReferenceTypeId, method_id: MethodId, bytecode_index: u64, suspend_policy: SuspendPolicy, filters: EventFilters, ) -> JdwpResult<i32>
Sourcepub async fn set_step_ex(
&mut self,
thread: ThreadId,
depth: StepDepth,
exclude: &[String],
only: &[String],
) -> JdwpResult<i32>
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.
Sourcepub async fn clear_step(&mut self, request_id: i32) -> JdwpResult<()>
pub async fn clear_step(&mut self, request_id: i32) -> JdwpResult<()>
Sourcepub async fn clear_all_breakpoints(&mut self) -> JdwpResult<()>
pub async fn clear_all_breakpoints(&mut self) -> JdwpResult<()>
Sourcepub async fn get_array_length(&mut self, array_id: ObjectId) -> JdwpResult<i32>
pub async fn get_array_length(&mut self, array_id: ObjectId) -> JdwpResult<i32>
Sourcepub async fn get_array_values(
&mut self,
array_id: ObjectId,
first: i32,
length: i32,
) -> JdwpResult<Vec<Value>>
pub async fn get_array_values( &mut self, array_id: ObjectId, first: i32, length: i32, ) -> JdwpResult<Vec<Value>>
Sourcepub async fn set_array_values(
&mut self,
array_id: ObjectId,
first: i32,
values: &[Value],
) -> JdwpResult<()>
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.
Sourcepub async fn create_string(&mut self, s: &str) -> JdwpResult<ObjectId>
pub async fn create_string(&mut self, s: &str) -> JdwpResult<ObjectId>
Sourcepub async fn set_frame_value(
&mut self,
thread_id: ThreadId,
frame_id: FrameId,
slot: i32,
value: &Value,
) -> JdwpResult<()>
pub async fn set_frame_value( &mut self, thread_id: ThreadId, frame_id: FrameId, slot: i32, value: &Value, ) -> JdwpResult<()>
Source§impl JdwpConnection
impl JdwpConnection
Sourcepub async fn get_line_table(
&mut self,
ref_type_id: ReferenceTypeId,
method_id: MethodId,
) -> JdwpResult<LineTable>
pub async fn get_line_table( &mut self, ref_type_id: ReferenceTypeId, method_id: MethodId, ) -> JdwpResult<LineTable>
Sourcepub async fn read_line_tables_independently(
&self,
pairs: &[(ReferenceTypeId, MethodId)],
) -> Vec<JdwpResult<LineTable>> ⓘ
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.
Sourcepub async fn get_bytecodes(
&mut self,
ref_type_id: ReferenceTypeId,
method_id: MethodId,
) -> JdwpResult<Vec<u8>>
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.
Sourcepub async fn get_variable_table(
&mut self,
ref_type_id: ReferenceTypeId,
method_id: MethodId,
) -> JdwpResult<Vec<Variable>>
pub async fn get_variable_table( &mut self, ref_type_id: ReferenceTypeId, method_id: MethodId, ) -> JdwpResult<Vec<Variable>>
Sourcepub async fn read_variable_tables_independently(
&self,
pairs: &[(ReferenceTypeId, MethodId)],
) -> Vec<JdwpResult<Vec<Variable>>> ⓘ
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
impl JdwpConnection
Sourcepub async fn get_object_reference_type(
&mut self,
object_id: ObjectId,
) -> JdwpResult<ReferenceTypeId>
pub async fn get_object_reference_type( &mut self, object_id: ObjectId, ) -> JdwpResult<ReferenceTypeId>
Sourcepub async fn read_reference_types_independently(
&self,
object_ids: &[ObjectId],
) -> Vec<JdwpResult<ReferenceTypeId>> ⓘ
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.
Sourcepub async fn is_collected(&mut self, object_id: ObjectId) -> JdwpResult<bool>
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.
Sourcepub async fn get_object_values(
&mut self,
object_id: ObjectId,
field_ids: Vec<FieldId>,
) -> JdwpResult<Vec<Value>>
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- TheObjectIdof the objectfield_ids- Vector ofFieldIdsto 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.
Sourcepub async fn read_object_values_independently(
&self,
reads: &[(ObjectId, Vec<FieldId>)],
) -> Vec<JdwpResult<Vec<Value>>> ⓘ
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.
Sourcepub async fn get_reference_values(
&mut self,
ref_type_id: ReferenceTypeId,
field_ids: Vec<FieldId>,
) -> JdwpResult<Vec<Value>>
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- TheReferenceTypeIdof the class (fromclasses_by_signature)field_ids- Vector of staticFieldIdsto retrieve (fromget_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.
Sourcepub async fn set_reference_values(
&mut self,
class_id: ReferenceTypeId,
updates: Vec<(FieldId, Value)>,
) -> JdwpResult<()>
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.
Sourcepub async fn set_object_values(
&mut self,
object_id: ObjectId,
updates: Vec<(FieldId, Value)>,
) -> JdwpResult<()>
pub async fn set_object_values( &mut self, object_id: ObjectId, updates: Vec<(FieldId, Value)>, ) -> JdwpResult<()>
Source§impl JdwpConnection
impl JdwpConnection
Sourcepub async fn get_methods(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<Vec<MethodInfo>>
pub async fn get_methods( &mut self, ref_type_id: ReferenceTypeId, ) -> JdwpResult<Vec<MethodInfo>>
Sourcepub async fn get_class_loader(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<Option<ObjectId>>
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.
Sourcepub async fn get_interfaces(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<Vec<ReferenceTypeId>>
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.
Sourcepub async fn get_modifiers(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<i32>
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.
Sourcepub async fn implements_interface(
&mut self,
type_id: ReferenceTypeId,
wanted: &str,
) -> JdwpResult<bool>
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.
Sourcepub async fn get_source_file(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<String>
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.
Sourcepub async fn get_source_debug_extension(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<Option<String>>
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.
Sourcepub async fn get_fields(
&mut self,
ref_type_id: ReferenceTypeId,
) -> JdwpResult<Vec<FieldInfo>>
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- TheReferenceTypeIdto 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.
Sourcepub async fn instances(
&mut self,
ref_type_id: ReferenceTypeId,
max_instances: i32,
) -> JdwpResult<Vec<Value>>
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
impl JdwpConnection
Sourcepub async fn get_frame_values(
&mut self,
thread_id: ThreadId,
frame_id: FrameId,
slots: Vec<VariableSlot>,
) -> JdwpResult<Vec<Value>>
pub async fn get_frame_values( &mut self, thread_id: ThreadId, frame_id: FrameId, slots: Vec<VariableSlot>, ) -> JdwpResult<Vec<Value>>
Sourcepub async fn read_frame_values_independently(
&self,
reads: &[(ThreadId, FrameId, Vec<VariableSlot>)],
) -> Vec<JdwpResult<Vec<Value>>> ⓘ
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.
Sourcepub async fn pop_frames(
&mut self,
thread_id: ThreadId,
frame_id: FrameId,
) -> JdwpResult<()>
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
impl JdwpConnection
Sourcepub async fn get_string_value(
&mut self,
string_id: ObjectId,
) -> JdwpResult<String>
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- TheObjectIdof 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.
Sourcepub async fn read_string_values_independently(
&self,
string_ids: &[ObjectId],
) -> Vec<JdwpResult<String>> ⓘ
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
impl JdwpConnection
Sourcepub async fn get_frames(
&mut self,
thread_id: ThreadId,
start_frame: i32,
length: i32,
) -> JdwpResult<Vec<Frame>>
pub async fn get_frames( &mut self, thread_id: ThreadId, start_frame: i32, length: i32, ) -> JdwpResult<Vec<Frame>>
Sourcepub async fn owned_monitors(
&mut self,
thread_id: ThreadId,
) -> JdwpResult<Vec<Monitor>>
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.
Sourcepub async fn current_contended_monitor(
&mut self,
thread_id: ThreadId,
) -> JdwpResult<Option<Monitor>>
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.
Sourcepub async fn get_all_threads(&mut self) -> JdwpResult<Vec<ThreadId>>
pub async fn get_all_threads(&mut self) -> JdwpResult<Vec<ThreadId>>
Sourcepub async fn get_thread_name(
&mut self,
thread_id: ThreadId,
) -> JdwpResult<String>
pub async fn get_thread_name( &mut self, thread_id: ThreadId, ) -> JdwpResult<String>
Sourcepub async fn read_thread_names_independently(
&self,
thread_ids: &[ThreadId],
) -> Vec<JdwpResult<String>> ⓘ
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.
Sourcepub async fn read_thread_statuses_independently(
&self,
thread_ids: &[ThreadId],
) -> Vec<JdwpResult<(i32, i32)>> ⓘ
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.
Sourcepub async fn get_thread_status(
&mut self,
thread_id: ThreadId,
) -> JdwpResult<(i32, i32)>
pub async fn get_thread_status( &mut self, thread_id: ThreadId, ) -> JdwpResult<(i32, i32)>
Sourcepub async fn suspend_count(&mut self, thread_id: ThreadId) -> JdwpResult<i32>
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.
Sourcepub async fn suspend_all(&mut self) -> JdwpResult<()>
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.
Sourcepub async fn resume_all(&mut self) -> JdwpResult<()>
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.
Sourcepub async fn resume_all_fully(
&mut self,
probe_thread: ThreadId,
max_resumes: u32,
) -> JdwpResult<(u32, i32)>
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.
Sourcepub async fn suspend_thread(&mut self, thread_id: ThreadId) -> JdwpResult<()>
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.
Sourcepub async fn resume_thread(&mut self, thread_id: ThreadId) -> JdwpResult<()>
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.
Sourcepub async fn force_early_return(
&mut self,
thread_id: ThreadId,
value: &Value,
) -> JdwpResult<()>
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
impl JdwpConnection
Sourcepub async fn get_version(&mut self) -> JdwpResult<VmVersion>
pub async fn get_version(&mut self) -> JdwpResult<VmVersion>
Sourcepub async fn capabilities(&mut self) -> JdwpResult<VmCapabilities>
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.
Sourcepub async fn capabilities_new(&mut self) -> JdwpResult<VmCapabilitiesNew>
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.
Sourcepub async fn instance_counts(
&mut self,
ref_types: &[ReferenceTypeId],
) -> JdwpResult<Vec<i64>>
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.
Sourcepub async fn redefine_classes(
&mut self,
defs: &[(ReferenceTypeId, Vec<u8>)],
) -> JdwpResult<()>
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.
Sourcepub async fn dispose(&mut self) -> JdwpResult<()>
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.
Sourcepub async fn classes_by_signature(
&mut self,
signature: &str,
) -> JdwpResult<Vec<ClassInfo>>
pub async fn classes_by_signature( &mut self, signature: &str, ) -> JdwpResult<Vec<ClassInfo>>
Sourcepub async fn all_classes(&mut self) -> JdwpResult<Vec<ClassInfo>>
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
impl Clone for JdwpConnection
Source§fn clone(&self) -> JdwpConnection
fn clone(&self) -> JdwpConnection
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more