diff z80_to_x86.c @ 2400:c97609fe8315

Implement watchpoints in Z80 debugger
author Michael Pavone <pavone@retrodev.com>
date Sat, 23 Dec 2023 23:03:31 -0800
parents 3e591869d135
children ed7b4d869989
line wrap: on
line diff
--- 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++)