Files
virtualjaguar-libretro/test/dump_pc.c
Joseph Mattiello 2f25682cac CD Tier 4: import CD test suites, dev tools, and framework headers
Brings in #109's test infrastructure for the CD subsystem. Per the
Tier 5 triage report, redundant general-hardware suites were dropped;
overlapping cherry-picks deferred to a follow-up.

Imported and wired into make test (84 new passing asserts):
  test/test_butch_cd.c, test_bios_config.c, test_boot_config.c,
  test_audio_dac.c

Built but not auto-run (private-ROM gated; surface real per-game
boot regressions when discs are present):
  test/test_cd_boot.c, test_cd_hle_boot.c, test_cd_bios_boot.c,
  test_blitter.c

Framework + helpers + tooling: test_framework.h, cd_assertions.h,
mister_ground_truth.h, dump_pc.c, heap_search.c, headless.py, and
test/tools/{analyze_cd_roms,bios_disasm,disasm_gpu_isr}.py.

Dropped (redundant with our coverage in test_hle_bios, test_dsp_ops,
test_gpu_ops, test_m68k_ops, test_blitter_simd):
  test_dsp_instructions, test_gpu_controlflow, test_gpu_instructions,
  test_m68k_instructions, test_timers, test_irq, test_gpu_ctrl,
  test_video_modes, test_memory_map, test_gpu_irq.

Cherry-picks deferred to a follow-up:
  resolution-derivation cases from test_video_modes ->test_hle_bios,
  gpu_ctrl_bus_hog_readback / gpu_flags_int_enable_all_five from
  test_gpu_ctrl -> test_hle_bios, BUTCH cases from test_irq ->
  test_butch_cd, line_buffer cases from test_memory_map ->
  test_hle_bios.

All imports pass scripts/c89-lint.sh (decls hoisted). test_hle_bios
unchanged at 211; full suite OK: 0 test(s) failed.
2026-05-07 20:53:23 -04:00

189 lines
7.8 KiB
C

/* dump_pc.c — Focused diagnostic: dump code around the stuck PC after transition.
* Build: cc -g -O0 -o test/dump_pc test/dump_pc.c -ldl
* Run: VJ_CD_BOOT_MODE=hle VJ_HLE_MODE=1 ./test/dump_pc "path/to.cue" 460
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdbool.h>
#include "../libretro-common/include/libretro.h"
static void (*p_retro_init)(void);
static void (*p_retro_deinit)(void);
static void (*p_retro_set_environment)(retro_environment_t);
static void (*p_retro_set_video_refresh)(retro_video_refresh_t);
static void (*p_retro_set_audio_sample)(retro_audio_sample_t);
static void (*p_retro_set_audio_sample_batch)(retro_audio_sample_batch_t);
static void (*p_retro_set_input_poll)(retro_input_poll_t);
static void (*p_retro_set_input_state)(retro_input_state_t);
static bool (*p_retro_load_game)(const struct retro_game_info *);
static void (*p_retro_unload_game)(void);
static void (*p_retro_run)(void);
static unsigned int (*p_m68k_get_reg)(void *, int);
static void video_refresh(const void *d, unsigned w, unsigned h, size_t p) { (void)d;(void)w;(void)h;(void)p; }
static void audio_sample(int16_t l, int16_t r) { (void)l;(void)r; }
static size_t audio_sample_batch(const int16_t *d, size_t f) { (void)d; return f; }
static void input_poll(void) {}
static int16_t input_state(unsigned a, unsigned b, unsigned c, unsigned d) { (void)a;(void)b;(void)c;(void)d; return 0; }
static void log_printf(enum retro_log_level level, const char *fmt, ...) {
va_list ap;
(void)level;
va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap);
}
static struct retro_log_callback log_cb = { log_printf };
static bool environment(unsigned cmd, void *data) {
switch (cmd) {
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE: *(struct retro_log_callback *)data = log_cb; return true;
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: return true;
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
*(const char **)data = (getenv("VJ_HLE_MODE") && strcmp(getenv("VJ_HLE_MODE"), "1") == 0) ? "/nonexistent" : "test/roms/private";
return true;
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY: *(const char **)data = "."; return true;
case RETRO_ENVIRONMENT_SET_VARIABLES: case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_V2: return true;
case RETRO_ENVIRONMENT_GET_VARIABLE: {
struct retro_variable *var = (struct retro_variable *)data;
if (var->key && strcmp(var->key, "virtualjaguar_bios") == 0) { var->value = "enabled"; return true; }
if (var->key && strcmp(var->key, "virtualjaguar_usefastblitter") == 0) { var->value = "enabled"; return true; }
if (var->key && strcmp(var->key, "virtualjaguar_cd_bios_type") == 0) { var->value = "retail"; return true; }
if (var->key && strcmp(var->key, "virtualjaguar_cd_boot_mode") == 0) {
const char *env = getenv("VJ_CD_BOOT_MODE");
var->value = (env ? env : "hle"); return true;
}
var->value = NULL; return false;
}
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: *(bool *)data = false; return true;
default: return false;
}
}
int main(int argc, char *argv[]) {
unsigned num_frames;
void *handle;
uint8_t *(*get_ram)(void);
struct retro_game_info game = {0};
uint32_t prev_pc;
unsigned f;
uint8_t *ram;
uint32_t pc, sp;
uint32_t base;
uint32_t a;
int r;
uint16_t opcode;
unsigned v;
if (argc < 2) { fprintf(stderr, "Usage: %s <cue> [frames]\n", argv[0]); return 1; }
num_frames = argc > 2 ? (unsigned)atoi(argv[2]) : 460;
handle = dlopen("./virtualjaguar_libretro.dylib", RTLD_NOW);
if (!handle) { fprintf(stderr, "dlopen: %s\n", dlerror()); return 1; }
#define LOAD(sym) do { p_##sym = dlsym(handle, #sym); if (!p_##sym) { fprintf(stderr, "Missing: %s\n", #sym); return 1; } } while(0)
LOAD(retro_init); LOAD(retro_deinit); LOAD(retro_set_environment);
LOAD(retro_set_video_refresh); LOAD(retro_set_audio_sample);
LOAD(retro_set_audio_sample_batch); LOAD(retro_set_input_poll);
LOAD(retro_set_input_state); LOAD(retro_load_game); LOAD(retro_unload_game); LOAD(retro_run);
p_m68k_get_reg = dlsym(handle, "m68k_get_reg");
get_ram = dlsym(handle, "GetRamPtr");
p_retro_set_environment(environment);
p_retro_set_video_refresh(video_refresh);
p_retro_set_audio_sample(audio_sample);
p_retro_set_audio_sample_batch(audio_sample_batch);
p_retro_set_input_poll(input_poll);
p_retro_set_input_state(input_state);
p_retro_init();
game.path = argv[1];
if (!p_retro_load_game(&game)) { fprintf(stderr, "Load failed\n"); return 1; }
prev_pc = 0;
for (f = 0; f < num_frames; f++) {
p_retro_run();
if (p_m68k_get_reg) {
uint32_t cur_pc = p_m68k_get_reg(NULL, 16);
uint32_t cur_sp = p_m68k_get_reg(NULL, 15);
if (cur_pc != prev_pc && f >= 400) {
printf("Frame %u: PC=$%06X SP=$%06X\n", f, cur_pc, cur_sp);
prev_pc = cur_pc;
}
}
}
if (!get_ram || !p_m68k_get_reg) { printf("Missing symbols\n"); goto done; }
ram = get_ram();
pc = p_m68k_get_reg(NULL, 16);
sp = p_m68k_get_reg(NULL, 15);
printf("\n=== Final: PC=$%06X SP=$%06X ===\n", pc, sp);
/* Dump code around stuck PC */
base = (pc > 0x40) ? pc - 0x40 : 0;
printf("\nCode at $%06X-$%06X:\n", base, pc + 0x60);
for (a = base; a < pc + 0x60 && a < 0x200000; a += 2)
printf(" $%06X: %02X%02X%s\n", a, ram[a], ram[a+1], (a == pc) ? " <-- PC" : "");
/* Dump stack */
printf("\nStack at SP=$%06X:\n", sp);
for (a = sp; a < sp + 0x40 && a + 3 < 0x200000; a += 4) {
uint32_t val32 = (ram[a]<<24)|(ram[a+1]<<16)|(ram[a+2]<<8)|ram[a+3];
printf(" $%06X: $%08X\n", a, val32);
}
/* Dump all 68K registers */
printf("\n68K regs:\n");
for (r = 0; r <= 7; r++)
printf(" D%d=$%08X A%d=$%08X\n", r, p_m68k_get_reg(NULL, r), r, p_m68k_get_reg(NULL, 8+r));
printf(" PC=$%08X SR=$%04X\n", p_m68k_get_reg(NULL, 16), p_m68k_get_reg(NULL, 17) & 0xFFFF);
/* Look for what the code at PC is polling */
/* Common pattern: TST.L <addr> / BEQ.S back_to_tst */
printf("\nChecking if stuck PC is polling a memory location...\n");
opcode = (ram[pc] << 8) | ram[pc+1];
printf(" Opcode at PC: $%04X\n", opcode);
if (opcode == 0x4AB9) { /* TST.L <abs32> */
uint32_t addr = (ram[pc+2]<<24)|(ram[pc+3]<<16)|(ram[pc+4]<<8)|ram[pc+5];
uint32_t val = 0;
if (addr < 0x200000)
val = (ram[addr]<<24)|(ram[addr+1]<<16)|(ram[addr+2]<<8)|ram[addr+3];
printf(" TST.L $%08X = $%08X\n", addr, val);
} else if (opcode == 0x4A39) { /* TST.B <abs32> */
uint32_t addr = (ram[pc+2]<<24)|(ram[pc+3]<<16)|(ram[pc+4]<<8)|ram[pc+5];
printf(" TST.B $%08X = $%02X\n", addr, (addr < 0x200000) ? ram[addr] : 0xFF);
} else if ((opcode & 0xFFF0) == 0x4A90) { /* TST.L (An) */
int reg = opcode & 7;
uint32_t addr = p_m68k_get_reg(NULL, 8 + reg);
uint32_t val = 0;
if (addr < 0x200000)
val = (ram[addr]<<24)|(ram[addr+1]<<16)|(ram[addr+2]<<8)|ram[addr+3];
printf(" TST.L (A%d) => TST.L ($%08X) = $%08X\n", reg, addr, val);
} else if ((opcode & 0xFF00) == 0x0C00 || (opcode & 0xFF00) == 0x0C80) {
printf(" CMP instruction\n");
}
/* Dump the VBlank/interrupt vectors in case the game re-installed them */
printf("\nException vectors at stuck point:\n");
for (v = 0; v < 4; v++) {
uint32_t val = (ram[v*4]<<24)|(ram[v*4+1]<<16)|(ram[v*4+2]<<8)|ram[v*4+3];
printf(" Vec %u ($%03X) = $%08X\n", v, v*4, val);
}
for (v = 24; v <= 31; v++) {
uint32_t val = (ram[v*4]<<24)|(ram[v*4+1]<<16)|(ram[v*4+2]<<8)|ram[v*4+3];
printf(" Vec %u ($%03X) = $%08X\n", v, v*4, val);
}
for (v = 64; v <= 71; v++) {
uint32_t val = (ram[v*4]<<24)|(ram[v*4+1]<<16)|(ram[v*4+2]<<8)|ram[v*4+3];
printf(" Vec %u ($%03X) = $%08X\n", v, v*4, val);
}
done:
p_retro_unload_game();
p_retro_deinit();
dlclose(handle);
return 0;
}