From 6195556524b45d7978e34bcca230e1c3c5ac8ab3 Mon Sep 17 00:00:00 2001 From: SDD Date: Sun, 18 Oct 2026 16:57:45 +0300 Subject: [PATCH] dwt: mailbox_watch.py -- poll 0x27C190/0x16F0 across a boot; results are consumed before rdebug is reachable --- dwt/mailbox_watch.py | 94 ++++++++++++++++++++++++++++++++++++++++++ dwt/notes/REAL_UNIT.md | 15 +++++++ 2 files changed, 109 insertions(+) create mode 100644 dwt/mailbox_watch.py diff --git a/dwt/mailbox_watch.py b/dwt/mailbox_watch.py new file mode 100644 index 0000000..1d7339e --- /dev/null +++ b/dwt/mailbox_watch.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Poll the VT1200 diagnostic handoff cells over rdebug during a boot. + +rdebug only exists once the OS/network stack is up, so this cannot see the +level-1 tests themselves. It *can* catch the window between the network stack +starting and the Terminal Manager consuming the diagnostic result: + + 0x27C190 diagnostic mailbox pointer (Ghidra: FUN_rom_07f5a026 dereferences it) + 0x16F0 level-1 dispatcher's transient status code slot + +If the mailbox pointer becomes non-zero, the nine fields it points at are +dumped. The log records every value change, so a value that exists only for a +few polls is still captured. + +Usage: + python3 dwt/mailbox_watch.py --host 100.103.100.103 --seconds 180 + +Start it, then power-cycle the terminal. +""" +from __future__ import annotations + +import argparse +import struct +import sys +import time + +import exploit + +MAILBOX = 0x27C190 +SLOT = 0x16F0 +FIELDS = 9 + + +def u32(rd: exploit.RDebug, bit: int) -> int: + return struct.unpack(" None: + log.write(line + "\n") + log.flush() + + emit(f"watch {a.host}:{a.port} for {a.seconds:.0f}s -> {a.out}") + while time.time() < deadline: + if rd is None: + try: + rd = exploit.RDebug(a.host, a.port, 2.0, False) + rd.connect() + emit(f"{time.time():.3f} connected") + except Exception as exc: # noqa: BLE001 + time.sleep(0.4) + continue + try: + m = u32(rd, MAILBOX) + s = u32(rd, SLOT) + if m != last_m or s != last_s: + emit(f"{time.time():.3f} mailbox={m:#010x} slot={s:#010x}") + last_m, last_s = m, s + if m: + for i in range(FIELDS): + try: + v = u32(rd, m + 0x20 * i) + except Exception: # noqa: BLE001 + break + emit(f" +0x{0x20 * i:03x}: {v:#010x}") + except Exception as exc: # noqa: BLE001 + emit(f"{time.time():.3f} lost: {type(exc).__name__}: {exc}") + try: + rd.close() + except Exception: # noqa: BLE001 + pass + rd = None + time.sleep(0.2) + if rd is not None: + rd.close() + log.close() + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/dwt/notes/REAL_UNIT.md b/dwt/notes/REAL_UNIT.md index 196d6aa..101ff86 100644 --- a/dwt/notes/REAL_UNIT.md +++ b/dwt/notes/REAL_UNIT.md @@ -214,3 +214,18 @@ real framebuffer is 1280x1024 with the 256-byte stride. The console refuses diagnostics while the DWT has active work — our injected xhello window is the blocker. The service guide requires no open windows before remote tests: power-cycle (or close all session windows) and retry. + +### Diagnostic results are not observable through rdebug + +`dwt/mailbox_watch.py` polled the mailbox pointer (`0x27C190`) and the level-1 +dispatcher slot (`0x16F0`) across a power cycle. The rdebug service was only +connectable **after** the OS was up, at which point: + +``` +mailbox=0x00000000 slot=0x8ac00000 (OS data) +``` + +During the boot it was unusable (timeouts / AssertionError). So the level-1 +result is consumed before rdebug exists; the mailbox is a dialog/debug +structure, not the boot handoff. The only durable record is the **screen**: +the boot alert, and the Diagnostics dialog's on-screen results.