dwt: trace the bottom diagnostics message box to @0xCB640 and the RAM message table

This commit is contained in:
SDD committed 2026-10-18 17:49:13 +03:00
1 parent 225394e1ae
commit f3d26ad687
5 files changed
+391

No files matched your search

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

+369
View File
@@ -0,0 +1,369 @@
# The bottom diagnostics message box — window, text source, and the Session→Quit re-show
Status: **DONE**, with one honest emulator caveat (the MAME skip-level1 restart
does not bring the Terminal Manager back; the last step is static code + the
real unit's observed box, not an end-to-end MAME reproduction).
Address convention: **Ghidra = `0x07E40000 + ROM file offset`**, firmware **bit
address = Ghidra × 8**. The TMS34010 program space is bit-addressed, so every
firmware address below is a **bit** address unless it is explicitly a Ghidra
address.
---
## 1. Which window is the bottom box?
It is a **separate top-level X window owned by the Terminal Manager
(`SESSIONMGR_PROC`)**, not a child of the TM window and not a status bar drawn by
the window manager.
Creation code: `FUN_rom_07e94880` (Ghidra `0x07E94880`, bit `0x3F4A4400`).
```
07E9492A MOVE *A11(0x40),*A14+,0x1 ; parent = screen->root
07E9492E MOVE A7,*A14+,0x1 ; display
07E94930 CALLA 0x07FA8372 ; XCreateSimpleWindow(d, root, ...)
07E94936 MOVE A8,@0xB7C60 ; <-- box window id
07E9494E CALLA 0x07FB2FAA ; XSelectInput(d, win, 0x8000)
07E94976 CALLA 0x07FA70EE ; XChangeWindowAttributes(d, win, 0x200, attrs)
```
The window is mapped at the end of the builder, `0x07E949FA CALLA 0x07FAC9EC`
(`XMapWindow(d, @0xB7C60)`).
The parent really is the root window: `0x07E948AC` loads `@0xB78E0` (the TM's
`Display *`, see `GUI_APPS.md`), walks `display->screens[]` (`+0x480`, `+0x440`)
and takes `screen->root` (`+0x40`) before the `XCreateSimpleWindow` call.
Live on the real unit (`peek`, read-only, two samples 2 s apart):
```
peek 0xB7C60 4 -> 2d 00 10 00 = 0x0010002d
```
`0x0010002d` is an X resource id (client base `0x00100000`), i.e. a real server
window, not a sentinel. The screenshot
`dwt/evidence/real-unit-msgbox-allpassed.png` shows the separate wide box at the
bottom, with its own border and no title bar.
**Owner/process:** the TM's process entry is `0x07E5CDBC` (bit `0x3F2E6DE0`,
`SESSIONMGR_PROC`); the box id and all its state are in that process's globals.
## 2. Where the text comes from
### 2.1 The message table
The strings are in ROM in a table of **22 records, 20 bytes each**, at Ghidra
`0x07FE5552` (bit `0x3FF2AA90`):
```
record = 8 zero bytes + [English ptr][French ptr][German ptr] (20 bytes)
```
The pointer for record `r`, language `L` is at
`0x3FF2AA90 + r*0xA0 + 0x40 + L*0x20` (the `+0x40` skips the 8-byte header;
`0x20` bits = 4 bytes per language pointer).
| record | mask bit | English text |
|---|---|---|
| 0 | 7 | `NVR Checksum Error - 14` |
| 1 | 8 | `Video RAM Error - 22` |
| 2 | 9 | `MAIN Memory Error - 13` |
| 3 | 10 | `Host Port Error - 15` |
| 4 | 11 | `Printer Port Error - 16` |
| 5 | 12 | `Keyboard Port Error - 18` |
| 6 | 13 | `Mouse Port Error - 19` |
| 7 | 14 | `Keyboard Error - 27` |
| 8 | **15** | **`Ethernet Port Error - 24`** |
| 9 | 16 | `Mouse Error - 26` |
| 10 | 17 | `Video Error - 25` |
| 11 | 18 | `Ethernet ROM Error - 23` |
| 12 | 19 | `Timer 1 Error - 17` |
| 13 | 20 | `Timer 2 Error - 21` |
| 14 | 21 | `Optional RAM Simm 1 Error - 13` |
| 15 | 22 | `Optional RAM Simm 2 Error - 13` |
| 16 | 23 | `Optional RAM Simm 3 Error - 13` |
| 17 | — | `UNEXPECTED DIAGNOSTIC ERROR RECEIVED` (table entry; not selected by the box builder's bit branches) |
| 18 | mask == 0 | **`All Diagnostics Passed`** |
| 19 | bits 0–3 | `ROM Checksum Error - 12` |
| 20 | — | `Memory Path Error - xx` (table entry; not selected by the box builder) |
| 21 | bit 28 | `Image Board Error - 43` |
Record 18's English pointer is `0x3F4AB9A0` (Ghidra `0x07E95734`), the exact
string in the box. Record 8's is `0x3F4AB090` (Ghidra `0x07E95612`).
### 2.2 The selection code
The builder is `FUN_rom_07e94986` (bit `0x3F4A4C30`), called from the TM's
session-start path at `0x07E5E6BC`. It calls `FUN_rom_07e94a08`
(bit `0x3F4A5040`) to turn the status mask into a list of message pointers:
```
07E94A16 MOVE @0xCB640,A11,0x1 ; <-- STATUS MASK
07E94A1C JREQ 0x07E94E4C ; mask == 0 -> "All Diagnostics Passed"
...
07E94A3A MOVE @0x19FF10,A7,0x0 ; language index
07E94A40 SLL 0x5,A7 ; *32 bits = *4 bytes
07E94A4C ADDI 0x19E3D0,A7 ; record 0 + 8 (pointer field)
07E94A52 MOVE *A7(0x0),*A9(0x40),0x1 ; message ptr -> list entry +8
```
Destination list arithmetic (`0x07E94A28`–`0x07E94A32`):
```
SLL 0x7,A9 ; count * 128
SLL 0x5,A7 ; count * 32
ADD A7,A9 ; count * 160 bits = count * 20 bytes
ADDI 0x19F150,A9 ; list base
```
So the live list is a **20-byte record per error** at bit **`0x19F150`**, with
the string pointer at **entry + 0x40 bits (8 bytes)**; the builder appends a
null-pointer terminator. The "All passed" branch uses record 18's pointer
(`0x19EF10`); the error branches use record 0..16's pointers
(`0x19E3D0` … `0x19EDD0`), record 19 for bits 0–3 (`0x19EFB0`), and record 21
for bit 28 (`0x19F0F0`).
The table is also present as a **RAM copy**, byte-for-byte:
`0x19E390` = record header 0 (bit address), `0x19E3D0` = record 0's English
pointer. (Verified by comparing the dump at frame 3000 with ROM
`0x7FE5552`/`0x7FE555A`; the RAM copy begins at byte `0x33C72` in a RAM dump,
i.e. bit `0x19E390`.)
Live on the real unit:
```
peek 0x19F190 4 -> a0 b9 4a 3f = 0x3F4AB9A0
peek 0x19F230 4 -> 00 00 00 00 (null terminator)
peek 0x19E3D0 4 -> f0 aa 4a 3f = record 0 (NVR)
peek 0x19E8D0 4 -> 90 b0 4a 3f = record 8 (Ethernet Port - 24)
peek 0x19EF10 4 -> a0 b9 4a 3f = record 18 (All Passed)
peek 0x3F4AB9A0 24 -> b'All Diagnostics Passed\x00A'
```
### 2.3 The status variable and its writer
**Status variable: the 32-bit failure mask at bit `0xCB640`.**
Read by the box builder at `0x07E94A16`. Accumulated by the diagnostics test
result handler `FUN_rom_07e7bcf6`:
```
07E7BD7C MOVE @0xCB640,A11,0x1
07E7BD82 OR A11,A9 ; A9 = one result bit (1 << test number)
07E7BD84 MOVE A9,@0xCB640,0x1 ; <-- OR the failure bit into the mask
```
The same routine prints the corresponding table entry at
`0x07E7BDA8 ADDI 0x19E390,A11` (record base) and `0x07E7BDB2 CALLA printf`, so
the mask and the printed diagnostic report use the same table.
Cleared by the two wrappers:
```
07E7C212 SUB A11,A11
07E7C214 MOVE A11,@0xCB640,0x1 ; FUN_07e7c20a
...
07E7C276 MOVE A11,@0xCB640,0x1 ; FUN_07e7c26a
```
`FUN_07e7c20a` is called from the boot diagnostics block
`0x07EAF184 CALLA 0x07E7C20A`; `FUN_07e7c26a` from the trap/error path
`0x07EAF526 CALLA 0x07E7C26A`. No other instruction writes `0xCB640`.
Live on the real unit (two samples):
```
peek 0xCB640 4 -> 00 00 00 00 (0 -> All Diagnostics Passed)
```
## 3. Why Session→Quit re-shows the box
The box is **not a boot-only alert**. It is created in the TM's session-start
path and re-reads the persistent mask every time that path runs:
```
07E5E676 CALLA 0x07EE0D48 ; create_process(VTE, ...) -- start the session
07E5E6B6 CALLA 0x07FAC9EC ; XMapWindow(session window)
07E5E6BC CALLA 0x07E94986 ; <-- build list + create box window + draw + map
```
`0x07E5E6BC` is unconditional: every session start reaches it.
The Session/Quit action sets the TM quit flag `@0xB70E0`:
```
07E891D8 MOVE @0xAECDF,A11,0x0 ; confirmation required?
07E891DE JRNE 0x07E891EC
07E891E0 MOVK 0x1,A11
07E891E2 MOVE A11,@0xB70E0,0x1 ; <-- quit flag
```
(The confirmation path `FUN_07e895e2` sets the same flag at `0x07E895F8`.)
The TM event loop then exits:
```
07E5E6DC MOVE @0xB70E0,A11,0x1
07E5E6E2 JRNE 0x07E5F762
...
07E5F762 CALLA 0x07E5CD00 ; close display / sleep / restart
```
`0x07E5CD00` ends with `0x07E5CD90 CALLA 0x07EAF18A` — the kernel/OS restart
entry. That entry is **after** the diagnostics call, and the restart sets
`A10 = 1` so the boot block's `0x07EAF182 JRNE 0x07EAF198` skips
`0x07EAF184 CALLA 0x07E7C20A`:
```
07EAF180 MOVE A10,A10
07EAF182 JRNE 0x07EAF198 ; skip diagnostics on restart
07EAF184 CALLA 0x07E7C20A ; boot diagnostics (initial boot only)
...
07EAF192 MOVI 0x1,A10
```
Therefore Session→Quit **does not re-run the diagnostics check and does not
clear `@0xCB640`**. It restarts the OS, the restarted `SESSIONMGR_PROC` runs
the same session-start code, and `0x07E5E6BC` recreates the box from the mask
that was already in RAM. On the real unit the mask is `0` (`All Diagnostics
Passed`), so the same box comes back.
On a plain Expose the TM does not rebuild the list; it only repaints it
(`0x07E5FAB8` checks that the event window is `@0xB7C60`, then calls the draw
routine `0x07E94EB0`). That is why the box stays put across ordinary repaints,
but the *re-creation* after Quit is the session-start builder.
## 4. Live read format
The box text is **not** stored as ASCII in RAM. The readable state is:
| what | bit address | format |
|---|---|---|
| status mask | **`0xCB640`** | 32-bit little-endian; `0` = All Passed; bit 15 (`0x8000`) = Ethernet Port Error - 24; bit 8 = Video RAM Error - 22; … |
| message list | **`0x19F150`** | 20 bytes/entry; string pointer at entry **+0x40 bits (+8 bytes)**; entry *i* pointer at `0x19F150 + i*0xA0 + 0x40`; null pointer terminates |
| language | **`0x19FF10`** | 16-bit field; 0 = English (source: 2-bit field at `@0xAEC E8`, copied at `0x07E94990`–`0x07E94998`) |
| RAM table copy | **`0x19E390`** | 22 × 20-byte records, same layout as ROM `0x3FF2AA90` |
| box window id | **`0xB7C60`** | 32-bit X resource id |
Example live reads (`dwt/exploit.py`-style `peek`, read-only):
```
peek 0xCB640 4 -> 00 00 00 00 mask = 0
peek 0x19F190 4 -> a0 b9 4a 3f list[0] = 0x3F4AB9A0
peek 0x3F4AB9A0 24-> "All Diagnostics Passed"
peek 0xB7C60 4 -> 2d 00 10 00 window 0x0010002d
```
`0x19F190` is `0x19F150 + 0x40`; no count is stored globally — the builder
returns it in `A8`, and the draw routine walks the list until the null pointer.
## 5. What `DIAGNOSTIC MAILBOX` actually is
`DIAGNOSTIC MAILBOX` is a **debug dump of a separate diagnostics structure**,
not the bottom box's source.
* The format string is `0x3FAD4890` (Ghidra `0x07F5A912`), only referenced from
the dump routine `FUN_rom_07f5a026` (`0x07F5A026`).
* It reads the pointer **`@0x27C190`** and prints `@+0x00, +0x20, … +0x100` as
`test_type`, `test_mode`, `test_result`, `error_ext0..6`, `diag_hld`,
`memory_bss`, `test_cnt`, `test_f_cnt`, `memory_lth`, `video_dydx`,
`num_of_lnks`, and a memory-error link table.
* **No code ever arms it.** The only instruction that writes `0x27C190` writes
zero:
```
07F5A38E SUB A9,A9
07F5A394 MOVE A9,@0x27C190,0x1 ; clears the pointer
```
A separate routine (`0x07F59FC8`) fills a fixed buffer at `@0x27C1F0`, but the
dump pointer is never pointed at it. On the real unit:
```
peek 0x27C190 4 -> 00 00 00 00
```
So the mailbox is a dead/debug report dump; the bottom box never reads it. The
old candidate `0x16F0` (level-1 dispatcher slot) is likewise not referenced by
`0x07E94986`/`0x07E94A08`.
## 6. Evidence
### Real unit (read-only; 2026-10-18)
| check | value |
|---|---|
| `@0xCB640` | `0x00000000` (twice, 2 s apart) |
| `@0xB7C60` | `0x0010002d` (twice) |
| `@0x19F190` | `0x3F4AB9A0` (twice) |
| `@0x19F230` | `0` (sentinel) |
| `@0x19FF10` (16-bit) | `0` (English) |
| `@0x19E3D0` / `@0x19E8D0` / `@0x19EF10` | `0x3F4AAAF0` / `0x3F4AB090` / `0x3F4AB9A0` |
| `@0x27C190` | `0` |
| string at `0x3F4AB9A0` | `All Diagnostics Passed` |
| screenshot | `dwt/evidence/real-unit-msgbox-allpassed.png` |
### MAME (skip-level1, RAM poke only — ROM untouched)
* Breakpoint on the builder entry `0x3F4A4C30` fires at **frame 569** with
`d@0xCB640 = 0`.
* Write `@0xCB640 = 0x8000` at frame 568, continue to frame 1200:
`list[0] = 0x3F4AB090`, and the frame shows **`Ethernet Port Error - 24`**
(`dwt/evidence/mame-msgbox-ether24-frame1200.png`).
* Write `@0xCB640 = 0x1` -> `list[0] = 0x3F4ABB20` (`ROM Checksum Error - 12`).
* Write `@0xCB640 = 0x10000000` -> `list[0] = 0x3F4ABC98`
(`Image Board Error - 43`).
* Write the quit flag `@0xB70E0 = 1` at frame 1200: `0x3F578C50`
(`0x07EAF18A`, OS restart) is reached at frame 3054 with `@0xCB640` still
`0x8000`; the boot diagnostics breakpoint `0x3F3E1050` does **not** fire
again. (In the skip-level1 emulator the restarted X server comes back at the
root weave but the TM does not recreate its window within 8500 frames — see
caveat.)
* Control frame with mask 0: `dwt/evidence/mame-msgbox-allpassed-frame3000.png`.
## 7. Caveats
* The **MAME skip-level1 restart** reproduces the OS restart and the mask
persistence, but not the TM's return. The real unit does return to the TM with
the box (the owner's observation and
`dwt/evidence/real-unit-msgbox-allpassed.png`). The final rebuild step is
therefore static (`0x07E5E6BC` is unconditional in the session-start path) plus
the real-unit observation, not an end-to-end emulator trace.
* The exact "Session → Quit" menu item may be the `Abort X Session` item or the
`Quit Control` button; both are session-ending actions. The quit flag
`@0xB70E0` and the `0x07E5CD00 → 0x07EAF18A` restart are the path proven here.
A Quit that only closes the VTE window would leave the box window untouched and
the Expose redraw (`0x07E5FAB8 → 0x07E94EB0`) would repaint the same list; the
end result (same text from the same mask) is identical.
* The box can hold **several** messages: the builder appends one record per set
mask bit and terminates with a null pointer. The window height is derived from
the count in `0x07E94880`.
* `UNEXPECTED DIAGNOSTIC ERROR RECEIVED` and `Memory Path Error - xx` are table
entries but are not selected by the box builder's current bit branches; they
are used by the diagnostics reporter.
## 8. Reproduce the MAME checks
```sh
# box builder location / mask at build time
VT1200_SKIP_LEVEL1=1 BOX_FRAMES=600 \
/home/arti/code/k-space/mame0289/vt1200 vt1200 \
-rompath harness/mame/roms -video none -sound none -nothrottle -skip_gameinfo \
-cfg_directory .vt1200-scratch/msgbox/cfg \
-nvram_directory .vt1200-scratch/msgbox/nvram \
-debug -debugger none -debuglog \
-autoboot_script .vt1200-scratch/msgbox/probe_box.lua -autoboot_delay 0 \
-seconds_to_run 30
# grep BOXBUILD debug.log -> BOXBUILD fr=569 ... mask=00000000
# force a failing bit just before the build
MASK_VAL=0x8000 BOX_FRAMES=1250 VT1200_DUMP_FRAME=1200 \
VT1200_DUMP_DIR=$PWD/.vt1200-scratch/msgbox VT1200_SKIP_LEVEL1=1 \
/home/arti/code/k-space/mame0289/vt1200 vt1200 ... \
-autoboot_script .vt1200-scratch/msgbox/probe_box_mask.lua ...
# frame 1200 shows "Ethernet Port Error - 24"; RAM @0x19F190 = 0x3F4AB090
```
The probe scripts live in `.vt1200-scratch/msgbox/` (scratch, not source of
truth). The ROM is untouched: `md5 b46a3e442ccb86134d2356bd70d22fd2`.
+22
View File
@@ -388,3 +388,25 @@ The build name must be <= 8 characters: `tftpc_real` silently fails (no
Evidence: `dwt/loader/evidence/real-unit-xtext/` (loads, markers, server logs,
screenshots, emulator runs, `xtext_probe.lua`).
### Bottom diagnostics box: live state (2026-10-18, read-only)
The separate bottom X window is owned by the Terminal Manager; its window id is
the global at bit `0xB7C60`. The text is selected from the status mask at bit
`0xCB640` and a pointer list at bit `0x19F150` (20-byte entries, string pointer
at +8 bytes). Live reads, two samples 2 s apart:
```
peek 0xCB640 4 -> 00 00 00 00 mask 0 -> "All Diagnostics Passed"
peek 0xB7C60 4 -> 2d 00 10 00 box window 0x0010002d
peek 0x19F190 4 -> a0 b9 4a 3f list[0] -> 0x3F4AB9A0
peek 0x19F230 4 -> 00 00 00 00 null terminator
peek 0x19FF10 2 -> 00 00 language 0 (English)
peek 0x19E8D0 4 -> 90 b0 4a 3f record 8: "Ethernet Port Error - 24"
peek 0x27C190 4 -> 00 00 00 00 DIAGNOSTIC MAILBOX pointer is NULL
peek 0x3F4AB9A0 -> "All Diagnostics Passed"
```
The mask bit mapping (bit 15 = Ethernet Port Error - 24, bit 8 = Video RAM
Error - 22, ...) was verified dynamically in MAME by setting `@0xCB640` before
the builder at frame 569. Full trace: `dwt/notes/MESSAGEBOX.md`.