Skip to content

DAP model

How the Debug Adapter Protocol is represented and driven. Two crates: dap-protocol for the types, dap-client for the conversation.

Bugsaur targets DAP 1.71.

dap-protocol: types are generated

The protocol types are generated from the DAP schema by crates/dap-protocol/generator, not written by hand.

That is a deliberate trade. The protocol is large and changes upstream; a hand-maintained copy drifts, and the drift shows up as a field that silently never arrives. Generated types make a schema change a compile error instead.

Round-trip tests (crates/dap-protocol/tests/roundtrip.rs) check that serialising and deserialising a message returns the same value — the property that matters when the other side is a program you did not write.

dap-client: two transports

Transport Used by How it works
stdio debugpy, php Bugsaur starts the adapter and speaks over its stdin / stdout
TCP dlv, codelldb Bugsaur picks a free port, passes it via tcp_argument, and connects

Both carry the same framing: Content-Length headers followed by a JSON body, implemented in codec.rs and tested against malformed and split input.

Splitting matters more than it sounds: a TCP read boundary can land anywhere, including in the middle of a header, and a codec that assumes one read equals one message fails only under load.

Request management

request.rs and the request manager pair responses with the requests that produced them, using the protocol's sequence numbers.

Three things it has to get right:

Late responses. A response can arrive after the state it belongs to is gone. The stop generation decides whether it still matters, and it is dropped if not.

Long requests. The startup handshake — launch / attach and configurationDone — is waited on for 120 seconds by default. Some adapters compile inside launch; others answer configurationDone only after the debuggee starts. Every 30 seconds the wait is reported to the log with the pid holding the answer — a silent debugger is indistinguishable from a hung one.

Adapter death. An adapter that exits mid-conversation must fail the session cleanly rather than leave requests pending forever.

What Bugsaur does not do

It does not interpret request bodies. launch_arguments from a profile goes to the adapter as-is, overriding whatever was generated, key by key. Neither project-config nor the launcher knows what those keys mean — that knowledge belongs to the adapter.

It does not special-case adapters in shared code. Where an adapter needs different treatment, that lives in the adapter catalog as data. This is a deliberate contrast with implementations that put, say, Python string unescaping in the common variable path.

It does not log request bodies. Values of env never reach the log. This is an invariant: request tracing cannot be extended without stripping them first, which is why the long-request message carries only mode and program.

Testing without an adapter

fake-dap-adapter is a scriptable adapter used by tests: it replies with prepared responses, so protocol handling, state reduction and the UI can be tested with no language toolchain installed and no network.

Real adapters are covered by ignored end-to-end tests, run explicitly:

make live-codelldb
make live-dlv-100k
make live-debugpy
make live-php-docker
ADR Subject
ADR-001 the protocol layer and generated types
ADR-002 transports
ADR-003 the state model and stale responses
ADR-006 the variable tree: laziness, pagination, caching
ADR-008 the PHP DBGp transport

See Architecture Decision Records.