Files
vt1200/dwt/notes/GUI_APPS.md
T
SDD a94e99579f dwt/gui: injected X window works via create_process
The xserver local service is reachable from a payload, but a connect made
inside the rdebug process is accepted at the service layer and then never
dequeued: the server's accept callback runs (3F34EC50), but no 12-byte X11
setup read follows and Xlib waits in select.

create_process(entry,name,stack,1,arg) from a new process fixes it. xlaunch
creates the child; the server reads the setup (READ12 ret 0x0C), writes the
reply (8 + 0x90), and xhello creates/maps/syncs a 400x200 window. The payload
also had to keep Display* in a11 (callee-saved) instead of reloading it from a
RAM cell, and re-run SETF after each Xlib call.

Final breakpoint-free frame: dwt/gui/evidence/xhello_window_frame2838.png.
ROM untouched (b46a3e442ccb86134d2356bd70d22fd2).
2026-10-18 14:28:58 +03:00

473 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# How the VT1200's GUI apps are built — and what a payload can do with that
Status: **static architecture DONE; injection verdict measured, not assumed.**
A payload *can* call ROM routines with the firmware calling convention (proved
live: `strlen("0::0")` returned 4), but `XOpenDisplay` from the injected process
**never completes**: the run stays at stage 2, the display pointer stays 0, the
X server's client watermark does not move, and the rdebug service stops answering.
Creating a *new* X window from a payload is therefore **hard**, not medium.
All work is static (Ghidra on `vt1200_v22.bin`) plus one live experiment on a
private NVRAM/cfg under the documented `VT1200_SKIP_LEVEL1=1` divergence. ROM
untouched (`md5 b46a3e442ccb86134d2356bd70d22fd2`), no driver edits.
Address convention used here: **Ghidra byte address** and **firmware bit address
(= Ghidra × 8)**. Ghidra address = `0x7E40000 + ROM file offset`.
---
## 1. The model in one paragraph
The terminal runs a small **process-per-application** OS on one TMS34010. RAM is
shared and unprotected; processes are contexts (a PCB from `create_process`, a
stack, a private file table at `*(0x1B3900)+0x100`). The graphics system is a
**real X11 server in ROM** (`dix`, the DEC/Athena server: strings
`Hello, this is the X server`, `CreateWellKnownSockets`,
`Internal error in ConfigureWindow`, `BadWindow - parameter not a Window`). The
GUI applications are **X clients built on Xlib, which is also in ROM**
(`XConnectDisplay`, `Xlib: sequence lost`, `XCreateCursor`-class request
builders). There is **no separate DWT window API**: the "DWT Window Manager" is
an ordinary X client that speaks the X11 WM protocol plus DECwindows atoms
(`DEC_WM_HINTS`, `WM_MOVED`, `DEC_WM_ICON_STATE`, `DEC_WM_TAKE_FOCUS`).
Because RAM is shared, app *state* (the Messages list, window ids, cursors) is
global and a routine in one process can update another app's data — that is why
rdebug opcode 8 can write the Terminal Manager's Messages box without a
connection of its own. The X protocol traffic, however, is done by each app's
own process through its own connection (`Display *` in a global: TM `@0xB78E0`,
WM `@0x27AA80`).
## 2. Function table
### 2.1 Processes / app lifecycle
| what | Ghidra | bits | notes |
|---|---|---|---|
| `create_process(entry,name,stack,1,arg)` | `0x07EE0D48` | `0x3F706A40` | mallocs a 0x39-byte PCB, copies the name, allocates the stack, links it at `@0x1B3860` |
| current-process pointer | — | `0x1B3900` | `+0x100` = user area (fd table) |
| process list head | — | `0x1B3860` | |
| `Main Program` (creates SERVER + SESSIONMGR) | `0x07E69B6A`…`0x07E69C1A` | `0x3F34DB50` | itself a created process (name `Main Program`) |
| X server entry **SERVER** | `0x07E6C522` | `0x3F362910` | dix main; prints `Hello, this is the X server` |
| Session Manager / Terminal Manager **SESSIONMGR_PROC** | `0x07E5CDBC` | `0x3F2E6DE0` | *is* the Terminal Manager (see §3) |
| Window Manager **WM** | `0x07F5702C` | `0x3FAB8160` | DWT WM; prints `DWT Window Manager is here to stay` |
| terminal emulator **VTE** | `0x07E9B60A` | `0x3F4DB050` | DECterm/VTE |
`create_process` argument order (from the pushes at `0x7E69B72`): the first
argument is the **entry point (bit address)**, the second the **name string**,
the third the **stack size**, the fourth a constant `1`, the fifth a pointer/arg.
Example (`main`, `0x07E69BE2`):
```
07E69BC8 MOVK 0x1,A9 ; arg3 = 1
07E69BCC MOVI 0x4E20,A9 ; arg2 = 0x4E20 stack
07E69BD2 MOVI 0x3F34E2E0,A8 ; arg1 = "SESSIONMGR_PROC"
07E69BDA MOVI 0x3F2E6DE0,A9 ; arg0 = entry
07E69BE2 CALLA 0x07EE0D48 ; create_process
```
### 2.2 Xlib (client side, all in ROM)
The request-builder opcode is visible in each function (`MOVB 0xNN` into the
request buffer), which is how these were named; the DB26 `XDrawString` and DB25
`XCreateSimpleWindow` shapes were then checked against their argument blocks.
| Xlib function | Ghidra | bits | shape seen |
|---|---|---|---|
| `XOpenDisplay(name)` | `0x07F384AE` | `0x3F9C2570` | mallocs 0x4D8, parses the name, connects |
| `XParseDisplay` / service lookup | `0x07FA7538` | `0x3FD3A9C0` | splits `host:display.screen`, resolves `"xserver"` |
| `XCreateSimpleWindow(d,p,x,y,w,h,bw,border,bg)` | `0x07FA8372` | `0x3FD41B90` | opcode 1, 9 args |
| `XMapWindow(d,w)` | `0x07FAC8B6` | `0x3FD645B0` | opcode 8, 2 args |
| `XMapSubwindows(d,w)` | `0x07FAC97E` | `0x3FD64BF0` | opcode 9 |
| `XSelectInput(d,w,mask)` | `0x07FB2FAA` | `0x3FD97D50` | ChangeWindowAttributes, mask 0x800 |
| `XChangeWindowAttributes` | `0x07FA70EE` | `0x3FD38770` | opcode 2 |
| `XConfigureWindow` | `0x07FA74A2` | `0x3FD3A510` | opcode 12 |
| `XInternAtom(d,name,only_if_exists)` | `0x07FABC84` | `0x3FD5E420` | opcode 16 |
| `XChangeProperty` | `0x07FA6E66` | `0x3FD37330` | opcode 18 |
| `XCreateGC(d,drawable,mask,values)` | `0x07F32E00` | `0x3F997000` | opcode 55; template copied from ROM `0x2317D0` |
| `XChangeGC` | `0x07F333F0` | `0x3F999F80` | opcode 56 |
| `XSetForeground(d,gc,pixel)` | `0x07FB334C` | `0x3FD99A60` | caches in GC, marks dirty |
| `XSync(d,discard)` | `0x07FB3ED6` | `0x3FD9F6B0` | sends GetInputFocus (43), waits |
| `XDrawString(d,drawable,gc,x,y,s,n)` | `0x07FB4000` | `0x3FDA0000` | opcode 74 (PolyText8) |
| `XLoadFont(d,name)` | `0x07FA95D0` | `0x3FD4AE80` | opcode 45 (OpenFont) |
| `XCopyArea` | `0x07FA7C44` | `0x3FD3E220` | opcode 62 |
| internal PolyFillRectangle fast path | `0x07F33D1E` | `0x3F99E8F0` | opcode 70; not a public entry point |
| `strlen` | `0x07F68B1A` | `0x3FB458D0` | used as the ABI check |
### 2.3 X server internals that matter
| what | Ghidra | bits |
|---|---|---|
| connection setup / accept entry | `0x07EB1700` | `0x3F58B800` |
| auth reject logger (`0x07E7E8D0`) | `0x07E7E8D0` | `0x3F3F4680` |
| client table (32 × 0x80) | RAM | `0x1B6600` |
| client watermark | RAM | `0x1B8620` |
| client bitmap (32 bits) | RAM | `0x1B86A0` |
| well-known socket / service name | ROM `"xserver"` | `0x3F591F08` |
The server's connection path reads the 12-byte X11 setup (`0xEBB1CE(conn,12,buf)`),
checks the byte-order byte, then reads the auth name/data and looks the peer up
in the user/node table. Failure goes to a routine that logs
`Client is not authorized to access server` and closes.
## 3. Terminal Manager: what it is and how it starts
**The Terminal Manager is not a separate application.** It is the UI half of the
process `main()` creates as `SESSIONMGR_PROC` (entry `0x3F2E6DE0`). The single
function that begins at `0x7E5CDBC` runs straight through the display setup
(`0x7E5D0F0`), the TM window creation (`0x7E5D2D0`), the WM launch
(`0x7E5D760`) and the VTE launch (`0x7E5E676`). This is continuous code: the
disassembly has no return between `0x7E5CDBC` and `0x7E5F7xx`.
Startup, quoted:
```
07E5D1FA MOVI 0x3F30DC00,A8 ; "0::0" at ROM 0x7E61B80
07E5D204 CALLA 0x07F384AE ; XOpenDisplay("0::0")
07E5D20A MOVE A8,@0xB78E0 ; TM's Display * (global)
07E5D212 JRNE 0x07E5D228
07E5D216 MOVI 0x3F30DC28,A8 ; "DWTM Failed to open display\n"
07E5D21E CALLA 0x07F6683C ; printf
```
The title string `Terminal Manager V2.2` is at ROM `0x7E61D8B`
(bit `0x3F30EC58`) and is referenced from the same function (`0x7E5E928`).
`Terminal Manager XIO error... restarting...` (`0x7E61B53`) confirms the TM is an
**XIO/Xlib client**, not a private drawing API.
The TM's first window is a 1×1 "WM protocol" window; the visible TM box is built
with the widget toolkit (the code at `0x7E7F24A`, `0x7E8465A`, `0x7E873FE`,
`0x7E94880`, … that calls `XCreateSimpleWindow` + `XMapWindow`).
**Launched from:** `main()`, not a menu or an init list. A menu does exist for
secondary apps — the `Create` menu (`Terminal Window on Host Port`, `TELNET
Terminal Window…`, `PRINT`) calls `create_process` at `0x7E91BA0`/`0x7E91C26`.
## 4. Minimum "hello window" call sequence
From the TM's own code (`0x7E5D2B4`–`0x7E5D2D0`) and the WM's
(`0x7F57044`–`0x7F5704E`). The calling convention is **not** "push on SP": the
caller reserves one slot with `MOVE A14,-*SP,1`, pushes arguments **upward**
with `MOVE arg,*A14+,1` (last argument first), and the callee restores A14/SP.
```
; root = *(*(display+0x480)+0x40) (TM 0x7E5D290..0x7E5D2AA)
MOVE @0xB78E0,A11 ; display
MOVE @0xB7F80,A9 ; default screen number
SLL 0x9,A9 / SLL 0x7,A7 / ADD A7,A9 ; * 0x280 (screen struct)
MOVE *A11(0x480),A7 / ADD A9,A7 ; screens[]
MOVE *A7(0x40),A9 / MOVE A9,@0xB7960 ; root window
; window = XCreateSimpleWindow(display, root, 0,0, 1,1, 0, 0, 1)
MOVE A14,-*SP,1
MOVK 1,A7 / MOVE A7,*A14+,1 ; background = 1
SUB A7,A7 / MOVE A7,*A14+,1 ; border
MOVE A7,*A14+,1 ; border width
MOVK 1,A7 / MOVE A7,*A14+,1 ; height
MOVE A7,*A14+,1 ; width
SUB A7,A7 / MOVE A7,*A14+,1 ; y
MOVE A7,*A14+,1 ; x
MOVE A9,*A14+,1 ; parent = root
MOVE A11,*A14+,1 ; display
CALLA 0x07FA8372 ; -> window id
```
The full minimum is **4 Xlib calls**:
1. `XOpenDisplay("0::0")` — `0x3F9C2570`
2. `XCreateSimpleWindow(d, root, x, y, w, h, bw, border, background)` —
`0x3FD41B90`
3. `XMapWindow(d, win)` — `0x3FD645B0`
4. `XSync(d, 0)` — `0x3FD9F6B0` (flush + round trip)
To draw text add `XLoadFont(d,"fixed")` (`0x3FD4AE80`), `XCreateGC`
(`0x3F997000`) with `GCFont=0x4000`, `XSetForeground` (`0x3FD99A60`) and
`XDrawString` (`0x3FDA0000`). No event loop is needed for a static window; the
server keeps it after the client goes idle.
Globals: only the `Display *` is per-app. There is **no global "current
display" to initialise**; `XOpenDisplay` allocates and returns it. Window ids,
GCs and fonts are server-side resources and are valid for any client.
## 5. The injection experiment (run) and the verdict
Payload: `dwt/gui/xhello.asm` → `dwt/gui/build/xhello.bin` (390 B), linked at bit
`0x01840000`, built with the TI SDK flow (`dwt/gui/build.sh`). It first calls
`strlen("0::0")` as an ABI check (must return 4; if not it returns without
touching Xlib), then `XOpenDisplay`, root lookup, `XCreateSimpleWindow`,
`XMapWindow`, `XSync`, storing a stage counter at bit `0x01850140` and the
display/window at `0x01850100`/`0x01850120`.
Run: `VT1200_SKIP_LEVEL1=1`, private `-nvram_directory`/`-cfg_directory`,
`-networkprovider taptun`, BOOTP/TFTP from `harness/serve.py --bootp`,
`-nothrottle`; frame/marker probe `dwt/gui/probe2.lua`. Injected with
`dwt/visual/inject.py` (`poke` + `jump`, challenge answered by
`dwt/challenge.respond`). The terminal booted to the Terminal Manager, answered
rdebug, and the payload was poked and jumped with no breakpoints.
Result:
```
strlen = 4 <- the firmware calling convention works
stage = 2 <- reached the XOpenDisplay call
display = 0 <- never returned
window = 0
xclient watermark = 1 (unchanged: no new client slot was taken)
jump: NO RETURN within 20 s; the same connection then times out
```
The machine stays alive (frames advance, the TM keeps running) but the
remote-debug process sits in Xlib's `select` wrapper (`0x3FD969F0`, waiting on
the display fd) and the rdebug service stops answering. No window appeared in
the frame dump (`dwt/gui/run/gui_frame15571.png` is the normal TM).
**Verdict: HARD** for "a payload creates its own window through XOpenDisplay".
The window API is ordinary Xlib — the missing piece is the **local transport
handshake**: a connection opened from the injected process is never accepted by
the server (client watermark does not move), so Xlib waits forever. The one
thing that decides it is whether the `"xserver"` service connection can be
established from a process other than the boot-time clients; that is not an API
question and is not answered by the ROM alone.
**MEDIUM** for a narrower goal: *influencing* an existing window. The shared-RAM
UI path is proven — `rdebug` opcode 8 calls the TM's append routine
`0x3F309350` and the text appears in the Messages pane with no connection of its
own. Any payload can call the same TK/UI routines, but they mutate the owning
app's data structures so that the *owner* does the X drawing.
## 6. Internal IPC to the X server
Both boot-time clients open the display with the literal string **`"0::0"`**
(TM at ROM `0x7E61B80`, bit `0x3F30DC00`; WM at `0x7F59D29`, bit `0x3FACE948`).
`XParseDisplay` (`0x7FA7538`) does **not** build a TCP address. It resolves a
**named local service**:
```
07FA766C CALLA 0x07F65C68 ; kernel_lock (per-process critical flag)
07FA7672 MOVI 0x3FD3DCA0,A8 ; "xserver"
07FA767C CALLA 0x07EE160C ; service attach by name
07FA7688 JREQ ... ; null -> unlock, sleep 100 ms, retry
```
`0x7EE160C` is a **service-table lookup/attach**; the lookup itself
(`0x7EE1B70`) scans a **32-entry table at `0x1E8D70`** (4-byte entries) with
`strcmp` against the name. The X server registers the same name on its side
(`"xserver"` appears next to `CreateWellKnownSockets` at `0x7E6A4B0`, and next to
the TCP accept `X Server's TCP Port` at `0x7E591F08`).
So the internal route is: **named local service `xserver`**, reached by
`XOpenDisplay("0::0")`, not TCP and not a Unix socket path. TCP port 6000 is a
*separate* listener; it is gated by the user/node authorisation list
(`Connection request from user %s on node %s rejected`, auth check
`0x7E7E8D0`), which is why an external `xdpyinfo` gets
`Client is not authorized to access server` while the local service clients do
not.
## 7. App inventory
GUI (X clients):
| process | entry (bits) | role |
|---|---|---|
| `SERVER` | `0x3F362910` | X11 dix server, well-known socket `xserver` + TCP 6000 |
| `SESSIONMGR_PROC` | `0x3F2E6DE0` | Session Manager **and** Terminal Manager UI (Messages, Ethernet/IP/LAT, memory bar, Customize/Print Screen panels) |
| `WM` | `0x3FAB8160` | DWT Window Manager: frames, title bars, icon box, DEC WM atoms |
| `VTE` | `0x3F4DB050` | terminal emulator window (created on demand by the TM) |
| `PRINT` | `0x3F3E2590` → `0x7E7C4B2` | print manager, launched from the `Print Screen`/`Create` menu |
Non-GUI processes (same `create_process` mechanism, for completeness):
`INETSERVER`, LAT server (`0x3F2BD3F0`), MOP server (`0x3FAED480`), LANCE server
(`0x3F9DB9A0`), `TCPIP_FONTS` (`0x3F95EB10`), `resolver` (`0x3F5B7EF0`),
`Telnet` (`0x3F6D2F90`), `Route` (`0x3F5BF380`), `rtvalidate` (`0x3F663360`),
`VTE_listen` (`0x3F50F6A0`), `VTE_listen_shr` (`0x3F50F860`), `Bootp`
(`0x3F57B710`), `Inetd` (`0x3F587C00`), `domain_name` (`0x3F5B9510`),
`remote debug` (`0x3F59F320`), `TCP_UP` (`0x3F22F940`), `Terminate_tcp`
(`0x3F22E7F0`), `Main Program` (`0x3F34DB50`), `DWTM`/`VTE` helpers.
There is no file manager, editor, clock or cardfiler in this image; the only
GUI apps are the five above, plus the TM's own modal/panel code in the
`0x7E7xxxx`–`0x7E9xxxx` toolkit range.
## 8. The experiment that would settle the transport question
Run the same payload but **instrument the server side**: breakpoint the server
accept entry `0x3F58B800` and the auth check `0x3F3F4680`, log hits to a file
from a Lua frame hook (MAME's `printf` action did not reach stdout with
`-debugger none` in this run, so use a `wpset`/`bpset` action that increments a
RAM counter, or a Lua hook that samples `PC` every frame), then inject
`xhello.bin`. Three outcomes:
1. Accept never hit → the service connect from a non-boot process never reaches
the server; the next step is to call `0x7EE160C("xserver")` by hand under the
debugger and inspect its return value / the fd it installs.
2. Accept hit, auth rejects → authorise the injected client (the same
user/node table the config UI edits), then the window should appear.
3. Accept hit, auth passes, no reply → the setup packet is wrong; compare the
bytes Xlib writes from the WM's connection with ours.
A second, independent route worth trying: `create_process` a fresh process whose
entry is the payload (`create_process(0x01840000, "GUI", 0x2000, 1, 0)`), so the
X connection is made by a normal new process rather than by the rdebug
dispatcher. That isolates "wrong process context" from "wrong transport".
Not run: the owner's MAME was not running, but the shared tap
(`tap-mess-1000-0` / bridge `vt1200`) was used only after confirming no
`mame0289/vt1200` process existed; a private NVRAM and cfg directory were used
and the run was stopped afterwards. No second MAME was started while another was
live.
## 9. Evidence files
| path | contents |
|---|---|
| `dwt/gui/xhello.asm` | the payload (ABI check + 4 Xlib calls) |
| `dwt/gui/build.sh` | TI SDK build wrapper, output under `dwt/gui/` |
| `dwt/gui/build/xhello.bin`, `.lst`, `.map` | assembled payload, listing, map |
| `dwt/gui/probe.lua`, `probe2.lua`, `probe3.lua` | frame snapshots + marker/PC telemetry + accept breakpoint |
| `dwt/gui/run/mame.log` | boot, markers (`strlen=4`, `stage=2`, `disp=0`, `xclients=1`) |
| `dwt/gui/run/gui_frame15571.png` | post-injection frame: normal Terminal Manager, no new window |
## 10. Transport settled: a `create_process` child opens a window
**Verdict: H3 holds, with one payload bug on top.** A process created by the
firmware's own `create_process` connects to the `"xserver"` local service,
completes the X11 setup handshake, creates and maps a window. The final,
**breakpoint-free** run shows it at frame 2838:
`dwt/gui/evidence/xhello_window_frame2838.png` (black 400x200 window at 100,100
with the WM frame/title bar, over the Terminal Manager).
The ROM is untouched (`md5 b46a3e442ccb86134d2356bd70d22fd2`); no driver edits.
Run configuration is the documented `VT1200_SKIP_LEVEL1=1` divergence, private
`-cfg_directory`/`-nvram_directory`, BOOTP from `harness/serve.py --bootp`.
### 10.1 H1: the connect *does* reach the server accept — even from rdebug
The old probe address `0x3F58B800` was wrong: it is the **epilogue** of the
function that creates the "remote debug" process (`mmfm sp,a7,a9,a11,a13; rets`),
not the X accept. The local-service path is:
| stage | bit address |
|---|---|
| `XOpenDisplay` | `3F9C2570` |
| `XParseDisplay` → service attach by name | `3FD3B3E0` |
| service attach / lookup | `3F70B060` / `3F70DB80` |
| server-side accept callback (registration arg 1) | `3F34EC50` |
| local accept worker, 12-byte setup read | `3F34F190` / `3F350170` |
| setup read implementation | `3F70C5C0` |
| TCP accept (separate, auth-guarded) | `3F58B830` |
Instrumentation of the **old** experiment (xhello called in the rdebug process)
shows the connect is accepted, not rejected:
```
TR OPEN entry pc=3F9C2570 a14=01852020 ...
TR ATTACH entry pc=3F70B060 ... proc=003FFBD0
TR ATTACH ret a8=008D3050
TR SETUP-WRITE pre ...
TR ACCEPT-CB entry proc=00414F90 ; SERVER runs the service callback
TR REGISTER ret a8=007ADA30 ; re-registers for the next client
(no READ12)
```
`proc=00414F90` is `SERVER` (confirmed from the process list). So the service
table lookup, the attach and the accept callback all succeed; the failure is
downstream of accept.
### 10.2 H2: no auth check on the local path
`3F3F4680` (the "Client is not authorized" path) is **never hit** for a local
connect. The auth/user-node table belongs to the TCP listener (`3F58B830`,
`X Server's TCP Port`), not to the named service. Nothing rejects `"0::0"`.
### 10.3 What the rdebug process does *not* get
After the accept callback, the server's local accept worker must dequeue the
connection and read the 12-byte X11 setup. In the rdebug-process run it never
does — there is no `READ12` after the injected `ACCEPT-CB`; the client then sits
in Xlib's `select` (`3FD969F0`), which is the original observation. The server
side was alive (boot clients' own `READ12` calls occur earlier in the same log).
A fresh `create_process` child is dequeued normally. Why the rdebug context is
not dequeued was not reduced to a single ROM check; what is measured is the
difference in outcome: same payload, same service, same server, only the
calling process differs.
### 10.4 H3: `create_process` makes it work
`dwt/gui/xlaunch.asm` (128 B, bit `01860000`) is the launcher. Its call shape is
the Telnet handler's (`0x3F6D4340`–`0x3F6D4440`):
```
create_process(entry, name, stack, 1, arg) ; entry = 01840000, stack = 6000
```
It is invoked by the real rdebug `jump`; `create_process` returns PCB
`003FFBD0` and the child's `xhello` runs. The trace then shows the full
handshake:
```
TR CREATE_PROC entry entryarg=003F30DC ... proc=004043D0
TR OPEN entry ... (child) TR ATTACH ret a8=008D3050
TR ACCEPT-CB entry proc=00414F90
TR READ12 ret a8=0000000C ; server read the 12-byte setup
TR SRV-WRITE ret a8=00000008 ; setup reply header
TR SRV-WRITE ret a8=00000090 ; vendor + screen list
TR XREAD ret a8=00000008 expect=00000008 ; client read both
TR XREAD ret a8=00000090 expect=00000090
TR CreateSimpleWindow
TR MapWindow
TR XSync
```
Raw excerpt: `dwt/gui/evidence/transport_evidence.txt`; full trace:
`dwt/gui/run12/debug.log` (instrumented) and `dwt/gui/run13/mame.log`
(breakpoint-free).
### 10.5 The second bug: do not reload `Display *` from the marker cell
With H3 alone the child still crashed in `XCreateSimpleWindow`. The cause is in
`xhello`, not in the transport: `XOpenDisplay` really returns `0x7C7290`
(`DISP-RET2 a8=007C7290`), but the value read back from the old RAM marker cell
was `0x006B7290`. The root lookup then dereferenced the wrong block (all `0x5555`
fill) and produced garbage; the call went wild.
The fix, in `dwt/gui/xhello.asm`, is to keep the Display in **a11** and the
window in **a10** — both are callee-saved by every Xlib entry point
(`mmtm sp,a5,a7,a9,a10,a11,a13`) — instead of reloading it from RAM, and to
re-run `setf 16,0,0; setf 32,0,1` after every Xlib call because Xlib changes the
CPU's field state. The markers are diagnostic only now.
### 10.6 Reproduce
```sh
./dwt/gui/build.sh dwt/gui/xhello.asm 01840000 # 390 B
./dwt/gui/build.sh dwt/gui/xlaunch.asm 01860000 # 128 B
# boot with the private cfg/nvram and the breakpoint-free probe
VT1200_SKIP_LEVEL1=1 MSG_TRIGGER=$PWD/trigger MSG_DIR=$PWD \
/home/arti/code/k-space/mame0289/vt1200 vt1200 \
-rompath harness/mame/roms -sound none -skip_gameinfo \
-networkprovider taptun -video none -nothrottle \
-cfg_directory $PWD/cfg -nvram_directory $PWD/nvram \
-autoboot_script $PWD/probe_win.lua -autoboot_delay 0
# after 192.168.99.2:771 answers (one rdebug connection per poke/jump)
python3 dwt/visual/inject.py --host 192.168.99.2 \
--poke-file 0x01840000 --file dwt/gui/build/xhello.bin
python3 dwt/visual/inject.py --host 192.168.99.2 \
--poke-file 0x01860000 --file dwt/gui/build/xlaunch.bin \
--jump 0x01860000 --jump-wait 8
```
`probe_win.lua` contains no `bpset`/`wpset`; the final frame is produced by the
payload alone. Stage markers: `01890140` reaches 6, `01890160` = 4.
### 10.7 Limits
* The demonstration depends on the `VT1200_SKIP_LEVEL1=1` divergence (as did the
original experiment); it is evidence about the OS/X server, not a real POST.
* The window is an empty black `XCreateSimpleWindow` (400x200, border 2) on the
root window. The WM frames it; no text is drawn. Adding text needs
`XLoadFont`/`XCreateGC`/`XDrawString` on the same working connection.
* The rdebug process's own connect remains unusable; use `create_process`.