From 71c2df0f546797409391b4dcf504a714985eddeb Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Fri, 10 Jul 2026 19:12:32 -0700 Subject: [PATCH] kernel-image bless: byte-verified static dispatch for the relocated kernel window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The generated dispatcher ran kernel-window table hits (RAM [0x500,0x8500), the BIOS's boot-time copy of ROM 0x1FC10000+) natively with NO check that the live RAM bytes still match the ROM image the functions were compiled from — unsound for runtime-patched kernel code (the BIOS installs pad/SIO handlers INSIDE compiled bodies; live diff: 15 clusters, 348 bytes). It never misfired only because entry points happened to avoid patched keys. A static kernel hit now requires psx_kernel_bless_dispatchable(): the emitter emits psx_bios_kernel_bodies[] (key -> full RAM extent of the code reachable from it; continuations carry their parent's extent, 499 entries), and the runtime lazily byte-compares each entry's body against the ROM source, caches the verdict, and invalidates precisely on writes into the body (store chokepoint, DMA/EXE range marks, savestate restore). Clean -> guarded native; patched/unverifiable -> the faithful dirty-RAM interpreter, exactly as Rule 18 prescribes. This implements the runtime half of the relocation-manifest contract (docs/RELOCATION_MANIFEST_FORMAT.md). Measured on Tomba (SCUS-94236): 208-214 of 219 verified functions clean (the 5 mismatches are the genuinely patched pad-driver family, faithfully interpreted); millions of guarded native hits per boot; title 1.00x, village 0.92x (parity — kernel keys were already native, now they are PROVEN native); LLE and HLE boots + gameplay input verified. Kernel-window interp measures 1.3% of interp burden (dirty_ram_stats), so this is a soundness fix, not a speedup; the dominant interp cost is overlay-region coverage (cg-tag hygiene), tracked separately. Observability: TCP kernel_bless (entries/clean/mismatch/native_hits/ verifies/invalidations), always-on. PSX_KERNEL_BLESS=0 reverts to unguarded-table behavior for A/B. Co-Authored-By: Claude Fable 5 --- recompiler/src/full_function_emitter.cpp | 64 ++++++++++ runtime/include/dirty_ram_interp.h | 11 ++ runtime/src/boot_state.c | 4 + runtime/src/debug_server.c | 18 +++ runtime/src/memory.c | 147 +++++++++++++++++++++++ 5 files changed, 244 insertions(+) diff --git a/recompiler/src/full_function_emitter.cpp b/recompiler/src/full_function_emitter.cpp index e92a5ab5..aed4c4fd 100644 --- a/recompiler/src/full_function_emitter.cpp +++ b/recompiler/src/full_function_emitter.cpp @@ -1372,6 +1372,55 @@ void FullFunctionEmitter::emit_dispatch( } out += "};\n\n"; + // --- Kernel body-extent table (runtime kernel-image bless) --- + // The BIOS copies Kernel Part 2 from ROM [0x1FC10000,0x1FC18000) to RAM + // [0x500,0x8500) at boot; the functions above with keys in that window + // were compiled FROM those ROM bytes. The runtime may execute a key's + // static native function instead of interpreting the (dirty) kernel page + // IFF the live RAM bytes of everything that function can execute still + // byte-match the ROM source. This table gives the runtime that judgment + // boundary: for each kernel-RAM dispatch key, the FULL RAM extent of the + // compiled code reachable from it. A continuation wrapper re-enters its + // PARENT (backward branches reachable), so continuations carry the + // parent's whole extent. Synthetic vector/alias wrappers are excluded — + // their stub bytes are runtime-installed and never ROM-matching. + { + // Function extents by normalized entry, from discovery (end inclusive). + std::map> extent_by_norm; + for (const auto& fn : dr.functions) { + uint32_t lo = normalize_address(fn.entry_addr); + uint32_t hi = normalize_address(fn.end_addr) + 4u; + extent_by_norm[normalize_address(fn.entry_addr)] = {lo, hi}; + (void)lo; + } + std::string kb; + size_t kb_count = 0; + for (uint32_t norm : emitted_normalized) { + if (norm < 0x500u || norm >= 0x8500u) continue; + uint32_t owner = norm; + if (continuations.count(norm)) + owner = continuations.at(norm).parent_func_norm; + auto it = extent_by_norm.find(owner); + if (it == extent_by_norm.end()) continue; + uint32_t lo = it->second.first, hi = it->second.second; + // Only bodies that live entirely inside the relocated window are + // verifiable against the ROM source; skip anything straddling it. + if (lo < 0x500u || hi > 0x8500u || lo >= hi) continue; + kb += fmt::format(" {{ 0x{:08X}u, 0x{:08X}u, 0x{:08X}u }},\n", + norm, lo, hi); + kb_count++; + } + out += "typedef struct {\n"; + out += " uint32_t key; /* dispatch key (RAM, normalized) */\n"; + out += " uint32_t body_lo; /* RAM extent of code reachable from key */\n"; + out += " uint32_t body_hi; /* exclusive */\n"; + out += "} PsxKernelBody;\n\n"; + out += fmt::format("const PsxKernelBody psx_bios_kernel_bodies[{}] = {{\n", kb_count); + out += kb; + out += "};\n"; + out += fmt::format("const uint32_t psx_bios_kernel_body_count = {}u;\n\n", kb_count); + } + // Dispatch function with binary search. out += "static uint32_t normalize(uint32_t addr) {\n"; out += " uint32_t phys = addr & 0x1FFFFFFFu;\n"; @@ -1386,6 +1435,7 @@ void FullFunctionEmitter::emit_dispatch( out += "extern int dirty_ram_dispatch(CPUState* cpu, uint32_t addr, uint32_t stop_addr);\n"; out += "extern int dirty_ram_is_dirty(uint32_t phys);\n"; + out += "extern int psx_kernel_bless_dispatchable(uint32_t phys);\n"; out += "extern void fntrace_record(CPUState* cpu, uint32_t target);\n"; out += "extern uint64_t g_dispatch_static_hits;\n"; out += "\n"; @@ -1479,6 +1529,20 @@ void FullFunctionEmitter::emit_dispatch( out += " while (lo <= hi) {\n"; out += " int mid = (lo + hi) / 2;\n"; out += " if (dispatch_table[mid].addr == phys) {\n"; + out += " /* Kernel-image bless guard (CLAUDE.md Rule 18). The keys in\n"; + out += " * the relocated kernel window [0x500,0x8500) were compiled\n"; + out += " * from the ROM source of the BIOS's boot-time kernel copy —\n"; + out += " * but the BIOS (and games) PATCH kernel RAM at runtime (pad/\n"; + out += " * SIO installs land inside compiled bodies). A static hit\n"; + out += " * here may only run if the live RAM bytes of everything the\n"; + out += " * function can execute still byte-match the ROM image\n"; + out += " * (memory.c psx_kernel_bless_dispatchable, lazily verified,\n"; + out += " * invalidated on writes). Mismatched/unverifiable bodies\n"; + out += " * fall through to the faithful dirty-RAM interpreter below.\n"; + out += " * Non-kernel keys are unaffected. */\n"; + out += " if (phys - 0x500u < 0x8000u &&\n"; + out += " !psx_kernel_bless_dispatchable(phys))\n"; + out += " break; /* found stays 0 -> dirty_ram_dispatch */\n"; out += " g_debug_current_func_addr = phys;\n"; out += " debug_server_trace_dispatch(phys);\n"; out += " dispatch_table[mid].func(cpu);\n"; diff --git a/runtime/include/dirty_ram_interp.h b/runtime/include/dirty_ram_interp.h index 92be23d9..b64e2418 100644 --- a/runtime/include/dirty_ram_interp.h +++ b/runtime/include/dirty_ram_interp.h @@ -86,6 +86,17 @@ extern uint32_t g_overlay_region_floor; * written-to since boot. Defined in memory.c. */ int dirty_ram_is_dirty(uint32_t phys); +/* Kernel-image bless (memory.c). A dispatch key in the relocated kernel + * window (RAM [0x500,0x8500), the BIOS's boot-time copy of ROM 0x1FC10000+) + * may run its statically-recompiled native function IFF the live RAM bytes + * of the code reachable from it (emitter-supplied body extent) byte-match + * the ROM source. Lazily verified, cached per entry, invalidated precisely + * on writes into the body. Runtime-patched bodies (pad/SIO install stubs) + * never verify and keep interpreting — faithful either way. */ +int psx_kernel_bless_dispatchable(uint32_t phys); +void psx_kernel_bless_note_range(uint32_t phys, uint32_t len); +void psx_kernel_bless_stats(uint64_t out[6]); + /* Capture/candidate window membership. Kernel window and overlay region are * unconditional; main-EXE text [KERNEL_WINDOW_END, FLOOR) is included only * when the page is dirty — i.e. a gameplay overlay overwrote the boot text diff --git a/runtime/src/boot_state.c b/runtime/src/boot_state.c index 1c62b50a..b3557b8f 100644 --- a/runtime/src/boot_state.c +++ b/runtime/src/boot_state.c @@ -205,6 +205,10 @@ static int apply_section(uint32_t tag, const uint8_t* p, uint32_t len, case BS_SEC_RAM: if (len != RAM_SIZE) return 0; memcpy(memory_get_ram_ptr(), p, RAM_SIZE); + { /* Kernel-bless: RAM changed wholesale — force re-verification. */ + extern void psx_kernel_bless_note_range(uint32_t phys, uint32_t l); + psx_kernel_bless_note_range(0, RAM_SIZE); + } return 1; case BS_SEC_SPAD: if (len != SPAD_SIZE) return 0; diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index a75270f2..d9afad2e 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -6743,6 +6743,23 @@ static void handle_ws_hud_mode(int id, const char *json) send_fmt("{\"id\":%d,\"ok\":true,\"tag_rects\":%d}", id, v ? 1 : 0); } +/* Kernel-image bless state: {"cmd":"kernel_bless"} -> + * entries/clean/mismatch/native_hits/verifies/invalidations. + * (memory.c psx_kernel_bless_*; PSX_KERNEL_BLESS=0 disables the mechanism.) */ +static void handle_kernel_bless(int id, const char *json) +{ + extern void psx_kernel_bless_stats(uint64_t out[6]); + (void)json; + uint64_t s[6]; + psx_kernel_bless_stats(s); + send_fmt("{\"id\":%d,\"ok\":true,\"entries\":%llu,\"clean\":%llu," + "\"mismatch\":%llu,\"native_hits\":%llu,\"verifies\":%llu," + "\"invalidations\":%llu}", + id, (unsigned long long)s[0], (unsigned long long)s[1], + (unsigned long long)s[2], (unsigned long long)s[3], + (unsigned long long)s[4], (unsigned long long)s[5]); +} + static void handle_ws_margin(int id, const char *json) { int v = json_get_int(json, "value", -2); @@ -11810,6 +11827,7 @@ static const CmdEntry s_commands[] = { { "gpu_state", handle_gpu_state }, { "ws_margin", handle_ws_margin }, { "ws_hud_mode", handle_ws_hud_mode }, + { "kernel_bless", handle_kernel_bless }, { "ws_aspect", handle_ws_aspect }, { "ws_nw", handle_ws_nw }, { "ws_backdrop_ring", handle_ws_backdrop_ring }, diff --git a/runtime/src/memory.c b/runtime/src/memory.c index d6d83232..32b6a936 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -100,9 +100,155 @@ static inline void dirty_ram_mark_page(uint32_t phys) { dirty_ram_bitmap[page >> 5] |= bit; } +/* ---- Kernel-image bless ------------------------------------------------- + * Kernel Part 2 (RAM [0x500,0x8500)) is the BIOS's boot-time copy of ROM + * [0x1FC10000,0x1FC18000); the statically-recompiled kernel functions in the + * generated dispatch table were compiled FROM those ROM bytes. Those pages + * are permanently dirty (TCB saves, event tables, install stubs share the + * window), which diverted EVERY kernel dispatch to the interpreter — the + * dominant cost of event-heavy scenes (a Tomba village frame samples ~97% + * in kernel PCs) and CD-load drains. + * + * A dispatch key may run its static native function IFF the live RAM bytes + * of everything that function can execute (its body extent, emitter-supplied + * via psx_bios_kernel_bodies[]) still byte-match the ROM source. Per-entry, + * lazily verified, cached; a guest write landing INSIDE an entry's body + * drops that entry back to unverified (next dispatch re-compares). Entries + * whose bodies were runtime-patched (the BIOS installs pad/SIO handlers into + * ROM-zero gaps of the pad driver) simply never verify and stay on the + * faithful interpreter path — their behavior is unchanged by design. This is + * the runtime half of the relocation-manifest contract + * (docs/RELOCATION_MANIFEST_FORMAT.md: "runtime verifies against live RAM + * before dispatching the AOT function"). */ +#define KBLESS_RAM_LO 0x500u +#define KBLESS_RAM_HI 0x8500u +#define KBLESS_ROM_OFF 0x10000u /* bios_rom[] offset of RAM 0x500 */ + +typedef struct { + uint32_t key; + uint32_t body_lo; + uint32_t body_hi; +} PsxKernelBody; +extern const PsxKernelBody psx_bios_kernel_bodies[]; +extern const uint32_t psx_bios_kernel_body_count; + +#define KBLESS_UNKNOWN 0u +#define KBLESS_CLEAN 1u +#define KBLESS_MISMATCH 2u +#define KBLESS_MAX_ENTRIES 4096u +static uint8_t kbless_state[KBLESS_MAX_ENTRIES]; /* parallel to bodies[] */ +static int kbless_enabled = -1; /* env PSX_KERNEL_BLESS=0 disables */ +/* Always-on counters (TCP kernel_bless). */ +static uint64_t kbless_native_hits = 0; +static uint64_t kbless_verifies = 0; +static uint64_t kbless_mismatches = 0; +static uint64_t kbless_invalidations = 0; + +static int kbless_on(void) { + if (kbless_enabled < 0) { + const char* e = getenv("PSX_KERNEL_BLESS"); + kbless_enabled = (e && e[0] == '0') ? 0 : 1; + if (psx_bios_kernel_body_count > KBLESS_MAX_ENTRIES) kbless_enabled = 0; + } + return kbless_enabled; +} + +/* Binary search the (key-sorted) body table. -1 if absent. */ +static int kbless_find(uint32_t phys) { + uint32_t lo = 0, hi = psx_bios_kernel_body_count; + while (lo < hi) { + uint32_t mid = (lo + hi) >> 1; + uint32_t k = psx_bios_kernel_bodies[mid].key; + if (k == phys) return (int)mid; + if (k < phys) lo = mid + 1; else hi = mid; + } + return -1; +} + +/* Dispatch-time query: may `phys` run its static native function? + * Verifies lazily; every failure mode falls back to the interpreter. */ +int psx_kernel_bless_dispatchable(uint32_t phys) { + if (phys - KBLESS_RAM_LO >= (KBLESS_RAM_HI - KBLESS_RAM_LO)) return 0; + if (!kbless_on()) return 0; + int i = kbless_find(phys); + if (i < 0) return 0; + uint8_t st = kbless_state[i]; + if (st == KBLESS_CLEAN) { kbless_native_hits++; return 1; } + if (st == KBLESS_MISMATCH) return 0; + const PsxKernelBody* b = &psx_bios_kernel_bodies[i]; + kbless_verifies++; + if (memcmp(ram + b->body_lo, + bios_rom + KBLESS_ROM_OFF + (b->body_lo - KBLESS_RAM_LO), + b->body_hi - b->body_lo) == 0) { + kbless_state[i] = KBLESS_CLEAN; + kbless_native_hits++; + return 1; + } + kbless_state[i] = KBLESS_MISMATCH; + kbless_mismatches++; + return 0; +} + +/* A write landed in the kernel window: any entry whose body contains the + * written byte must re-verify before its next native dispatch. Kernel DATA + * writes (TCB saves, event tables — the frequent case) lie outside every + * body and fall through the loop without invalidating anything; genuine + * code-window writes (install-time, patches) are rare. */ +static void kbless_note_write(uint32_t phys) { + if (phys - KBLESS_RAM_LO >= (KBLESS_RAM_HI - KBLESS_RAM_LO)) return; + if (kbless_enabled == 0) return; + uint32_t n = psx_bios_kernel_body_count; + if (n > KBLESS_MAX_ENTRIES) return; + for (uint32_t i = 0; i < n; i++) { + /* No early exit: a continuation key can sort ABOVE the written + * address while its parent body contains it (bodies overlap). n is + * a few hundred; this path fires only on kernel-window writes. */ + const PsxKernelBody* b = &psx_bios_kernel_bodies[i]; + if (phys >= b->body_lo && phys < b->body_hi && + kbless_state[i] != KBLESS_UNKNOWN) { + kbless_state[i] = KBLESS_UNKNOWN; + kbless_invalidations++; + } + } +} + +/* Range write (DMA / EXE load / savestate restore) overlapping the window: + * bulk, rare events — reset every entry rather than per-byte scanning. */ +void psx_kernel_bless_note_range(uint32_t phys, uint32_t len) { + if (len == 0) return; + uint32_t end = phys + len; + if (end < phys) end = 0xFFFFFFFFu; + if (end <= KBLESS_RAM_LO || phys >= KBLESS_RAM_HI) return; + uint32_t n = psx_bios_kernel_body_count; + if (n > KBLESS_MAX_ENTRIES) return; + for (uint32_t i = 0; i < n; i++) { + if (kbless_state[i] != KBLESS_UNKNOWN) { + kbless_state[i] = KBLESS_UNKNOWN; + kbless_invalidations++; + } + } +} + +void psx_kernel_bless_stats(uint64_t out[6]) { + uint32_t n = psx_bios_kernel_body_count; + uint32_t clean = 0, mism = 0; + if (n > KBLESS_MAX_ENTRIES) n = KBLESS_MAX_ENTRIES; + for (uint32_t i = 0; i < n; i++) { + if (kbless_state[i] == KBLESS_CLEAN) clean++; + if (kbless_state[i] == KBLESS_MISMATCH) mism++; + } + out[0] = psx_bios_kernel_body_count; + out[1] = clean; + out[2] = mism; + out[3] = kbless_native_hits; + out[4] = kbless_verifies; + out[5] = kbless_invalidations; +} + static inline void dirty_ram_mark_kernel_write(uint32_t phys) { if (phys >= DIRTY_RAM_KERNEL_TRACK_BYTES) return; dirty_ram_mark_page(phys); + kbless_note_write(phys); } int dirty_ram_is_dirty(uint32_t phys); @@ -247,6 +393,7 @@ uint32_t dirty_ram_text_diverged_pages(void) { return g_text_diverged_pages; } void dirty_ram_mark_executable_range(uint32_t phys, uint32_t len) { if (len == 0 || phys >= RAM_SIZE) return; + psx_kernel_bless_note_range(phys, len); uint32_t end = phys + len - 1u; if (end >= RAM_SIZE || end < phys) end = RAM_SIZE - 1u;