diff --git a/runtime/include/psx_memory.h b/runtime/include/psx_memory.h index 07c9a7ae..a370934b 100644 --- a/runtime/include/psx_memory.h +++ b/runtime/include/psx_memory.h @@ -94,6 +94,10 @@ void psx_host_write_word(uint32_t addr, uint32_t val); void psx_host_write_half(uint32_t addr, uint16_t val); void psx_host_write_byte(uint32_t addr, uint8_t val); +/* Side-effect-free aligned word peek (RAM / scratchpad / BIOS ROM only), for + * the debug path. Returns 0 (and leaves *out alone) for anything else. */ +int psx_peek_word(uint32_t addr, uint32_t *out); + #ifdef __cplusplus } #endif diff --git a/runtime/src/memory.c b/runtime/src/memory.c index 60602bd1..9a8307fc 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -164,6 +164,28 @@ static inline uint32_t psx_phys_addr_store(uint32_t addr) { /* Expose RAM pointer for oracle comparison (find_first_divergence). */ uint8_t *memory_get_ram_ptr(void) { return ram; } uint8_t *memory_get_scratchpad_ptr(void) { return scratchpad; } + +/* Side-effect-FREE aligned word peek for the debug/observability path ONLY: + * main RAM (every KUSEG/KSEG0/KSEG1 mirror), scratchpad and BIOS ROM. Never + * touches MMIO, the lockstep/DS read hooks, read watches, or unmapped_fatal. + * Returns 1 and fills *out when addr is backed by one of those; 0 otherwise. */ +int psx_peek_word(uint32_t addr, uint32_t *out) { + uint32_t phys, off; + const uint8_t *p; + if (addr >= 0xC0000000u || (addr & 3u)) return 0; + phys = addr & 0x1FFFFFFFu; + if (psx_ram_resolve(addr, 4u, &off)) { + p = &ram[off]; + } else if (phys >= 0x1F800000u && phys <= 0x1F8003FCu) { + p = &scratchpad[phys - 0x1F800000u]; + } else if (phys >= 0x1FC00000u && phys <= 0x1FC7FFFCu) { + p = &bios_rom[phys - 0x1FC00000u]; + } else { + return 0; + } + if (out) memcpy(out, p, sizeof(*out)); + return 1; +} void memory_clear_low_boot_scratch(void) { memset(ram, 0, 0x10u); } diff --git a/runtime/tests/psx_ram_runtime_map_stubs.c b/runtime/tests/psx_ram_runtime_map_stubs.c index 3341c7b9..2e98819d 100644 --- a/runtime/tests/psx_ram_runtime_map_stubs.c +++ b/runtime/tests/psx_ram_runtime_map_stubs.c @@ -57,6 +57,7 @@ uint32_t ls_read_hook(uint32_t a, int s, uint32_t v) { (void)a; (void)s; return void ls_write_hook(uint32_t a, int s, uint32_t v) { (void)a; (void)s; (void)v; } void ls_shadow_record_unreplayable(void) {} int fntrace_is_game_started(void) { return 0; } +void fntrace_restore_game_started(int started) { (void)started; } void overlay_loader_note_code_write(void) {} void overlay_loader_resync_validation_after_restore(void) {} void gte_canonicalize_cpu_state(CPUState *cpu) { (void)cpu; } diff --git a/runtime/tests/test_psx_ram_runtime_map.c b/runtime/tests/test_psx_ram_runtime_map.c index 320a586a..148c2d23 100644 --- a/runtime/tests/test_psx_ram_runtime_map.c +++ b/runtime/tests/test_psx_ram_runtime_map.c @@ -35,6 +35,7 @@ extern void psx_write_word(uint32_t addr, uint32_t val); extern void psx_write_half(uint32_t addr, uint16_t val); extern void psx_write_byte(uint32_t addr, uint8_t val); extern uint8_t *memory_get_ram_ptr(void); +extern uint8_t *memory_get_scratchpad_ptr(void); /* Stub-side mutation counters (psx_ram_runtime_map_stubs.c). */ extern int g_stub_device_restores; @@ -130,6 +131,39 @@ static void check_guest_access(int expanded) { check(psx_read_byte(0x80204005u) == 0x5Au, "byte reads back at its own address"); } +/* ---- psx_peek_word: debug read that must never reach the bus ----------- */ +static void check_peek(int expanded) { + uint32_t v = 0xDEADBEEFu; + memset(memory_get_ram_ptr(), 0, PSX_MAIN_RAM_BACKING_BYTES); + psx_write_word(0x80001000u, 0x11223344u); + check(psx_peek_word(0x80001000u, &v) && v == 0x11223344u, "peek: KSEG0 RAM"); + check(psx_peek_word(0x00001000u, &v) && v == 0x11223344u, "peek: KUSEG RAM"); + check(psx_peek_word(0xA0001000u, &v) && v == 0x11223344u, "peek: KSEG1 RAM"); + v = 0; + check(psx_peek_word(0x80201000u, &v) && ((v == 0x11223344u) == !expanded), + "peek: 2nd mirror follows the live geometry"); + check(psx_peek_word(0x807FFFFCu, &v), "peek: top of the RAM window"); + memory_get_scratchpad_ptr()[0x10] = 0x78; memory_get_scratchpad_ptr()[0x13] = 0x12; + check(psx_peek_word(0x1F800010u, &v) && (v & 0xFF) == 0x78 && (v >> 24) == 0x12, + "peek: scratchpad"); + check(psx_peek_word(0x1F8003FCu, &v) && !psx_peek_word(0x1F800400u, &v), + "peek: scratchpad end bound"); + check(psx_peek_word(0xBFC00000u, &v) && psx_peek_word(0x1FC7FFFCu, &v) && + !psx_peek_word(0x1FC80000u, &v), "peek: BIOS ROM bounds"); + /* Device / unmapped space is refused WITHOUT touching the bus (the stubbed + * psx_fatal_halt traps if a real access reached unmapped_fatal). */ + v = 0xDEADBEEFu; + check(!psx_peek_word(0x1F801044u, &v), "peek: JOY_RX refused"); + check(!psx_peek_word(0x1F801810u, &v), "peek: GPUREAD refused"); + check(!psx_peek_word(0x1F802000u, &v), "peek: expansion-2 MMIO refused"); + check(!psx_peek_word(0x1F000000u, &v), "peek: expansion-1 refused"); + check(!psx_peek_word(0x80800000u, &v), "peek: past the RAM window refused"); + check(!psx_peek_word(0xFFFE0130u, &v) && !psx_peek_word(0xC0000000u, &v), + "peek: KSEG2 refused"); + check(!psx_peek_word(0x80001002u, &v), "peek: unaligned refused"); + check(v == 0xDEADBEEFu, "peek: refused reads leave *out untouched"); +} + /* ---- 3. a real DMA writing RAM from a guest-chosen MADR --------------- */ #define DMA_BASE 0x1F801080u static void run_otc(uint32_t madr, uint32_t entries) { @@ -288,6 +322,7 @@ int main(void) { for (int expanded = 0; expanded < 2; ++expanded) { set_geometry(expanded); check_guest_access(expanded); + check_peek(expanded); check_dma(expanded); } check_savestates();