"""Target-first runtime differential for three public RenderDebug functions. The real public point, line, and box-outline machine code is executed. Box outline also executes the real same-object polygon-edges and public-line code. Deferred calls execute the real same-object variadic cache helper; only genuine outside APIs (assert/exit, time/error, and the rasterizer line sink) are modeled. The accepted domain is deliberately finite: valid non-NULL inputs with exactly representable finite coordinates/sizes, the documented cache bounds, and seven NULL assertion exits. This does not claim exhaustive floating-point proof for the natural point residual. Synthetic outcome controls mutate captured results. One separate execution control changes a verified target instruction byte only in Unicorn memory; no on-disk object is modified. """ from __future__ import annotations import argparse import copy import hashlib import json from pathlib import Path import struct import sys import unicorn as u from unicorn.x86_const import * ROOT = next(parent for parent in Path(__file__).resolve().parents if (parent / "tools/coff_compare.py").is_file()) sys.path.insert(0, str(ROOT)) from tools import coff_compare as cc from tools.campaign._coff_tools import function_sections from tools.regression_gate import _section_aux_selection PATHS = { "target": ROOT / "scratch/render-debug-three-before-20260906/build/split/source/render/render_debug.obj", "first": ROOT / "scratch/render-debug-three-first-20260906.obj", "actual": ROOT / "scratch/render-debug-three-actual-20260906.obj", "source": ROOT / "scratch/render-debug-three-source-final-20260906.c", "cache_reference": ROOT / "scratch/render_debug_cache_behavior_20260905.py", } PINS = { "target": "d3bbaa9484914bd9cf4695ef1775198a60c7891f466aff4fcefe87caf293fdd0", "first": "c17a3543cac88b90690090da553f4e6b3d252354f122cf9b73ead08a3581909b", "actual": "62a44f55c5a75662b70c068d6d4390b0dd6abddc1d699bd82e47bd0ae5c5bf90", "source": "8a1d5cd0765136789b8c04fa5850acfa20a54f47274c90c0f766fc68cd41a79b", "cache_reference": "6705d663d86f5185fcf9b7e71ce9e0549d9716bd3c86399a97bc2cecd8d28964", } POINT = "_render_debug_point" LINE = "_render_debug_line" BOX = "_render_debug_box_outline" POLYGON = "_render_debug_polygon_edges" CACHE_ENTRY = "_render_debug_add_cache_entry" CACHE_STRING = "_render_debug_add_cache_string" FUNCTIONS = (POINT, LINE, BOX, POLYGON, CACHE_ENTRY, CACHE_STRING) CODE = 0x100000 STUB = 0x200000 STOP = 0x20F000 LITERAL = 0x210000 CACHE = 0x300100 INPUT = 0x400100 STACK = 0x500000 SP = 0x508000 CACHE_SIZE = 0x740C POOL_SIZE = 1024 ENTRY_SIZE = 0x38 ENTRY_LIMIT = 512 TIME_OFFSET = 0x7400 COUNT_OFFSET = 0x7404 POOL_OFFSET = 0x7408 CACHE_WARNING_OFFSET = 0x740A STRING_WARNING_OFFSET = 0x740B FPCW_MODES = (0x027F, 0x037F) SAVED = { UC_X86_REG_EBP: 0x11223344, UC_X86_REG_EBX: 0x22334455, UC_X86_REG_ESI: 0x33445566, UC_X86_REG_EDI: 0x44556677, } APIS = ( "_display_assert", "_system_exit", "_rasterizer_debug_line", "_game_time_get", "_error", "_csstrcmp", "_csstrncpy", "_csstrlen", ) STUBS = {name: STUB + index * 0x100 for index, name in enumerate(APIS)} FUNCTION_ADDRESSES = {name: CODE + index * 0x1000 for index, name in enumerate(FUNCTIONS)} SOURCE_TEXT = b"c:\\halo\\SOURCE\\render\\render_debug.c\0" LITERALS = { "??_C@_0CF@JKFPHJJE@c?3?2halo?2SOURCE?2render?2render_deb@": SOURCE_TEXT, "??_C@_05BBEJFBFA@point?$AA@": b"point\0", "??_C@_06LEDMCOFG@point0?$AA@": b"point0\0", "??_C@_06KNCHBPBH@point1?$AA@": b"point1\0", "??_C@_06GPGIDCJA@points?$AA@": b"points\0", "??_C@_05PEENBMOG@color?$AA@": b"color\0", "??_C@_06CGBPGGIE@bounds?$AA@": b"bounds\0", "??_C@_0BN@JJPKCCPJ@render?5debug?5cache?5overflow?4?$AA@": b"render debug cache overflow.\0", "??_C@_0CD@CLODFBGE@render?5debug?5cache?5string?5overfl@": b"render debug cache string overflow\0", "__real@3f000000": struct.pack(" str: return hashlib.sha256(raw).hexdigest() def p32(value: int) -> bytes: return struct.pack(" bytes: return struct.pack(" bytes: return struct.pack(" float: return struct.unpack(" bytes: return struct.pack("<3f", *values) def color_bytes(values) -> bytes: return struct.pack("<4f", *values) def s16(raw: bytes | bytearray, offset: int) -> int: return struct.unpack_from(" bool: return base <= address and address + size <= base + length def cases() -> list[dict]: return [ {"name": "line_immediate", "function": LINE, "immediate": True}, {"name": "line_immediate_aliased_endpoints", "function": LINE, "immediate": True, "alias": True}, {"name": "line_deferred_same_tick", "function": LINE, "immediate": False, "count": 3}, {"name": "line_deferred_new_tick", "function": LINE, "immediate": False, "cache_time": 9, "game_time": 10, "second_time": 11, "count": 12, "pool": b"old\0"}, {"name": "point_immediate_positive", "function": POINT, "immediate": True, "size": 4.0}, {"name": "point_immediate_zero", "function": POINT, "immediate": True, "size": 0.0}, {"name": "point_immediate_negative", "function": POINT, "immediate": True, "size": -2.0}, {"name": "point_deferred_same_tick", "function": POINT, "immediate": False, "size": 2.25, "count": 7}, {"name": "point_deferred_new_tick", "function": POINT, "immediate": False, "size": -2.0, "cache_time": 9, "game_time": 10, "second_time": 11, "count": 511, "pool": b"stale\0"}, {"name": "box_immediate_ordinary", "function": BOX, "immediate": True}, {"name": "box_immediate_degenerate", "function": BOX, "immediate": True, "bounds": (2.0, 2.0, -1.0, -1.0, 0.0, 0.0)}, {"name": "box_immediate_inverted", "function": BOX, "immediate": True, "bounds": (4.0, -3.0, 6.0, -5.0, 8.0, -7.0)}, {"name": "box_deferred_same_tick", "function": BOX, "immediate": False, "count": 9}, {"name": "box_deferred_last_slot", "function": BOX, "immediate": False, "count": 511}, {"name": "line_deferred_full_cache_warns", "function": LINE, "immediate": False, "count": 512, "cache_warning": 0}, {"name": "assert_point_null", "function": POINT, "immediate": True, "null": "point"}, {"name": "assert_point_color_null", "function": POINT, "immediate": True, "null": "color"}, {"name": "assert_line_point0_null", "function": LINE, "immediate": True, "null": "point0"}, {"name": "assert_line_point1_null", "function": LINE, "immediate": True, "null": "point1"}, {"name": "assert_line_color_null", "function": LINE, "immediate": True, "null": "color"}, {"name": "assert_box_bounds_null", "function": BOX, "immediate": True, "null": "bounds"}, {"name": "assert_box_color_null", "function": BOX, "immediate": True, "null": "color"}, ] def input_image(case: dict) -> tuple[bytes, dict[str, bytes], dict[str, int]]: raw = bytearray((index * 23 + 11) & 0xFF for index in range(0x10000)) bounds = case.get("bounds", (-3.0, 4.0, -5.0, 6.0, -7.0, 8.0)) blocks = { "point": point_bytes((1.25, -2.5, 3.75)), "point0": point_bytes((1.25, -2.5, 3.75)), "point1": point_bytes((-4.5, 5.25, -6.0)), "color": color_bytes((0.75, 0.125, 0.5, 1.0)), "bounds": struct.pack("<6f", *bounds), } locations = {name: INPUT + index * 0x100 for index, name in enumerate(blocks)} if case.get("alias"): locations["point1"] = locations["point0"] blocks["point1"] = blocks["point0"] for name, data in blocks.items(): at = locations[name] - INPUT raw[at:at + len(data)] = data return bytes(raw), blocks, locations def public_arguments(case: dict, locations: dict[str, int]) -> bytes: null = case.get("null") address = lambda name: 0 if null == name else locations[name] immediate = p32(int(case["immediate"])) if case["function"] == POINT: return immediate + p32(address("point")) + f32(case.get("size", 4.0)) + p32(address("color")) if case["function"] == LINE: return immediate + p32(address("point0")) + p32(address("point1")) + p32(address("color")) assert case["function"] == BOX return immediate + p32(address("bounds")) + p32(address("color")) def initial_cache(case: dict) -> bytearray: raw = bytearray((index * 37 + 19) & 0xFF for index in range(CACHE_SIZE)) pool = case.get("pool", b"") assert len(pool) <= 1023 raw[:len(pool)] = pool raw[len(pool)] = 0 raw[TIME_OFFSET:TIME_OFFSET + 2] = p16(case.get("cache_time", 10)) raw[COUNT_OFFSET:COUNT_OFFSET + 2] = p16(case.get("count", 0)) raw[POOL_OFFSET:POOL_OFFSET + 2] = p16(len(pool)) raw[CACHE_WARNING_OFFSET] = case.get("cache_warning", 0) raw[STRING_WARNING_OFFSET] = case.get("string_warning", 0) return raw def cache_model(before: bytearray, case: dict, blocks: dict[str, bytes]) -> tuple[bytearray, list[dict], int | None]: raw = bytearray(before) events = [{"api": "game_time_get", "result": case.get("game_time", 10) & 0xFFFFFFFF}] now = case.get("game_time", 10) if s16(raw, TIME_OFFSET) != struct.unpack("= ENTRY_LIMIT: if not raw[CACHE_WARNING_OFFSET]: events.append({"api": "error", "priority": 2, "message": "render debug cache overflow."}) raw[CACHE_WARNING_OFFSET] = 1 return raw, events, None raw[COUNT_OFFSET:COUNT_OFFSET + 2] = p16(index + 1) start = POOL_SIZE + index * ENTRY_SIZE if case["function"] == POINT: raw[start:start + 2] = p16(1) raw[start + 4:start + 0x10] = blocks["point"] raw[start + 0x10:start + 0x14] = f32(case.get("size", 4.0)) raw[start + 0x14:start + 0x24] = blocks["color"] elif case["function"] == LINE: raw[start:start + 2] = p16(2) raw[start + 4:start + 0x10] = blocks["point0"] raw[start + 0x10:start + 0x1C] = blocks["point1"] raw[start + 0x1C:start + 0x2C] = blocks["color"] else: raw[start:start + 2] = p16(7) raw[start + 4:start + 0x1C] = blocks["bounds"] raw[start + 0x1C:start + 0x2C] = blocks["color"] return raw, events, index def point_axis_pairs(point: bytes, size: float) -> list[tuple[bytes, bytes]]: x, y, z = struct.unpack("<3f", point) half = f32_value(size) * 0.5 values = [ ((x - half, y, z), (x + half, y, z)), ((x, y - half, z), (x, y + half, z)), ((x, y, z - half), (x, y, z + half)), ] return [(point_bytes(a), point_bytes(b)) for a, b in values] def box_points(bounds: bytes) -> list[bytes]: x0, x1, y0, y1, z0, z1 = struct.unpack("<6f", bounds) return [point_bytes(point) for point in ( (x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1), )] def raster_event(point0: bytes, point1: bytes, color: bytes) -> dict: return {"api": "rasterizer_debug_line", "point0": point0.hex(), "point1": point1.hex(), "color": color.hex()} def expected_success(case: dict, before: bytearray, blocks: dict[str, bytes]) -> tuple[bytearray, list[dict], list[str], int | None]: if not case["immediate"]: cache, events, index = cache_model(before, case, blocks) return cache, events, [case["function"], CACHE_ENTRY], index if case["function"] == LINE: return before, [raster_event(blocks["point0"], blocks["point1"], blocks["color"])], [LINE], None if case["function"] == POINT: events = [raster_event(a, b, blocks["color"]) for a, b in point_axis_pairs(blocks["point"], case.get("size", 4.0))] return before, events, [POINT], None points = box_points(blocks["bounds"]) edges = [(3, 0), (0, 1), (1, 2), (2, 3), (7, 4), (4, 5), (5, 6), (6, 7), (0, 4), (1, 5), (2, 6), (3, 7)] events = [raster_event(points[a], points[b], blocks["color"]) for a, b in edges] trace = [BOX, POLYGON] + [LINE] * 4 + [POLYGON] + [LINE] * 8 return before, events, trace, None ASSERTIONS = { (POINT, "point"): ("point", 327), (POINT, "color"): ("color", 328), (LINE, "point0"): ("point0", 363), (LINE, "point1"): ("point1", 364), (LINE, "color"): ("color", 365), (BOX, "bounds"): ("bounds", 812), (BOX, "color"): ("color", 813), } class Loader: def __init__(self, label: str, path: Path, code_patches: dict[str, list[tuple[int, int, int]]] | None = None): self.label = label self.path = path self.obj = cc.load(path) self.sections = function_sections(self.obj) self.code_patches = code_patches or {} self.uc = u.Uc(u.UC_ARCH_X86, u.UC_MODE_32) for base, size in ((CODE, 0x10000), (STUB, 0x10000), (LITERAL, 0x10000), (CACHE - 0x100, 0x8000), (INPUT - 0x100, 0x11000), (STACK, 0x10000)): self.uc.mem_map(base, size) self.loaded: set[str] = set() self.extents: dict[int, int] = {} for address in STUBS.values(): self.uc.mem_write(address, b"\xC3") self.uc.mem_write(STOP, b"\xCC") for name, payload in LITERALS.items(): self.uc.mem_write(LITERAL_ADDRESSES[name], payload) for name in (POINT, LINE, BOX): self.function(name) self.uc.mem_protect(CODE, 0x10000, u.UC_PROT_READ | u.UC_PROT_EXEC) self.uc.mem_protect(STUB, 0x10000, u.UC_PROT_READ | u.UC_PROT_EXEC) self.uc.mem_protect(LITERAL, 0x10000, u.UC_PROT_READ) self.uc.mem_protect(CACHE - 0x100, 0x8000, u.UC_PROT_READ | u.UC_PROT_WRITE) self.uc.mem_protect(INPUT - 0x100, 0x11000, u.UC_PROT_READ) self.uc.mem_protect(STACK, 0x10000, u.UC_PROT_READ | u.UC_PROT_WRITE) def function(self, name: str) -> int: assert name in FUNCTION_ADDRESSES, (self.label, "unexpected internal function", name) address = FUNCTION_ADDRESSES[name] if name in self.loaded: return address self.loaded.add(name) section = self.sections[name] raw = bytearray(cc._section_bytes(self.obj, self.obj["sections"][section.section_number - 1])) assert len(raw) <= 0x1000 for offset, expected, replacement in self.code_patches.get(name, []): assert raw[offset] == expected, (self.label, name, offset, raw[offset], expected) raw[offset] = replacement self.extents[address] = len(raw) for relocation in section.info["relocations"]: target = relocation.get("symbolic_target", relocation["target"]) if target[0] == "internal": destination = address + target[1] else: assert target[0] in ("symbol", "defined-noncode"), target symbol, addend = target[1:] if target[0] == "symbol" else target[2:] if symbol == "_render_debug_globals": destination = CACHE elif symbol in FUNCTION_ADDRESSES: destination = self.function(symbol) elif symbol in STUBS: destination = STUBS[symbol] else: destination = self.constant(symbol) destination += addend if relocation["type"] == cc.IMAGE_REL_I386_REL32: destination -= address + relocation["address"] + 4 else: assert relocation["type"] == cc.IMAGE_REL_I386_DIR32, relocation raw[relocation["address"]:relocation["address"] + 4] = p32(destination) self.uc.mem_write(address, bytes(raw)) return address def constant(self, name: str) -> int: assert name in LITERALS, (self.label, "unexpected constant owner", name) expected = LITERALS[name] try: symbol = cc.symbol(self.obj, name) except cc.CoffError: symbol = None if symbol is not None: section = self.obj["sections"][symbol["section"] - 1] raw = bytes(cc._section_bytes(self.obj, section))[symbol["value"]:] assert raw == expected, (self.label, name, raw.hex(), expected.hex()) return LITERAL_ADDRESSES[name] def cstring(self, address: int) -> str: if contains(LITERAL, 0x10000, address, 1): limit = LITERAL + 0x10000 else: raise AssertionError((self.label, "invalid cstring", hex(address))) raw = bytes(self.uc.mem_read(address, limit - address)) assert b"\0" in raw return raw.split(b"\0", 1)[0].decode("ascii") @staticmethod def pointer_region(address: int) -> list: for label, base, size in (("input", INPUT, 0x10000), ("stack", STACK, 0x10000), ("cache", CACHE, CACHE_SIZE), ("literal", LITERAL, 0x10000)): if contains(base, size, address, 1): return [label, address - base] return ["unknown", address] def run(self, case: dict, fpcw: int) -> dict: uc = self.uc input_raw, blocks, locations = input_image(case) cache_before = initial_cache(case) expected_cache = bytearray(cache_before) expected_events: list[dict] expected_trace: list[str] expected_index = None assertion = case.get("null") if assertion: information, line = ASSERTIONS[(case["function"], assertion)] expected_events = [ {"api": "display_assert", "information": information, "file": SOURCE_TEXT[:-1].decode("ascii"), "line": line, "fatal": 1}, {"api": "system_exit", "code": 0xFFFFFFFF}, ] expected_trace = [case["function"]] else: expected_cache, expected_events, expected_trace, expected_index = expected_success( case, cache_before, blocks) uc.mem_write(CACHE - 64, b"\xA5" * 64 + bytes(cache_before) + b"\xA5" * 64) uc.mem_write(INPUT - 64, b"\xA5" * 64 + input_raw + b"\xA5" * 64) payload = public_arguments(case, locations) stack = bytearray(b"\x5A" * 0x10000) stack_at = SP - STACK stack[stack_at:stack_at + 4 + len(payload)] = p32(STOP) + payload uc.mem_write(STACK, bytes(stack)) state = {"events": [], "trace": [], "snapshots": [], "time_calls": 0, "exited": False, "forbidden_access": None} def args(count: int) -> tuple[int, ...]: esp = uc.reg_read(UC_X86_REG_ESP) return struct.unpack("<" + "I" * count, uc.mem_read(esp + 4, count * 4)) def code_hook(emulator, address, size, unused): assert (address == STOP or address in STUBS.values() or any(contains(base, length, address, size) for base, length in self.extents.items())), ( self.label, "unexpected executable address", hex(address), size) if address in FUNCTION_ADDRESSES.values(): state["trace"].append(next(name for name, value in FUNCTION_ADDRESSES.items() if value == address)) name = next((name for name, value in STUBS.items() if value == address), None) if name is None: return state["snapshots"].append({"api": name, "cache_sha256": digest(bytes(uc.mem_read(CACHE, CACHE_SIZE)))}) result = 0x76543210 if name == "_rasterizer_debug_line": p0, p1, color = args(3) event = raster_event(bytes(uc.mem_read(p0, 12)), bytes(uc.mem_read(p1, 12)), bytes(uc.mem_read(color, 16))) event.update({"point0_pointer": self.pointer_region(p0), "point1_pointer": self.pointer_region(p1), "color_pointer": self.pointer_region(color)}) elif name == "_game_time_get": key = "game_time" if state["time_calls"] == 0 else "second_time" result = case.get(key, case.get("game_time", 10)) & 0xFFFFFFFF state["time_calls"] += 1 event = {"api": "game_time_get", "result": result} elif name == "_error": priority, message = args(2) event = {"api": "error", "priority": priority, "message": self.cstring(message)} elif name == "_display_assert": information, file_name, line, fatal = args(4) event = {"api": "display_assert", "information": self.cstring(information), "file": self.cstring(file_name), "line": line, "fatal": fatal} elif name == "_system_exit": (code,) = args(1) event = {"api": "system_exit", "code": code} state["exited"] = True state["events"].append(event) emulator.emu_stop() return else: raise AssertionError((self.label, "unexpected cache-string API", name)) state["events"].append(event) uc.reg_write(UC_X86_REG_EAX, result & 0xFFFFFFFF) uc.reg_write(UC_X86_REG_ECX, 0xA1B2C3D4) uc.reg_write(UC_X86_REG_EDX, 0xB2C3D4E5) def memory_hook(emulator, access, address, size, value, unused): if access == u.UC_MEM_WRITE: allowed = (contains(CACHE, CACHE_SIZE, address, size) or contains(STACK + 64, SP - STACK - 64, address, size)) else: allowed = (contains(CACHE, CACHE_SIZE, address, size) or contains(INPUT, 0x10000, address, size) or contains(STACK + 64, 0x10000 - 128, address, size) or contains(LITERAL, 0x10000, address, size) or contains(CODE, 0x10000, address, size)) if not allowed: state["forbidden_access"] = [access, address, size, value] raise AssertionError((self.label, "forbidden memory access", access, hex(address), size, value)) code_handle = uc.hook_add(u.UC_HOOK_CODE, code_hook) memory_handle = uc.hook_add(u.UC_HOOK_MEM_READ | u.UC_HOOK_MEM_WRITE, memory_hook) initial_tag = None try: uc.reg_write(UC_X86_REG_ESP, SP) uc.reg_write(UC_X86_REG_EFLAGS, 0x202) uc.reg_write(UC_X86_REG_FPCW, fpcw) uc.reg_write(UC_X86_REG_FPTAG, 0xFFFF) uc.reg_write(UC_X86_REG_FPSW, 0) initial_tag = uc.reg_read(UC_X86_REG_FPTAG) for register, value in SAVED.items(): uc.reg_write(register, value) uc.emu_start(FUNCTION_ADDRESSES[case["function"]], STOP, count=500000) finally: uc.hook_del(code_handle) uc.hook_del(memory_handle) returned = not assertion esp_restored = (uc.reg_read(UC_X86_REG_ESP) == SP + 4) if returned else None nonvolatiles_restored = ( all(uc.reg_read(register) == value for register, value in SAVED.items()) if returned else None) fpcw_final = uc.reg_read(UC_X86_REG_FPCW) fptag_final = uc.reg_read(UC_X86_REG_FPTAG) fpsw_final = uc.reg_read(UC_X86_REG_FPSW) assert state["exited"] == bool(assertion) if returned: assert uc.reg_read(UC_X86_REG_EIP) == STOP assert esp_restored and nonvolatiles_restored assert fpcw_final == fpcw assert fptag_final == initial_tag, "x87 tag word changed" assert (fpsw_final & 0x3800) == 0, "x87 TOP not restored" assert not (uc.reg_read(UC_X86_REG_EFLAGS) & 0x400), "direction flag set" guarded_cache = bytes(uc.mem_read(CACHE - 64, CACHE_SIZE + 128)) guarded_input = bytes(uc.mem_read(INPUT - 64, 0x10000 + 128)) after_stack = bytes(uc.mem_read(STACK, 0x10000)) cache_guard = guarded_cache[:64] == guarded_cache[-64:] == b"\xA5" * 64 input_guard = guarded_input[:64] == guarded_input[-64:] == b"\xA5" * 64 input_immutable = guarded_input[64:-64] == input_raw stack_guard = (after_stack[:64] == bytes(stack[:64]) and after_stack[SP - STACK:] == bytes(stack[SP - STACK:])) actual_cache = guarded_cache[64:-64] assert cache_guard and input_guard and input_immutable and stack_guard assert actual_cache == bytes(expected_cache), ( self.label, case["name"], "cache mismatch", [index for index, pair in enumerate(zip(actual_cache, expected_cache)) if pair[0] != pair[1]][:20]) assert state["trace"] == expected_trace, (self.label, case["name"], state["trace"], expected_trace) semantic_events = [{key: value for key, value in event.items() if not key.endswith("_pointer")} for event in state["events"]] assert semantic_events == expected_events, ( self.label, case["name"], semantic_events, expected_events) entry_hex = None if expected_index is not None: start = POOL_SIZE + expected_index * ENTRY_SIZE entry_hex = actual_cache[start:start + ENTRY_SIZE].hex() return { "case": case["name"], "fpcw_initial": fpcw, "events": state["events"], "function_trace": state["trace"], "api_cache_snapshots": state["snapshots"], "cache_sha256": digest(actual_cache), "cache_entry_hex": entry_hex, "cache_count": s16(actual_cache, COUNT_OFFSET), "cache_time": s16(actual_cache, TIME_OFFSET), "cache_pool_offset": s16(actual_cache, POOL_OFFSET), "cache_flags": list(actual_cache[CACHE_WARNING_OFFSET:STRING_WARNING_OFFSET + 1]), "returned": returned, "system_exit_nonreturn": state["exited"], "abi": { "esp_restored": esp_restored, "nonvolatiles_restored": nonvolatiles_restored, "direction_flag_clear": True, "fpcw_final": fpcw_final, "fptag_restored": fptag_final == initial_tag, "x87_top_restored": (fpsw_final & 0x3800) == 0, "cache_guard": cache_guard, "input_guard": input_guard, "input_immutable": input_immutable, "stack_guard": stack_guard, "forbidden_access": state["forbidden_access"], }, } def comparable(outcome: dict) -> dict: return {key: value for key, value in outcome.items() if key not in ("case",)} def captured_outcome_negative_controls(outcomes: dict[tuple[str, int], dict]) -> dict: controls = {} def changed(name: str, case_name: str, mutate): key = (case_name, 0x037F) original = outcomes[key] modified = copy.deepcopy(original) mutate(modified) controls[name] = comparable(modified) != comparable(original) assert controls[name], name def wrong_axis(item): events = [event for event in item["events"] if event["api"] == "rasterizer_debug_line"] events[0]["point0"] = events[1]["point0"] def wrong_sign(item): event = next(event for event in item["events"] if event["api"] == "rasterizer_debug_line") raw = bytearray.fromhex(event["point0"]) raw[:4] = bytearray.fromhex(item["events"][0]["point1"][:8])[:4] event["point0"] = raw.hex() def wrong_tag(item): raw = bytearray.fromhex(item["cache_entry_hex"]) raw[:2] = p16(2) item["cache_entry_hex"] = raw.hex() changed("wrong_point_axis", "point_immediate_positive", wrong_axis) changed("wrong_point_sign", "point_immediate_positive", wrong_sign) changed("wrong_cache_tag", "point_deferred_same_tick", wrong_tag) changed("wrong_box_call_order", "box_immediate_ordinary", lambda item: item["events"].__setitem__(slice(0, 2), list(reversed(item["events"][:2])))) changed("wrong_stack_result", "line_immediate", lambda item: item["abi"].__setitem__("esp_restored", False)) changed("wrong_nonvolatile_result", "line_immediate", lambda item: item["abi"].__setitem__("nonvolatiles_restored", False)) changed("wrong_guard_result", "box_immediate_ordinary", lambda item: item["abi"].__setitem__("cache_guard", False)) changed("wrong_fpcw_result", "point_immediate_positive", lambda item: item["abi"].__setitem__("fpcw_final", 0x027F)) return controls def in_memory_target_byte_negative_control() -> dict: # January point +0x77 is `d8 e1` (fsub st(1)); replace only its ModR/M byte # with c1 (`d8 c1`, fadd st(1)). This reverses the first x-axis endpoint # while leaving the function runnable. Loader.run must reject its modeled # event. The pinned target object on disk remains untouched. mutation = {POINT: [(0x78, 0xE1, 0xC1)]} loader = Loader("target_in_memory_wrong_x_sign", PATHS["target"], mutation) case = next(item for item in cases() if item["name"] == "point_immediate_positive") try: loader.run(case, 0x037F) except AssertionError as error: return { "detected": True, "kind": "transient Unicorn code-memory mutation; on-disk object unchanged", "function": POINT, "offset": 0x78, "expected_byte": 0xE1, "replacement_byte": 0xC1, "instruction_change": "d8 e1 fsub st(1) -> d8 c1 fadd st(1)", "case": case["name"], "fpcw": 0x037F, "rejection": repr(error), } raise AssertionError("in-memory target instruction mutation was not detected") def object_metadata(loader: Loader) -> dict: metadata = {} for name in FUNCTIONS: symbol = cc.symbol(loader.obj, name) metadata[name] = { "storage": symbol["storage"], "type": symbol["type"], "value": symbol["value"], "selection": _section_aux_selection(loader.obj, symbol["section"]), **loader.sections[name].info, } half = next((symbol for symbol in loader.obj["symbols"] if symbol["name"] == "__real@3f000000" and symbol["section"] > 0), None) metadata["half_literal"] = None if half is None else { "size": loader.obj["sections"][half["section"] - 1]["size"], "selection": _section_aux_selection(loader.obj, half["section"]), "payload": bytes(cc._section_bytes(loader.obj, loader.obj["sections"][half["section"] - 1])).hex(), } return metadata def main() -> None: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--output", type=Path, default=ROOT / "scratch/render-debug-three-runtime-report.json", help="report path (default preserves the original scratch artifact name)", ) args = parser.parse_args() for label, path in PATHS.items(): actual = digest(path.read_bytes()) assert actual == PINS[label], (label, actual, PINS[label]) loaders = {label: Loader(label, PATHS[label]) for label in ("target", "first", "actual")} results = [] failures = [] target_outcomes: dict[tuple[str, int], dict] = {} for case in cases(): for fpcw in FPCW_MODES: try: outcomes = {label: loader.run(case, fpcw) for label, loader in loaders.items()} target = outcomes["target"] target_outcomes[(case["name"], fpcw)] = target first_equal = comparable(outcomes["first"]) == comparable(target) actual_equal = comparable(outcomes["actual"]) == comparable(target) assert first_equal and actual_equal results.append({"case": case["name"], "fpcw": fpcw, "target": target, "first_equal": first_equal, "actual_equal": actual_equal}) except Exception as error: failures.append({"case": case["name"], "fpcw": fpcw, "error": repr(error)}) outcome_controls = captured_outcome_negative_controls(target_outcomes) if not failures else {} byte_control = in_memory_target_byte_negative_control() if not failures else {} accepted_execution_count = len(cases()) * len(FPCW_MODES) * len(loaders) report = { "scope": __doc__, "harness_sha256": digest(Path(__file__).read_bytes()), "unicorn_version": u.__version__, "pins": {label: {"path": str(path), "sha256": PINS[label]} for label, path in PATHS.items()}, "objects": {label: object_metadata(loader) for label, loader in loaders.items()}, "scenario_count": len(cases()), "fpcw_modes": list(FPCW_MODES), "accepted_execution_count": accepted_execution_count, "in_memory_negative_execution_count": 1 if byte_control else 0, "execution_count": accepted_execution_count + (1 if byte_control else 0), "failures": failures, "all_target_models_pass": not failures, "first_differential_pass": not failures and all(item["first_equal"] for item in results), "actual_differential_pass": not failures and all(item["actual_equal"] for item in results), "captured_outcome_negative_controls": { "kind": "synthetic captured-outcome mutations; object bytes are never modified", "results": outcome_controls, "all_detected": bool(outcome_controls) and all(outcome_controls.values()), }, "in_memory_target_byte_negative_control": byte_control, "results": results, "limitations": [ "Finite exact-value scenarios only; no NaN, infinity, denormal, or arbitrary invalid-input claim.", "The point residual differs by four independent integer-store schedules around x87 operations; this harness proves the listed outcomes, not exhaustive equivalence.", "system_exit is modeled as a genuine nonreturn boundary, so post-exit callee restoration is intentionally not asserted.", "Only genuine external APIs are modeled; polygon_edges, public line, and private cache helpers execute their real same-object bytes.", ], } output = args.output.resolve() output.parent.mkdir(parents=True, exist_ok=True) output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") print(json.dumps({ "output": str(output), "scenario_count": report["scenario_count"], "fpcw_modes": report["fpcw_modes"], "execution_count": report["execution_count"], "failures": failures, "first_differential_pass": report["first_differential_pass"], "actual_differential_pass": report["actual_differential_pass"], "captured_outcome_negative_controls": report["captured_outcome_negative_controls"], "in_memory_target_byte_negative_control": report["in_memory_target_byte_negative_control"], })) if (failures or not report["captured_outcome_negative_controls"]["all_detected"] or not report["in_memory_target_byte_negative_control"].get("detected")): raise SystemExit(1) if __name__ == "__main__": main()