Skip to content

Remote Debugging

Guillaume Binet edited this page Oct 2, 2026 · 1 revision

Remote Debugging and Replay Inspection

Copper's remote debug API exposes a replaying application over Zenoh. A debugger such as Time Traveler can navigate the recorded timeline, replay CopperLists, inspect task state, query schemas, and read structured logs without adding ad hoc instrumentation to the runtime.

Starting a replay-backed server

Replay binaries should use the standard CLI contract:

--debug-base <zenoh-prefix>
--log-base <recorded-log>
--replay-log-base <replay-output>

cu29::replay::ReplayCli provides this contract for a dedicated replay binary. Use ReplayArgs with #[command(flatten)] when the binary has additional application-specific arguments. Each replay-server process should write to its own replay log path.

Run the application's replay target with --debug-base, --log-base, and --replay-log-base pointing to its Zenoh prefix, recording, and replay output. See the remote debug session example for a runnable host server.

Useful API methods

After session.open, a client can use:

Area Methods
Navigation nav.seek, nav.step, nav.replay
Timeline timeline.get_cursor, timeline.get_cl, timeline.list
State state.inspect, state.read, state.search
Schema schema.get_stack, schema.get_type, schema.get_outputs
Logs logs.strings, logs.list

Session lease

A replay server permits one active debug session. While otherwise idle, the debugger sends a session-scoped health.ping every second. Any request carrying the active session_id also renews the lease. If no session-scoped request arrives for 3 seconds, the server closes the session automatically and another debugger can connect. A health.ping without session_id checks whether the server is reachable but does not renew the lease.

While the lease is active, a second session.open fails with SessionBusy. Run another replay-server process with a different --debug-base and --replay-log-base when two independent logs or timeline cursors must be inspected concurrently.

schema.get_outputs accepts separate page and field_page values so tools can page both output slots and large field catalogs.

Debug values preserve Rust values that plain JSON cannot represent directly. This includes 128-bit integers, non-finite floats, and maps with non-string keys. Schema descriptors also distinguish structs, tuple structs, tuples, lists, arrays, maps, enums, and CuArrayVec containers so inspectors do not have to infer those shapes from display strings.

Handle-backed payloads

CuHandle contents are deferred by default. A payload contains a small handle descriptor, but the debugger does not read or copy the backing buffer unless the request sets:

{
  "include_handle_contents": true,
  "output_indices": [0, 2]
}

The selected contents are returned out of line in the response attachments array. Primitive buffers use raw little-endian bytes; other handle values use CBOR. Binary attachments require the CBOR wire codec. JSON requests that ask for attachments fail explicitly instead of converting the bytes into a large JSON array.

Linux shared-memory requirements

On Linux, the default local transport reserves a 1 GiB Zenoh shared-memory pool and sends replies of 256 KiB or larger through explicitly allocated SHM buffers. Startup checks both RLIMIT_MEMLOCK and available /dev/shm capacity. A large reply is rejected if it cannot use SHM; Copper will not silently copy it over the Unix socket.

Configure the shell that launches both Copper and the debugger before starting either process:

sudo prlimit --pid $$ --memlock=2147483648:2147483648
ulimit -l 2097152
df -h /dev/shm

The ulimit -l value is in KiB. A shell cannot raise its soft limit above its current hard limit, which is why changing only ulimit may fail. For a systemd service use LimitMEMLOCK=2G. For Docker, configure both memory locking and SHM capacity:

--ulimit memlock=2147483648:2147483648 --shm-size=2g

Tests and memory-constrained deployments can choose an explicit profile with RemoteDebugShmConfig, validate it with remote_debug_shm_preflight, pass it to RemoteDebugZenohServer::new_with_shm_config, and configure the client builder with the same profile.

Non-Linux platforms retain the existing 4 MiB pool and 3 KiB transport-optimization threshold. They do not run the Linux RLIMIT_MEMLOCK or /dev/shm checks.

Clone this wiki locally