From 05cd81b77c81ddbbdbe74606512ac9606b800cbb Mon Sep 17 00:00:00 2001 From: Lior Halphon Date: Fri, 12 Apr 2019 20:29:43 +0300 Subject: [PATCH] Implemented jump-to breakpoints --- Core/debugger.c | 311 ++++++++++++++++++++++++++++++++++++++++++++++-- Core/gb.c | 8 ++ Core/gb.h | 3 + 3 files changed, 311 insertions(+), 11 deletions(-) diff --git a/Core/debugger.c b/Core/debugger.c index 9265ce6..c18c1df 100644 --- a/Core/debugger.c +++ b/Core/debugger.c @@ -34,6 +34,7 @@ struct GB_breakpoint_s { uint32_t key; /* For sorting and comparing */ }; char *condition; + bool is_jump_to; }; #define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key) @@ -845,7 +846,15 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr) static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) { - NO_MODIFIERS + bool is_jump_to = true; + if (!modifiers) { + is_jump_to = false; + } + else if (strcmp(modifiers, "j") != 0) { + print_usage(gb, command); + return true; + } + if (strlen(lstrip(arguments)) == 0) { print_usage(gb, command); return true; @@ -904,6 +913,12 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const gb->breakpoints[index].condition = NULL; } gb->n_breakpoints++; + + gb->breakpoints[index].is_jump_to = is_jump_to; + + if (is_jump_to) { + gb->has_jump_to_breakpoints = true; + } GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true)); return true; @@ -955,6 +970,16 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb free(gb->breakpoints[index].condition); } + if (gb->breakpoints[index].is_jump_to) { + gb->has_jump_to_breakpoints = false; + for (unsigned i = 0; i < gb->n_breakpoints; i++) { + if (i == index) continue; + if (gb->breakpoints[i].is_jump_to) { + gb->has_jump_to_breakpoints = true; + break; + } + } + } memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0])); gb->n_breakpoints--; @@ -1152,12 +1177,15 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug for (uint16_t i = 0; i < gb->n_breakpoints; i++) { value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr}; if (gb->breakpoints[i].condition) { - GB_log(gb, " %d. %s (Condition: %s)\n", i + 1, + GB_log(gb, " %d. %s (%sCondition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? "Jump to, ": "", gb->breakpoints[i].condition); } else { - GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank)); + GB_log(gb, " %d. %s%s\n", i + 1, + debugger_value_to_string(gb, addr, addr.has_bank), + gb->breakpoints[i].is_jump_to? " (Jump to)" : ""); } } } @@ -1186,12 +1214,12 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug return true; } -static bool _should_break(GB_gameboy_t *gb, value_t addr) +static bool _should_break(GB_gameboy_t *gb, value_t addr, bool jump_to) { uint16_t index = find_breakpoint(gb, addr); uint32_t key = BP_KEY(addr); - if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) { + if (index < gb->n_breakpoints && gb->breakpoints[index].key == key && gb->breakpoints[index].is_jump_to == jump_to) { if (!gb->breakpoints[index].condition) { return true; } @@ -1208,16 +1236,16 @@ static bool _should_break(GB_gameboy_t *gb, value_t addr) return false; } -static bool should_break(GB_gameboy_t *gb, uint16_t addr) +static bool should_break(GB_gameboy_t *gb, uint16_t addr, bool jump_to) { /* Try any-bank breakpoint */ value_t full_addr = (VALUE_16(addr)); - if (_should_break(gb, full_addr)) return true; + if (_should_break(gb, full_addr, jump_to)) return true; /* Try bank-specific breakpoint */ full_addr.has_bank = true; full_addr.bank = bank_for_addr(gb, addr); - return _should_break(gb, full_addr); + return _should_break(gb, full_addr, jump_to); } static bool print(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command) @@ -1562,8 +1590,10 @@ static const debugger_command_t commands[] = { {"lcd", 3, lcd, "Displays information about the current state of the LCD controller"}, {"palettes", 3, palettes, "Displays the current CGB palettes"}, {"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression" HELP_NEWLINE - "Can also modify the condition of existing breakpoints.", - "[ if ]"}, + "Can also modify the condition of existing breakpoints." HELP_NEWLINE + "If the j modifier is used, the breakpoint will occur just" HELP_NEWLINE + "before jumping to the target.", + "[ if ]", "(j)"}, {"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[]"}, {"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE "Can also modify the condition and type of existing watchpoints." HELP_NEWLINE @@ -1834,6 +1864,14 @@ bool GB_debugger_execute_command(GB_gameboy_t *gb, char *input) } } +typedef enum { + JUMP_TO_NONE, + JUMP_TO_BREAK, + JUMP_TO_NONTRIVIAL, +} jump_to_return_t; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address); + void GB_debugger_run(GB_gameboy_t *gb) { if (gb->debug_disable) return; @@ -1852,11 +1890,55 @@ next_command: if (input) { free(input); } - if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc)) { + if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) { gb->debug_stopped = true; GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); GB_cpu_disassemble(gb, gb->pc, 5); } + + if (gb->breakpoints && !gb->debug_stopped) { + uint16_t address = 0; + jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address); + + bool should_delete_state = true; + if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) { + if (gb->non_trivial_jump_breakpoint_occured) { + gb->non_trivial_jump_breakpoint_occured = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1); + gb->debug_stopped = true; + } + } + else if (jump_to_result == JUMP_TO_BREAK) { + gb->debug_stopped = true; + GB_log(gb, "Jumping to breakpoint: PC = %s\n", value_to_string(gb, address, true)); + GB_cpu_disassemble(gb, gb->pc, 5); + gb->non_trivial_jump_breakpoint_occured = false; + } + else if (jump_to_result == JUMP_TO_NONTRIVIAL) { + if (!gb->nontrivial_jump_state) { + gb->nontrivial_jump_state = malloc(GB_get_save_state_size(gb)); + } + GB_save_state_to_buffer(gb, gb->nontrivial_jump_state); + gb->non_trivial_jump_breakpoint_occured = false; + should_delete_state = false; + } + else { + gb->non_trivial_jump_breakpoint_occured = false; + } + + if (should_delete_state) { + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + } + } + if (gb->debug_stopped && !gb->debug_disable) { gb->debug_next_command = false; gb->debug_fin_command = false; @@ -1986,3 +2068,210 @@ void GB_debugger_set_disabled(GB_gameboy_t *gb, bool disabled) { gb->debug_disable = disabled; } + +/* Jump-to breakpoints */ + +static bool is_in_trivial_memory(uint16_t addr) +{ + /* ROM */ + if (addr < 0x8000) { + return true; + } + + /* HRAM */ + if (addr >= 0xFF80 && addr < 0xFFFF) { + return true; + } + + /* RAM */ + if (addr >= 0xC000 && addr < 0xE000) { + return true; + } + + return false; +} + +typedef uint16_t GB_opcode_address_getter_t(GB_gameboy_t *gb, uint8_t opcode); + +uint16_t trivial_1(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 1; +} + +uint16_t trivial_2(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2; +} + +uint16_t trivial_3(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 3; +} + +static uint16_t jr_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static bool condition_code(GB_gameboy_t *gb, uint8_t opcode) +{ + switch ((opcode >> 3) & 0x3) { + case 0: + return !(gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 1: + return (gb->registers[GB_REGISTER_AF] & GB_ZERO_FLAG); + case 2: + return !(gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + case 3: + return (gb->registers[GB_REGISTER_AF] & GB_CARRY_FLAG); + } + + return false; +} + +static uint16_t jr_cc_r8(GB_gameboy_t *gb, uint8_t opcode) +{ + if (!condition_code(gb, opcode)) { + return gb->pc + 2; + } + + return gb->pc + 2 + (int8_t)GB_read_memory(gb, gb->pc + 1); +} + +static uint16_t ret(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->registers[GB_REGISTER_SP]) | + (GB_read_memory(gb, gb->registers[GB_REGISTER_SP] + 1) << 8); +} + + +static uint16_t ret_cc(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return ret(gb, opcode); + } + else { + return gb->pc + 1; + } +} + +static uint16_t jp_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + return GB_read_memory(gb, gb->pc + 1) | + (GB_read_memory(gb, gb->pc + 2) << 8); +} + +static uint16_t jp_cc_a16(GB_gameboy_t *gb, uint8_t opcode) +{ + if (condition_code(gb, opcode)) { + return jp_a16(gb, opcode); + } + else { + return gb->pc + 3; + } +} + +static uint16_t rst(GB_gameboy_t *gb, uint8_t opcode) +{ + return opcode ^ 0xC7; +} + +static uint16_t jp_hl(GB_gameboy_t *gb, uint8_t opcode) +{ + return gb->hl; +} + +static GB_opcode_address_getter_t *opcodes[256] = { + /* X0 X1 X2 X3 X4 X5 X6 X7 */ + /* X8 X9 Xa Xb Xc Xd Xe Xf */ + trivial_1, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 0X */ + trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_2, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 1X */ + jr_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 2X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + jr_cc_r8, trivial_3, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, /* 3X */ + jr_cc_r8, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_2, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 4X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 5X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 6X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, NULL, trivial_1, /* 7X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 8X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* 9X */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* aX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, /* bX */ + trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, trivial_1, + ret_cc, trivial_1, jp_cc_a16, jp_a16, jp_cc_a16, trivial_1, trivial_2, rst, /* cX */ + ret_cc, ret, jp_cc_a16, trivial_2, jp_cc_a16, jp_a16, trivial_2, rst, + ret_cc, trivial_1, jp_cc_a16, NULL, jp_cc_a16, trivial_1, trivial_2, rst, /* dX */ + ret_cc, ret, jp_cc_a16, NULL, jp_cc_a16, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, NULL, NULL, trivial_1, trivial_2, rst, /* eX */ + trivial_2, jp_hl, trivial_3, NULL, NULL, NULL, trivial_2, rst, + trivial_2, trivial_1, trivial_1, trivial_1, NULL, trivial_1, trivial_2, rst, /* fX */ + trivial_2, trivial_1, trivial_3, trivial_1, NULL, NULL, trivial_2, rst, +}; + +static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address) +{ + if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE; + + if (!is_in_trivial_memory(gb->pc) || !is_in_trivial_memory(gb->pc + 2) || + !is_in_trivial_memory(gb->registers[GB_REGISTER_SP]) || !is_in_trivial_memory(gb->registers[GB_REGISTER_SP] + 1)) { + return JUMP_TO_NONTRIVIAL; + } + + /* Interrupts */ + if (gb->ime) { + for (unsigned i = 0; i < 5; i++) { + if ((gb->interrupt_enable & (1 << i)) && (gb->io_registers[GB_IO_IF] & (1 << i))) { + if (should_break(gb, 0x40 + i * 8, true)) { + if (address) { + *address = 0x40 + i * 8; + } + return JUMP_TO_BREAK; + } + } + } + } + + uint16_t n_watchpoints = gb->n_watchpoints; + gb->n_watchpoints = 0; + + uint8_t opcode = GB_read_memory(gb, gb->pc); + + if (opcode == 0x76) { + gb->n_watchpoints = n_watchpoints; + if (gb->ime) { /* Already handled in above */ + return JUMP_TO_NONE; + } + + if (gb->interrupt_enable & gb->io_registers[GB_IO_IF] & 0x1F) { + return JUMP_TO_NONTRIVIAL; /* HALT bug could occur */ + } + + return JUMP_TO_NONE; + } + + GB_opcode_address_getter_t *getter = opcodes[opcode]; + if (!getter) { + gb->n_watchpoints = n_watchpoints; + return JUMP_TO_NONE; + } + + uint16_t new_pc = getter(gb, opcode); + + gb->n_watchpoints = n_watchpoints; + + if (address) { + *address = new_pc; + } + + return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE; +} diff --git a/Core/gb.c b/Core/gb.c index e509445..9456f10 100644 --- a/Core/gb.c +++ b/Core/gb.c @@ -142,6 +142,9 @@ void GB_free(GB_gameboy_t *gb) if (gb->sgb) { free(gb->sgb); } + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + } #ifndef DISABLE_DEBUGGER GB_debugger_clear_symbols(gb); #endif @@ -689,6 +692,11 @@ void GB_reset(GB_gameboy_t *gb) GB_apu_update_cycles_per_sample(gb); + if (gb->nontrivial_jump_state) { + free(gb->nontrivial_jump_state); + gb->nontrivial_jump_state = NULL; + } + gb->magic = (uintptr_t)'SAME'; } diff --git a/Core/gb.h b/Core/gb.h index f20fd77..f793f30 100644 --- a/Core/gb.h +++ b/Core/gb.h @@ -531,6 +531,9 @@ struct GB_gameboy_internal_s { /* Breakpoints */ uint16_t n_breakpoints; struct GB_breakpoint_s *breakpoints; + bool has_jump_to_breakpoints; + void *nontrivial_jump_state; + bool non_trivial_jump_breakpoint_occured; /* SLD (Todo: merge with backtrace) */ bool stack_leak_detection;