Debugger: Conditional breakpoints
jump to
@@ -11,6 +11,7 @@ - Map viewer
- Automatic cheat loading and saving - GameShark and Action Replay button support - AGBPrint support + - Debugger: Conditional breakpoints Bugfixes: - GB Audio: Make audio unsigned with bias (fixes mgba.io/i/749) - GB Serialize: Fix audio state loading
@@ -70,6 +70,7 @@ } type;
}; struct mDebugger; +struct ParseTree; struct mDebuggerPlatform { struct mDebugger* p;@@ -79,6 +80,7 @@ void (*entered)(struct mDebuggerPlatform*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
bool (*hasBreakpoints)(struct mDebuggerPlatform*); void (*setBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); + void (*setConditionalBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); void (*clearBreakpoint)(struct mDebuggerPlatform*, uint32_t address, int segment); void (*setWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); void (*clearWatchpoint)(struct mDebuggerPlatform*, uint32_t address, int segment);
@@ -15,8 +15,10 @@
#include <mgba/internal/arm/arm.h> #include <mgba-util/vector.h> +struct ParseTree; struct ARMDebugBreakpoint { uint32_t address; + struct ParseTree* condition; bool isSw; struct { uint32_t opcode;
@@ -12,6 +12,9 @@ #include <mgba-util/vector.h>
CXX_GUARD_START +struct Token; +DECLARE_VECTOR(LexVector, struct Token); + enum Operation { OP_ASSIGN, OP_ADD,@@ -54,15 +57,13 @@ enum Operation operatorValue;
}; }; -DECLARE_VECTOR(LexVector, struct Token); - struct ParseTree { struct Token token; struct ParseTree* lhs; struct ParseTree* rhs; }; -size_t lexExpression(struct LexVector* lv, const char* string, size_t length); +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol); void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv); void lexFree(struct LexVector* lv);
@@ -15,10 +15,11 @@
#include <mgba/internal/lr35902/lr35902.h> #include <mgba-util/vector.h> - +struct ParseTree; struct LR35902DebugBreakpoint { uint16_t address; int segment; + struct ParseTree* condition; }; struct LR35902DebugWatchpoint {
@@ -10,6 +10,7 @@ #include <mgba/internal/arm/arm.h>
#include <mgba/internal/arm/decoder.h> #include <mgba/internal/arm/isa-inlines.h> #include <mgba/internal/arm/debugger/memory-debugger.h> +#include <mgba/internal/debugger/parser.h> DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); DEFINE_VECTOR(ARMDebugWatchpointList, struct ARMDebugWatchpoint);@@ -22,6 +23,13 @@ return ARMDebugBreakpointListGetPointer(breakpoints, i);
} } return 0; +} + +static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } } static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {@@ -37,6 +45,13 @@ struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength);
if (!breakpoint) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address, .type.bp.breakType = BREAKPOINT_HARDWARE@@ -50,6 +65,7 @@
static void ARMDebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void ARMDebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); static void ARMDebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment);@@ -65,6 +81,7 @@ platform->entered = ARMDebuggerEnter;
platform->init = ARMDebuggerInit; platform->deinit = ARMDebuggerDeinit; platform->setBreakpoint = ARMDebuggerSetBreakpoint; + platform->setConditionalBreakpoint = ARMDebuggerSetConditionalBreakpoint; platform->clearBreakpoint = ARMDebuggerClearBreakpoint; platform->setWatchpoint = ARMDebuggerSetWatchpoint; platform->clearWatchpoint = ARMDebuggerClearWatchpoint;@@ -97,6 +114,10 @@ }
} ARMDebuggerRemoveMemoryShim(debugger); + size_t i; + for (i = 0; i < ARMDebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } ARMDebugBreakpointListDeinit(&debugger->breakpoints); ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); ARMDebugWatchpointListDeinit(&debugger->watchpoints);@@ -168,6 +189,16 @@ static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) {
UNUSED(segment); struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->condition = NULL; + breakpoint->address = address; + breakpoint->isSw = false; +} + +static void ARMDebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { + UNUSED(segment); + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->condition = condition; breakpoint->address = address; breakpoint->isSw = false; }@@ -179,6 +210,7 @@ struct ARMDebugBreakpointList* breakpoints = &debugger->breakpoints;
size_t i; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) { if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) { + _destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i)); ARMDebugBreakpointListShift(breakpoints, i, 1); } }
@@ -62,8 +62,8 @@ static void _source(struct CLIDebugger*, struct CLIDebugVector*);
#endif static struct CLIDebuggerCommandSummary _debuggerCommands[] = { - { "b", _setBreakpoint, "I", "Set a breakpoint" }, - { "break", _setBreakpoint, "I", "Set a breakpoint" }, + { "b", _setBreakpoint, "Is", "Set a breakpoint" }, + { "break", _setBreakpoint, "Is", "Set a breakpoint" }, { "c", _continue, "", "Continue execution" }, { "continue", _continue, "", "Continue execution" }, { "d", _clearBreakpoint, "I", "Delete a breakpoint" },@@ -458,7 +458,39 @@ debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
return; } uint32_t address = dv->intValue; - debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) { + struct LexVector lv; + bool error = false; + LexVectorInit(&lv, 0); + const char* string = dv->next->charValue; + size_t length = strlen(dv->next->charValue); + size_t adjusted = lexExpression(&lv, string, length, NULL); + struct ParseTree* tree = malloc(sizeof(*tree)); + if (!adjusted) { + error = true; + } else { + parseLexedExpression(tree, &lv); + + if (adjusted > length) { + error = true; + } else { + length -= adjusted; + string += adjusted; + } + } + lexFree(&lv); + LexVectorClear(&lv); + LexVectorDeinit(&lv); + if (error) { + parseFree(tree); + free(tree); + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } else { + debugger->d.platform->setConditionalBreakpoint(debugger->d.platform, address, dv->segmentValue, tree); + } + } else { + debugger->d.platform->setBreakpoint(debugger->d.platform, address, dv->segmentValue); + } } static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {@@ -547,7 +579,7 @@ struct CLIDebugVector dvTemp = { .type = CLIDV_INT_TYPE, .segmentValue = -1 };
struct LexVector lv; LexVectorInit(&lv, 0); - size_t adjusted = lexExpression(&lv, string, length); + size_t adjusted = lexExpression(&lv, string, length, " "); if (adjusted > length) { dvTemp.type = CLIDV_ERROR_TYPE; }@@ -562,8 +594,7 @@ dvTemp.type = CLIDV_ERROR_TYPE;
} } - parseFree(tree.lhs); - parseFree(tree.rhs); + parseFree(&tree); lexFree(&lv); LexVectorDeinit(&lv);
@@ -166,13 +166,20 @@ lvNext = LexVectorAppend(lv);
lvNext->type = TOKEN_CLOSE_PAREN_TYPE; *state = LEX_EXPECT_OPERATOR; break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_UINT_TYPE; + lvNext->uintValue = next; + *state = LEX_EXPECT_OPERATOR; + break; default: *state = LEX_ERROR; break; } } -size_t lexExpression(struct LexVector* lv, const char* string, size_t length) { +size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol) { if (!string || length < 1) { return 0; }@@ -184,7 +191,11 @@ enum LexState state = LEX_ROOT;
const char* tokenStart = 0; struct Token* lvNext; - while (length > 0 && string[0] && string[0] != ' ' && state != LEX_ERROR) { + if (!eol) { + eol = " \r\n"; + } + + while (length > 0 && string[0] && !strchr(eol, string[0]) && state != LEX_ERROR) { char token = string[0]; ++string; ++adjusted;@@ -236,6 +247,9 @@ state = LEX_ROOT;
lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_OPEN_PAREN_TYPE; break; + case ' ': + case '\t': + break; default: if (tolower(token) >= 'a' && tolower(token <= 'z')) { state = LEX_EXPECT_IDENTIFIER;@@ -272,6 +286,13 @@ lvNext = LexVectorAppend(lv);
lvNext->type = TOKEN_CLOSE_PAREN_TYPE; state = LEX_EXPECT_OPERATOR; break; + case ' ': + case '\t': + lvNext = LexVectorAppend(lv); + lvNext->type = TOKEN_IDENTIFIER_TYPE; + lvNext->identifierValue = strndup(tokenStart, string - tokenStart - 1); + state = LEX_EXPECT_OPERATOR; + break; default: break; }@@ -412,7 +433,9 @@ break;
case ')': lvNext = LexVectorAppend(lv); lvNext->type = TOKEN_CLOSE_PAREN_TYPE; - state = LEX_EXPECT_OPERATOR; + break; + case ' ': + case '\t': break; default: state = LEX_ERROR;@@ -585,13 +608,18 @@ if (!tree) {
return; } - parseFree(tree->lhs); - parseFree(tree->rhs); + if (tree->lhs) { + parseFree(tree->lhs); + free(tree->lhs); + } + if (tree->rhs) { + parseFree(tree->rhs); + free(tree->rhs); + } if (tree->token.type == TOKEN_IDENTIFIER_TYPE) { free(tree->token.identifierValue); } - free(tree); } static bool _performOperation(enum Operation operation, int32_t current, int32_t next, int32_t* value) {
@@ -11,7 +11,7 @@ #define LEX(STR) \
struct LexVector* lv = *state; \ lexFree(lv); \ LexVectorClear(lv); \ - size_t adjusted = lexExpression(lv, STR, strlen(STR)); \ + size_t adjusted = lexExpression(lv, STR, strlen(STR), ""); \ assert_false(adjusted > strlen(STR)) M_TEST_SUITE_SETUP(Lexer) {@@ -715,6 +715,68 @@ assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE);
assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); } +M_TEST_DEFINE(lexSpaceSimple) { + LEX(" 1 "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); +} + +M_TEST_DEFINE(lexSpaceIdentifier) { + LEX(" x "); + + assert_int_equal(LexVectorSize(lv), 1); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_IDENTIFIER_TYPE); + assert_string_equal(LexVectorGetPointer(lv, 0)->identifierValue, "x"); +} + +M_TEST_DEFINE(lexSpaceOperator) { + LEX("1 + 2"); + + assert_int_equal(LexVectorSize(lv), 3); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 0)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->uintValue, 2); +} + +M_TEST_DEFINE(lexSpaceParen) { + LEX(" ( 1 + 2 ) "); + + assert_int_equal(LexVectorSize(lv), 5); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 3)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_CLOSE_PAREN_TYPE); +} + +M_TEST_DEFINE(lexSpaceParens) { + LEX(" ( 1 + ( 2 + 3 ) ) "); + + assert_int_equal(LexVectorSize(lv), 9); + assert_int_equal(LexVectorGetPointer(lv, 0)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 1)->uintValue, 1); + assert_int_equal(LexVectorGetPointer(lv, 2)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 2)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 3)->type, TOKEN_OPEN_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 4)->uintValue, 2); + assert_int_equal(LexVectorGetPointer(lv, 5)->type, TOKEN_OPERATOR_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 5)->operatorValue, OP_ADD); + assert_int_equal(LexVectorGetPointer(lv, 6)->type, TOKEN_UINT_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 6)->uintValue, 3); + assert_int_equal(LexVectorGetPointer(lv, 7)->type, TOKEN_CLOSE_PAREN_TYPE); + assert_int_equal(LexVectorGetPointer(lv, 8)->type, TOKEN_CLOSE_PAREN_TYPE); +} + M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer, cmocka_unit_test(lexEmpty), cmocka_unit_test(lexInt),@@ -785,4 +847,9 @@ cmocka_unit_test(lexOpenParen),
cmocka_unit_test(lexCloseParen), cmocka_unit_test(lexIdentifierCloseParen), cmocka_unit_test(lexParentheticalExpression), - cmocka_unit_test(lexNestedParentheticalExpression)) + cmocka_unit_test(lexNestedParentheticalExpression), + cmocka_unit_test(lexSpaceSimple), + cmocka_unit_test(lexSpaceIdentifier), + cmocka_unit_test(lexSpaceOperator), + cmocka_unit_test(lexSpaceParen), + cmocka_unit_test(lexSpaceParens))
@@ -16,7 +16,7 @@ #define PARSE(STR) \
struct LPTest* lp = *state; \ lexFree(&lp->lv); \ LexVectorClear(&lp->lv); \ - size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR)); \ + size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR), ""); \ assert_false(adjusted > strlen(STR)); \ struct ParseTree* tree = &lp->tree; \ parseLexedExpression(tree, &lp->lv)@@ -30,8 +30,7 @@ }
M_TEST_SUITE_TEARDOWN(Parser) { struct LPTest* lp = *state; - parseFree(lp->tree.lhs); \ - parseFree(lp->tree.rhs); \ + parseFree(&lp->tree); \ lexFree(&lp->lv); LexVectorDeinit(&lp->lv); free(lp);
@@ -6,6 +6,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include <mgba/internal/lr35902/debugger/debugger.h> #include <mgba/core/core.h> +#include <mgba/internal/debugger/parser.h> #include <mgba/internal/lr35902/decoder.h> #include <mgba/internal/lr35902/lr35902.h> #include <mgba/internal/lr35902/debugger/memory-debugger.h>@@ -21,6 +22,13 @@ return LR35902DebugBreakpointListGetPointer(breakpoints, i);
} } return 0; +} + +static void _destroyBreakpoint(struct LR35902DebugBreakpoint* breakpoint) { + if (breakpoint->condition) { + parseFree(breakpoint->condition); + free(breakpoint->condition); + } } static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {@@ -32,6 +40,13 @@ }
if (breakpoint->segment >= 0 && debugger->cpu->memory.currentSegment(debugger->cpu, breakpoint->address) != breakpoint->segment) { return; } + if (breakpoint->condition) { + int32_t value; + int segment; + if (!mDebuggerEvaluateParseTree(d->p, breakpoint->condition, &value, &segment) || !(value || segment >= 0)) { + return; + } + } struct mDebuggerEntryInfo info = { .address = breakpoint->address };@@ -44,6 +59,7 @@
static void LR35902DebuggerEnter(struct mDebuggerPlatform* d, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info); static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment, struct ParseTree* condition); static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform*, uint32_t address, int segment); static void LR35902DebuggerSetWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment, enum mWatchpointType type); static void LR35902DebuggerClearWatchpoint(struct mDebuggerPlatform*, uint32_t address, int segment);@@ -59,6 +75,7 @@ platform->entered = LR35902DebuggerEnter;
platform->init = LR35902DebuggerInit; platform->deinit = LR35902DebuggerDeinit; platform->setBreakpoint = LR35902DebuggerSetBreakpoint; + platform->setConditionalBreakpoint = LR35902DebuggerSetConditionalBreakpoint; platform->clearBreakpoint = LR35902DebuggerClearBreakpoint; platform->setWatchpoint = LR35902DebuggerSetWatchpoint; platform->clearWatchpoint = LR35902DebuggerClearWatchpoint;@@ -79,6 +96,10 @@ }
void LR35902DebuggerDeinit(struct mDebuggerPlatform* platform) { struct LR35902Debugger* debugger = (struct LR35902Debugger*) platform; + size_t i; + for (i = 0; i < LR35902DebugBreakpointListSize(&debugger->breakpoints); ++i) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(&debugger->breakpoints, i)); + } LR35902DebugBreakpointListDeinit(&debugger->breakpoints); LR35902DebugWatchpointListDeinit(&debugger->watchpoints); }@@ -100,6 +121,15 @@ struct LR35902Debugger* debugger = (struct LR35902Debugger*) d;
struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); breakpoint->address = address; breakpoint->segment = segment; + breakpoint->condition = NULL; +} + +static void LR35902DebuggerSetConditionalBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment, struct ParseTree* condition) { + struct LR35902Debugger* debugger = (struct LR35902Debugger*) d; + struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListAppend(&debugger->breakpoints); + breakpoint->address = address; + breakpoint->segment = segment; + breakpoint->condition = condition; } static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t address, int segment) {@@ -109,6 +139,7 @@ size_t i;
for (i = 0; i < LR35902DebugBreakpointListSize(breakpoints); ++i) { struct LR35902DebugBreakpoint* breakpoint = LR35902DebugBreakpointListGetPointer(breakpoints, i); if (breakpoint->address == address && breakpoint->segment == segment) { + _destroyBreakpoint(LR35902DebugBreakpointListGetPointer(breakpoints, i)); LR35902DebugBreakpointListShift(breakpoints, i, 1); } }