changeset 2400:c97609fe8315

Implement watchpoints in Z80 debugger
author Michael Pavone <pavone@retrodev.com>
date Sat, 23 Dec 2023 23:03:31 -0800
parents 68eba54b60f7
children 34b4ff091891
files backend_x86.c debug.c m68k_core.c sms.c z80_to_x86.c z80_to_x86.h
diffstat 6 files changed, 200 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/backend_x86.c	Sat Dec 23 22:11:43 2023 -0800
+++ b/backend_x86.c	Sat Dec 23 23:03:31 2023 -0800
@@ -138,7 +138,7 @@
 		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);
+		jcc(code, CC_A, code->cur + 2);
 
 		push_r(code, opts->scratch1);
 		push_r(code, opts->scratch2);
--- a/debug.c	Sat Dec 23 22:11:43 2023 -0800
+++ b/debug.c	Sat Dec 23 23:03:31 2023 -0800
@@ -4339,7 +4339,11 @@
 		return 1;
 	}
 	bp_def *tmp = *this_bp;
-	zremove_breakpoint(root->cpu_context, tmp->address);
+	if (tmp->type == BP_TYPE_CPU) {
+		zremove_breakpoint(root->cpu_context, tmp->address);
+	} else if (tmp->type == BP_TYPE_CPU_WATCH) {
+		z80_remove_watchpoint(root->cpu_context, tmp->address, tmp->mask);
+	}
 	*this_bp = (*this_bp)->next;
 	if (tmp->commands) {
 		for (uint32_t i = 0; i < tmp->num_commands; i++)
@@ -4371,6 +4375,32 @@
 	return 1;
 }
 
+static uint8_t cmd_watchpoint_z80(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 = 1;
+	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;
+		}
+	}
+	z80_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("Z80 Watchpoint %d set for $%X\n", new_bp->index, address);
+	return 1;
+}
+
 static uint8_t cmd_advance_z80(debug_root *root, parsed_command *cmd)
 {
 	uint32_t address;
@@ -4572,6 +4602,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 1",
+		.impl = cmd_watchpoint_z80,
+		.min_args = 1,
+		.max_args = 2
+	},
+	{
+		.names = (const char *[]){
 			"advance", NULL
 		},
 		.usage = "advance ADDRESS",
@@ -5183,6 +5223,7 @@
 	}
 	root->address = address;
 	//Check if this is a user set breakpoint, or just a temporary one
+	int debugging;
 	bp_def ** this_bp = find_breakpoint(&root->breakpoints, address, BP_TYPE_CPU);
 	if (*this_bp) {
 		if ((*this_bp)->condition) {
@@ -5197,7 +5238,7 @@
 				(*this_bp)->condition = NULL;
 			}
 		}
-		int debugging = 1;
+		debugging = 1;
 		for (uint32_t i = 0; debugging && i < (*this_bp)->num_commands; i++)
 		{
 			debugging = run_command(root, (*this_bp)->commands + i);
@@ -5205,11 +5246,45 @@
 		if (debugging) {
 			printf("Z80 Breakpoint %d hit\n", (*this_bp)->index);
 		} else {
+			fflush(stdout);
 			return context;
 		}
 	} else {
 		zremove_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 context;
+					}
+				} else {
+					fprintf(stderr, "Failed to eval condition for Z80 watchpoint %u\n", (*this_bp)->index);
+					free_expr((*this_bp)->condition);
+					(*this_bp)->condition = NULL;
+				}
+			}
+			debugging = 1;
+			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("Z80 Watchpoint %d hit, old value: %X, new value %X\n", (*this_bp)->index, context->wp_old_value, context->wp_hit_value);
+				} else {
+					printf("Z80 Watchpoint %d hit\n", (*this_bp)->index);
+				}
+			} else {
+				fflush(stdout);
+				return context;
+			}
+		}
+	}
 	uint8_t * pc = get_native_pointer(address, (void **)context->mem_pointers, &context->Z80_OPTS->gen);
 	if (!pc) {
 		fatal_error("Failed to get native pointer on entering Z80 debugger at address %X\n", address);
@@ -5306,7 +5381,7 @@
 						return;
 					}
 				} else {
-					fprintf(stderr, "Failed to eval condition for M68K breakpoint %u\n", (*this_bp)->index);
+					fprintf(stderr, "Failed to eval condition for M68K watchpoint %u\n", (*this_bp)->index);
 					free_expr((*this_bp)->condition);
 					(*this_bp)->condition = NULL;
 				}
--- a/m68k_core.c	Sat Dec 23 22:11:43 2023 -0800
+++ b/m68k_core.c	Sat Dec 23 23:03:31 2023 -0800
@@ -923,7 +923,7 @@
 
 void m68k_add_watchpoint(m68k_context *context, uint32_t address, uint32_t size)
 {
-	uint32_t end = address + size;
+	uint32_t end = address + size - 1;
 	for (uint32_t i = 0; i < context->num_watchpoints; i++)
 	{
 		if (context->watchpoints[i].start == address && context->watchpoints[i].end == end) {
@@ -944,14 +944,14 @@
 	if (context->watchpoint_min > address) {
 		context->watchpoint_min = address;
 	}
-	if (context->watchpoint_max < address + size) {
-		context->watchpoint_max = address + size;
+	if (context->watchpoint_max < end) {
+		context->watchpoint_max = end;
 	}
 }
 
 void m68k_remove_watchpoint(m68k_context *context, uint32_t address, uint32_t size)
 {
-	uint32_t end = address + size;
+	uint32_t end = address + size - 1;
 	for (uint32_t i = 0; i < context->num_watchpoints; i++)
 	{
 		if (context->watchpoints[i].start == address && context->watchpoints[i].end == end) {
--- a/sms.c	Sat Dec 23 22:11:43 2023 -0800
+++ b/sms.c	Sat Dec 23 23:03:31 2023 -0800
@@ -494,6 +494,10 @@
 				z80_assert_nmi(sms->z80, nmi);
 			}
 		}
+
+		if (system->enter_debugger) {
+			target_cycle = sms->z80->Z80_CYCLE + 1;
+		}
 		z80_run(sms->z80, target_cycle);
 		if (sms->z80->reset) {
 			z80_clear_reset(sms->z80, sms->z80->Z80_CYCLE + 128*15);
--- a/z80_to_x86.c	Sat Dec 23 22:11:43 2023 -0800
+++ b/z80_to_x86.c	Sat Dec 23 23:03:31 2023 -0800
@@ -3201,6 +3201,7 @@
 	options->gen.max_address = 0x10000;
 	options->gen.bus_cycles = 3;
 	options->gen.clock_divider = clock_divider;
+	options->gen.watchpoint_range_off = offsetof(z80_context, watchpoint_min);
 	options->gen.mem_ptr_off = offsetof(z80_context, mem_pointers);
 	options->gen.ram_flags_off = offsetof(z80_context, ram_code_flags);
 	options->gen.ram_flags_shift = 7;
@@ -3866,7 +3867,6 @@
 		}
 	}
 }
-
 void zremove_breakpoint(z80_context * context, uint16_t address)
 {
 	context->breakpoint_flags[address / 8] &= ~(1 << (address % 8));
@@ -3881,6 +3881,101 @@
 	}
 }
 
+static void *z80_watchpoint_check(uint32_t address, void *vcontext, uint8_t value)
+{
+	z80_context *context = vcontext;
+	z80_watchpoint *watch = NULL;
+	address &= 0xFFFF;
+	for (uint32_t i = 0; i < context->num_watchpoints; i++)
+	{
+		if (address >= context->watchpoints[i].start && address <= context->watchpoints[i].end) {
+			watch = context->watchpoints + i;
+			break;
+		}
+	}
+	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 z80_enable_watchpoints(z80_context *context)
+{
+	if (context->options->gen.check_watchpoints_8) {
+		//already enabled
+		return;
+	}
+	context->watchpoint_min = 0xFFFF;
+	context->watchpoint_max = 0;
+	context->options->gen.check_watchpoints_8 = z80_watchpoint_check;
+	//re-generate write handlers with watchpoints enabled
+	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_8,
+		.last = context->options->write_8 + 256
+	};
+	jmp(&code, new_write8);
+	context->options->write_8 = new_write8;
+}
+
+void z80_add_watchpoint(z80_context *context, uint16_t address, uint16_t size)
+{
+	uint32_t end = address + size - 1;
+	for (uint32_t i = 0; i < context->num_watchpoints; i++)
+	{
+		if (context->watchpoints[i].start == address && context->watchpoints[i].end == end) {
+			return;
+		}
+	}
+	z80_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(z80_watchpoint));
+	}
+	const memmap_chunk *chunk = find_map_chunk(address, &context->options->gen, 0, NULL);
+	context->watchpoints[context->num_watchpoints++] = (z80_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 < end) {
+		context->watchpoint_max = end;
+	}
+}
+
+void z80_remove_watchpoint(z80_context *context, uint32_t address, uint32_t size)
+{
+	uint32_t end = address + size - 1;
+	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;
+		}
+	}
+}
+
 void z80_serialize(z80_context *context, serialize_buffer *buf)
 {
 	for (int i = 0; i <= Z80_A; i++)
--- a/z80_to_x86.h	Sat Dec 23 22:11:43 2023 -0800
+++ b/z80_to_x86.h	Sat Dec 23 23:03:31 2023 -0800
@@ -27,6 +27,12 @@
 	ZF_NUM
 };
 
+typedef struct {
+	uint16_t start;
+	uint16_t end;
+	uint8_t  check_change;
+} z80_watchpoint;
+
 typedef struct z80_context z80_context;
 typedef void (*z80_ctx_fun)(z80_context * context);
 
@@ -78,6 +84,15 @@
 	uint32_t          int_pulse_start;
 	uint32_t          int_pulse_end;
 	uint32_t          nmi_start;
+	z80_watchpoint    *watchpoints;
+	uint32_t          num_watchpoints;
+	uint32_t          wp_storage;
+	uint16_t          watchpoint_min;
+	uint16_t          watchpoint_max;
+	uint16_t          wp_hit_address;
+	uint8_t           wp_hit_value;
+	uint8_t           wp_old_value;
+	uint8_t           wp_hit;
 	uint8_t           breakpoint_flags[(16 * 1024)/sizeof(uint8_t)];
 	uint8_t *         bp_handler;
 	uint8_t *         bp_stub;
@@ -102,6 +117,8 @@
 void z80_reset(z80_context * context);
 void zinsert_breakpoint(z80_context * context, uint16_t address, uint8_t * bp_handler);
 void zremove_breakpoint(z80_context * context, uint16_t address);
+void z80_add_watchpoint(z80_context *context, uint16_t address, uint16_t size);
+void z80_remove_watchpoint(z80_context *context, uint32_t address, uint32_t size);
 void z80_run(z80_context * context, uint32_t target_cycle);
 void z80_assert_reset(z80_context * context, uint32_t cycle);
 void z80_clear_reset(z80_context * context, uint32_t cycle);