diff --git a/Core/debugger.c b/Core/debugger.c index ecf2f1a..fe49058 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -396,6 +396,27 @@ static bool registers(GB_gameboy_t *gb, char *arguments) return true; } +/* Find the index of the closest breakpoint equal or greater to addr */ +static unsigned short find_breakpoint(GB_gameboy_t *gb, unsigned short addr) +{ + if (!gb->breakpoints) { + return 0; + } + int min = 0; + int max = gb->n_breakpoints; + while (min < max) { + unsigned short pivot = (min + max) / 2; + if (gb->breakpoints[pivot] == addr) return pivot; + if (gb->breakpoints[pivot] > addr) { + max = pivot - 1; + } + else { + min = pivot + 1; + } + } + return (unsigned short) min; +} + static bool breakpoint(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { @@ -405,12 +426,85 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments) bool error; unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); - if (!error) { - gb_log(gb, "Breakpoint moved to %04x\n", gb->breakpoint = result); + + if (error) return true; + + unsigned short index = find_breakpoint(gb, result); + if (index < gb->n_breakpoints && gb->breakpoints[index] == result) { + gb_log(gb, "Breakpoint already set at %04x\n", result); + return true; } + + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->breakpoints[index] = result; + gb->n_breakpoints++; + + gb_log(gb, "Breakpoint set at %04x\n", result); return true; } +static bool delete(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments)) == 0) { + gb_log(gb, "Delete all breakpoints? "); + char *answer = gb->input_callback(gb); + if (answer[0] == 'Y' || answer[0] == 'y') { + free(gb->breakpoints); + gb->breakpoints = NULL; + gb->n_breakpoints = 0; + } + return true; + } + + bool error; + unsigned short result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error); + + if (error) return true; + + unsigned short index = find_breakpoint(gb, result); + if (index >= gb->n_breakpoints || gb->breakpoints[index] != result) { + gb_log(gb, "No breakpoint set at %04x\n", result); + return true; + } + + gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0])); + memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0])); + gb->n_breakpoints--; + + gb_log(gb, "Breakpoint removed from %04x\n", result); + return true; +} + +static bool list(GB_gameboy_t *gb, char *arguments) +{ + if (strlen(lstrip(arguments))) { + gb_log(gb, "Usage: list\n"); + return true; + } + + if (gb->n_breakpoints == 0) { + gb_log(gb, "No breakpoints set.\n"); + return true; + } + + gb_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints); + for (unsigned short i = 0; i < gb->n_breakpoints; i++) { + gb_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]); + } + + return true; +} + +static bool should_break(GB_gameboy_t *gb, unsigned short addr) +{ + unsigned short index = find_breakpoint(gb, addr); + if (index < gb->n_breakpoints && gb->breakpoints[index] == addr) { + return true; + } + return false; +} + static bool print(GB_gameboy_t *gb, char *arguments) { if (strlen(lstrip(arguments)) == 0) { @@ -453,6 +547,8 @@ static const debugger_command_t commands[] = { {"finish", 1, finish, "Run until the current function returns"}, {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"breakpoint", 1, breakpoint, "Move the breakpoint to a new position"}, + {"list", 1, list, "List all set breakpoints"}, + {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"}, {"print", 1, print, "Evaluate and print an expression"}, {"eval", 2, print, NULL}, {"examine", 2, examine, "Examine values at address"}, @@ -505,7 +601,7 @@ next_command: if (input) { free(input); } - if (gb->pc == gb->breakpoint && !gb->debug_stopped) { + if (!gb->debug_stopped && should_break(gb, gb->pc)) { gb->debug_stopped = true; gb_log(gb, "Breakpoint: PC = %04x\n", gb->pc); cpu_disassemble(gb, gb->pc, 5); diff --git a/Core/gb.c b/Core/gb.c index 7102f8f..62b7950 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -124,7 +124,6 @@ void gb_init(GB_gameboy_t *gb) gb->mbc_rom_bank = 1; gb->last_rtc_second = time(NULL); gb->last_vblank = clock(); - gb->breakpoint = 0xFFFF; gb->cgb_ram_bank = 1; /* Todo: this bypasses the rgb encoder because it is not set yet. */ @@ -152,7 +151,6 @@ void gb_init_cgb(GB_gameboy_t *gb) gb->mbc_rom_bank = 1; gb->last_rtc_second = time(NULL); gb->last_vblank = clock(); - gb->breakpoint = 0xFFFF; gb->cgb_ram_bank = 1; gb->input_callback = default_input_callback; gb->cartridge_type = &cart_defs[0]; // Default cartridge type @@ -175,6 +173,9 @@ void gb_free(GB_gameboy_t *gb) if (gb->audio_buffer) { free(gb->audio_buffer); } + if (gb->breakpoints) { + free(gb->breakpoints); + } } int gb_load_bios(GB_gameboy_t *gb, const char *path) diff --git a/Core/gb.h b/Core/gb.h index f5061bb..cf14078 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -270,13 +270,14 @@ typedef struct GB_gameboy_s{ struct {} first_unsaved_data; bool turbo; bool debug_stopped; - unsigned short breakpoint; GB_log_callback_t log_callback; GB_input_callback_t input_callback; GB_rgb_encode_callback_t rgb_encode_callback; void *user_data; int debug_call_depth; bool debug_fin_command, debug_next_command; + unsigned short n_breakpoints; + unsigned short *breakpoints; } GB_gameboy_t;