DF-2362 / rig236.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | #!/usr/bin/env python3 -u # rig236.py -- shared rig for DF-2361/DF-2362 verification against the REAL # in-kernel gdb stub (sys/cpu/x86_64/misc/x86_64-gdbstub.c, sha256-gated # bda2d9a4..., compiled into the stock X86_64_GENERIC kernel). # # Transport: guest com0 (the sio0 console UART) wired by QEMU to a unix # socket. We speak the exact bytes a physical serial debug console would. # # gdb_tab attach: the stock kernel boots with gdb_tab == NULL (the boot-time # hint hint.sio.0.flags=0x90 did not attach gdb_tab on this ACPI guest; see # VERDICT.md). The rig attaches the documented state at runtime via DDB: # gdb_tab = &sio_consdev, siocniobase=0x3f8 already, siogdbunit=-1 already # => stub I/O on com0. This mirrors exactly what sio.c:3113-3119 is supposed # to do for flags 0x80/0x90. No code change; stock binary untouched. import os, sys, time, socket, subprocess, json HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) from rsp import SerialPeer, parse_t05, cks SOCK = os.environ.get("DF_SOCK", "/home/maxx/dfbsd/dfbsd/dfbsd-qemu/df236x/ser.sock") RUNNER = os.environ.get("DF_VMSH", "/home/maxx/dfbsd/dfbsd/dfbsd-qemu/run236.sh") # nm of the RUNNING kernel (stock X86_64_GENERIC build, Jul 2 2026). # Override via DF_ADDRS env (json) for other builds (fix validation). ADDRS = json.loads(os.environ.get("DF_ADDRS", json.dumps({ "gdb_tab": 0xffffffff8131dd20, "sio_consdev": 0xffffffff81142e60, "siogdbunit": 0xffffffff811435d0, "siogdbiobase": 0xffffffff81b256f4, "siocniobase": 0xffffffff81b256fc, "siocnunit": 0xffffffff81b256f8, "remcomOutBuffer": 0xffffffff817a6f20, "remcomInBuffer": 0xffffffff817a70c0, "ddb_regs": 0xffffffff817a7ac0, "gdb_handle_exception": 0xffffffff80b94280, "kdb_trap": 0xffffffff80bcc890, "kdb_call_ret": 0xffffffff80bcc9aa, # retaddr of gdb_handle_exception }))) def hx(v): return "%x" % (v & 0xffffffffffffffff) class Rig: def __init__(self, name, rawlog): self.name = name self.p = SerialPeer(SOCK, rawlog, name=name) # ---- DDB helpers -------------------------------------------------- def ddb(self, cmds, settle=2.0): """Enter DDB via sysctl (async), run cmds at db>, then continue.""" p = self.p p.buf.clear() subprocess.Popen([RUNNER, "ddbtrigger"]) m = p.wait(r"db> ", 45) if m is None: raise RuntimeError("no db> prompt after ddb trigger") out = {} for c in cmds: p.buf.clear() p.send(c.encode() + b"\n") time.sleep(settle) o = bytes(p.buf) out[c] = o.decode(errors="replace") print("[ddb] %-38s -> %s" % (c, " ".join(out[c].split())[:80])) p.buf.clear() p.send(b"continue\n") m = p.wait(r"restarted", 30) print("[ddb] continue -> %s" % ("RESUMED" if m else "no-restart-marker")) return out def attach_gdb_tab(self, force=False): """Set gdb_tab=&sio_consdev via DDB (documented flags=0x90 state).""" a = ADDRS lo = a["sio_consdev"] & 0xffffffff hi = a["sio_consdev"] >> 32 out = self.ddb([ "x/x %s" % hx(a["gdb_tab"]), ]) cur = out.get("x/x %s" % hx(a["gdb_tab"]), "") if "81142e60" in cur and not force: print("[rig] gdb_tab already set") return True out = self.ddb([ "wl %s %s" % (hx(a["gdb_tab"]), hx(lo)), "wl %s %s" % (hx(a["gdb_tab"] + 4), hx(hi)), "x/x %s" % hx(a["gdb_tab"]), ]) chk = out.get("x/x %s" % hx(a["gdb_tab"]), "") ok = ("%x" % a["sio_consdev"]) in chk.replace("0x", "") print("[%s] gdb_tab attach: %s" % ("+" if ok else "-", chk.strip())) return ok # ---- stub session ------------------------------------------------- def enter_stub(self, timeout=45): p = self.p p.buf.clear() for attempt in range(4): subprocess.call([RUNNER, "trigger"], timeout=40) m = p.wait(r"\$T[0-9a-fA-F]{2}[^#]*#[0-9a-fA-F]{2}", 20) if m: break print("[stub] trigger attempt %d: no T05 yet, retrying" % (attempt + 1)) else: raise RuntimeError("no T05 from stub") t05 = parse_t05(m.group(0)[1:-3].decode()) p.send(b"+") print("[stub] ALIVE rip=%s rbp=%s rsp=%s" % (t05.get("10"), t05.get("06"), t05.get("07"))) return t05 def detach(self): try: r = self.p.pkt("D", timeout=15) print("[stub] D -> %r" % r) m = self.p.wait(r"restarted", 30) print("[stub] %s" % ("kernel resumed" if m else "NO resume marker")) return r except Exception as e: print("[stub] detach failed: %s" % e) return None def close(self): try: self.p.pump(quiet=True) self.p.close() except Exception: pass def read_mem(p, addr, n): return p.read_mem(addr, n) def write_mem(p, addr, data): return p.write_mem(addr, data) def find_registers(p, t05): return __import__("rsp").find_registers(p, t05) |