# 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 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.