Files
vt1200/dwt/loader/evidence/hostload/README.md
T
SDD 8588340527 dwt/loader: end-to-end host loader over rdebug
hostload.py drives one rdebug connection: jump(malloc) for the COFF buffer,
poke the COFF, poke the loader at 0x01880000, write the descriptor, call the
loader, and verify status 0 plus app evidence (marker cells or xhello stage 6
and a VRAM screenshot). --tftp uses the existing tftpc.bin fetch instead of a
host poke. Adds evidence for two/three fresh boots and appends the integrate
section to dwt/notes/LOADER.md, including the bit-offset result-block layout
and the create_process clobber of the descriptor.
2026-10-18 15:34:25 +03:00

92 lines
4.7 KiB
Markdown

# hostload evidence (rdebug -> malloc -> COFF -> loader -> app)
All runs: `VT1200_SKIP_LEVEL1=1`, private `-cfg_directory`/`-nvram_directory`,
`-networkprovider taptun`, BOOTP/TFTP from `dwt/tcpip/serve_probe.py` on
`192.168.99.2`. The ROM is untouched
(`md5 b46a3e442ccb86134d2356bd70d22fd2`, re-checked after the runs); no driver
edits, no breakpoints.
Driver: `dwt/loader/hostload.py`. Each log is one complete connection.
## Malloc -> poke -> descriptor -> loader -> markers (three boots)
| log | boot | `jump(malloc)` bit ptr | byte | status / PCB | markers |
|---|---|---|---|---|---|
| `marker_boot2_run4.log` | 2 | `0x007df450` | `0x0FBE8A` | 0 / `0x003fc5d0` | start/data/data2/bss OK, count moving |
| `marker_boot3.log` | 3 | `0x007c7290` | `0x0F8E52` | 0 / `0x003ffbd0` | start/data/data2/bss OK, count moving |
| `marker_boot4.log` | 4 | `0x007c8030` | `0x0F9006` | 0 / `0x003ffbd0` | start/data/data2/bss OK, count moving |
| `marker_nopokeloader_boot4.log` | 4 | `0x007d8970` | `0x0FB12E` | 0 / `0x003fd590` | loader readback 610 words, 0 differing |
The marker values in every run are `start=0x018C0000`, `data=0xDEADBEEF`,
`data2=0x12345678`, `bss=0` after a fresh boot. The counter changes between two
samples (multiple children from successive runs can make it go backwards; a
fresh boot is monotonic).
Each log shows the full result block: status 0, entry/text/data/bss addresses
and sizes, `pcb != 0`, `nscns=3`, `coff_len=510`, `loader_base=0x01880000`.
## xhello -> X window (two boots)
| log | boot | malloc bit ptr | stage | screenshot |
|---|---|---|---|---|
| `xhello_boot3.log` | 3 | `0x007c82d0` | 6 | `hostload_xhello_boot3_xhello.png` |
| `xhello_boot4.log` | 4 | `0x007c9070` | 6 | `hostload_xhello_boot4_xhello.png` |
`stage=6` is set after `XSync`; `strlen=4` is the ABI check. The PNGs (1-bit
1024x1024 VRAM, lossless) show the black 400x200 window with its WM title bar at
`(100,100)` over the Terminal Manager — same visual as
`dwt/loader/evidence/loader_frame2650.png`.
## Heap placement (read-only `dwt/heapmap.py`)
`heapmap_boot4.txt` shows the global free list after the boot-4 runs and checks
the two app `.text` targets: `0x018C0000` and `0x01840000` are both **inside**
the last free block (`byte 0x1719BA..0x400000`) with about 950 KiB / 0.97 MiB
of headroom to its end. The malloc'd COFF buffers (`0x007C…`, `0x009F…`,
`0x00AD…`) are in the low blocks, far from the loader page (byte `0x310000`)
and from both targets. That is why the loader's free-list check (a check, not
a reservation) passes on these boots.
## `--tftp`: fetched COFF, no host poke of the COFF (two boots)
| log | boot | tftpc buffer | RRQ line |
|---|---|---|---|
| `tftp_marker_boot3.log` | 3 | `0x009fccd0` | `15:31:47 ... requested 'dwt/tcpip/probe.bin' (510 bytes)` |
| `tftp_marker_boot4.log` | 4 | `0x009fccd0` | `15:32:16 ... requested 'dwt/tcpip/probe.bin' (510 bytes)` |
(`serve_tftp_marker_boot2.log` is the boot-2 RRQ from the first, pre-logging
run: `15:31:07`, buffer `0x00b4ce90`; its host-side output was not saved.)
The TFTP server was serving `dwt/loader/build/marker.coff` for **any** name
(`--file dwt/loader/build/marker.coff`); `tftpc.asm`'s RRQ name is fixed. Each
log shows `stage=4 fd=1 total=510 done=0x05A5A5A5`, then a `peek` comparison of
the fetched buffer against the local COFF: 510/510 bytes, sha256
`519e4724aa537436a35f5e49a3306b3f6bcebdc216a0ce4c103822b0df28d749` (same as
the file). The loader then used that buffer's bit address in the descriptor.
## Failure that is not a failure
The first integration attempts parsed the result block at byte offsets
`0x20, 0x40, ...` and saw `0x55555555`. The loader's constants are **bit**
offsets (`RESULT_BASE+020h` = +4 bytes), so the real block is 13 adjacent
32-bit cells (52 bytes) at byte offsets `0x00, 0x04, ... 0x30`. The stale/free
heap fill beyond byte 0x34 reads `0x55`. `hostload.py` now converts bit
offsets to bytes; no loader change was needed.
## Descriptor quirk worth knowing
After a successful load, a 16-byte window at `0x01882C00` (the descriptor) is
overwritten with `0d 00 00 00 <pcb> <ptr> 00 60 00 00` — the `create_process`
call frame from the loader's `ARG_AREA` (`0x01882B00`, 0x100 bits below the
descriptor). It is harmless: the loader has already read the descriptor, and
the result block at `0x01882D00` is untouched. It does mean a second call must
rewrite the descriptor, which `hostload.py` always does immediately before the
jump.
## Frame dumps
`hostload.py --frame N` only waits for `vt1200-frame-N.ppm` (a MAME
`VT1200_DUMP_FRAME` dump). Frame numbers are boot-relative and the dump must be
configured on the MAME side; the load-relative evidence here is the rdebug VRAM
screenshot, not a MAME frame dump.