Breakpoints and watchpoints are now identified by a unique id rather than an address

This commit is contained in:
Lior Halphon 2023-07-28 19:07:56 +03:00
parent 61f3c8d7f3
commit dd1330d707
1 changed files with 164 additions and 325 deletions

View File

@ -26,10 +26,11 @@ typedef struct {
#define VALUE_16(x) ((value_t){false, 0, (x)})
struct GB_breakpoint_s {
unsigned id;
union {
struct {
uint16_t addr;
uint16_t bank; /* -1 = any bank*/
uint16_t addr;
uint16_t bank; /* -1 = any bank*/
};
uint32_t key; /* For sorting and comparing */
};
@ -39,10 +40,11 @@ struct GB_breakpoint_s {
#define BP_KEY(x) (((struct GB_breakpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key)
#define GB_WATCHPOINT_R (1)
#define GB_WATCHPOINT_W (2)
#define WATCHPOINT_READ (1)
#define WATCHPOINT_WRITE (2)
struct GB_watchpoint_s {
unsigned id;
union {
struct {
uint16_t addr;
@ -925,30 +927,6 @@ static bool softbreak(GB_gameboy_t *gb, char *arguments, char *modifiers, const
return true;
}
/* Find the index of the closest breakpoint equal or greater to addr */
static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
{
if (!gb->breakpoints) {
return 0;
}
uint32_t key = BP_KEY(addr);
unsigned min = 0;
unsigned max = gb->n_breakpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->breakpoints[pivot].key == key) return pivot;
if (gb->breakpoints[pivot].key > key) {
max = pivot;
}
else {
min = pivot + 1;
}
}
return (uint16_t) min;
}
static inline bool is_legal_symbol_char(char c)
{
if (c >= '0' && c <= '9') return true;
@ -1043,49 +1021,29 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments, char *modifiers, const
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result);
if (error) return true;
uint16_t index = find_breakpoint(gb, result);
if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) {
GB_log(gb, "Breakpoint already set at %s\n", debugger_value_to_string(gb, result, true));
if (!gb->breakpoints[index].condition && condition) {
GB_log(gb, "Added condition to breakpoint\n");
gb->breakpoints[index].condition = strdup(condition);
}
else if (gb->breakpoints[index].condition && condition) {
GB_log(gb, "Replaced breakpoint condition\n");
free(gb->breakpoints[index].condition);
gb->breakpoints[index].condition = strdup(condition);
}
else if (gb->breakpoints[index].condition && !condition) {
GB_log(gb, "Removed breakpoint condition\n");
free(gb->breakpoints[index].condition);
gb->breakpoints[index].condition = NULL;
}
return true;
}
uint32_t key = BP_KEY(result);
gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0]));
memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0]));
gb->breakpoints[index].key = key;
if (condition) {
gb->breakpoints[index].condition = strdup(condition);
unsigned id = 1;
if (gb->n_breakpoints) {
id = gb->breakpoints[gb->n_breakpoints - 1].id + 1;
}
else {
gb->breakpoints[index].condition = NULL;
}
gb->n_breakpoints++;
gb->breakpoints[index].is_jump_to = is_jump_to;
gb->breakpoints[gb->n_breakpoints++] = (struct GB_breakpoint_s){
.id = id,
.key = key,
.condition = condition? strdup(condition) : NULL,
.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));
GB_log(gb, "Breakpoint %u set at %s\n", id, debugger_value_to_string(gb, result, true));
return true;
}
@ -1104,78 +1062,45 @@ static bool delete(GB_gameboy_t *gb, char *arguments, char *modifiers, const deb
return true;
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = BP_KEY(result);
if (error) return true;
uint16_t index = 0;
for (unsigned i = 0; i < gb->n_breakpoints; i++) {
if (gb->breakpoints[i].key == key) {
/* Full match */
index = i;
break;
}
if (gb->breakpoints[i].addr == result.value && result.has_bank != (gb->breakpoints[i].bank != (uint16_t) -1)) {
/* Partial match */
index = i;
}
char *end;
unsigned id = strtol(arguments, &end, 10);
if (*end) {
print_usage(gb, command);
return true;
}
for (unsigned i = 0; i < gb->n_breakpoints; i++) {
if (gb->breakpoints[i].id != id) continue;
if (gb->breakpoints[i].condition) {
free(gb->breakpoints[i].condition);
}
if (gb->breakpoints[i].is_jump_to) {
gb->has_jump_to_breakpoints = false;
for (unsigned j = 0; j < gb->n_breakpoints; j++) {
if (j == i) continue;
if (gb->breakpoints[j].is_jump_to) {
gb->has_jump_to_breakpoints = true;
break;
}
}
}
memmove(&gb->breakpoints[i], &gb->breakpoints[i + 1], (gb->n_breakpoints - i - 1) * sizeof(gb->breakpoints[0]));
gb->n_breakpoints--;
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
value_t addr = (value_t){gb->breakpoints[i].bank != (uint16_t)-1, gb->breakpoints[i].bank, gb->breakpoints[i].addr};
if (index >= gb->n_breakpoints) {
GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
GB_log(gb, "Breakpoint %u removed from %s\n", id, debugger_value_to_string(gb, addr, addr.has_bank));
return true;
}
result.bank = gb->breakpoints[index].bank;
result.has_bank = gb->breakpoints[index].bank != (uint16_t) -1;
if (gb->breakpoints[index].condition) {
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--;
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true));
GB_log(gb, "Breakpoint %u was not found\n", id);
return true;
}
/* Find the index of the closest watchpoint equal or greater to addr */
static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
{
if (!gb->watchpoints) {
return 0;
}
uint32_t key = WP_KEY(addr);
unsigned min = 0;
unsigned max = gb->n_watchpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->watchpoints[pivot].key == key) return pivot;
if (gb->watchpoints[pivot].key > key) {
max = pivot;
}
else {
min = pivot + 1;
}
}
return (uint16_t) min;
}
static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
{
size_t length = strlen(string);
@ -1192,7 +1117,6 @@ static char *rw_completer(GB_gameboy_t *gb, const char *string, uintptr_t *conte
static bool watch(GB_gameboy_t *gb, char *arguments, char *modifiers, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
print_usage:
print_usage(gb, command);
return true;
}
@ -1210,19 +1134,21 @@ print_usage:
while (*modifiers) {
switch (*modifiers) {
case 'r':
flags |= GB_WATCHPOINT_R;
flags |= WATCHPOINT_READ;
break;
case 'w':
flags |= GB_WATCHPOINT_W;
flags |= WATCHPOINT_WRITE;
break;
default:
goto print_usage;
print_usage(gb, command);
return true;
}
modifiers++;
}
if (!flags) {
goto print_usage;
print_usage(gb, command);
return true;
}
char *condition = NULL;
@ -1244,44 +1170,23 @@ print_usage:
uint32_t key = WP_KEY(result);
if (error) return true;
uint16_t index = find_watchpoint(gb, result);
if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
GB_log(gb, "Watchpoint already set at %s\n", debugger_value_to_string(gb, result, true));
if (gb->watchpoints[index].flags != flags) {
GB_log(gb, "Modified watchpoint type\n");
gb->watchpoints[index].flags = flags;
}
if (!gb->watchpoints[index].condition && condition) {
GB_log(gb, "Added condition to watchpoint\n");
gb->watchpoints[index].condition = strdup(condition);
}
else if (gb->watchpoints[index].condition && condition) {
GB_log(gb, "Replaced watchpoint condition\n");
free(gb->watchpoints[index].condition);
gb->watchpoints[index].condition = strdup(condition);
}
else if (gb->watchpoints[index].condition && !condition) {
GB_log(gb, "Removed watchpoint condition\n");
free(gb->watchpoints[index].condition);
gb->watchpoints[index].condition = NULL;
}
return true;
unsigned id = 1;
if (gb->n_watchpoints) {
id = gb->watchpoints[gb->n_watchpoints - 1].id + 1;
}
gb->watchpoints = realloc(gb->watchpoints, (gb->n_watchpoints + 1) * sizeof(gb->watchpoints[0]));
memmove(&gb->watchpoints[index + 1], &gb->watchpoints[index], (gb->n_watchpoints - index) * sizeof(gb->watchpoints[0]));
gb->watchpoints[index].key = key;
gb->watchpoints[index].flags = flags;
if (condition) {
gb->watchpoints[index].condition = strdup(condition);
}
else {
gb->watchpoints[index].condition = NULL;
}
gb->n_watchpoints++;
gb->watchpoints[gb->n_watchpoints++] = (struct GB_watchpoint_s){
.id = id,
.key = key,
.condition = condition? strdup(condition) : NULL,
.flags = flags,
};
GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
GB_log(gb, "Watchpoint %u set at %s\n", id, debugger_value_to_string(gb, result, true));
return true;
}
@ -1299,43 +1204,32 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments, char *modifiers, const de
gb->n_watchpoints = 0;
return true;
}
bool error;
value_t result = debugger_evaluate(gb, arguments, (unsigned)strlen(arguments), &error, NULL, NULL);
uint32_t key = WP_KEY(result);
if (error) return true;
uint16_t index = 0;
for (unsigned i = 0; i < gb->n_watchpoints; i++) {
if (gb->watchpoints[i].key == key) {
/* Full match */
index = i;
break;
}
if (gb->watchpoints[i].addr == result.value && result.has_bank != (gb->watchpoints[i].bank != (uint16_t) -1)) {
/* Partial match */
index = i;
}
}
if (index >= gb->n_watchpoints) {
GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
char *end;
unsigned id = strtol(arguments, &end, 10);
if (*end) {
print_usage(gb, command);
return true;
}
result.bank = gb->watchpoints[index].bank;
result.has_bank = gb->watchpoints[index].bank != (uint16_t) -1;
if (gb->watchpoints[index].condition) {
free(gb->watchpoints[index].condition);
for (unsigned i = 0; i < gb->n_watchpoints; i++) {
if (gb->watchpoints[i].id != id) continue;
if (gb->watchpoints[i].condition) {
free(gb->watchpoints[i].condition);
}
memmove(&gb->watchpoints[i], &gb->watchpoints[i + 1], (gb->n_watchpoints - i - 1) * sizeof(gb->watchpoints[0]));
gb->n_watchpoints--;
gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints * sizeof(gb->watchpoints[0]));
value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr};
GB_log(gb, "Watchpoint %u removed from %s\n", id, debugger_value_to_string(gb, addr, addr.has_bank));
return true;
}
memmove(&gb->watchpoints[index], &gb->watchpoints[index + 1], (gb->n_watchpoints - index - 1) * sizeof(gb->watchpoints[0]));
gb->n_watchpoints--;
gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints *sizeof(gb->watchpoints[0]));
GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true));
GB_log(gb, "Watchpoint %u was not found\n", id);
return true;
}
@ -1355,13 +1249,13 @@ 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 (%sCondition: %s)\n", i + 1,
GB_log(gb, " %d. %s (%sCondition: %s)\n", gb->breakpoints[i].id,
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%s\n", i + 1,
GB_log(gb, " %d. %s%s\n", gb->breakpoints[i].id,
debugger_value_to_string(gb, addr, addr.has_bank),
gb->breakpoints[i].is_jump_to? " (Jump to)" : "");
}
@ -1376,15 +1270,15 @@ static bool list(GB_gameboy_t *gb, char *arguments, char *modifiers, const debug
for (uint16_t i = 0; i < gb->n_watchpoints; i++) {
value_t addr = (value_t){gb->watchpoints[i].bank != (uint16_t)-1, gb->watchpoints[i].bank, gb->watchpoints[i].addr};
if (gb->watchpoints[i].condition) {
GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank),
(gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-',
(gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-',
GB_log(gb, " %d. %s (%c%c, Condition: %s)\n", gb->watchpoints[i].id, debugger_value_to_string(gb, addr, addr.has_bank),
(gb->watchpoints[i].flags & WATCHPOINT_READ)? 'r' : '-',
(gb->watchpoints[i].flags & WATCHPOINT_WRITE)? 'w' : '-',
gb->watchpoints[i].condition);
}
else {
GB_log(gb, " %d. %s (%c%c)\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank),
(gb->watchpoints[i].flags & GB_WATCHPOINT_R)? 'r' : '-',
(gb->watchpoints[i].flags & GB_WATCHPOINT_W)? 'w' : '-');
GB_log(gb, " %d. %s (%c%c)\n", gb->watchpoints[i].id, debugger_value_to_string(gb, addr, addr.has_bank),
(gb->watchpoints[i].flags & WATCHPOINT_READ)? 'r' : '-',
(gb->watchpoints[i].flags & WATCHPOINT_WRITE)? 'w' : '-');
}
}
}
@ -1392,47 +1286,31 @@ 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, bool jump_to)
// Returns the id or 0
static unsigned should_break(GB_gameboy_t *gb, uint16_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 && gb->breakpoints[index].is_jump_to == jump_to) {
if (addr.has_bank && !gb->boot_rom_finished) {
if (addr.value < 0x100) {
return false;
}
if (addr.value >= 0x200 && addr.value < 0x900 && GB_is_cgb(gb)) {
return false;
}
}
if (!gb->breakpoints[index].condition) {
return true;
uint16_t bank = bank_for_addr(gb, addr);
for (unsigned i = 0; i < gb->n_breakpoints; i++) {
struct GB_breakpoint_s *breakpoint = &gb->breakpoints[i];
if (breakpoint->addr != addr) continue;
if (breakpoint->bank != (uint16_t)-1) {
if (breakpoint->bank != bank) continue;
if (!gb->boot_rom_finished) continue;
}
if (breakpoint->is_jump_to != jump_to) continue;
if (!breakpoint->condition) return breakpoint->id;
bool error;
bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition,
(unsigned)strlen(gb->breakpoints[index].condition), &error, NULL, NULL).value;
bool condition = debugger_evaluate(gb, breakpoint->condition,
(unsigned)strlen(breakpoint->condition),
&error, NULL, NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
return true;
GB_log(gb, "The condition for breakpoint %u is no longer a valid expression\n", breakpoint->id);
return breakpoint->id;
}
return condition;
if (condition) return breakpoint->id;
}
return false;
}
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, 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, jump_to);
return 0;
}
static char *format_completer(GB_gameboy_t *gb, const char *string, uintptr_t *context)
@ -2107,14 +1985,14 @@ static const debugger_command_t commands[] = {
"jumping to the target.",
"<expression>[ if <condition expression>]", "j",
.argument_completer = symbol_completer, .modifiers_completer = j_completer},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]", .argument_completer = symbol_completer},
{"delete", 2, delete, "Delete a breakpoint by its identifier, or all breakpoints", "[<breakpoint id>]"},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression. "
"Can also modify the condition and type of existing watchpoints. "
"Default watchpoint type is write-only.",
"<expression>[ if <condition expression>]", "(r|w|rw)",
.argument_completer = symbol_completer, .modifiers_completer = rw_completer
},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]", .argument_completer = symbol_completer},
{"unwatch", 3, unwatch, "Delete a watchpoint by its identifier, or all watchpoints", "[<watchpoint id>]"},
{"softbreak", 2, softbreak, "Enable or disable software breakpoints ('ld b, b' opcodes)", "(on|off)", .argument_completer = on_off_completer},
{"list", 1, list, "List all set breakpoints and watchpoints"},
{"ticks", 2, ticks, "Display the number of CPU ticks since the last time 'ticks' was "
@ -2248,94 +2126,54 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
}
}
static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value)
{
uint16_t index = find_watchpoint(gb, addr);
uint32_t key = WP_KEY(addr);
if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_W)) {
return false;
// Returns the id or 0
static void test_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t flags, uint8_t value)
{
uint16_t bank = bank_for_addr(gb, addr);
for (unsigned i = 0; i < gb->n_watchpoints; i++) {
struct GB_watchpoint_s *watchpoint = &gb->watchpoints[i];
if (watchpoint->addr != addr) continue;
if (watchpoint->bank != (uint16_t)-1) {
if (watchpoint->bank != bank) continue;
}
if (!gb->watchpoints[index].condition) {
if (!(watchpoint->flags & flags)) continue;
if (!watchpoint->condition) {
condition_ok:
GB_debugger_break(gb);
GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
return true;
if (flags == WATCHPOINT_READ) {
GB_log(gb, "Watchpoint %u: [%s]\n", watchpoint->id, value_to_string(gb, addr, true));
}
else {
GB_log(gb, "Watchpoint %u: [%s] = $%02x\n", watchpoint->id, value_to_string(gb, addr, true), value);
}
return;
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
bool condition = debugger_evaluate(gb, watchpoint->condition,
(unsigned)strlen(watchpoint->condition),
&error, &addr, flags == WATCHPOINT_WRITE? &value : NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
return false;
GB_log(gb, "The condition for watchpoint %u is no longer a valid expression\n", watchpoint->id);
GB_debugger_break(gb);
}
if (condition) {
GB_debugger_break(gb);
GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
return true;
goto condition_ok;
}
}
return false;
}
void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
{
if (gb->debug_stopped) return;
/* Try any-bank breakpoint */
value_t full_addr = (VALUE_16(addr));
if (_GB_debugger_test_write_watchpoint(gb, full_addr, value)) return;
/* Try bank-specific breakpoint */
full_addr.has_bank = true;
full_addr.bank = bank_for_addr(gb, addr);
_GB_debugger_test_write_watchpoint(gb, full_addr, value);
test_watchpoint(gb, addr, WATCHPOINT_WRITE, value);
}
static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr)
{
uint16_t index = find_watchpoint(gb, addr);
uint32_t key = WP_KEY(addr);
if (index < gb->n_watchpoints && gb->watchpoints[index].key == key) {
if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) {
return false;
}
if (!gb->watchpoints[index].condition) {
GB_debugger_break(gb);
GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true));
return true;
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned)strlen(gb->watchpoints[index].condition), &error, &addr.value, NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
return false;
}
if (condition) {
GB_debugger_break(gb);
GB_log(gb, "Watchpoint: [%s]\n", debugger_value_to_string(gb, addr, true));
return true;
}
}
return false;
}
void GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->debug_stopped) return;
/* Try any-bank breakpoint */
value_t full_addr = (VALUE_16(addr));
if (_GB_debugger_test_read_watchpoint(gb, full_addr)) return;
/* Try bank-specific breakpoint */
full_addr.has_bank = true;
full_addr.bank = bank_for_addr(gb, addr);
_GB_debugger_test_read_watchpoint(gb, full_addr);
test_watchpoint(gb, addr, WATCHPOINT_READ, 0);
}
/* Returns true if debugger waits for more commands */
@ -2467,7 +2305,7 @@ typedef enum {
JUMP_TO_NONTRIVIAL,
} jump_to_return_t;
static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address);
static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address, unsigned *breakpoint_id);
static void noinline debugger_run(GB_gameboy_t *gb)
{
@ -2494,35 +2332,36 @@ next_command:
if (input) {
free(input);
}
if (gb->breakpoints && !gb->debug_stopped && should_break(gb, gb->pc, false)) {
unsigned breakpoint_id = 0;
if (gb->breakpoints && !gb->debug_stopped && (breakpoint_id = should_break(gb, gb->pc, false))) {
GB_debugger_break(gb);
GB_log(gb, "Breakpoint: PC = %s\n", value_to_string(gb, gb->pc, true));
GB_log(gb, "Breakpoint %u: PC = %s\n", breakpoint_id, 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);
jump_to_return_t jump_to_result = test_jump_to_breakpoints(gb, &address, &breakpoint_id);
bool should_delete_state = true;
if (gb->nontrivial_jump_state && should_break(gb, gb->pc, true)) {
if (jump_to_result == JUMP_TO_BREAK) {
GB_debugger_break(gb);
GB_log(gb, "Jumping to breakpoint %u: PC = %s\n", breakpoint_id, value_to_string(gb, address, true));
GB_cpu_disassemble(gb, gb->pc, 5);
gb->non_trivial_jump_breakpoint_occured = false;
}
else if (gb->nontrivial_jump_state && (breakpoint_id = 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_log(gb, "Jumping to breakpoint %u: PC = %s\n", breakpoint_id, value_to_string(gb, gb->pc, true));
GB_load_state_from_buffer(gb, gb->nontrivial_jump_state, -1);
GB_cpu_disassemble(gb, gb->pc, 5);
GB_debugger_break(gb);
}
}
else if (jump_to_result == JUMP_TO_BREAK) {
GB_debugger_break(gb);
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_no_bess(gb));
@ -2848,7 +2687,7 @@ static opcode_address_getter_t *opcodes[256] = {
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)
static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *address, unsigned *breakpoint_id)
{
if (!gb->has_jump_to_breakpoints) return JUMP_TO_NONE;
@ -2861,7 +2700,7 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
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 ((*breakpoint_id = should_break(gb, 0x40 + i * 8, true))) {
if (address) {
*address = 0x40 + i * 8;
}
@ -2903,5 +2742,5 @@ static jump_to_return_t test_jump_to_breakpoints(GB_gameboy_t *gb, uint16_t *add
*address = new_pc;
}
return should_break(gb, new_pc, true) ? JUMP_TO_BREAK : JUMP_TO_NONE;
return (*breakpoint_id = should_break(gb, new_pc, true)) ? JUMP_TO_BREAK : JUMP_TO_NONE;
}