# HG changeset patch # User Michael Pavone # Date 1703401411 28800 # Node ID c97609fe8315b1bff744aa8968c2e7f03117e57a # Parent 68eba54b60f7d071b40e3413aa9dc5e6e8b610ed Implement watchpoints in Z80 debugger diff -r 68eba54b60f7 -r c97609fe8315 backend_x86.c --- 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); diff -r 68eba54b60f7 -r c97609fe8315 debug.c --- 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; } diff -r 68eba54b60f7 -r c97609fe8315 m68k_core.c --- 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) { diff -r 68eba54b60f7 -r c97609fe8315 sms.c --- 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); diff -r 68eba54b60f7 -r c97609fe8315 z80_to_x86.c --- 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++) diff -r 68eba54b60f7 -r c97609fe8315 z80_to_x86.h --- 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);