-- 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. -- -- vt1200 ... -debug -debugger none \ -- -autoboot_script dwt/tcpip/probe.lua -autoboot_delay 0 -- -- Stop with PROBE_END= (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")