mirror of
https://github.com/libretro/bsnes-libretro.git
synced 2024-12-11 02:53:59 +00:00
Added condition breakpoint. Fixed a possible crash when deleting a breakpoint.
This commit is contained in:
parent
8eee70aed9
commit
19aea4096b
@ -20,6 +20,11 @@ typedef struct {
|
|||||||
};
|
};
|
||||||
} lvalue_t;
|
} lvalue_t;
|
||||||
|
|
||||||
|
struct GB_breakpoint_s {
|
||||||
|
uint16_t addr;
|
||||||
|
char *condition;
|
||||||
|
};
|
||||||
|
|
||||||
static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
|
static uint16_t read_lvalue(GB_gameboy_t *gb, lvalue_t lvalue)
|
||||||
{
|
{
|
||||||
/* Not used until we add support for operators like += */
|
/* Not used until we add support for operators like += */
|
||||||
@ -438,8 +443,8 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr)
|
|||||||
int max = gb->n_breakpoints;
|
int max = gb->n_breakpoints;
|
||||||
while (min < max) {
|
while (min < max) {
|
||||||
uint16_t pivot = (min + max) / 2;
|
uint16_t pivot = (min + max) / 2;
|
||||||
if (gb->breakpoints[pivot] == addr) return pivot;
|
if (gb->breakpoints[pivot].addr == addr) return pivot;
|
||||||
if (gb->breakpoints[pivot] > addr) {
|
if (gb->breakpoints[pivot].addr > addr) {
|
||||||
max = pivot - 1;
|
max = pivot - 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -452,24 +457,55 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, uint16_t addr)
|
|||||||
static bool breakpoint(GB_gameboy_t *gb, char *arguments)
|
static bool breakpoint(GB_gameboy_t *gb, char *arguments)
|
||||||
{
|
{
|
||||||
if (strlen(lstrip(arguments)) == 0) {
|
if (strlen(lstrip(arguments)) == 0) {
|
||||||
GB_log(gb, "Usage: breakpoint <expression>\n");
|
GB_log(gb, "Usage: breakpoint <expression>[ if <condition expression>]\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *condition = NULL;
|
||||||
|
if ((condition = strstr(arguments, " if "))) {
|
||||||
|
*condition = 0;
|
||||||
|
condition += strlen(" if ");
|
||||||
|
/* Verify condition is sane (Todo: This might have side effects!) */
|
||||||
|
bool error;
|
||||||
|
debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error);
|
||||||
|
if (error) return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
bool error;
|
bool error;
|
||||||
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error);
|
uint16_t result = debugger_evaluate(gb, arguments, (unsigned int)strlen(arguments), &error);
|
||||||
|
|
||||||
if (error) return true;
|
if (error) return true;
|
||||||
|
|
||||||
uint16_t index = find_breakpoint(gb, result);
|
uint16_t index = find_breakpoint(gb, result);
|
||||||
if (index < gb->n_breakpoints && gb->breakpoints[index] == result) {
|
if (index < gb->n_breakpoints && gb->breakpoints[index].addr == result) {
|
||||||
GB_log(gb, "Breakpoint already set at %04x\n", result);
|
GB_log(gb, "Breakpoint already set at %04x\n", result);
|
||||||
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
gb->breakpoints = realloc(gb->breakpoints, (gb->n_breakpoints + 1) * sizeof(gb->breakpoints[0]));
|
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]));
|
memmove(&gb->breakpoints[index + 1], &gb->breakpoints[index], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0]));
|
||||||
gb->breakpoints[index] = result;
|
gb->breakpoints[index].addr = result;
|
||||||
|
if (condition) {
|
||||||
|
gb->breakpoints[index].condition = strdup(condition);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gb->breakpoints[index].condition = NULL;
|
||||||
|
}
|
||||||
gb->n_breakpoints++;
|
gb->n_breakpoints++;
|
||||||
|
|
||||||
GB_log(gb, "Breakpoint set at %04x\n", result);
|
GB_log(gb, "Breakpoint set at %04x\n", result);
|
||||||
@ -482,6 +518,11 @@ static bool delete(GB_gameboy_t *gb, char *arguments)
|
|||||||
GB_log(gb, "Delete all breakpoints? ");
|
GB_log(gb, "Delete all breakpoints? ");
|
||||||
char *answer = gb->input_callback(gb);
|
char *answer = gb->input_callback(gb);
|
||||||
if (answer[0] == 'Y' || answer[0] == 'y') {
|
if (answer[0] == 'Y' || answer[0] == 'y') {
|
||||||
|
for (unsigned i = gb->n_breakpoints; i--;) {
|
||||||
|
if (gb->breakpoints[i].condition) {
|
||||||
|
free(gb->breakpoints[i].condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
free(gb->breakpoints);
|
free(gb->breakpoints);
|
||||||
gb->breakpoints = NULL;
|
gb->breakpoints = NULL;
|
||||||
gb->n_breakpoints = 0;
|
gb->n_breakpoints = 0;
|
||||||
@ -495,12 +536,16 @@ static bool delete(GB_gameboy_t *gb, char *arguments)
|
|||||||
if (error) return true;
|
if (error) return true;
|
||||||
|
|
||||||
uint16_t index = find_breakpoint(gb, result);
|
uint16_t index = find_breakpoint(gb, result);
|
||||||
if (index >= gb->n_breakpoints || gb->breakpoints[index] != result) {
|
if (index >= gb->n_breakpoints || gb->breakpoints[index].addr != result) {
|
||||||
GB_log(gb, "No breakpoint set at %04x\n", result);
|
GB_log(gb, "No breakpoint set at %04x\n", result);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index) * sizeof(gb->breakpoints[0]));
|
if (gb->breakpoints[index].condition) {
|
||||||
|
free(gb->breakpoints[index].condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
memmove(&gb->breakpoints[index], &gb->breakpoints[index + 1], (gb->n_breakpoints - index - 1) * sizeof(gb->breakpoints[0]));
|
||||||
gb->n_breakpoints--;
|
gb->n_breakpoints--;
|
||||||
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
|
gb->breakpoints = realloc(gb->breakpoints, gb->n_breakpoints * sizeof(gb->breakpoints[0]));
|
||||||
|
|
||||||
@ -522,7 +567,12 @@ static bool list(GB_gameboy_t *gb, char *arguments)
|
|||||||
|
|
||||||
GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints);
|
GB_log(gb, "%d breakpoint(s) set:\n", gb->n_breakpoints);
|
||||||
for (uint16_t i = 0; i < gb->n_breakpoints; i++) {
|
for (uint16_t i = 0; i < gb->n_breakpoints; i++) {
|
||||||
GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i]);
|
if (gb->breakpoints[i].condition) {
|
||||||
|
GB_log(gb, " %d. %04x (Condition: %s)\n", i + 1, gb->breakpoints[i].addr, gb->breakpoints[i].condition);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GB_log(gb, " %d. %04x\n", i + 1, gb->breakpoints[i].addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -531,8 +581,19 @@ static bool list(GB_gameboy_t *gb, char *arguments)
|
|||||||
static bool should_break(GB_gameboy_t *gb, uint16_t addr)
|
static bool should_break(GB_gameboy_t *gb, uint16_t addr)
|
||||||
{
|
{
|
||||||
uint16_t index = find_breakpoint(gb, addr);
|
uint16_t index = find_breakpoint(gb, addr);
|
||||||
if (index < gb->n_breakpoints && gb->breakpoints[index] == addr) {
|
if (index < gb->n_breakpoints && gb->breakpoints[index].addr == addr) {
|
||||||
return true;
|
if (!gb->breakpoints[index].condition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool error;
|
||||||
|
bool condition = debugger_evaluate(gb, gb->breakpoints[index].condition,
|
||||||
|
(unsigned int)strlen(gb->breakpoints[index].condition), &error);
|
||||||
|
if (error) {
|
||||||
|
/* Should never happen */
|
||||||
|
GB_log(gb, "An internal error has occured\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -611,7 +672,6 @@ static bool mbc(GB_gameboy_t *gb, char *arguments)
|
|||||||
GB_log(gb, "Cart contains a real time clock\n");
|
GB_log(gb, "Cart contains a real time clock\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -625,7 +685,7 @@ static const debugger_command_t commands[] = {
|
|||||||
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
{"registers", 1, registers, "Print values of processor registers and other important registers"},
|
||||||
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
|
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
|
||||||
{"mbc", 3, mbc, NULL},
|
{"mbc", 3, mbc, NULL},
|
||||||
{"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."},
|
||||||
{"list", 1, list, "List all set breakpoints"},
|
{"list", 1, list, "List all set breakpoints"},
|
||||||
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"},
|
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"},
|
||||||
{"print", 1, print, "Evaluate and print an expression"},
|
{"print", 1, print, "Evaluate and print an expression"},
|
||||||
|
@ -180,6 +180,8 @@ typedef struct {
|
|||||||
/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */
|
/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */
|
||||||
_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1");
|
_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1");
|
||||||
|
|
||||||
|
struct GB_breakpoint_s;
|
||||||
|
|
||||||
typedef struct GB_gameboy_s {
|
typedef struct GB_gameboy_s {
|
||||||
GB_SECTION(header,
|
GB_SECTION(header,
|
||||||
/* The magic makes sure a state file is:
|
/* The magic makes sure a state file is:
|
||||||
@ -315,7 +317,7 @@ typedef struct GB_gameboy_s {
|
|||||||
int debug_call_depth;
|
int debug_call_depth;
|
||||||
bool debug_fin_command, debug_next_command;
|
bool debug_fin_command, debug_next_command;
|
||||||
uint16_t n_breakpoints;
|
uint16_t n_breakpoints;
|
||||||
uint16_t *breakpoints;
|
struct GB_breakpoint_s *breakpoints;
|
||||||
bool stack_leak_detection;
|
bool stack_leak_detection;
|
||||||
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
|
uint16_t sp_for_call_depth[0x200]; /* Should be much more than enough */
|
||||||
uint16_t addr_for_call_depth[0x200];
|
uint16_t addr_for_call_depth[0x200];
|
||||||
|
Loading…
Reference in New Issue
Block a user