From 5b37d3c402009739959d8ba7bb75da59834e1c54 Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Tue, 11 Apr 2023 12:02:32 +0300 Subject: [PATCH] Add a debugger reset command, with a frontend-handled reload option. Closes #537 --- Cocoa/Document.m | 12 +++++++++ Core/debugger.c | 67 +++++++++++++++++++++++++++++++++++++++++++++--- Core/gb.c | 5 ++++ Core/gb.h | 4 +++ SDL/main.c | 32 +++++++++++++++++++++++ 5 files changed, 116 insertions(+), 4 deletions(-) diff --git a/Cocoa/Document.m b/Cocoa/Document.m index bb5f76f..34e9192 100644 --- a/Cocoa/Document.m +++ b/Cocoa/Document.m @@ -226,6 +226,17 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) [self infraredStateChanged:on]; } +static void debuggerReloadCallback(GB_gameboy_t *gb) +{ + Document *self = (__bridge Document *)GB_get_user_data(gb); + dispatch_sync(dispatch_get_main_queue(), ^{ + bool wasRunning = self->running; + self->running = false; // Hack for output capture + [self loadROM]; + self->running = wasRunning; + GB_reset(gb); + }); +} - (instancetype)init { @@ -320,6 +331,7 @@ static void infraredStateChanged(GB_gameboy_t *gb, bool on) GB_apu_set_sample_callback(&gb, audioCallback); GB_set_rumble_callback(&gb, rumbleCallback); GB_set_infrared_callback(&gb, infraredStateChanged); + GB_set_debugger_reload_callback(&gb, debuggerReloadCallback); [self updateRumbleMode]; } diff --git a/Core/debugger.c b/Core/debugger.c index 51fda80..2db3174 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -767,6 +767,61 @@ static bool interrupt(GB_gameboy_t *gb, char *arguments, char *modifiers, const return true; } +static char *reset_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context) +{ + size_t length = strlen(string); + const char *suggestions[] = {"quick", "reload"}; + while (*context < sizeof(suggestions) / sizeof(suggestions[0])) { + if (memcmp(string, suggestions[*context], length) == 0) { + return strdup(suggestions[(*context)++] + length); + } + (*context)++; + } + return NULL; +} + +static bool reset(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) +{ + NO_MODIFIERS + + const char *stripped_argument = lstrip(arguments); + + if (stripped_argument[0] == 0) { + GB_reset(gb); + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } + return true; + } + + if (strcmp(stripped_argument, "quick") == 0) { + GB_quick_reset(gb); + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } + return true; + } + + if (strcmp(stripped_argument, "reload") == 0) { + if (gb->debugger_reload_callback) { + gb->debugger_reload_callback(gb); + if (gb->undo_state) { + free(gb->undo_state); + gb->undo_state = NULL; + } + if (gb->debug_stopped) { + GB_cpu_disassemble(gb, gb->pc, 5); + } + return true; + } + GB_log(gb, "ROM reloading via the debugger is not supported in this frontend.\n"); + return true; + } + + print_usage(gb, command); + return true; +} + static bool next(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { NO_MODIFIERS @@ -2022,6 +2077,10 @@ static bool help(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug static const debugger_command_t commands[] = { {"continue", 1, cont, "Continue running until next stop"}, {"interrupt", 1, interrupt, "Interrupt the program execution"}, + {"reset", 3, reset, "Reset the program execution. " + "Add 'quick' as an argument to perform a quick reset that does not reset RAM. " + "Add 'reload' as an argument to reload the ROM and symbols before resetting.", + "[quick|reload]", .argument_completer = reset_completer}, {"next", 1, next, "Run the next instruction, skipping over function calls"}, {"step", 1, step, "Run the next instruction, stepping into function calls"}, {"finish", 1, finish, "Run until the current function returns"}, @@ -2029,7 +2088,7 @@ static const debugger_command_t commands[] = { {"registers", 1, registers, "Print values of processor registers and other important registers"}, {"backtrace", 2, backtrace, "Display the current call stack"}, {"bt", 2, }, /* Alias */ - {"print", 1, print, "Evaluate and print an expression " + {"print", 1, print, "Evaluate and print an expression. " "Use modifier to format as an address (a, default) or as a number in " "decimal (d), hexadecimal (x), octal (o) or binary (b).", "", "format", .argument_completer = symbol_completer, .modifiers_completer = format_completer}, @@ -2037,7 +2096,7 @@ static const debugger_command_t commands[] = { {"examine", 2, examine, "Examine values at address", "", "count", .argument_completer = symbol_completer}, {"x", 1, }, /* Alias */ {"disassemble", 1, disassemble, "Disassemble instructions at address", "", "count", .argument_completer = symbol_completer}, - {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression " + {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression. " "Can also modify the condition of existing breakpoints. " "If the j modifier is used, the breakpoint will occur just before " "jumping to the target.", @@ -2058,8 +2117,8 @@ static const debugger_command_t commands[] = { "the count.", "(keep)", .argument_completer = keep_completer}, {"cartridge", 2, mbc, "Display information about the MBC and cartridge"}, {"mbc", 3, }, /* Alias */ - {"apu", 3, apu, "Display information about the current state of the audio processing " - "unit", "[channel (1-4, 5 for NR5x)]"}, + {"apu", 3, apu, "Display information about the current state of the audio processing unit", + "[channel (1-4, 5 for NR5x)]"}, {"wave", 3, wave, "Print a visual representation of the wave RAM. " "Modifiers can be used for a (f)ull print (the default), " "a more (c)ompact one, or a one-(l)iner", "", "(f|c|l)", .modifiers_completer = wave_completer}, diff --git a/Core/gb.c b/Core/gb.c index abf42c5..d916502 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -1253,6 +1253,11 @@ void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback) #endif } +void GB_set_debugger_reload_callback(GB_gameboy_t *gb, GB_debugger_reload_callback_t callback) +{ + gb->debugger_reload_callback = callback; +} + void GB_set_execution_callback(GB_gameboy_t *gb, GB_execution_callback_t callback) { gb->execution_callback = callback; diff --git a/Core/gb.h b/Core/gb.h index e072bbf..433a591 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -264,6 +264,7 @@ typedef enum { typedef void (*GB_vblank_callback_t)(GB_gameboy_t *gb, GB_vblank_type_t type); typedef void (*GB_log_callback_t)(GB_gameboy_t *gb, const char *string, GB_log_attributes attributes); typedef char *(*GB_input_callback_t)(GB_gameboy_t *gb); +typedef void (*GB_debugger_reload_callback_t)(GB_gameboy_t *gb); typedef uint32_t (*GB_rgb_encode_callback_t)(GB_gameboy_t *gb, uint8_t r, uint8_t g, uint8_t b); typedef void (*GB_infrared_callback_t)(GB_gameboy_t *gb, bool on); typedef void (*GB_rumble_callback_t)(GB_gameboy_t *gb, double rumble_amplitude); @@ -730,6 +731,8 @@ struct GB_gameboy_internal_s { GB_execution_callback_t execution_callback; GB_lcd_line_callback_t lcd_line_callback; GB_lcd_status_callback_t lcd_status_callback; + GB_debugger_reload_callback_t debugger_reload_callback; + /*** Debugger ***/ volatile bool debug_stopped, debug_disable; bool debug_fin_command, debug_next_command; @@ -930,6 +933,7 @@ void GB_set_vblank_callback(GB_gameboy_t *gb, GB_vblank_callback_t callback); void GB_set_log_callback(GB_gameboy_t *gb, GB_log_callback_t callback); void GB_set_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); void GB_set_async_input_callback(GB_gameboy_t *gb, GB_input_callback_t callback); +void GB_set_debugger_reload_callback(GB_gameboy_t *gb, GB_debugger_reload_callback_t callback); void GB_set_rgb_encode_callback(GB_gameboy_t *gb, GB_rgb_encode_callback_t callback); void GB_set_infrared_callback(GB_gameboy_t *gb, GB_infrared_callback_t callback); void GB_set_rumble_callback(GB_gameboy_t *gb, GB_rumble_callback_t callback); diff --git a/SDL/main.c b/SDL/main.c index 9c47a2c..d52b572 100644 --- a/SDL/main.c +++ b/SDL/main.c @@ -683,6 +683,36 @@ static bool is_path_writeable(const char *path) return true; } +static void debugger_reload_callback(GB_gameboy_t *gb) +{ + size_t path_length = strlen(filename); + char extension[4] = {0,}; + if (path_length > 4) { + if (filename[path_length - 4] == '.') { + extension[0] = tolower((unsigned char)filename[path_length - 3]); + extension[1] = tolower((unsigned char)filename[path_length - 2]); + extension[2] = tolower((unsigned char)filename[path_length - 1]); + } + } + if (strcmp(extension, "isx") == 0) { + GB_load_isx(gb, filename); + } + else { + GB_load_rom(gb, filename); + } + + GB_load_battery(gb, battery_save_path_ptr); + + GB_debugger_clear_symbols(gb); + GB_debugger_load_symbol_file(gb, resource_path("registers.sym")); + + char symbols_path[path_length + 5]; + replace_extension(filename, path_length, symbols_path, ".sym"); + GB_debugger_load_symbol_file(gb, symbols_path); + + GB_reset(gb); +} + static void run(void) { SDL_ShowCursor(SDL_DISABLE); @@ -740,6 +770,8 @@ restart: GB_set_input_callback(&gb, input_callback); GB_set_async_input_callback(&gb, asyc_input_callback); } + + GB_set_debugger_reload_callback(&gb, debugger_reload_callback); } if (stop_on_start) { stop_on_start = false;