Files
vt1200/dwt/tcpip/probe.lua
T
SDD 01923ecb69 dwt/tcpip: map the socket API and fetch a file over TFTP into RAM
The firmware's tftp_open hangs in its resolver from the rdebug process, so
the payload speaks RFC 783 over the firmware socket API: socket(2,2,0),
sendto(RRQ), recvfrom(DATA)/ACK into a malloc'd buffer, close. Verified
live: 600 B fetched from 192.168.99.1 into the malloc buffer, server RRQ
logged, and rdebug peek matches probe.bin byte-for-byte.

Notes: dwt/notes/TCPIP.md (function table, ABI, limits) and the
loader/transport hand-off in dwt/notes/LOADER_CROSS.md.
2026-10-18 15:24:34 +03:00

106 lines
3.8 KiB
Lua

-- probe.lua -- watch the dwt/tcpip payloads from inside MAME.
--
-- Samples marker cells and the CPU PC once per frame. This is independent of
-- the terminal's network stack, so it still reports after a payload has wedged
-- rdebug or TCP. PCs inside the payload or the socket/TFTP code are printed,
-- which is how a hang is localised.
--
-- <mame> vt1200 ... -debug -debugger none \
-- -autoboot_script dwt/tcpip/probe.lua -autoboot_delay 0
--
-- Stop with PROBE_END=<frame> (default 20000).
local machine = manager.machine
local cpu = machine.devices[':maincpu']
local prog = cpu.spaces['program']
local function hex(v) return string.format("%08X", v & 0xFFFFFFFF) end
local M = {
-- tfetch.asm
stage = 0x01871000, fd = 0x01871020, buf = 0x01871040,
total = 0x01871060, last = 0x01871080, done = 0x018710A0,
close = 0x018710C0, sp = 0x01871200, a14 = 0x01871220,
-- launcher.asm
lstage = 0x01872500, pcb = 0x01872520,
-- socktest.asm
sstage = 0x01874000, sfd = 0x01874020,
-- tftpc.asm
cstage = 0x0187C000, cfd = 0x0187C020, cbuf = 0x0187C040,
ctotal = 0x0187C060, clast = 0x0187C080, cblock = 0x0187C0A0,
cdone = 0x0187C0C0,
}
local END_AT = tonumber(os.getenv("PROBE_END") or "1000000")
local last = {}
local frames = 0
local pc_lines = 0
local checked = false
-- Ground truth for the fetch: read the malloc'd buffer from inside MAME and
-- compare it with the file the server holds. This avoids trusting rdebug peek.
local function check_buffer(buf, total)
local f = io.open("dwt/tcpip/probe.bin", "rb")
if not f then print("PROBE: no probe.bin to compare") return end
local want = f:read("*a")
f:close()
local n = math.min(total, #want)
local bad, first = 0, -1
for i = 0, n - 1 do
local got = prog:read_u8(buf + i * 8)
if got ~= want:byte(i + 1) then
bad = bad + 1
if first < 0 then first = i end
end
end
print(string.format("PROBE BUFFER buf=%s total=%d file=%d bad=%d first_bad=%d",
hex(buf), total, #want, bad, first))
end
local function in_range(pc, lo, hi) return pc >= lo and pc < hi end
local function sample()
frames = frames + 1
local vals = {}
for k, a in pairs(M) do vals[k] = prog:read_u32(a) end
local pc = cpu.state['PC'].value
local changed = false
for k, v in pairs(vals) do
if last[k] ~= v then changed = true end
end
local interesting = in_range(pc, 0x01870000, 0x01874000)
or in_range(pc, 0x3F5C0000, 0x3F5F0000)
or in_range(pc, 0x3F700000, 0x3F710000)
if changed or interesting or frames % 120 == 1 then
if interesting then pc_lines = pc_lines + 1 end
if not interesting or pc_lines <= 400 then
print(string.format("PROBE f=%d pc=%s stage=%s fd=%s buf=%s total=%s last=%s done=%s close=%s sp=%s a14=%s lstage=%s pcb=%s sstage=%s sfd=%s",
frames, hex(pc), hex(vals.stage), hex(vals.fd), hex(vals.buf),
hex(vals.total), hex(vals.last), hex(vals.done),
hex(vals.close), hex(vals.sp), hex(vals.a14),
hex(vals.lstage), hex(vals.pcb), hex(vals.sstage), hex(vals.sfd)))
print(string.format("TCPC cstage=%s cfd=%s cbuf=%s ctotal=%s clast=%s cblock=%s cdone=%s",
hex(vals.cstage), hex(vals.cfd), hex(vals.cbuf), hex(vals.ctotal),
hex(vals.clast), hex(vals.cblock), hex(vals.cdone)))
end
end
if vals.cdone == 0x05A5A5A5 and not checked then
checked = true
check_buffer(vals.cbuf, vals.ctotal)
end
last = vals
if frames >= END_AT then
print("PROBE end")
machine:exit()
end
end
if not machine.debugger then
print("PROBE ERROR: pass -debug")
machine:exit()
return
end
emu.register_frame_done(sample, "probe")
print("PROBE armed")