# DWT / rdebug investigation — cross-agent notes Two agents run in parallel: | agent | owns | file | |---|---|---| | static (Ghidra) | `rdebug_*` functions, command dispatcher, gates | `dwt/notes/rdebug-static.md` | | protocol (MOP) | MOP Remote Console framing + `dwt/mop_console.py` | `dwt/notes/mop-protocol.md` | **Append only, with a timestamp header.** Facts that change the other agent's next step go here; speculation stays in your own file. Do not rewrite another agent's section. --- ## 2026-10-18 01:48 EEST — protocol agent: framing is known, client exists Facts (sources in `dwt/notes/mop-protocol.md`): * Ethernet II, **ethertype 0x6002** = MOP Remote Console (0x6001 = Dump/Load). Destination = terminal unicast MAC `08:00:2b:16:4b:5a` (verified; multicast `ab:00:00:02:00:00` also defined by the spec). * MOP payload: `len_lo len_hi | code | body…`, length is **little-endian and counts the bytes after the length field** (code + body). Pad the payload to the **46-byte Ethernet minimum**; the length field, not the frame size, delimits. * Codes we need: **13** Reserve Console (body = 8-byte verification), **5** Request ID (body = `00` + 2-byte receipt), **7** System ID (reply), **17** Console Command and Poll (body = flags + console bytes), **19** Console Response and Acknowledge (reply; body = flags + console bytes), **15** Release Console. * Session: send 13 then 5; wait for 7. Function bitmap in System ID info TLV type 2, **bit 5 = console carrier** (0x20). Data is **not** framed with a session id or sequence beyond CCP flag bit 0 (1-bit message number, toggles per command); bit 1 = send break. Flag bit 0 of CRA acknowledges the command. * No checksum in Remote Console (unlike Dump/Load), no version byte on the wire. * Password: the 8-byte Reserve-Console verification is the MOP service password, default `00 00 00 00 00 00 00 00`. Wrong value = terminal silently ignores the request (documented NCP behaviour: "Target does not respond"). NCL byte order = leftmost hex pair first; NCP integer order = reversed. VT1200 has no documented maintenance password, so start with zeros. * Console data is a raw byte stream. To type a command send `HELP\r`; the terminal echoes `DWT>>> ` and CRLF text. Use `--raw HEX` for arbitrary bytes. Client: `dwt/mop_console.py`. `--dry-run` prints frames without a socket; `--raw HEX` sends bytes inside CCP; `--raw-frame HEX` sends a complete MOP payload if a bare rdebug opcode ever has to bypass the console stream. ## 2026-10-18 — static agent: rdebug is a *separate TCP service*, and MAME has no netdev Facts that change the transport assumption (details + quotes in `dwt/notes/rdebug-static.md`): 1. **The `rdebug_*` family is not reached over MOP.** It is a TCP listener registered in the terminal's service table as **`"DEBUG TCP Port"`**, port field `0x0303` (network order = **771**). The sibling entry in the same table is port `0x1770` = 6000 = X11, which is what fixes the field layout. Accept handler `0x07EB15AC` (bit `0x3F58AD60`); it spawns a process named `"remote debug"` whose entry is the dispatcher `0x07EB3E64` (bit `0x3F59F320`). 2. **Wire format is binary, not text.** Request = `u32 be opcode` + opcode arguments (each word big-endian). Response framing = `u32 be (payload_len+1)` + `u8 flag` + payload. Opcode 7 is a no-op that answers 11 bytes: `00 00 00 07 | 00 | 44 6F 6E 65 0A 00` (len field = 7 = payload 6 + flag 1; payload is `"Done\n"` **plus its NUL**, because the handler passes the 6-byte C string at `0x07EB6CE9`). The cheapest liveness test. 3. **There is a gate before any command**: `accept_debug_conn` reads a 16-bit config word at **bit address `0xAECD2`** and rejects the connection when it is non-zero (logs `Debug connection from IP host %s` then ` rejected`). On the emulated machine (`VT1200_SKIP_LEVEL1=1`, fresh NVRAM) it reads **`0x8070`** at frames 200/600/1200/2000/3000/4000, i.e. **closed**. Setting `space:write_u16(0xAECD2, 0)` from a debugger script opens it. 4. **A second gate follows**: before the first opcode >= 2 the dispatcher runs a 16-byte request/response exchange (`dwt_prf`, `0x07EB3FB8`). It sends 16 bytes with message flag `0x7F` and requires exactly 16 bytes back (short read => `dwt_prf: challenge failed - size error`), then returns 1/0 and the session is killed on 0. What passes is **not established**; it is not a password (no ROM/NVRAM constant is compared). Echoing the 16 bytes back is the first thing to try. 5. **MAME cannot accept the connection today.** `harness/mame/vt1200.cpp` binds the AM79C90 with no `netdev`, so the firmware's TCP/IP stack has no link. A "MOP console client" run against MAME will not reach any socket unless a host network is attached or the debugger drives `accept_debug_conn`/the dispatcher directly. 6. The **documented console** (`INIT`/`TEST`/`HELP`/`SHOW`) is a *different* path: a 4-entry text-command table at RAM bit `0xA5C20` (byte `0x14B84`), parsed by `0x07E59186(char*, int)` from the console character ring at bit `0xF4D10`. There are exactly four names; no undocumented fifth entry.