3.10.5 The ZRCP server (ZEsarUX's protocol)¶
--zrcp-port N makes JNEXT serve ZRCP, the line-based text protocol ZEsarUX
offers on its --remoteprotocol-port, speaking as ZEsarUX 12.0 does. Its main
client is DeZog's zrcp remote (the one DeZog uses for ZEsarUX); because every
command is a line of text, telnet is a client too. The server is
src/remote/zrcp/, one adapter over the shared socket transport
(3.10.1), reaching the machine only through jnext::dbg::Debugger. The design, with the
ZEsarUX transcripts and DeZog source lines every rule below comes from, is
doc/design/debug-subsystem/zrcp-frontend.md (§11 records what was built and
every deviation; §10 lists the deliberate divergences from ZEsarUX).
The pieces¶
| File | What it holds |
|---|---|
src/remote/zrcp/zrcp_server.{h,cpp} |
ZrcpServer, a remote::Protocol and a dbg::Listener: the line reader, the command table — every one of the 125 names ZEsarUX 12.0's own ls prints: 67 served, 1 declined (exit-emulator), 57 answered Error. Unsupported command in jnext: — the session state, the run state machine, the breakpoint slots and the memory-breakpoint map |
src/remote/zrcp/zrcp_format.{h,cpp} |
the reply formatters: the register line at the widths DeZog's decodezesaruxdata.ts reads, MMU=, disassembly at column 7, hexdump, the cpu-history line, quoted-argument splitting |
src/remote/zrcp/zrcp_condition.{h,cpp} |
ZEsarUX's breakpoint-condition dialect: its tokeniser and its operator grouping, translated into a fully bracketed DSL expression that script::compile_expr (3.10.6) compiles; SEGn / ROM / RAM are evaluated here, since the DSL cannot read whether a slot is ROM |
src/platform/debug_servers.{h,cpp} |
DebugServers opens it beside the DZRP and GDB servers — its own listener, its own backend client — in all three loop owners |
The session and run¶
The client is attached on connect and detached on disconnect or quit, which
removes everything it subscribed and releases a pause that is its own, or hands
it to another arming client if one is attached (the backend's rule,
3.9.3). cpu-step mode, the prompt, set-cr, the debug-settings
byte and the partial T-state base are adapter state; the backend learns none
of them.
run, run n and cpu-step-over do not answer at once: the Running until …
line goes out first, and the stop reply is written the first time the adapter
is asked after the machine stops — at the top of a pump or after its drain —
so it is level-triggered on state().paused, not on the Paused edge. While
one is in flight no line is executed: any byte received stops the machine and
the line it belongs to is discarded, which is how ZEsarUX's clients interrupt a
run. run n is a loop of step_into in time slices, so a large n never
holds one pump. hard-reset-cpu is a cold boot completed inside the reply,
through the loop owner's LoopDriver (the reconstruct contract of 3.9.3): the session
and its subscriptions survive it, and a stopped machine stays stopped.
get-memory-pages¶
On the Next memory_pages() names each slot from SlotInfo::now, with ZEsarUX's
spellings: O<2*image + half> for ROM, A<page>, SR<NR 0x04> (the full value) in
config mode, RO for the boot ROM, DIV for either DivMMC memory (also over a RAM-mapped
slot: the VHDL overlays it too). The Multiface, Layer 2, alt ROM and a floating slot have no
ZEsarUX spelling and print the slot's own mapping (O<n> / A<nr_page>). On the legacy
machines segment 0 is DIV while slot 0 reads DivMMC. MMU= stays the DeZog projection
(ROM half >= 0x8000) and does not use now. cpu-step-over reads the opcode at the PC
through memory_reader(), the instruction the CPU will fetch.
CVAR(path): jnext's extension of the dialect¶
evaluate and a breakpoint condition both go through translate_condition(), ZEsarUX's tokeniser and grouping, and only the
translation reaches the DSL: cvar(...) typed into evaluate was Error parsing. The tokeniser therefore takes one new
operand, CVAR( and the raw path up to the first ) (Tok::CVar, Token::raw), before its function branch; a (, a ", a missing
) and an empty path make tokenise() fail, which is ZEsarUX's Error parsing. The path is one token, so first_top() never
splits inside it (CVAR(p->x)+1 splits at the + only) and its case is kept (canonical_of() prints CVAR(path), which
get-breakpoints lists and Breakpoint fired: echoes). Translator::operand() turns it into the DSL text cvar(path), so ZRCP
reaches c_int32_scalar() only through the evaluator: one rule for scripts and ZRCP (3.10.6).
No ZEsarUX exchange changes meaning, because ZEsarUX answers this text Error parsing, and the recorded fixture is untouched. A read
that fails answers Error evaluating parsed string: CVAR(path), with no reason (the reply is ZEsarUX's; scripts and GDB's
monitor print show the reason). A condition with no PC= first term is checked at every instruction, so one with a CVAR
builds the C frames each time: help set-breakpoint says to put a PC= term first.
The write is the same extension on the one assignment verb: set-register CVAR(path)=value (cmd_set_register(), zrcp_server.cpp:1190) writes
a C variable of frame 0 through write_c_value() (7.10). The table's census (ZRCP-TAB-01)
pins the command names to ZEsarUX's ls, so no set-cvar could be added. Success answers the register line, and every failure (no ) or =, an
empty path or value, a refusal of the facade) is ZEsarUX's Error changing register, with no reason, as a failed CVAR() read has none.
Breakpoints, history, loading¶
The 100 condition slots of set-breakpoint are one backend subscription each,
owned by the client. A PC= first term is the fast path (an Execute
subscription at that address); any other condition fires on its false-to-true
edge, as in ZEsarUX, keyed to the master cycle. Memory breakpoints are kept as
ZEsarUX's per-address type map and turned into one Mem range subscription
per run of equal type. The history (cpu-history) is a view over the
machine's trace log: clear and set-max-size move the session's view, never
the trace jnext's own Step Back reads. Turning the trace, coverage or call
tracking on is owned: a client turns off only what it turned on. smartload
goes through the loop owner's LoopDriver::load, the same path as --load;
snapshot-save / snapshot-load are the backend's bookmarks, kept for the
session.
Tests¶
zrcp_adapter_test(Qt-free, both configurations) runs the productionServerover the transport's in-memory fake, on a real machine throughpump(): the framing, the command census, every formatter against ZEsarUX's bytes, the run state machine (a stop is proved by the machine being stopped where it should be), the condition translator on both sides of every grouping rule, the slots, the memory map, history, coverage and loading.test/fixtures/zrcp/zesarux-12.0-exchanges.txtholds scenes recorded from the real ZEsarUX 12.0 server, one fresh server per scene: the exchanges whose replies depend on nothing but the session.zrcp_adapter_test(rowsZRCP-FIX-*) and thezrcp-funcregression row both replay every scene and compare the bytes. A new deliberate divergence from ZEsarUX therefore shows up as a fixture failure, and belongs in the design's §10, not in the fixture.zrcp-func,zrcp-bp-func,zrcp-hist-func,zrcp-sdl-funcandzrcp-qt-funcdrive the real binary over a socket with an independent client (test/00regression/zrcp-peer.py), which knows ZRCP only from the fixture, the transcripts and DeZog's parser.