From bd47ac995d3c9c80e6d20412eb6d9c90180e2237 Mon Sep 17 00:00:00 2001 From: "LGB (Gabor Lenart)" Date: Mon, 5 Aug 2024 20:45:57 +0200 Subject: [PATCH] MEGA65: "read of unwritten memory" debug mode #405 Rhialto had the feature request to have a mode, where Xemu/MEGA65 can warn you on reading memory which was not written before, can be useful to find ROM bugs. In this current form, it's quite minimal and simple: 1. Only works on the first 126K of physical RAM 2. Only works from command line, start emulation with paramter: -ramcheckread 3. You need to watch the output of the emulator, so on UNIX-like/Linux/Mac systems, emulator should be started from terminal window, on Windows, Xemu console must be open (start also with parameter: -syscon) 4. Check the output of the emulator, you can find lines like: MEM: DEBUG: main RAM at linear address $101A0 has been read without prior write; PC=$8613 [$20613] The PC value can be off by some bytes, because it may have been incremented already during opcode emulation. The [...] is the linear address for the mentioned CPU PC value. Well, that's it. --- targets/mega65/configdb.c | 3 +- targets/mega65/configdb.h | 1 + targets/mega65/hypervisor.c | 2 ++ targets/mega65/memory_mapper.c | 58 ++++++++++++++++++++++++++++++++++ targets/mega65/memory_mapper.h | 1 + 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/targets/mega65/configdb.c b/targets/mega65/configdb.c index 449a9e4d..5670f975 100644 --- a/targets/mega65/configdb.c +++ b/targets/mega65/configdb.c @@ -112,6 +112,7 @@ static const struct xemutools_configdef_switch_st switch_options[] = { { "fastboot", "Try to use sleepless emulation mode during booting", &configdb.fastboot }, { "matrixstart", "Start with matrix-mode activated", &configdb.matrixstart }, { "matrixdisable", "Disable the matrix hotkey", &configdb.matrixdisable }, + { "ramcheckread", "Enabled warnings on reading unwritten memory (first 126K only)", &configdb.ramcheckread }, { NULL } }; @@ -152,7 +153,7 @@ static const void *do_not_save_opts[] = { &configdb.matrixstart, &configdb.dumpmem, &configdb.dumpscreen, &configdb.screenshot_and_exit, &configdb.testing, &configdb.hyperdebug, &configdb.hyperdebugfreezer, &configdb.usestubrom, &configdb.useinitrom, &configdb.useutilmenu, - &configdb.cartbin8000, &configdb.winpos, + &configdb.cartbin8000, &configdb.winpos, &configdb.ramcheckread, NULL }; diff --git a/targets/mega65/configdb.h b/targets/mega65/configdb.h index 89cef071..f5f69ca9 100644 --- a/targets/mega65/configdb.h +++ b/targets/mega65/configdb.h @@ -112,6 +112,7 @@ struct configdb_st { int matrixstart; int matrixdisable; char *winpos; + int ramcheckread; }; extern struct configdb_st configdb; diff --git a/targets/mega65/hypervisor.c b/targets/mega65/hypervisor.c index 6dca5ed3..c7f76e52 100644 --- a/targets/mega65/hypervisor.c +++ b/targets/mega65/hypervisor.c @@ -269,6 +269,7 @@ void hypervisor_start_machine ( void ) extract_version_string(hyppo_version_string, sizeof hyppo_version_string); DEBUGPRINT("HYPERVISOR: HYPPO version \"%s\" (%s) starting with TRAP reset (#$%02X)" NL, hyppo_version_string, hickup_is_overriden ? "OVERRIDEN" : "built-in", TRAP_RESET); hypervisor_enter(TRAP_RESET); + memory_reset_unwritten_debug_stat(); } @@ -299,6 +300,7 @@ static inline void first_leave ( void ) hdos_notify_system_start_end(); xemu_sleepless_temporary_mode(0); // turn off temporary sleepless mode which may have been enabled before vic_frame_counter_since_boot = 0; + //memory_reset_unwritten_debug_stat(); // FIXME/TODO: commented out since it generates a **tons** of warnings then with the "unwritten mem read" debug mode (-ramcheckread emu option) DEBUGPRINT("HYPERVISOR: first return after RESET, end of processing workarounds." NL); } diff --git a/targets/mega65/memory_mapper.c b/targets/mega65/memory_mapper.c index 9c677bec..b7f96f11 100644 --- a/targets/mega65/memory_mapper.c +++ b/targets/mega65/memory_mapper.c @@ -48,6 +48,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // 512K is the max "main" RAM. Currently only 384K is used by M65. We want to make sure, the _total_ size is power of 2, so we can protect accesses with simple bit masks as a last-resort-protection Uint8 main_ram[512 << 10]; +static Uint8 main_ram_written[0x1F800U]; // 32K of colour RAM. VIC-IV can see this as for colour information only. The first 2K can be seen at the last 2K of // the chip-RAM. Also, the first 1 or 2K can be seen in the C64-style I/O area too, at $D800 Uint8 colour_ram[0x8000]; @@ -162,12 +163,38 @@ static Uint32 policy4k[0x10]; // memory policy with MAP taken account (the real static Uint8 zero_page_reader ( const Uint32 addr32 ); static void zero_page_writer ( const Uint32 addr32, const Uint8 data ); + +static void checked_reader_warning ( const Uint32 addr32 ) +{ + DEBUGPRINT("MEM: DEBUG: main RAM at linear address $%X has been read without prior write; PC=$%04X [$%X]" NL, addr32, cpu65.pc, memory_cpu_addr_to_linear(cpu65.pc, NULL)); +} + + +static Uint8 zero_page_checked_reader ( const Uint32 addr32 ) { + if (!main_ram_written[addr32]) + checked_reader_warning(addr32); + return zero_page_reader(addr32); +} +static void zero_page_checked_writer ( const Uint32 addr32, const Uint8 data ) { + main_ram_written[addr32] = 1; + zero_page_writer(addr32, data); +} + static Uint8 main_ram_reader ( const Uint32 addr32 ) { return main_ram[addr32]; } static void main_ram_writer ( const Uint32 addr32, const Uint8 data ) { main_ram[addr32] = data; } +static Uint8 main_ram_checked_reader ( const Uint32 addr32 ) { + if (!main_ram_written[addr32]) + checked_reader_warning(addr32); + return main_ram[addr32]; +} +static void main_ram_checked_writer ( const Uint32 addr32, const Uint8 data ) { + main_ram_written[addr32] = 1; + main_ram[addr32] = data; +} static void shared_main_ram_writer ( const Uint32 addr32, const Uint8 data ) { main_ram[addr32] = data; colour_ram[addr32 - 0x1F800U] = data; @@ -348,6 +375,30 @@ static struct mem_map_st mem_map[] = { static XEMU_INLINE void slot_assignment_postprocessing ( const Uint32 slot ) { + if (XEMU_UNLIKELY(configdb.ramcheckread && mem_slot_rd_addr32[slot] < 0x1F800U)) { + if (mem_slot_rd_func[slot] == main_ram_reader) { +#ifdef MEM_USE_DATA_POINTERS + mem_slot_rd_data[slot] = NULL; +#endif + mem_slot_rd_func[slot] = main_ram_checked_reader; + } else if (mem_slot_rd_func[slot] == zero_page_reader) { +#ifdef MEM_USE_DATA_POINTERS + mem_slot_rd_data[slot] = NULL; +#endif + mem_slot_rd_func[slot] = zero_page_checked_reader; + } + if (mem_slot_wr_func[slot] == main_ram_writer) { +#ifdef MEM_USE_DATA_POINTERS + mem_slot_wr_data[slot] = NULL; +#endif + mem_slot_wr_func[slot] = main_ram_checked_writer; + } else if (mem_slot_wr_func[slot] == zero_page_writer) { +#ifdef MEM_USE_DATA_POINTERS + mem_slot_wr_data[slot] = NULL; +#endif + mem_slot_wr_func[slot] = zero_page_checked_writer; + } + } mem_slot_rd_func_real[slot] = mem_slot_rd_func[slot]; mem_slot_wr_func_real[slot] = mem_slot_wr_func[slot]; #ifdef MEM_WATCH_SUPPORT @@ -934,6 +985,7 @@ void memory_init (void ) // Initiailize memory content with something ... // NOTE: make sure the first 2K of colour_ram is the **SAME** as the 2K part of main_ram at offset $1F800 memset(main_ram, 0x00, sizeof main_ram); + memory_reset_unwritten_debug_stat(); memset(colour_ram, 0x00, sizeof colour_ram); memset(attic_ram, 0xFF, sizeof attic_ram); DEBUGPRINT("MEM: memory decoder initialized, %uK fast, %uK attic, %uK colour, %uK font RAM" NL, @@ -945,6 +997,12 @@ void memory_init (void ) } +void memory_reset_unwritten_debug_stat ( void ) +{ + memset(main_ram_written, 0x00, sizeof main_ram_written); +} + + // Warning: this overwrites ref_slot! static XEMU_INLINE Uint32 cpu_get_flat_addressing_mode_address ( const Uint8 index ) { diff --git a/targets/mega65/memory_mapper.h b/targets/mega65/memory_mapper.h index 5bfdd3b1..bfcaf91a 100644 --- a/targets/mega65/memory_mapper.h +++ b/targets/mega65/memory_mapper.h @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define XEMU_MEGA65_MEMORY_MAPPER_H_INCLUDED extern void memory_init ( void ); +extern void memory_reset_unwritten_debug_stat ( void ); extern void memory_set_rom_protection ( const bool protect ); extern void memory_reconfigure ( const Uint8 d030_value, const Uint8 new_io_mode, const Uint8 new_cpu_port0, const Uint8 new_cpu_port1,