From e8094affb4ecee8e35eba5dd7db91c4db2984441 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Mon, 13 Apr 2026 21:19:32 -0700 Subject: [PATCH] Phase 4: seed VBlank chain handler + add RAM write-trace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EvCB write-trace instrumentation confirmed OpenEvent is never called for any event class during boot, not only VBlank. Static ROM analysis found that the only instruction in SCPH1001.BIN that stores to the VSync counter at 0x80079D9C lives in an unrecompiled 15-instruction function at 0xBFC425BC (identified as the VBlank chain handler: increments the counter, optionally calls the user callback at *0x800DFF40). Added as a seed so the function is now compiled and present in the dispatch table. Recompile alone does not unblock the boot — no JAL, literal, or lui 0xBFC4 exists in ROM that would load this address, so the kernel registrar that enqueues it on the root-counter chain is itself missing and needs to be found (next session, via DuckStation cross-reference). Changes: - recompiler/seeds: +0xBFC425BC (vblank_chain_handler), 825 seeds total - debug_server: wtrace_range / wtrace_dump / wtrace_clear TCP commands, 1024-entry ring buffer capturing (addr, old, new, ra, width); default range 0x0000E000..0x0000E1F0 active from process start so early-boot writes are captured before a TCP client can attach - memory.c: inline bounds check + hook call in psx_write_{word,half,byte} - tools: print_wtrace.py, sample_ra.py, sample_ra_long.py, scan_f2_sites.py — investigation helpers Runtime behaviour unchanged (still hangs in VSync spin at Sony logo) — the seed is a prerequisite; the upstream registrar is the next bug. Co-Authored-By: Claude Opus 4.6 (1M context) --- generated/SCPH1001_dispatch.c | 10 ++- generated/SCPH1001_full.c | 104 +++++++++++++++++++++- recompiler/seeds/phase2_ghidra_seeds.json | 12 ++- runtime/src/debug_server.c | 92 +++++++++++++++++++ runtime/src/memory.c | 24 +++++ tools/print_wtrace.py | 6 ++ tools/sample_ra.py | 44 +++++++++ tools/sample_ra_long.py | 54 +++++++++++ tools/scan_f2_sites.py | 69 ++++++++++++++ 9 files changed, 410 insertions(+), 5 deletions(-) create mode 100644 tools/print_wtrace.py create mode 100644 tools/sample_ra.py create mode 100644 tools/sample_ra_long.py create mode 100644 tools/scan_f2_sites.py diff --git a/generated/SCPH1001_dispatch.c b/generated/SCPH1001_dispatch.c index d1c30c5f..c0b719c7 100644 --- a/generated/SCPH1001_dispatch.c +++ b/generated/SCPH1001_dispatch.c @@ -1,7 +1,7 @@ /* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT. * * BIOS SHA256: 71af94d1e47a68c11e8fdb9f8368040601514a42a5a399cda48c7d3bff1e99d3 - * Dispatch entries: 1043 + * Dispatch entries: 1045 */ #include "cpu_state.h" @@ -1036,6 +1036,7 @@ extern void func_1FC41ED8(CPUState* cpu); extern void func_1FC41F10(CPUState* cpu); extern void func_1FC41F54(CPUState* cpu); extern void func_1FC41F88(CPUState* cpu); +extern void func_1FC41FC0(CPUState* cpu); extern void func_1FC42014(CPUState* cpu); extern void func_1FC4203C(CPUState* cpu); extern void func_1FC42060(CPUState* cpu); @@ -1048,6 +1049,7 @@ extern void func_1FC42380(CPUState* cpu); extern void func_1FC42424(CPUState* cpu); extern void func_1FC424A8(CPUState* cpu); extern void func_1FC42540(CPUState* cpu); +extern void func_1FC425BC(CPUState* cpu); extern void func_1FC425FC(CPUState* cpu); extern void func_1FC42640(CPUState* cpu); extern void func_1FC426DC(CPUState* cpu); @@ -1063,7 +1065,7 @@ typedef struct { PsxRecompFunc func; } DispatchEntry; -static const DispatchEntry dispatch_table[1043] = { +static const DispatchEntry dispatch_table[1045] = { { 0x00000500u, func_00000500 }, { 0x00000510u, func_00000510 }, { 0x00000520u, func_00000520 }, @@ -2088,6 +2090,7 @@ static const DispatchEntry dispatch_table[1043] = { { 0x1FC41F10u, func_1FC41F10 }, { 0x1FC41F54u, func_1FC41F54 }, { 0x1FC41F88u, func_1FC41F88 }, + { 0x1FC41FC0u, func_1FC41FC0 }, { 0x1FC42014u, func_1FC42014 }, { 0x1FC4203Cu, func_1FC4203C }, { 0x1FC42060u, func_1FC42060 }, @@ -2100,6 +2103,7 @@ static const DispatchEntry dispatch_table[1043] = { { 0x1FC42424u, func_1FC42424 }, { 0x1FC424A8u, func_1FC424A8 }, { 0x1FC42540u, func_1FC42540 }, + { 0x1FC425BCu, func_1FC425BC }, { 0x1FC425FCu, func_1FC425FC }, { 0x1FC42640u, func_1FC42640 }, { 0x1FC426DCu, func_1FC426DC }, @@ -2127,7 +2131,7 @@ void psx_dispatch(CPUState* cpu, uint32_t addr) { for (;;) { cpu->pc = 0; uint32_t phys = normalize(addr); - int lo = 0, hi = 1043 - 1; + int lo = 0, hi = 1045 - 1; int found = 0; while (lo <= hi) { int mid = (lo + hi) / 2; diff --git a/generated/SCPH1001_full.c b/generated/SCPH1001_full.c index c633afef..d988b3ad 100644 --- a/generated/SCPH1001_full.c +++ b/generated/SCPH1001_full.c @@ -1,7 +1,7 @@ /* AUTO-GENERATED by psxrecomp-bios --emit-full. DO NOT EDIT. * * BIOS SHA256: 71af94d1e47a68c11e8fdb9f8368040601514a42a5a399cda48c7d3bff1e99d3 - * Functions: 1043 + * Functions: 1045 */ #include "cpu_state.h" @@ -1034,6 +1034,7 @@ void func_1FC41ED8(CPUState* cpu); void func_1FC41F10(CPUState* cpu); void func_1FC41F54(CPUState* cpu); void func_1FC41F88(CPUState* cpu); +void func_1FC41FC0(CPUState* cpu); void func_1FC42014(CPUState* cpu); void func_1FC4203C(CPUState* cpu); void func_1FC42060(CPUState* cpu); @@ -1046,6 +1047,7 @@ void func_1FC42380(CPUState* cpu); void func_1FC42424(CPUState* cpu); void func_1FC424A8(CPUState* cpu); void func_1FC42540(CPUState* cpu); +void func_1FC425BC(CPUState* cpu); void func_1FC425FC(CPUState* cpu); void func_1FC42640(CPUState* cpu); void func_1FC426DC(CPUState* cpu); @@ -159730,6 +159732,62 @@ label_BFC41FA0: return; } +void func_1FC41FC0(CPUState* cpu) { +label_BFC41FC0: + psx_check_interrupts(cpu); + /* 0xBFC41FC0: 3C018008 lui $at, 0x8008 */ + cpu->gpr[1] = 0x80080000u; + /* 0xBFC41FC4: AC249F30 sw $a0, -24784($at) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[1] + (-24784)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC41FC4u); return; } cpu->write_word(psx_addr, cpu->gpr[4]); } + /* 0xBFC41FC8: 3C01800E lui $at, 0x800E */ + cpu->gpr[1] = 0x800E0000u; + /* 0xBFC41FCC: 27BDFFE8 addiu $sp, $sp, -24 */ + cpu->gpr[29] = (uint32_t)((int32_t)cpu->gpr[29] + (-24)); + /* 0xBFC41FD0: AC20EED4 sw $zero, -4396($at) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[1] + (-4396)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC41FD0u); return; } cpu->write_word(psx_addr, cpu->gpr[0]); } + /* 0xBFC41FD4: AFBF0014 sw $ra, 20($sp) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[29] + (20)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC41FD4u); return; } cpu->write_word(psx_addr, cpu->gpr[31]); } + /* 0xBFC41FD8: 3C01800E lui $at, 0x800E */ + cpu->gpr[1] = 0x800E0000u; + /* 0xBFC41FDC: 240EFFFF addiu $t6, $zero, -1 */ + cpu->gpr[14] = (uint32_t)((int32_t)cpu->gpr[0] + (-1)); + /* 0xBFC41FE0: 0C016818 jal 0xB005A060 */ + /* 0xBFC41FE4: AC2EEED0 sw $t6, -4400($at) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[1] + (-4400)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC41FE4u); return; } cpu->write_word(psx_addr, cpu->gpr[14]); } + cpu->gpr[31] = 0xBFC41FE8u; + psx_dispatch(cpu, 0x8005A060u); +label_BFC41FE8: + psx_check_interrupts(cpu); + /* 0xBFC41FE8: 3C042000 lui $a0, 0x2000 */ + cpu->gpr[4] = 0x20000000u; + /* 0xBFC41FEC: 3C05800E lui $a1, 0x800E */ + cpu->gpr[5] = 0x800E0000u; + /* 0xBFC41FF0: 24A5EED0 addiu $a1, $a1, -4400 */ + cpu->gpr[5] = (uint32_t)((int32_t)cpu->gpr[5] + (-4400)); + /* 0xBFC41FF4: 0C016AEC jal 0xB005ABB0 */ + /* 0xBFC41FF8: 34840001 ori $a0, $a0, 0x1 */ + cpu->gpr[4] = cpu->gpr[4] | 0x1u; + cpu->gpr[31] = 0xBFC41FFCu; + psx_dispatch(cpu, 0x8005ABB0u); +label_BFC41FFC: + psx_check_interrupts(cpu); + /* 0xBFC41FFC: 0C016A60 jal 0xB005A980 */ + /* 0xBFC42000: 00002021 addu $a0, $zero, $zero */ + cpu->gpr[4] = cpu->gpr[0] + cpu->gpr[0]; + cpu->gpr[31] = 0xBFC42004u; + psx_dispatch(cpu, 0x8005A980u); +label_BFC42004: + psx_check_interrupts(cpu); + /* 0xBFC42004: 8FBF0014 lw $ra, 20($sp) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[29] + (20)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC42004u); return; } cpu->gpr[31] = cpu->read_word(psx_addr); } + /* 0xBFC42008: 27BD0018 addiu $sp, $sp, 24 */ + cpu->gpr[29] = (uint32_t)((int32_t)cpu->gpr[29] + (24)); + /* 0xBFC4200C: 03E00008 jr $ra */ + /* 0xBFC42010: 00000000 nop */ + /* nop */ + return; +} + void func_1FC42014(CPUState* cpu) { label_BFC42014: psx_check_interrupts(cpu); @@ -160662,6 +160720,50 @@ label_BFC425AC: return; } +void func_1FC425BC(CPUState* cpu) { +label_BFC425BC: + psx_check_interrupts(cpu); + /* 0xBFC425BC: 3C0E8008 lui $t6, 0x8008 */ + cpu->gpr[14] = 0x80080000u; + /* 0xBFC425C0: 8DCE9D9C lw $t6, -25188($t6) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[14] + (-25188)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC425C0u); return; } cpu->gpr[14] = cpu->read_word(psx_addr); } + /* 0xBFC425C4: 3C02800E lui $v0, 0x800E */ + cpu->gpr[2] = 0x800E0000u; + /* 0xBFC425C8: 8C42FF40 lw $v0, -192($v0) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[2] + (-192)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC425C8u); return; } cpu->gpr[2] = cpu->read_word(psx_addr); } + /* 0xBFC425CC: 27BDFFE8 addiu $sp, $sp, -24 */ + cpu->gpr[29] = (uint32_t)((int32_t)cpu->gpr[29] + (-24)); + /* 0xBFC425D0: 3C018008 lui $at, 0x8008 */ + cpu->gpr[1] = 0x80080000u; + /* 0xBFC425D4: 25CF0001 addiu $t7, $t6, 1 */ + cpu->gpr[15] = (uint32_t)((int32_t)cpu->gpr[14] + (1)); + /* 0xBFC425D8: AFBF0014 sw $ra, 20($sp) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[29] + (20)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC425D8u); return; } cpu->write_word(psx_addr, cpu->gpr[31]); } + /* 0xBFC425DC: 10400003 beq $v0, $zero, 0xBFC425EC */ + uint32_t psx_brA_BFC425DC = cpu->gpr[2]; uint32_t psx_brB_BFC425DC = cpu->gpr[0]; + int psx_taken_BFC425DC = (psx_brA_BFC425DC == psx_brB_BFC425DC); + /* 0xBFC425E0: AC2F9D9C sw $t7, -25188($at) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[1] + (-25188)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC425E0u); return; } cpu->write_word(psx_addr, cpu->gpr[15]); } + if (psx_taken_BFC425DC) goto label_BFC425EC; +label_BFC425E4: + psx_check_interrupts(cpu); + /* 0xBFC425E4: 0040F809 jalr $ra, $v0 */ + /* 0xBFC425E8: 00000000 nop */ + /* nop */ + cpu->gpr[31] = 0xBFC425ECu; + psx_dispatch(cpu, cpu->gpr[2]); +label_BFC425EC: + psx_check_interrupts(cpu); + /* 0xBFC425EC: 8FBF0014 lw $ra, 20($sp) */ + { uint32_t psx_addr = (uint32_t)((int32_t)cpu->gpr[29] + (20)); if (psx_addr & 3u) { psx_unaligned_access(cpu, psx_addr, 0xBFC425ECu); return; } cpu->gpr[31] = cpu->read_word(psx_addr); } + /* 0xBFC425F0: 27BD0018 addiu $sp, $sp, 24 */ + cpu->gpr[29] = (uint32_t)((int32_t)cpu->gpr[29] + (24)); + /* 0xBFC425F4: 03E00008 jr $ra */ + /* 0xBFC425F8: 00000000 nop */ + /* nop */ + return; +} + void func_1FC425FC(CPUState* cpu) { label_BFC425FC: psx_check_interrupts(cpu); diff --git a/recompiler/seeds/phase2_ghidra_seeds.json b/recompiler/seeds/phase2_ghidra_seeds.json index a5227529..c6bf3fe0 100644 --- a/recompiler/seeds/phase2_ghidra_seeds.json +++ b/recompiler/seeds/phase2_ghidra_seeds.json @@ -1,7 +1,7 @@ { "schema": "psxrecomp-v4 phase2 seeds", "source": "generated/ghidra_function_starts.json + phase1c seeds + dispatch_miss_seeds + preserved manual", - "seed_count": 823, + "seed_count": 824, "seeds": [ { "address": "0xBFC00000", @@ -4117,6 +4117,16 @@ "address": "0xBFC33CC0", "label": "jal_BFC33CC0", "rationale": "JAL target from code region" + }, + { + "address": "0xBFC41FC0", + "label": "shell_InitRCnt_setup", + "rationale": "Undiscovered function: calls B0:0x15 (InitRCnt) with arg 0x20000001. Only reachable via indirect jalr from shell init table. Missing seed causes VSync counter to never increment." + }, + { + "address": "0xBFC425BC", + "label": "vblank_chain_handler", + "rationale": "VBlank chain handler: the ONLY instruction in the entire BIOS ROM that stores to 0x80079D9C (VSync counter) is at 0xBFC425E0, inside this 15-instruction function at 0xBFC425BC. Function reads counter, increments it, optionally calls user callback at *0x800DFF40. Not reachable via any JAL/literal/lui in ROM — installed at runtime by kernel chain-registration code. Missing seed means counter-increment code is never recompiled, so even if kernel installs the handler pointer it cannot dispatch to recompiled code." } ], "excluded": [ diff --git a/runtime/src/debug_server.c b/runtime/src/debug_server.c index f46faaa6..0b0ecbb5 100644 --- a/runtime/src/debug_server.c +++ b/runtime/src/debug_server.c @@ -92,6 +92,28 @@ typedef struct { } Watchpoint; static Watchpoint s_watchpoints[MAX_WATCHPOINTS]; +/* ---- Write trace (ring buffer) ---- + * Records every RAM write whose physical address is in [trace_lo, trace_hi). + * Filter variables are non-static so memory.c can inline the bounds check. + * Disabled when trace_lo == trace_hi. */ +#define WRITE_TRACE_CAP 1024 +typedef struct { + uint64_t seq; + uint32_t addr; + uint32_t old_val; + uint32_t new_val; + uint32_t ra; + uint8_t width; +} WriteTraceEntry; +static WriteTraceEntry s_wtrace[WRITE_TRACE_CAP]; +static uint64_t s_wtrace_seq = 0; /* total writes ever recorded */ +static uint32_t s_wtrace_head = 0; +/* Default: trace the EvCB region from program start so we catch OpenEvent + * writes that happen during early BIOS boot, before TCP clients can connect. + * Can be overridden at runtime via the wtrace_range command. */ +uint32_t debug_server_wtrace_lo = 0x0000E000; /* physical */ +uint32_t debug_server_wtrace_hi = 0x0000E1F0; /* physical, exclusive — EvCB slots (stops before PCB@0xE1EC/TCB@0xE1F4) */ + /* ---- Platform helpers ---- */ static void set_nonblocking(sock_t s) { @@ -783,6 +805,71 @@ static void handle_vram_peek(int id, const char *json) id, x, y, w, h, hex); } +/* ---- Write trace: hook + handlers ---- */ +extern CPUState *debug_cpu_ptr; + +/* Called from memory.c write paths after the bounds check. */ +void debug_server_trace_write(uint32_t phys, uint32_t old_val, uint32_t new_val, uint8_t width) +{ + uint32_t ra = debug_cpu_ptr ? debug_cpu_ptr->gpr[31] : 0; + WriteTraceEntry *e = &s_wtrace[s_wtrace_head]; + e->seq = s_wtrace_seq++; + e->addr = phys; + e->old_val = old_val; + e->new_val = new_val; + e->ra = ra; + e->width = width; + s_wtrace_head = (s_wtrace_head + 1) % WRITE_TRACE_CAP; +} + +static void handle_wtrace_range(int id, const char *json) +{ + char lo_str[32], hi_str[32]; + if (!json_get_str(json, "lo", lo_str, sizeof(lo_str))) { send_err(id, "missing lo"); return; } + if (!json_get_str(json, "hi", hi_str, sizeof(hi_str))) { send_err(id, "missing hi"); return; } + debug_server_wtrace_lo = hex_to_u32(lo_str) & 0x1FFFFFFFu; + debug_server_wtrace_hi = hex_to_u32(hi_str) & 0x1FFFFFFFu; + send_fmt("{\"id\":%d,\"ok\":true,\"lo\":\"0x%08X\",\"hi\":\"0x%08X\"}", + id, debug_server_wtrace_lo, debug_server_wtrace_hi); +} + +static void handle_wtrace_clear(int id, const char *json) +{ + (void)json; + s_wtrace_seq = 0; + s_wtrace_head = 0; + memset(s_wtrace, 0, sizeof(s_wtrace)); + send_ok(id); +} + +static void handle_wtrace_dump(int id, const char *json) +{ + (void)json; + uint64_t total = s_wtrace_seq; + uint32_t count = (total < WRITE_TRACE_CAP) ? (uint32_t)total : WRITE_TRACE_CAP; + uint32_t start = (total < WRITE_TRACE_CAP) ? 0 : s_wtrace_head; + + const size_t BUF_SZ = 256 * 1024; + char *buf = (char *)malloc(BUF_SZ); + if (!buf) { send_err(id, "oom"); return; } + size_t pos = 0; + pos += snprintf(buf + pos, BUF_SZ - pos, + "{\"id\":%d,\"ok\":true,\"total\":%llu,\"count\":%u,\"entries\":[", + id, (unsigned long long)total, count); + for (uint32_t i = 0; i < count && pos < BUF_SZ - 256; i++) { + uint32_t idx = (start + i) % WRITE_TRACE_CAP; + WriteTraceEntry *e = &s_wtrace[idx]; + pos += snprintf(buf + pos, BUF_SZ - pos, + "%s{\"seq\":%llu,\"addr\":\"0x%08X\",\"old\":\"0x%08X\",\"new\":\"0x%08X\",\"ra\":\"0x%08X\",\"w\":%u}", + (i == 0) ? "" : ",", + (unsigned long long)e->seq, + e->addr, e->old_val, e->new_val, e->ra, (unsigned)e->width); + } + pos += snprintf(buf + pos, BUF_SZ - pos, "]}"); + debug_server_send_line(buf); + free(buf); +} + static void handle_quit(int id, const char *json) { (void)json; @@ -809,6 +896,9 @@ static const CmdEntry s_commands[] = { { "sio_state", handle_sio_state }, { "watch", handle_watch }, { "unwatch", handle_unwatch }, + { "wtrace_range", handle_wtrace_range }, + { "wtrace_dump", handle_wtrace_dump }, + { "wtrace_clear", handle_wtrace_clear }, { "set_input", handle_set_input }, { "press", handle_press }, { "clear_input", handle_clear_input }, @@ -856,10 +946,12 @@ static void process_command(const char *line) /* Extended init that accepts a CPU state pointer for register queries. */ static CPUState *s_init_cpu = NULL; +CPUState *debug_cpu_ptr = NULL; /* Global, used by memory.c watchpoints */ void debug_server_set_cpu(CPUState *cpu) { s_cpu = cpu; + debug_cpu_ptr = cpu; } void debug_server_init(int port) diff --git a/runtime/src/memory.c b/runtime/src/memory.c index 9a4cf25e..cdac6aea 100644 --- a/runtime/src/memory.c +++ b/runtime/src/memory.c @@ -45,6 +45,21 @@ static const uint32_t *sr_ptr; uint32_t i_stat; /* 0x1F801070 — interrupt status (AND-acknowledge semantics) */ uint32_t i_mask; /* 0x1F801074 — interrupt enable mask */ +/* Write-trace hook (implemented in debug_server.c). Disabled when lo == hi. */ +extern uint32_t debug_server_wtrace_lo; +extern uint32_t debug_server_wtrace_hi; +extern void debug_server_trace_write(uint32_t phys, uint32_t old_val, uint32_t new_val, uint8_t width); + +static inline uint32_t read_ram_word(uint32_t phys) { + return (uint32_t)ram[phys] + | ((uint32_t)ram[phys + 1] << 8) + | ((uint32_t)ram[phys + 2] << 16) + | ((uint32_t)ram[phys + 3] << 24); +} +static inline uint16_t read_ram_half(uint32_t phys) { + return (uint16_t)ram[phys] | ((uint16_t)ram[phys + 1] << 8); +} + /* SPU registers are now handled by spu.c */ void memory_set_sr_ptr(const uint32_t *p) { sr_ptr = p; } @@ -311,6 +326,9 @@ void psx_write_word(uint32_t addr, uint32_t val) { uint32_t phys = addr & 0x1FFFFFFFu; if (phys < RAM_SIZE) { + if (phys >= debug_server_wtrace_lo && phys < debug_server_wtrace_hi) { + debug_server_trace_write(phys, read_ram_word(phys), val, 4); + } ram[phys] = (uint8_t)(val); ram[phys + 1] = (uint8_t)(val >> 8); ram[phys + 2] = (uint8_t)(val >> 16); @@ -366,6 +384,9 @@ void psx_write_half(uint32_t addr, uint16_t val) { uint32_t phys = addr & 0x1FFFFFFFu; if (phys < RAM_SIZE) { + if (phys >= debug_server_wtrace_lo && phys < debug_server_wtrace_hi) { + debug_server_trace_write(phys, read_ram_half(phys), val, 2); + } ram[phys] = (uint8_t)(val); ram[phys + 1] = (uint8_t)(val >> 8); return; @@ -413,6 +434,9 @@ void psx_write_byte(uint32_t addr, uint8_t val) { uint32_t phys = addr & 0x1FFFFFFFu; if (phys < RAM_SIZE) { + if (phys >= debug_server_wtrace_lo && phys < debug_server_wtrace_hi) { + debug_server_trace_write(phys, (uint32_t)ram[phys], (uint32_t)val, 1); + } ram[phys] = val; return; } diff --git a/tools/print_wtrace.py b/tools/print_wtrace.py new file mode 100644 index 00000000..74bd4222 --- /dev/null +++ b/tools/print_wtrace.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 +import json, sys +d = json.load(open(sys.argv[1])) +print(f"total={d['total']} captured={d['count']}") +for e in d['entries']: + print(f"seq={e['seq']:4} addr={e['addr']} old={e['old']} new={e['new']} ra={e['ra']} w={e['w']}") diff --git a/tools/sample_ra.py b/tools/sample_ra.py new file mode 100644 index 00000000..c84c3b41 --- /dev/null +++ b/tools/sample_ra.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +"""Sample $ra + EPC over N seconds to profile hot code regions.""" +import socket, json, sys, time, collections + +HOST, PORT = "127.0.0.1", 4370 +DURATION = float(sys.argv[1]) if len(sys.argv) > 1 else 5.0 + +s = socket.socket() +s.connect((HOST, PORT)) +s.settimeout(2.0) +buf = b"" +ra_hist = collections.Counter() +epc_hist = collections.Counter() +n_samples = 0 +t_end = time.time() + DURATION + +def read_line(): + global buf + while b"\n" not in buf: + chunk = s.recv(16384) + if not chunk: return None + buf += chunk + line, _, buf = buf.partition(b"\n") + return line.decode() + +while time.time() < t_end: + s.sendall(b'{"cmd":"get_registers"}\n') + line = read_line() + if not line: break + d = json.loads(line) + ra = d['gpr'][31] + epc = d['cop0_epc'] + ra_hist[ra] += 1 + epc_hist[epc] += 1 + n_samples += 1 + +s.close() +print(f"samples: {n_samples}") +print(f"--- top 15 $ra ---") +for ra, cnt in ra_hist.most_common(15): + print(f" {ra} {cnt:5} ({100.0*cnt/n_samples:.1f}%)") +print(f"--- top 15 EPC ---") +for epc, cnt in epc_hist.most_common(15): + print(f" {epc} {cnt:5} ({100.0*cnt/n_samples:.1f}%)") diff --git a/tools/sample_ra_long.py b/tools/sample_ra_long.py new file mode 100644 index 00000000..a1c89be6 --- /dev/null +++ b/tools/sample_ra_long.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Sample $ra and also sp+20 (saved ra on stack) to trace the full chain.""" +import socket, json, sys, time, collections + +HOST, PORT = "127.0.0.1", 4370 +DURATION = float(sys.argv[1]) if len(sys.argv) > 1 else 5.0 + +s = socket.socket() +s.connect((HOST, PORT)) +s.settimeout(3.0) +buf = b"" + +def read_line(): + global buf + while b"\n" not in buf: + chunk = s.recv(16384) + if not chunk: return None + buf += chunk + line, _, buf = buf.partition(b"\n") + return line.decode() + +def send(cmd): + s.sendall(cmd.encode() + b"\n") + return json.loads(read_line()) + +ra_hist = collections.Counter() +stack0_hist = collections.Counter() +n = 0 +t_end = time.time() + DURATION + +while time.time() < t_end: + r = send('{"cmd":"get_registers"}') + ra = r['gpr'][31] + sp = r['gpr'][29] + ra_hist[ra] += 1 + # Read saved $ra at sp+20 (common store-location) + sp_int = int(sp, 16) + sp20 = f"0x{(sp_int + 20) & 0xFFFFFFFF:08X}" + r2 = send(f'{{"cmd":"read_ram","addr":"{sp20}","len":4}}') + if r2.get('ok'): + hex4 = r2['hex'] + # little-endian 4 bytes + saved_ra = int.from_bytes(bytes.fromhex(hex4), 'little') + stack0_hist[f"0x{saved_ra:08X}"] += 1 + n += 1 + +s.close() +print(f"samples: {n}") +print("--- $ra (register) ---") +for v, c in ra_hist.most_common(15): + print(f" {v} {c:5}") +print("--- saved $ra at sp+20 (outer frame) ---") +for v, c in stack0_hist.most_common(15): + print(f" {v} {c:5}") diff --git a/tools/scan_f2_sites.py b/tools/scan_f2_sites.py new file mode 100644 index 00000000..32f4e8c7 --- /dev/null +++ b/tools/scan_f2_sites.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +import sys +rom = open('F:/Projects/psxrecomp-v4/bios/SCPH1001.BIN','rb').read() + +matches = [] +for rt in range(32): + word = (0x0F << 26) | (rt << 16) | 0xF200 + b = word.to_bytes(4, 'little') + off = 0 + while True: + o = rom.find(b, off) + if o == -1: break + bios_addr = 0xBFC00000 + o + next_word = int.from_bytes(rom[o+4:o+8], 'little') + op = (next_word >> 26) & 0x3F + rs = (next_word >> 21) & 0x1F + rt_out = (next_word >> 16) & 0x1F + imm = next_word & 0xFFFF + matches.append((bios_addr, rt, op, rs, rt_out, imm, next_word, o)) + off = o + 4 + +print("--- ROM sites forming 0xF200xxxx (class values) ---") +for addr, rt, op, rs, rt_out, imm, nw, offset in matches: + if rs == rt and rt_out == rt: + if op == 0x0D: + marker = "OPENEVENT-TARGET" if imm in (1,2,3,9) else "" + print(f" 0x{addr:08X}: lui r{rt}, 0xF200; ori r{rt}, 0x{imm:04X} class=0xF200{imm:04X} {marker}") + +print("\n--- 0xF2000003 (VBlank) sites, JAL-adjacency check ---") +for addr, rt, op, rs, rt_out, imm, nw, offset in matches: + if rs == rt and rt_out == rt and op == 0x0D and imm == 0x0003: + o = offset + # JAL preceding? (i.e. at offset-4) + if o >= 4: + prev = int.from_bytes(rom[o-4:o], 'little') + op_prev = (prev >> 26) & 0x3F + if op_prev == 0x03: + jal_addr = 0xBFC00000 + o - 4 + target = (jal_addr & 0xF0000000) | ((prev & 0x3FFFFFF) << 2) + print(f" 0x{addr:08X}: VBlank class; PREV_JAL at 0x{jal_addr:08X} -> 0x{target:08X}") + else: + print(f" 0x{addr:08X}: VBlank class; prev op=0x{op_prev:02x} at 0x{0xBFC00000+o-4:08X}") + +# Now: find which functions contain these addresses +# A function "contains" addr if it's the closest-entry <= addr +# Read dispatch.c: +print("\n--- Functions containing 0xF2000003 sites ---") +import re +dispatch = open('F:/Projects/psxrecomp-v4/generated/SCPH1001_dispatch.c').read() +entries = re.findall(r'\{\s*0x([0-9A-F]+)u,\s*func_([0-9A-F]+)\s*\}', dispatch) +# Only keep those in code range (phys 0x1FC00000+) +fn_list = sorted([(int(a,16), name) for a,name in entries]) +def containing_fn(addr): + phys = addr & 0x1FFFFFFF + prev = None + for faddr, name in fn_list: + if faddr <= phys: + prev = (faddr, name) + else: + break + return prev + +for addr, rt, op, rs, rt_out, imm, nw, offset in matches: + if rs == rt and rt_out == rt and op == 0x0D and imm == 0x0003: + cf = containing_fn(addr) + if cf: + print(f" 0x{addr:08X} (lui+ori F2000003) is inside func_{cf[1]} (entry 0x{cf[0]:08X})") + else: + print(f" 0x{addr:08X}: NO CONTAINING FUNCTION (orphan code region!)")