forked from arti/vt1200
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.
This commit is contained in:
1 parent
bebc0ccbc1
commit
8588340527
22 files changed
+1104
No files matched your search
@@ -477,3 +477,97 @@ the descriptor in `A8`; `LOADER_SNAP=1` writes a PNG at `LOADER_END`.
|
||||
| `dwt/loader/evidence/xhello_run.log` | xhello run |
|
||||
| `dwt/loader/evidence/loader_frame2650.png` | the xhello window |
|
||||
| `dwt/loader/evidence/reloc_refused.log` | `.obj` refused with status 7 |
|
||||
|
||||
---
|
||||
|
||||
## 10. End-to-end host-side loader (rdebug): malloc -> poke -> load -> run
|
||||
|
||||
Date: 2026-10-18. Driver: `dwt/loader/hostload.py`. Evidence:
|
||||
`dwt/loader/evidence/hostload/` (its `README.md` indexes the logs and PNGs).
|
||||
This is the integration of the two existing halves; the loader and the apps are
|
||||
unchanged.
|
||||
|
||||
### Exact sequence (one TCP connection to `192.168.99.2:771`)
|
||||
|
||||
1. `opcode 7` liveness; the computed challenge is answered by
|
||||
`dwt/challenge.py` (the `RDebug` default).
|
||||
2. `jump 0x3F701230` (`malloc`) with **one** argument, the COFF byte length.
|
||||
`jump` returns the callee's `A8`, i.e. a **bit** pointer.
|
||||
3. Poke the COFF to that bit pointer in 1024-byte chunks
|
||||
(`bit + off*8`, never `bit + off`) and `peek` it back: 510/510 or 774/774
|
||||
bytes equal, sha256 matches the local file.
|
||||
4. Poke the raw loader (`build/loader.bin`, 1220 B) at bit `0x01880000`, then
|
||||
`peek` it back (610 words, 0 differing). `--no-poke-loader` skips the write
|
||||
and requires that readback to match.
|
||||
5. Build the 16-byte descriptor at bit `0x01882C00` from **bit** offsets
|
||||
+0/+0x10/+0x20/+0x40/+0x60 (bytes 0/2/4/8/12):
|
||||
`44 4c 31 52 <buf LE32> <len LE32> 01 00 00 00`.
|
||||
6. `jump 0x01880000` with **zero** arguments. The rdebug jump handler's
|
||||
zero-arg path is a bare `CALL` after `MOVE A9,A8` with `A9 = 0`, so the
|
||||
loader enters with `A8 = 0` and uses the fixed descriptor. (With N args the
|
||||
stubs do not set `A8` to the descriptor either; `A8 = 0` is the reliable
|
||||
calling convention from rdebug.)
|
||||
7. Read the result block at bit `0x01882D00`, then app evidence: marker cells
|
||||
by `peek`, or xhello `stage`/`strlen` plus a VRAM screenshot.
|
||||
|
||||
### What worked (two or three fresh boots each)
|
||||
|
||||
* `jump(malloc)` returns a usable, byte-aligned bit pointer in the firmware
|
||||
heap: `0x007c7290` (byte `0x0F8E52`), `0x007c8030` (byte `0x0F9006`),
|
||||
`0x007df450` (byte `0x0FBE8A`), `0x00adcd90`, `0x009fccd0`. These are far
|
||||
below the loader page (`0x01880000`, byte `0x310000`) and below the current
|
||||
apps (`.text` byte `0x318000`), and `hostload.py` rejects a pointer that
|
||||
overlaps either, the loader page or the marker cells.
|
||||
* The loader accepts a malloc'd buffer: status `0` and `A8 = 0` on every run.
|
||||
Its free-list check (status 8) passed for `.text 0x018C0000` and for
|
||||
`xhello` `.text 0x01840000`; the result block reports entry/text/data/bss,
|
||||
`pcb != 0`, `nscns=3`, and `loader_base=0x01880000`.
|
||||
* `create_process` really runs the app: `marker.coff` leaves
|
||||
`start=0x018C0000`, `data=0xDEADBEEF`, `data2=0x12345678`, `bss=0`, and a
|
||||
live counter; `xhello.coff` reaches `stage=6` (`XSync`) and its black
|
||||
400x200 window is visible at `(100,100)` in
|
||||
`hostload_xhello_boot{3,4}_xhello.png`.
|
||||
* `--tftp` also works: the existing `dwt/tcpip/tftpc.bin` (bit `0x01870000`)
|
||||
fetches into its own `malloc` buffer (`stage=4`, `total=510`,
|
||||
`done=0x05A5A5A5`); `peek` matches the COFF byte-for-byte, and that buffer is
|
||||
fed to the same descriptor/loader. Server RRQ, boot 3 and 4:
|
||||
`... requested 'dwt/tcpip/probe.bin' (510 bytes)`.
|
||||
|
||||
### Two traps found
|
||||
|
||||
1. **Result-block offsets are bits.** `loader.asm`'s `RESULT_BASE+020h` is
|
||||
+4 **bytes**, so the block is 13 adjacent 32-bit cells (52 B), not cells at
|
||||
byte intervals of 0x20. Reading at byte offsets `0x20, 0x40, ...` returns
|
||||
`0x55555555` from the free heap beyond the real block; that was a host
|
||||
parsing bug, not a loader failure.
|
||||
2. **The descriptor is clobbered after a load.** A successful
|
||||
`create_process` leaves a call frame at `0x01882C00`
|
||||
(`0d 00 00 00 <pcb> <ptr> 00 60 00 00`); `ARG_AREA` is `0x01882B00` and the
|
||||
frame is 0x100 bits wide. The loader has already consumed the descriptor,
|
||||
and the result block (`0x01882D00`) is untouched, but a second call must
|
||||
rewrite the descriptor. `hostload.py` does so immediately before every
|
||||
jump.
|
||||
|
||||
### Limits
|
||||
|
||||
* **No reservation.** `malloc` only finds a buffer; the loader's free-list
|
||||
check is still a check. The apps stay linked at byte `0x318000`/`0x308000`
|
||||
while allocations grow from the low free blocks; enough allocations on one
|
||||
boot would eventually split the big block up to them. Fresh-boot runs are
|
||||
safe; on a long-lived unit the caller must still choose a free link address.
|
||||
* **`--tftp` needs a server that serves the COFF under tftpc's fixed name**
|
||||
(`dwt/tcpip/probe.bin`); `tftpc.asm` has no filename argument. The server
|
||||
used here was
|
||||
`serve_probe.py 192.168.99.1 --bootp --file dwt/loader/build/marker.coff`.
|
||||
* **`--frame N` cannot trigger a MAME dump.** It only waits for
|
||||
`vt1200-frame-N.ppm` from an externally configured
|
||||
`VT1200_DUMP_FRAME(S)`; frame numbers are boot-relative, so the load-relative
|
||||
visual evidence is the rdebug VRAM screenshot.
|
||||
* The loader is still position-dependent and fixed at `0x01880000`, and the
|
||||
loaded images are position-dependent; none of this adds relocation.
|
||||
* rdebug process only: `create_process` is used for the *app*, not for the
|
||||
loader or the TFTP client. A `create_process` child previously returned `-1`
|
||||
from `socket()` (TCPIP.md), so the fetch stays in the rdebug process.
|
||||
* The real unit would need TCP 771 reachable and the same debug-gate state;
|
||||
the emulator run uses the documented `VT1200_SKIP_LEVEL1=1` divergence to
|
||||
reach the OS.
|
||||
Reference in new issue
Block a user