Files
psxrecomp/tools/tests/test_frameless_dispatch_root_cfg_proof.py
T
Kevin Kline 5803def02a compile_overlays: require CFG proof for a frameless dispatch walk root
Overlay bands are shared address windows, so every observed interpreted PC
is demanded for every occupant of the band. classify_overlay_seeds promotes
such a demand to a linear walk root whenever _callable_legacy_seed accepts
it, and that predicate accepts three unequal grades of evidence: the image
entry, an `addiu sp, sp, -imm` prologue, or a `jr $ra` two words back.

The third grade fires exactly where it is least trustworthy. The first word
after a function's return is also the first word of whatever the linker laid
down next, and in an overlay image that is routinely a pointer table, a
packed record array or zero fill. The walk then starts at the table head,
never reaches a return, and runs to the image end; the generated-C audit
reports a column of UNSUPPORTED_INSTRUCTION and the shard fails.

Require the bounded CFG probe the discovery roots already use
(plausible_callable_target: reachable return, no fallthrough past the probe)
before a prologue-less dispatch entry may be a walk root. A failure demotes
it to DISPATCH_INTERIOR, which keeps the dispatch evidence and the isolated
fragment demand and only declines to start a walk there; the image entry and
the prologue keep their own evidence and are not probed.

Measured on Breath of Fire III (SLPS-00990), 406 overlay captures:

  dispatch entries already accepted as walk roots   5,339
    with a prologue                                 2,066   unchanged
    on the preceding `jr $ra` alone                 3,273
      rejected by the probe                            41

  DISPATCH_ENTRY roots        1,141 ->  1,100   (-41)
  DISPATCH_INTERIOR          81,474 -> 80,851  (-623, interiors of the
                                                 fabricated walks)
  STATIC_DISCOVERY_ROOT      10,741 -> 10,741
  STATIC_DISPATCH_ENTRY         194 ->    194
  FUNCTION_POINTER_TARGET        20 ->     20
  isolated fragment demands 105,891 -> 105,891  (nothing lost)

41 of 406 captures change at all. All 41 rejected addresses are data by
inspection: in-image code-pointer tables, packed 16-bit record arrays,
signed delta pairs, zero fill. 27 of them are precisely the 27 shards whose
all-bands compile failed the audit -- one bad root per failing shard. The
other 14 are the same defect in shards whose data happened to decode all the
way to the image end without meeting an unsupported opcode.

New CTest: frameless_dispatch_root_cfg_proof.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:48:10 -05:00

125 lines
5.0 KiB
Python

#!/usr/bin/env python3
"""A prologue-less dispatch entry needs CFG proof before it becomes a walk root.
Overlay bands are shared address windows, so every observed interpreted PC is
demanded for every occupant of the band. `_callable_legacy_seed` then admits a
demanded address as a callable walk root on any of three grades of evidence:
it is the image entry, it opens a stack frame, or the word two slots back is
`jr $ra`. That third grade is the weak one, and it fires precisely where it is
least trustworthy -- the first word after a function's return is also the first
word of whatever the linker placed next, which in these images is routinely a
pointer table, a packed record array or zero fill. The walk then runs from the
table head to the image end and the generated-C audit reports a column of
UNSUPPORTED_INSTRUCTION.
Measured on Breath of Fire III (SLPS-00990), 406 overlay captures, 5,339
dispatch entries already accepted as walk roots: 2,066 have a prologue, 3,273
ride on the preceding `jr $ra` alone, and the bounded CFG probe rejects 41 of
those 3,273. All 41 are data by inspection (in-image pointer tables, packed
16-bit record arrays, signed delta pairs, zero fill), and 27 of them are
exactly the 27 shards whose all-bands compile failed the audit -- one bad root
per failing shard. The other 14 are the same bug in shards whose data happened
to decode all the way to the image end.
Demotion is not a discard: DISPATCH_INTERIOR keeps the dispatch evidence and
the isolated-fragment demand, and only declines to start a linear walk there.
"""
import struct
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "tools"))
import compile_overlays # noqa: E402
LOAD = 0x801EEC00
NOP = 0x00000000
JR_RA = 0x03E00008
ADDIU_SP_NEG = 0x27BDFFE0 # addiu sp, sp, -0x20
ADDIU_SP_POS = 0x27BD0020 # addiu sp, sp, 0x20
ORI_V0_ZERO = 0x34020001 # ori v0, zero, 1
def image(words):
return b"".join(struct.pack("<I", w) for w in words)
class FramelessDispatchRootTests(unittest.TestCase):
def proven(self, data, addr):
return compile_overlays._frameless_dispatch_root_proven(
data, LOAD, len(data), addr, LOAD + len(data))
def test_pointer_table_after_a_return_is_not_a_walk_root(self):
"""BATL_OVR 0x801EF5F8: five in-image code pointers, then a word table.
Every word decodes as some R3000 instruction, so opcode validity alone
says nothing; the walk simply never reaches a return.
"""
data = image([
ADDIU_SP_NEG, NOP, JR_RA, ADDIU_SP_POS, # a real leaf function
0x801EEC40, 0x801EEC7C, 0x801EEEB8, # the pointer table
0x801EEF10, 0x801EF0A8,
0x000000A0, 0x00000084, 0x00000070, # the word table
])
table = LOAD + 4 * 4
self.assertTrue(compile_overlays._callable_legacy_seed(data, LOAD, table))
self.assertFalse(self.proven(data, table))
def test_packed_record_table_after_a_return_is_not_a_walk_root(self):
"""AREA050 0x801F2DD4: 12-byte script records, no pointers at all."""
data = image([
ADDIU_SP_NEG, NOP, JR_RA, NOP,
0x00322F08, 0x93800500, 0x00070201,
0x00329206, 0x2F800900, 0x00030401,
])
table = LOAD + 4 * 4
self.assertTrue(compile_overlays._callable_legacy_seed(data, LOAD, table))
self.assertFalse(self.proven(data, table))
def test_zero_fill_after_a_return_is_not_a_walk_root(self):
data = image([ADDIU_SP_NEG, NOP, JR_RA, NOP] + [0] * 8)
self.assertFalse(self.proven(data, LOAD + 4 * 4))
def test_a_frameless_leaf_after_a_return_stays_a_walk_root(self):
"""The probe must not cost real frameless exports their root.
3,273 of BoF3's accepted dispatch roots are admitted by the preceding
`jr $ra` alone; only 41 fail this probe.
"""
data = image([
ADDIU_SP_NEG, NOP, JR_RA, NOP,
ORI_V0_ZERO, JR_RA, NOP, NOP,
])
frameless = LOAD + 4 * 4
self.assertTrue(compile_overlays._callable_legacy_seed(data, LOAD, frameless))
self.assertTrue(self.proven(data, frameless))
def test_a_prologue_needs_no_probe(self):
"""A stack frame is its own evidence, even without a reachable return.
The walk from a prologue is the recompiler's ordinary business; this
gate exists only for the grade of evidence that has none.
"""
data = image([
ADDIU_SP_NEG, NOP, JR_RA, NOP,
ADDIU_SP_NEG, 0xAFBF0018, 0x0C000000, NOP,
])
entry = LOAD + 4 * 4
self.assertFalse(
compile_overlays.plausible_callable_target(
data, LOAD, len(data), entry, LOAD + len(data)))
self.assertTrue(self.proven(data, entry))
def test_the_image_entry_needs_no_probe(self):
data = image([0x00000070, 0x0000007A, 0x0000009C, 0x00000094])
self.assertTrue(self.proven(data, LOAD))
if __name__ == "__main__":
unittest.main()