Bank-specific breakpoints and watchpoints

This commit is contained in:
Lior Halphon 2016-07-14 23:25:16 +03:00
parent ce837b3727
commit 909f3ba75e
1 changed files with 173 additions and 81 deletions

View File

@ -29,19 +29,55 @@ typedef struct {
#define VALUE_16(x) ((value_t){false, 0, (x)})
struct GB_breakpoint_s {
union {
struct {
uint16_t addr;
uint16_t bank; /* -1 = any bank*/
};
uint32_t key; /* For sorting and comparing */
};
char *condition;
};
#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)
struct GB_watchpoint_s {
union {
struct {
uint16_t addr;
uint16_t bank; /* -1 = any bank*/
};
uint32_t key; /* For sorting and comparing */
};
char *condition;
uint8_t flags;
};
#define WP_KEY(x) (((struct GB_watchpoint_s){.addr = ((x).value), .bank = (x).has_bank? (x).bank : -1 }).key)
static uint16_t bank_for_addr(GB_gameboy_t *gb, uint16_t addr)
{
if (addr < 0x4000) {
return gb->mbc_rom0_bank;
}
if (addr < 0x8000) {
return gb->mbc_rom_bank;
}
if (addr < 0xD000) {
return 0;
}
if (addr < 0xE000) {
return gb->cgb_ram_bank;
}
return 0;
}
static const char *value_to_string(GB_gameboy_t *gb, uint16_t value, bool prefer_name)
{
@ -579,18 +615,21 @@ static bool registers(GB_gameboy_t *gb, char *arguments)
}
/* Find the index of the closest breakpoint equal or greater to addr */
static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr)
static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
{
if (!gb->breakpoints) {
return 0;
}
uint32_t key = BP_KEY(addr);
int min = 0;
int max = gb->n_breakpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->breakpoints[pivot].addr == addr) return pivot;
if (gb->breakpoints[pivot].addr > addr) {
max = pivot - 1;
if (gb->breakpoints[pivot].key == key) return pivot;
if (gb->breakpoints[pivot].key > key) {
max = pivot;
}
else {
min = pivot + 1;
@ -606,6 +645,11 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments)
return true;
}
if (gb->n_breakpoints == (typeof(gb->n_breakpoints)) -1) {
GB_log(gb, "Too many breakpoints set\n");
return true;
}
char *condition = NULL;
if ((condition = strstr(arguments, " if "))) {
*condition = 0;
@ -618,13 +662,14 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments)
}
bool error;
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)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].addr == result) {
GB_log(gb, "Breakpoint already set at %s\n", value_to_string(gb, result, true));
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);
@ -644,7 +689,8 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments)
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].addr = result;
gb->breakpoints[index].key = key;
if (condition) {
gb->breakpoints[index].condition = strdup(condition);
}
@ -653,7 +699,7 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments)
}
gb->n_breakpoints++;
GB_log(gb, "Breakpoint set at %s\n", value_to_string(gb, result, true));
GB_log(gb, "Breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true;
}
@ -676,13 +722,14 @@ static bool delete(GB_gameboy_t *gb, char *arguments)
}
bool error;
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)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].addr != result) {
GB_log(gb, "No breakpoint set at %s\n", value_to_string(gb, result, true));
if (index >= gb->n_breakpoints || gb->breakpoints[index].key != key) {
GB_log(gb, "No breakpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true;
}
@ -694,23 +741,24 @@ static bool delete(GB_gameboy_t *gb, char *arguments)
gb->n_breakpoints--;
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
GB_log(gb, "Breakpoint removed from %s\n", value_to_string(gb, result, true));
GB_log(gb, "Breakpoint removed from %s\n", debugger_value_to_string(gb, result, true));
return true;
}
/* Find the index of the closest watchpoint equal or greater to addr */
static uint16_t find_watchpoint(GB_gameboy_t *gb, uint16_t addr)
static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
{
if (!gb->watchpoints) {
return 0;
}
uint32_t key = WP_KEY(addr);
int min = 0;
int max = gb->n_watchpoints;
while (min < max) {
uint16_t pivot = (min + max) / 2;
if (gb->watchpoints[pivot].addr == addr) return pivot;
if (gb->watchpoints[pivot].addr > addr) {
max = pivot - 1;
if (gb->watchpoints[pivot].key == key) return pivot;
if (gb->watchpoints[pivot].key > key) {
max = pivot;
}
else {
min = pivot + 1;
@ -727,6 +775,11 @@ print_usage:
return true;
}
if (gb->n_watchpoints == (typeof(gb->n_watchpoints)) -1) {
GB_log(gb, "Too many watchpoints set\n");
return true;
}
uint8_t flags = 0;
while (*arguments != ' ' && *arguments) {
switch (*arguments) {
@ -761,13 +814,14 @@ print_usage:
}
bool error;
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
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].addr == result) {
GB_log(gb, "Watchpoint already set at %s\n", value_to_string(gb, result, true));
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;
@ -791,7 +845,7 @@ print_usage:
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].addr = result;
gb->watchpoints[index].key = key;
gb->watchpoints[index].flags = flags;
if (condition) {
gb->watchpoints[index].condition = strdup(condition);
@ -801,7 +855,7 @@ print_usage:
}
gb->n_watchpoints++;
GB_log(gb, "Watchpoint set at %s\n", value_to_string(gb, result, true));
GB_log(gb, "Watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true;
}
@ -824,13 +878,14 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments)
}
bool error;
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL).value;
value_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error, NULL, NULL);
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].addr != result) {
GB_log(gb, "No watchpoint set at %s\n", value_to_string(gb, result, true));
if (index >= gb->n_watchpoints || gb->watchpoints[index].key != key) {
GB_log(gb, "No watchpoint set at %s\n", debugger_value_to_string(gb, result, true));
return true;
}
@ -842,7 +897,7 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments)
gb->n_watchpoints--;
gb->watchpoints = realloc(gb->watchpoints, gb->n_watchpoints* sizeof(gb->watchpoints[0]));
GB_log(gb, "Watchpoint removed from %s\n", value_to_string(gb, result, true));
GB_log(gb, "Watchpoint removed from %s\n", debugger_value_to_string(gb, result, true));
return true;
}
@ -859,13 +914,14 @@ static bool list(GB_gameboy_t *gb, char *arguments)
else {
GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints);
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,
value_to_string(gb, gb->breakpoints[i].addr, true),
debugger_value_to_string(gb, addr, addr.has_bank),
gb->breakpoints[i].condition);
}
else {
GB_log(gb, " %d. %s\n", i + 1, value_to_string(gb, gb->breakpoints[i].addr, true));
GB_log(gb, " %d. %s\n", i + 1, debugger_value_to_string(gb, addr, addr.has_bank));
}
}
}
@ -876,14 +932,15 @@ static bool list(GB_gameboy_t *gb, char *arguments)
else {
GB_log(gb, "%d watchpoint(s) set:\n", gb->n_watchpoints);
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, value_to_string(gb, gb->watchpoints[i].addr, true),
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->watchpoints[i].condition);
}
else {
GB_log(gb, " %d. %s (%c%c)\n", i + 1, value_to_string(gb, gb->watchpoints[i].addr, true),
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' : '-');
}
@ -893,10 +950,12 @@ static bool list(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool should_break(GB_gameboy_t *gb, uint16_t addr)
static bool _should_break(GB_gameboy_t *gb, value_t addr)
{
uint16_t index = find_breakpoint(gb, addr);
if (index < gb->n_breakpoints && gb->breakpoints[index].addr == addr) {
uint32_t key = BP_KEY(addr);
if (index < gb->n_breakpoints && gb->breakpoints[index].key == key) {
if (!gb->breakpoints[index].condition) {
return true;
}
@ -913,6 +972,18 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr)
return false;
}
static bool should_break(GB_gameboy_t *gb, uint16_t addr)
{
/* Try any-bank breakpoint */
value_t full_addr = (VALUE_16(addr));
if (_should_break(gb, full_addr)) 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);
}
static bool print(GB_gameboy_t *gb, char *arguments)
{
if (strlen(lstrip(arguments)) == 0) {
@ -1116,58 +1187,94 @@ void GB_debugger_ret_hook(GB_gameboy_t *gb)
}
}
void GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
static bool _GB_debugger_test_write_watchpoint(GB_gameboy_t *gb, value_t addr, uint8_t value)
{
uint16_t index = find_watchpoint(gb, addr);
if (index < gb->n_watchpoints && gb->watchpoints[index].addr == 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;
return false;
}
if (!gb->watchpoints[index].condition) {
gb->debug_stopped = true;
GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value);
return;
GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
return true;
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, &value).value;
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr.value, &value).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
return;
return false;
}
if (condition) {
gb->debug_stopped = true;
GB_log(gb, "Watchpoint: [%s] = $%02x\n", value_to_string(gb, addr, true), value);
GB_log(gb, "Watchpoint: [%s] = $%02x\n", debugger_value_to_string(gb, addr, true), value);
return true;
}
}
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);
}
static bool _GB_debugger_test_read_watchpoint(GB_gameboy_t *gb, value_t addr)
{
uint16_t index = find_breakpoint(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->debug_stopped = true;
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 int)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->debug_stopped = true;
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)
{
uint16_t index = find_breakpoint(gb, addr);
if (index < gb->n_watchpoints && gb->watchpoints[index].addr == addr) {
if (!(gb->watchpoints[index].flags & GB_WATCHPOINT_R)) {
return;
}
if (!gb->watchpoints[index].condition) {
gb->debug_stopped = true;
GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true));
return;
}
bool error;
bool condition = debugger_evaluate(gb, gb->watchpoints[index].condition,
(unsigned int)strlen(gb->watchpoints[index].condition), &error, &addr, NULL).value;
if (error) {
/* Should never happen */
GB_log(gb, "An internal error has occured\n");
return;
}
if (condition) {
gb->debug_stopped = true;
GB_log(gb, "Watchpoint: [%s]\n", value_to_string(gb, addr, true));
}
}
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);
}
void GB_debugger_run(GB_gameboy_t *gb)
@ -1263,22 +1370,7 @@ void GB_debugger_load_symbol_file(GB_gameboy_t *gb, const char *path)
const GB_bank_symbol_t *GB_debugger_find_symbol(GB_gameboy_t *gb, uint16_t addr)
{
uint16_t bank = 0;
if (addr < 0x4000) {
bank = gb->mbc_rom0_bank;
}
else if (addr < 0x8000) {
bank = gb->mbc_rom_bank;
}
else if (addr < 0xD000) {
bank = 0;
}
else if (addr < 0xE000) {
bank = gb->cgb_ram_bank;
}
if (bank > 0x1FF) return NULL;
uint16_t bank = bank_for_addr(gb, addr);
const GB_bank_symbol_t *symbol = GB_map_find_symbol(gb->bank_symbols[bank], addr);
if (symbol) return symbol;