# HG changeset patch # User Michael Pavone # Date 1703381877 28800 # Node ID bf4f1a8d1d48c85bb502a9f3a898f28381585af6 # Parent ebca8ab027019d3a3c5917d83bbdb96586f1a416 Implement 68K watchpoints in internal debugger diff -r ebca8ab02701 -r bf4f1a8d1d48 backend.h --- 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; diff -r ebca8ab02701 -r bf4f1a8d1d48 backend_x86.c --- 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; diff -r ebca8ab02701 -r bf4f1a8d1d48 debug.c --- 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; diff -r ebca8ab02701 -r bf4f1a8d1d48 debug.h --- 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 diff -r ebca8ab02701 -r bf4f1a8d1d48 m68k_core.c --- 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, diff -r ebca8ab02701 -r bf4f1a8d1d48 m68k_core.h --- 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); diff -r ebca8ab02701 -r bf4f1a8d1d48 m68k_core_x86.c --- 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;