Add side-effect-free psx_peek_word + test

Debug reads of RAM/scratchpad/BIOS without MMIO, read hooks or unmapped_fatal.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matthew Stanley
2026-10-04 10:50:02 -07:00
parent 2fd4625d74
commit 07aaa6e700
4 changed files with 62 additions and 0 deletions
+4
View File
@@ -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
+22
View File
@@ -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);
}
@@ -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; }
+35
View File
@@ -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();