changeset 2396:bf4f1a8d1d48

Implement 68K watchpoints in internal debugger
author Michael Pavone <pavone@retrodev.com>
date Sat, 23 Dec 2023 17:37:57 -0800
parents ebca8ab02701
children 39a009aea113
files backend.h backend_x86.c debug.c debug.h m68k_core.c m68k_core.h m68k_core_x86.c
diffstat 7 files changed, 269 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/backend.h	Wed Dec 13 20:09:18 2023 -0800
+++ b/backend.h	Sat Dec 23 17:37:57 2023 -0800
@@ -43,6 +43,9 @@
 #include "memmap.h"
 #include "system.h"
 
+typedef void * (*watchpoint16_fun)(uint32_t address, void * context, uint16_t);
+typedef void * (*watchpoint8_fun)(uint32_t address, void * context, uint8_t);
+
 typedef struct {
 	uint32_t flags;
 	native_map_slot    *native_code_map;
@@ -57,6 +60,8 @@
 	code_ptr           handle_code_write;
 	code_ptr           handle_align_error_write;
 	code_ptr           handle_align_error_read;
+	watchpoint16_fun   check_watchpoints_16;
+	watchpoint8_fun    check_watchpoints_8;
 	system_str_fun_r8  debug_cmd_handler;
 	uint32_t           memmap_chunks;
 	uint32_t           address_mask;
@@ -65,6 +70,7 @@
 	uint32_t           clock_divider;
 	uint32_t           move_pc_off;
 	uint32_t           move_pc_size;
+	int32_t            watchpoint_range_off;
 	int32_t            mem_ptr_off;
 	int32_t            ram_flags_off;
 	uint8_t            ram_flags_shift;
--- a/backend_x86.c	Wed Dec 13 20:09:18 2023 -0800
+++ b/backend_x86.c	Sat Dec 23 17:37:57 2023 -0800
@@ -129,6 +129,30 @@
 	} else if (opts->address_size == SZ_W && opts->address_mask != 0xFFFF) {
 		and_ir(code, opts->address_mask, adr_reg, SZ_W);
 	}
+
+	code_ptr check_watchpoints = size == SZ_W ? (code_ptr)opts->check_watchpoints_16 : (code_ptr)opts->check_watchpoints_8;
+	if (is_write && check_watchpoints) {
+		//watchpoints are enabled, check if the address is within the watchpoint range
+		cmp_rdispr(code, opts->context_reg, opts->watchpoint_range_off, adr_reg, opts->address_size);
+		code_ptr watch_lb = code->cur + 1;
+		jcc(code, CC_C, code->cur + 2);
+		cmp_rdispr(code, opts->context_reg, opts->watchpoint_range_off + (opts->address_size == SZ_D ? 4 : 2), adr_reg, opts->address_size);
+		code_ptr watch_ub = code->cur + 1;
+		jcc(code, CC_NC, code->cur + 2);
+
+		push_r(code, opts->scratch1);
+		push_r(code, opts->scratch2);
+		call(code, opts->save_context);
+		call_args_abi(code, check_watchpoints, 3, opts->scratch2, opts->context_reg, opts->scratch1);
+		mov_rr(code, RAX, opts->context_reg, SZ_PTR);
+		call(code, opts->load_context);
+		pop_r(code, opts->scratch2);
+		pop_r(code, opts->scratch1);
+
+		*watch_lb = code->cur - (watch_lb + 1);
+		*watch_ub = code->cur - (watch_ub + 1);
+	}
+
 	code_ptr lb_jcc = NULL, ub_jcc = NULL;
 	uint16_t access_flag = is_write ? MMAP_WRITE : MMAP_READ;
 	uint32_t ram_flags_off = opts->ram_flags_off;
--- a/debug.c	Wed Dec 13 20:09:18 2023 -0800
+++ b/debug.c	Sat Dec 23 17:37:57 2023 -0800
@@ -380,11 +380,20 @@
 
 bp_def ** find_breakpoint(bp_def ** cur, uint32_t address, uint8_t type)
 {
-	while (*cur) {
-		if ((*cur)->type == type && (*cur)->address == (((*cur)->mask) & address)) {
-			break;
+	if (type == BP_TYPE_CPU_WATCH) {
+		while (*cur) {
+			if ((*cur)->type == type && address >= (*cur)->address && address < ((*cur)->address + (*cur)->mask)) {
+				break;
+			}
+			cur = &((*cur)->next);
 		}
-		cur = &((*cur)->next);
+	} else {
+		while (*cur) {
+			if ((*cur)->type == type && (*cur)->address == (((*cur)->mask) & address)) {
+				break;
+			}
+			cur = &((*cur)->next);
+		}
 	}
 	return cur;
 }
@@ -3114,7 +3123,7 @@
 	debug_val val;
 	debug_array * arr = NULL;
 	if (cmd->args[1].parsed->type == EXPR_MEM) {
-		
+
 		if (!eval_expr(root, cmd->args[1].parsed->left, &val)) {
 			fprintf(stderr, "Failed to eval start index\n");
 			goto cleanup;
@@ -3277,7 +3286,7 @@
 	debug_val val;
 	debug_array * arr = NULL;
 	if (cmd->args[1].parsed->type == EXPR_MEM) {
-		
+
 		if (!eval_expr(root, cmd->args[1].parsed->left, &val)) {
 			fprintf(stderr, "Failed to eval start index\n");
 			goto cleanup;
@@ -3440,6 +3449,8 @@
 	bp_def *tmp = *this_bp;
 	if (tmp->type == BP_TYPE_CPU) {
 		remove_breakpoint(root->cpu_context, tmp->address);
+	} else if (tmp->type == BP_TYPE_CPU_WATCH) {
+		m68k_remove_watchpoint(root->cpu_context, tmp->address, tmp->mask);
 	}
 	*this_bp = (*this_bp)->next;
 	if (tmp->commands) {
@@ -3472,6 +3483,35 @@
 	return 1;
 }
 
+static uint8_t cmd_watchpoint_m68k(debug_root *root, parsed_command *cmd)
+{
+	uint32_t address;
+	if (!debug_cast_int(cmd->args[0].value, &address)) {
+		fprintf(stderr, "First argument to watchpoint must be an integer\n");
+		return 1;
+	}
+	uint32_t size;
+	if (cmd->num_args > 1) {
+		if (!debug_cast_int(cmd->args[1].value, &size)) {
+			fprintf(stderr, "Second argument to watchpoint must be an integer if provided\n");
+			return 1;
+		}
+	} else {
+		//default to byte for odd addresses, word for even
+		size = (address & 1) ? 1 : 2;
+	}
+	m68k_add_watchpoint(root->cpu_context, address, size);
+	bp_def *new_bp = calloc(1, sizeof(bp_def));
+	new_bp->next = root->breakpoints;
+	new_bp->address = address;
+	new_bp->mask = size;
+	new_bp->index = root->bp_index++;
+	new_bp->type = BP_TYPE_CPU_WATCH;
+	root->breakpoints = new_bp;
+	printf("68K Watchpoint %d set for $%X\n", new_bp->index, address);
+	return 1;
+}
+
 static void on_vdp_reg_write(vdp_context *context, uint16_t reg, uint16_t value)
 {
 	value &= 0xFF;
@@ -4102,6 +4142,16 @@
 	},
 	{
 		.names = (const char *[]){
+			"watchpoint", NULL
+		},
+		.usage = "watchpoint ADDRESS [SIZE]",
+		.desc = "Set a watchpoint at ADDRESS with an optional SIZE in bytes. SIZE defaults to 2 for even address and 1 for odd",
+		.impl = cmd_watchpoint_m68k,
+		.min_args = 1,
+		.max_args = 2
+	},
+	{
+		.names = (const char *[]){
 			"advance", NULL
 		},
 		.usage = "advance ADDRESS",
@@ -5195,10 +5245,6 @@
 	init_terminal();
 
 	context->options->sync_components(context, 0);
-	if (context->system == current_system) {
-		genesis_context *gen = context->system;
-		vdp_force_update_framebuffer(gen->vdp);
-	}
 	debug_root *root = find_m68k_root(context);
 	if (!root) {
 		return;
@@ -5243,11 +5289,48 @@
 		if (debugging) {
 			printf("68K Breakpoint %d hit\n", (*this_bp)->index);
 		} else {
+			fflush(stdout);
 			return;
 		}
 	} else {
 		remove_breakpoint(context, address);
 	}
+	if (context->wp_hit) {
+		context->wp_hit = 0;
+		this_bp = find_breakpoint(&root->breakpoints, context->wp_hit_address, BP_TYPE_CPU_WATCH);
+		if (*this_bp) {
+			if ((*this_bp)->condition) {
+				debug_val condres;
+				if (eval_expr(root, (*this_bp)->condition, &condres)) {
+					if (!condres.v.u32) {
+						return;
+					}
+				} else {
+					fprintf(stderr, "Failed to eval condition for M68K breakpoint %u\n", (*this_bp)->index);
+					free_expr((*this_bp)->condition);
+					(*this_bp)->condition = NULL;
+				}
+			}
+			for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++)
+			{
+				debugging = run_command(root, (*this_bp)->commands + i);
+			}
+			if (debugging) {
+				if (context->wp_old_value != context->wp_hit_value) {
+					printf("68K Watchpoint %d hit, old value: %X, new value %X\n", (*this_bp)->index, context->wp_old_value, context->wp_hit_value);
+				} else {
+					printf("68K Watchpoint %d hit\n", (*this_bp)->index);
+				}
+			} else {
+				fflush(stdout);
+				return;
+			}
+		}
+	}
+	if (context->system == current_system) {
+		genesis_context *gen = context->system;
+		vdp_force_update_framebuffer(gen->vdp);
+	}
 	uint32_t after = m68k_decode(m68k_instruction_fetch, context, &inst, address);
 	root->after = after;
 	root->inst = &inst;
--- a/debug.h	Wed Dec 13 20:09:18 2023 -0800
+++ b/debug.h	Sat Dec 23 17:37:57 2023 -0800
@@ -122,6 +122,7 @@
 
 enum {
 	BP_TYPE_CPU,
+	BP_TYPE_CPU_WATCH,
 	BP_TYPE_VDPREG,
 	BP_TYPE_VDPDMA,
 	BP_TYPE_VDPDATA
--- a/m68k_core.c	Wed Dec 13 20:09:18 2023 -0800
+++ b/m68k_core.c	Sat Dec 23 17:37:57 2023 -0800
@@ -835,6 +835,133 @@
 	return context;
 }
 
+static m68k_watchpoint *m68k_find_watchpoint(uint32_t address, m68k_context *context)
+{
+	for (uint32_t i = 0; i < context->num_watchpoints; i++)
+	{
+		if (address >= context->watchpoints[i].start && address < context->watchpoints[i].end) {
+			return context->watchpoints + i;
+		}
+	}
+	return NULL;
+}
+
+static void *m68k_watchpoint_check16(uint32_t address, void *vcontext, uint16_t value)
+{
+	m68k_context *context = vcontext;
+	m68k_watchpoint *watch = m68k_find_watchpoint(address, context);
+	if (!watch) {
+		return vcontext;
+	}
+	if (watch->check_change) {
+		uint16_t old = read_word(address, (void **)context->mem_pointers, &context->options->gen, context);
+		if (old == value) {
+			return vcontext;
+		}
+		context->wp_old_value = old;
+	} else {
+		context->wp_old_value = value;
+	}
+	context->wp_hit_address = address;
+	context->wp_hit_value = value;
+	context->wp_hit = 1;
+	context->target_cycle = context->sync_cycle = context->current_cycle;
+	system_header *system = context->system;
+	system->enter_debugger = 1;
+	return vcontext;
+}
+
+static void *m68k_watchpoint_check8(uint32_t address, void *vcontext, uint8_t value)
+{
+	m68k_context *context = vcontext;
+	m68k_watchpoint *watch = m68k_find_watchpoint(address, context);
+	if (!watch) {
+		return vcontext;
+	}
+	if (watch->check_change) {
+		uint8_t old = read_byte(address, (void **)context->mem_pointers, &context->options->gen, context);
+		if (old == value) {
+			return vcontext;
+		}
+		context->wp_old_value = old;
+	} else {
+		context->wp_old_value = value;
+	}
+	context->wp_hit_address = address;
+	context->wp_hit_value = value;
+	context->wp_hit = 1;
+	context->target_cycle = context->sync_cycle = context->current_cycle;
+	system_header *system = context->system;
+	system->enter_debugger = 1;
+	return vcontext;
+}
+
+static void m68k_enable_watchpoints(m68k_context *context)
+{
+	if (context->options->gen.check_watchpoints_16) {
+		//already enabled
+		return;
+	}
+	context->options->gen.check_watchpoints_16 = m68k_watchpoint_check16;
+	context->options->gen.check_watchpoints_8 = m68k_watchpoint_check8;
+	//re-generate write handlers with watchpoints enabled
+	code_ptr new_write16 = gen_mem_fun(&context->options->gen, context->options->gen.memmap, context->options->gen.memmap_chunks, WRITE_16, NULL);
+	code_ptr new_write8 = gen_mem_fun(&context->options->gen, context->options->gen.memmap, context->options->gen.memmap_chunks, WRITE_8, NULL);
+
+	//patch old write handlers to point to the new ones
+	code_info code = {
+		.cur = context->options->write_16,
+		.last = context->options->write_16 + 256
+	};
+	jmp(&code, new_write16);
+	code.cur = context->options->write_8;
+	code.last = code.cur + 256;
+	jmp(&code, new_write8);
+	context->options->write_16 = new_write16;
+	context->options->write_8 = new_write8;
+}
+
+void m68k_add_watchpoint(m68k_context *context, uint32_t address, uint32_t size)
+{
+	uint32_t end = address + size;
+	for (uint32_t i = 0; i < context->num_watchpoints; i++)
+	{
+		if (context->watchpoints[i].start == address && context->watchpoints[i].end == end) {
+			return;
+		}
+	}
+	m68k_enable_watchpoints(context);
+	if (context->wp_storage == context->num_watchpoints) {
+		context->wp_storage = context->wp_storage ? context->wp_storage * 2 : 4;
+		context->watchpoints = realloc(context->watchpoints, context->wp_storage * sizeof(m68k_breakpoint));
+	}
+	const memmap_chunk *chunk = find_map_chunk(address, &context->options->gen, 0, NULL);
+	context->watchpoints[context->num_watchpoints++] = (m68k_watchpoint){
+		.start = address,
+		.end = end,
+		.check_change = chunk && (chunk->flags & MMAP_READ)
+	};
+	if (context->watchpoint_min > address) {
+		context->watchpoint_min = address;
+	}
+	if (context->watchpoint_max < address + size) {
+		context->watchpoint_max = address + size;
+	}
+}
+
+void m68k_remove_watchpoint(m68k_context *context, uint32_t address, uint32_t size)
+{
+	uint32_t end = address + size;
+	for (uint32_t i = 0; i < context->num_watchpoints; i++)
+	{
+		if (context->watchpoints[i].start == address && context->watchpoints[i].end == end) {
+			context->watchpoints[i] = context->watchpoints[context->num_watchpoints-1];
+			context->num_watchpoints--;
+			return;
+		}
+	}
+}
+
 typedef enum {
 	RAW_FUNC = 1,
 	BINARY_ARITH,
--- a/m68k_core.h	Wed Dec 13 20:09:18 2023 -0800
+++ b/m68k_core.h	Sat Dec 23 17:37:57 2023 -0800
@@ -80,6 +80,12 @@
 	uint32_t           address;
 } m68k_breakpoint;
 
+typedef struct {
+	uint32_t start;
+	uint32_t end;
+	uint8_t  check_change;
+} m68k_watchpoint;
+
 #ifdef X86_64
 #define M68K_STACK_STORAGE 12
 #else
@@ -109,6 +115,15 @@
 	m68k_breakpoint *breakpoints;
 	uint32_t        num_breakpoints;
 	uint32_t        bp_storage;
+	uint32_t        watchpoint_min;
+	uint32_t        watchpoint_max;
+	m68k_watchpoint *watchpoints;
+	uint32_t        num_watchpoints;
+	uint32_t        wp_storage;
+	uint32_t        wp_hit_address;
+	uint16_t        wp_hit_value;
+	uint16_t        wp_old_value;
+	uint8_t         wp_hit;
 	uint8_t         int_pending;
 	uint8_t         trace_pending;
 	uint8_t         should_return;
@@ -128,6 +143,8 @@
 void m68k_options_free(m68k_options *opts);
 void insert_breakpoint(m68k_context * context, uint32_t address, m68k_debug_handler bp_handler);
 void remove_breakpoint(m68k_context * context, uint32_t address);
+void m68k_add_watchpoint(m68k_context *context, uint32_t address, uint32_t size);
+void m68k_remove_watchpoint(m68k_context *context, uint32_t address, uint32_t size);
 m68k_context * m68k_handle_code_write(uint32_t address, m68k_context * context);
 uint32_t get_instruction_start(m68k_options *opts, uint32_t address);
 uint16_t m68k_get_ir(m68k_context *context);
--- a/m68k_core_x86.c	Wed Dec 13 20:09:18 2023 -0800
+++ b/m68k_core_x86.c	Sat Dec 23 17:37:57 2023 -0800
@@ -2595,6 +2595,7 @@
 	opts->gen.max_address = 0x1000000;
 	opts->gen.bus_cycles = BUS;
 	opts->gen.clock_divider = clock_divider;
+	opts->gen.watchpoint_range_off = offsetof(m68k_context, watchpoint_min);
 	opts->gen.mem_ptr_off = offsetof(m68k_context, mem_pointers);
 	opts->gen.ram_flags_off = offsetof(m68k_context, ram_code_flags);
 	opts->gen.ram_flags_shift = 11;