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.
25 KiB
The COFF loader: GSPLNK images, position dependence, and create_process
Status: DONE for the loader half. A GSPLNK COFF image poked into RAM is
parsed, its sections are placed at the addresses in their headers, .bss is
zeroed, and the image runs as a first-class process from
create_process(entry, "LOADER", 0x6000, 1, 0). Two apps prove it: a marker app
(MAGIC/counter cells) and xhello (an X window in a frame dump).
The transport half is separate. The loader takes an already-received image and chooses no address of its own: the COFF section headers carry the addresses.
All work is emulator-only: no driver edits, no ROM change
(md5 b46a3e442ccb86134d2356bd70d22fd2), private NVRAM/cfg, no network.
1. What GSPLNK emits
dwt/loader/coff.py is the reference reader. All addresses and sizes in the
section headers and optional header are bit addresses/bits; file offsets
(scnptr, relptr, symptr) are bytes. This is the same model as
harness/hello-world/cofextract.py, confirmed again here.
1.1 The file header (20 bytes)
00000000: 90 00 03 00 de e0 d4 6a 02 01 00 00 0e 00 00 00
00000010: 1c 00 03 01 08 01 c7 00 90 02 00 00 40 00 00 00
00000020: 40 00 00 00 00 00 8c 01 00 00 8c 01 00 00 8d 01
00000030: 2e 74 65 78 74 00 00 00 00 00 8c 01 00 00 8c 01
00000040: 90 02 00 00 a8 00 00 00 00 00 00 00 00 00 00 00
00000050: 00 00 00 00 20 00 00 00 2e 64 61 74 61 00 00 00
00000060: 00 00 8d 01 00 00 8d 01 40 00 00 00 fa 00 00 00
00000070: 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 00
00000080: 2e 62 73 73 00 00 00 00 00 80 8d 01 00 80 8d 01
00000090: 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
000000a0: 00 00 00 00 80 00 00 00 50 05 40 07 e2 09 00 00
This is dwt/loader/build/marker.coff (510 bytes). The header at 0x00:
| offset | size | field | this file |
|---|---|---|---|
| 0x00 | 2 | magic | 0x0090 |
| 0x02 | 2 | section count | 3 |
| 0x04 | 4 | time stamp | 0x6AD4E0DE |
| 0x08 | 4 | symbol table offset (bytes) | 0x102 |
| 0x0C | 4 | symbol count | 14 |
| 0x10 | 2 | optional header size (bytes) | 0x1c (28) |
| 0x12 | 2 | flags | 0x0103 |
0x0100 is F_RELFLG (relocation info stripped); 0x0003 is the executable
flag set. The same magic 0x0090 appears in .obj files, whose flags are
0x0100 and whose optional header size is 0.
1.2 The optional header (28 bytes, at 0x14)
| offset | size | field | this file |
|---|---|---|---|
| 0x14 | 2 | magic | 0x0108 |
| 0x16 | 2 | version | 0x00c7 |
| 0x18 | 4 | text size (bits) | 0x290 = 656 bits = 82 B |
| 0x1C | 4 | data size (bits) | 0x40 = 8 B |
| 0x20 | 4 | bss size (bits) | 0x40 = 8 B |
| 0x24 | 4 | entry point (bits) | 0x018C0000 |
| 0x28 | 4 | text start (bits) | 0x018C0000 |
| 0x2C | 4 | data start (bits) | 0x018D0000 |
1.3 Section headers (40 bytes each, at 20 + opthdr)
| offset | size | field |
|---|---|---|
| +0 | 8 | name, NUL-padded |
| +8 | 4 | paddr (bits) |
| +12 | 4 | vaddr (bits) |
| +16 | 4 | size (bits) |
| +20 | 4 | scnptr (file offset, bytes) |
| +24 | 4 | relptr (file offset, bytes) |
| +28 | 4 | line-number pointer |
| +32 | 2 | nreloc |
| +34 | 2 | nlnno |
| +36 | 4 | flags (0x20 text, 0x40 data, 0x80 bss) |
For marker.coff:
sec 1 .text paddr=018C0000 size=0x290 bits (82 B) scnptr=0xa8 nreloc=0 flags=0x20
sec 2 .data paddr=018D0000 size=0x40 bits (8 B) scnptr=0xfa nreloc=0 flags=0x40
sec 3 .bss paddr=018D8000 size=0x40 bits (8 B) scnptr=0 nreloc=0 flags=0x80
Note that GSPLNK reorders sections in the output: the input .obj order is
.text, .data, .bss; the linked output puts .data, .bss, .text in
xhello.coff. A loader must not assume .text is first.
1.4 Symbol table and relocations
Symbols are 18 bytes: 8-byte name, 4-byte value, 2-byte section number, 2-byte type, 1-byte storage class, 1-byte aux count. Section symbols have one 18-byte aux entry (the section size). Names longer than 8 characters live in the string table; the 8-byte field is then an offset.
Relocations are 10 bytes: 4-byte address (bit address in the section), 4-byte symbol index, 2-byte type. Documented types (Assembler Tools Guide, Table A-7):
| type | mnemonic |
|---|---|
0x10 |
R-RELWORD (16-bit direct) |
0x11 |
R-RELLONG (32-bit direct) |
0x18 |
R-OCRLONG (1's-complement 32-bit) |
0x19 |
R-GSPPCR16 (16-bit relative, words) |
The .obj has relocations; the linked -a output has none (see §2).
1.5 Entry point
GSPLNK writes the entry only when -e names a global symbol. With
.def _start in the source and -e _start in the command file, the map says:
ENTRY POINT SYMBOL: "_start" address: 018C0000
and the optional header entry is 0x018C0000. Without a global _start
(dwt/gui/xhello.asm has a local _start), the map says
ENTRY POINT SYMBOL: 0, the optional entry is 0, but textstart is still
0x01840000. The loader therefore uses:
entry = optional.entry if nonzero, else optional.textstart
For the SDK flow, the entry is the first instruction of .text (_start) when
no -e is given. It is not the raw file offset and not the section index.
2. Position-dependent, not relocatable
The linked image is fully resolved and position-dependent. The loader must place each section at the bit address in its header; it cannot load anywhere and relocate.
Evidence, from dwt/loader/reldemo.sh (full output in
dwt/loader/evidence/reldemo.txt):
-
Two objects with cross references (
rel_a.asmcallsext_funcand loadsext_data;rel_b.asmdefines both) carry real relocations in the.obj:sec 1 .text ... size=0xa0 bits (20 B) scnptr=0x8c nreloc=2 flags=0x20 [text] reloc at 0x10 sym=8 type=0x11 R-RELLONG reloc at 0x70 sym=7 type=0x11 R-RELLONG -
Linking those objects with
-aproduces an output whose sections all havenreloc=0:sec 1 .text paddr=00018c0000 ... nreloc=0 sec 2 .data paddr=00018d0000 ... nreloc=0 -
Linking the same objects at two origin sets (
018C0000/018D0000/018D8000and018E0000/018F0000/018F8000) produces different bytes — the absoluteext_data/ext_funcoperands:== .text: 22 vs 22 bytes differing bytes: 2 0x0004: 8d -> 8f 0x0010: 8c -> 8eThe marker app behaves the same way: with
.textlinked at018C0000it contains the immediate0x018C0000; at018E0000the same byte changes to0x018E0000.movi _startandcallaare absolute. -
Linking without
-astill yieldsnreloc=0(and paddr values relative to 0, i.e. an unbound image). GSPLNK 2.00 does not emit a relocatable executable for this flow; the toolchain's "relocatable" form is the input.obj, not the linked image.
Consequence: the loader copies to the linked address and does not malloc
the segments. It refuses any input COFF that still carries relocation records
(status 7), rather than guessing at relocation semantics. That also catches the
easy mistake of feeding the .obj instead of the linked .out.
What "fixed address" means for the loader
The loader checks that the target range lies inside one block of the firmware's
global free list (head at bit 0x1E8AC0, block header {u32 size_bits; …; u32 next@+0x20}, user data at block+0x40; see dwt/notes/HEAP.md). This is a
check, not a reservation: the loader cannot make malloc return a chosen
address. The caller is responsible for choosing a link address that is free
when the app starts and for not handing that range out later. The loader's own
page and the COFF buffer are checked separately for overlap.
3. ELF: not produced, not supported
The 1987 toolchain has no ELF output. The Assembler Tools Guide describes COFF as the object format and GSPROM as the converter to EPROM formats:
"The object format converter converts a COFF object file into Intel hex, Tektronix hex, or TI-tagged object format."
The only "ELF" case-insensitive hits in the assembler/C guides are unrelated
words (the CIF command text). There is no ELF writer, no ELF reader and no
ELF linker option. The 1990 OS/2 r5.01 tools are also TI COFF, not ELF. So the
loader targets COFF only; an ELF loader would have nothing in this SDK to
load. (dwt/loader/evidence/elf_absent.txt records the check.)
4. The loader
Source dwt/loader/loader.asm, built by dwt/loader/build.sh and delivered as
raw bytes at its linked bit address.
| item | value |
|---|---|
| loader link/load address | bit 0x01880000 (byte 0x310000) |
loader .text size |
0x2620 bits = 1220 bytes |
| reserved loader page | bits 0x01880000..0x01883200 |
| descriptor | bit 0x01882C00 |
| result block | bit 0x01882D00 |
| return stub (harness only) | bit 0x01883100 |
| app stack | create_process allocates it; the loader passes 0x6000 bytes |
create_process |
bit 0x3F706A40 / Ghidra 0x07EE0D48 |
The loader is position-dependent itself: its labels are absolute, so the raw
image must be placed at 0x01880000. That is acceptable for the current
delivery (debugger now, transport later); relinking the loader is a build-time
change, not a runtime one.
4.1 Interface
The loader takes a 16-byte descriptor either at the fixed address or through
A8. Offsets below are byte offsets; the assembly uses the equivalent bit
offsets (+0, +0x10, +0x20, +0x40, +0x60):
DESC + 0x00 u16 0x4C44 "LD" ) both must match
DESC + 0x02 u16 0x5231 "R1" )
DESC + 0x04 u32 buf bit address of the COFF image
DESC + 0x08 u32 len image length in bytes
DESC + 0x0C u32 mode 1 = return to caller; 0 = debug resume
A8 may be 0 (fixed descriptor) or the descriptor's bit address. Any other
value falls back to the fixed descriptor if it does not carry the magic. The
loader reads buf, len and mode once and then parses the COFF.
The result block is at 0x01882D00, 32-bit cells at +0x20 intervals:
| offset | meaning |
|---|---|
+0x000 |
status: 0 = OK, else error 1..10 |
+0x020 |
entry passed to create_process (bits) |
+0x040 |
.text paddr (bits) |
+0x060 |
.text size (bits) |
+0x080 |
.data paddr / +0x0A0 size |
+0x0C0 |
.bss paddr / +0x0E0 size |
+0x100 |
relocation records seen |
+0x120 |
PCB returned by create_process |
+0x140 |
section count |
+0x160 |
descriptor length (bytes) |
+0x180 |
loader base sanity value 0x01880000 |
Status codes: 1 bad descriptor; 2 truncated file/section headers; 3 bad COFF
magic; 4 section size not a whole number of 16-bit words; 5 target outside RAM;
6 target overlaps the loader page or the COFF buffer; 7 relocation records
present; 8 target not inside a global free-list block; 9 no entry point;
10 create_process returned 0.
4.2 Algorithm
-
Save
A14andSP, set 16/32-bit field sizes, read the descriptor. -
Read the file header: magic
0x0090,nscns,opthdr; check20 + opthdr + nscns*40 <= len. -
Read the optional header:
entry, elsetextstart. -
For each 40-byte section header:
- record the first
.text/.data/.bssaddress and size from flags; - refuse if
nreloc != 0; - require
size % 16 == 0; - check
scnptr + size/8 <= lenwhenscnptr != 0; - check
0x100 <= paddrandpaddr + size <= 0x2000000(4 MiB of bits); - check the range does not overlap
[0x01880000,0x01883200)or the COFF buffer; - check the range is inside one global free-list block;
- if
scnptr != 0, copysize/16words frombuf + scnptr*8topaddr; - else store
size/16zero words atpaddr(this is.bssand also any uninitialized named section).
- record the first
-
Call
create_process(entry, "LOADER", 0x6000, 1, 0)with the firmware's upward-argument convention (the same shape asdwt/gui/xlaunch.asm):move a14, -*sp, 1 movi ARG_AREA, a14, L movi 0, a0, L / move a0,*a14+,1 ; arg4 = 0 movi 1, a0, L / move a0,*a14+,1 ; arg3 = 1 movi 6000h, a0, L / move a0,*a14+,1 ; arg2 = stack bytes movi appname, a0, L / move a0,*a14+,1 ; arg1 = name move entry, a0, 1 / move a0,*a14+,1 ; arg0 = entry (bits) calla 3F706A40h -
Store
A8(the PCB) and the status. Ifmode == 1, restoreSP/A14, return the status inA8andrets. Ifmode == 0, restore the interrupted state andjumpto the PC the harness saved, so the OS idle loop runs and schedules the child.
4.3 Why mode 0 returns to the idle loop
A payload that spins forever in the current process starves the firmware's
scheduler in this OS: the child is created but never runs. The first loader
version did exactly that (PC=01882460, no markers). The fix is to restore the
hijacked context and resume at the saved PC (the OS idle loop), which yields.
Mode 1 returns to the caller instead; a transport that returns to its own
event loop behaves the same way. The debugger harness also builds a return stub
(jauc <orig_pc>) for mode 1, so both modes were exercised without a spin.
5. Evidence
5.1 Marker app: sections placed, .bss zeroed, child scheduled
dwt/loader/build/marker.coff, linked at .text 018C0000, .data 018D0000,
.bss 018D8000. The harness pre-fills .bss with 0xA5A5 before the load.
LOADER_MODE=0:
LOADER: result status=0 (OK) entry=018C0000 text=018C0000/656 bits data=018D0000/64 bits bss=018D8000/64 bits relocs=0 pcb=003FFBD0 nscns=3 len=510
LOADER: placed .text at 018C0000: 82 bytes, 0 differing
LOADER: placed .data at 018D0000: 8 bytes, 0 differing
LOADER: marker app: start=018C0000 data=DEADBEEF data2=12345678 bss=00000000 count=619
start=018C0000is the app reading its own_startlabel: it ran at the linked address.data=DEADBEEF,data2=12345678:.datawas copied.bss=00000000after anA5A5pre-fill:.bsswas zeroed by the loader.countgrows every sample: the child is live, not a one-shot jump.pcb=003FFBD0:create_processreturned a real PCB.
LOADER_MODE=1 (descriptor + A8 variants) gives the same status and markers;
the loader returns to the caller (dwt/loader/evidence/marker_mode1_run.log,
marker_a8desc_run.log).
5.2 xhello: an X window from a created process
Same loader, dwt/loader/build/xhello.coff (entry 0, textstart=01840000):
LOADER: result status=0 (OK) entry=01840000 text=01840000/3120 bits data=00000000/0 bits bss=00000000/0 bits relocs=0 pcb=003FFBD0 nscns=3 len=774
LOADER: placed .text at 01840000: 390 bytes, 0 differing
LOADER: xhello: stage=6 strlen=4 display=006BC910 root=0001006B window=00010001 sync=00060001
stage=6 is the last marker in xhello.asm (after XSync), and the frame dump
dwt/loader/evidence/loader_frame2650.png shows the black 400x200 window with
the WM title bar at (100,100), over the Terminal Manager. The root/window
cells are unreliable here because xhello's marker cells are only 0x10 bits
apart and a 32-bit write overlaps the next cell; stage and the frame are the
evidence. (The same overlap exists in the committed xhello markers; it does
not affect execution.)
5.3 Negative: an unlinked .obj is refused
LOADER_COFF=dwt/loader/build/rela.obj (the two-object demo input):
LOADER: result status=7 (relocation records present (unsupported)) entry=00000000 ... relocs=2 pcb=00000000
LOADER: placed .text at 00000000: 20 bytes, 5 differing
The loader saw the two R-RELLONG records, refused before copying, and left the
target memory untouched. This is the correct failure for the transport sending
a .obj rather than the linked .out.
6. What the TFTP transport must hand it
See dwt/notes/LOADER_CROSS.md for the append-only interface note. In short:
- a RAM buffer holding the complete GSPLNK COFF, with its bit address and byte length;
- the loader resident at
0x01880000(rawdwt/loader/build/loader.bin); - a descriptor at
0x01882C00withbuf,len,mode=1; - a normal
calla 0x01880000(orA8 = descriptor address); - the loader chooses the load address from the COFF and returns status in
A8.
The transport does not pass a load address, and it must not use
mode=0 (that mode resumes the debugger-hijacked PC).
7. Limits and failure modes
- Position-dependent. The image must be linked for the address it will run at; the loader never relocates. A moved image is a different link.
- No reservation. The free-list check proves the target is in a free block
now; nothing stops a later
mallocfrom splitting that block into the app's address. Link apps away from the allocator's low-address growth, as the current examples do (the big free block grows from byte0x12191Aupward; the apps are at byte0x318000+). - No relocation records. Status 7. The SDK cannot emit them in a linked image anyway.
- Word-granular. Section sizes must be multiples of 16 bits; the copy and zero loops move 16-bit words. GSPA/GSPLNK always emit such sizes.
- Fixed loader address. The loader's own code is absolute. A transport that wants the loader elsewhere must relink it, not move it.
- One process per call, no unload.
create_processgets a name string"LOADER", a0x6000-byte stack andarg = 0; the loader has no argv or environment channel.create_processreturns 0 only on failure (status 10). - The COFF buffer must not overlap the loader page or the target sections. The loader checks both; the transport must keep its own receive buffer and code out of the target range too (the loader cannot know about them).
- Heap-list read. Only the global free list is walked (the per-arena lists
are unsafe to follow —
HEAP.md). If the list is empty or the target is not in it, the loader fails with status 8 instead of corrupting memory. - C runtime.
.text/.data/.cinit/named initialized sections are copied and.bssis zeroed, but the loader does not run the.cinittable; a C app must still enter through_c_int00and do its own autoinit as the SDK runtime expects. The loader'sentryis whatever the COFF says.
8. Reproduce
# loader (raw image, fixed address)
./dwt/loader/build.sh dwt/loader/loader.asm 01880000
# marker app: three sections at three addresses
./dwt/loader/build.sh dwt/loader/marker.asm 018C0000 018D0000 018D8000
# xhello COFF (entry stays 0; the loader uses textstart)
./dwt/loader/build.sh dwt/gui/xhello.asm 01840000
# relocation / position-dependence demo
./dwt/loader/reldemo.sh
# debugger-assisted run (no network, private NVRAM/cfg)
VT1200_SKIP_LEVEL1=1 LOADER_AT=2500 LOADER_END=2620 \
LOADER_COFF=dwt/loader/build/marker.coff LOADER_OUT=$PWD/dwt/loader/evidence \
/home/arti/code/k-space/mame0289/vt1200 vt1200 \
-rompath harness/mame/roms -video none -sound none -nothrottle -skip_gameinfo \
-nvram_directory $PWD/dwt/loader/run/nvram \
-cfg_directory $PWD/dwt/loader/run/cfg \
-debug -debugger none \
-autoboot_script dwt/loader/loader.lua -autoboot_delay 0
LOADER_MODE=1 exercises the callable/return path; LOADER_A8_DESC=1 passes
the descriptor in A8; LOADER_SNAP=1 writes a PNG at LOADER_END.
9. Evidence files
| path | contents |
|---|---|
dwt/loader/loader.asm |
the loader |
dwt/loader/marker.asm |
marker app with .data/.bss at separate addresses |
dwt/loader/coff.py |
COFF reader / dumper (summary, raw, manifest) |
dwt/loader/build.sh |
SDK build wrapper, keeps the COFF |
dwt/loader/reldemo.sh, rel_a.asm, rel_b.asm |
relocation / origin demo |
dwt/loader/loader.lua |
debugger-assisted harness (no network) |
dwt/loader/probe_os.lua |
heap-list + register-write probe used to design the harness |
dwt/loader/evidence/coff_layout.txt |
hexdump + parsed headers of marker/xhello/obj |
dwt/loader/evidence/reldemo.txt |
the full relocation/origin output |
dwt/loader/evidence/elf_absent.txt |
the ELF check |
dwt/loader/evidence/marker_run.log |
mode-0 marker run |
dwt/loader/evidence/marker_mode1_run.log |
callable/return run |
dwt/loader/evidence/marker_a8desc_run.log |
descriptor in A8 |
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)
opcode 7liveness; the computed challenge is answered bydwt/challenge.py(theRDebugdefault).jump 0x3F701230(malloc) with one argument, the COFF byte length.jumpreturns the callee'sA8, i.e. a bit pointer.- Poke the COFF to that bit pointer in 1024-byte chunks
(
bit + off*8, neverbit + off) andpeekit back: 510/510 or 774/774 bytes equal, sha256 matches the local file. - Poke the raw loader (
build/loader.bin, 1220 B) at bit0x01880000, thenpeekit back (610 words, 0 differing).--no-poke-loaderskips the write and requires that readback to match. - Build the 16-byte descriptor at bit
0x01882C00from 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. jump 0x01880000with zero arguments. The rdebug jump handler's zero-arg path is a bareCALLafterMOVE A9,A8withA9 = 0, so the loader enters withA8 = 0and uses the fixed descriptor. (With N args the stubs do not setA8to the descriptor either;A8 = 0is the reliable calling convention from rdebug.)- Read the result block at bit
0x01882D00, then app evidence: marker cells bypeek, or xhellostage/strlenplus 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(byte0x0F8E52),0x007c8030(byte0x0F9006),0x007df450(byte0x0FBE8A),0x00adcd90,0x009fccd0. These are far below the loader page (0x01880000, byte0x310000) and below the current apps (.textbyte0x318000), andhostload.pyrejects a pointer that overlaps either, the loader page or the marker cells.- The loader accepts a malloc'd buffer: status
0andA8 = 0on every run. Its free-list check (status 8) passed for.text 0x018C0000and forxhello.text 0x01840000; the result block reports entry/text/data/bss,pcb != 0,nscns=3, andloader_base=0x01880000. create_processreally runs the app:marker.coffleavesstart=0x018C0000,data=0xDEADBEEF,data2=0x12345678,bss=0, and a live counter;xhello.coffreachesstage=6(XSync) and its black 400x200 window is visible at(100,100)inhostload_xhello_boot{3,4}_xhello.png.--tftpalso works: the existingdwt/tcpip/tftpc.bin(bit0x01870000) fetches into its ownmallocbuffer (stage=4,total=510,done=0x05A5A5A5);peekmatches 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
- Result-block offsets are bits.
loader.asm'sRESULT_BASE+020his +4 bytes, so the block is 13 adjacent 32-bit cells (52 B), not cells at byte intervals of 0x20. Reading at byte offsets0x20, 0x40, ...returns0x55555555from the free heap beyond the real block; that was a host parsing bug, not a loader failure. - The descriptor is clobbered after a load. A successful
create_processleaves a call frame at0x01882C00(0d 00 00 00 <pcb> <ptr> 00 60 00 00);ARG_AREAis0x01882B00and 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.pydoes so immediately before every jump.
Limits
- No reservation.
malloconly finds a buffer; the loader's free-list check is still a check. The apps stay linked at byte0x318000/0x308000while 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. --tftpneeds a server that serves the COFF under tftpc's fixed name (dwt/tcpip/probe.bin);tftpc.asmhas no filename argument. The server used here wasserve_probe.py 192.168.99.1 --bootp --file dwt/loader/build/marker.coff.--frame Ncannot trigger a MAME dump. It only waits forvt1200-frame-N.ppmfrom an externally configuredVT1200_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_processis used for the app, not for the loader or the TFTP client. Acreate_processchild previously returned-1fromsocket()(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=1divergence to reach the OS.