Debugger: Conditional breakpoints

This commit is contained in:
Vicki Pfau 2017-12-29 16:11:40 -05:00
parent 178017a9e0
commit 0383c82b46
11 changed files with 216 additions and 21 deletions

View File

@ -11,6 +11,7 @@ Features:
- 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

View File

@ -70,6 +70,7 @@ struct mDebuggerEntryInfo {
};
struct mDebugger;
struct ParseTree;
struct mDebuggerPlatform {
struct mDebugger* p;
@ -79,6 +80,7 @@ struct mDebuggerPlatform {
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);

View File

@ -15,8 +15,10 @@ CXX_GUARD_START
#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;

View File

@ -12,6 +12,9 @@
CXX_GUARD_START
struct Token;
DECLARE_VECTOR(LexVector, struct Token);
enum Operation {
OP_ASSIGN,
OP_ADD,
@ -54,15 +57,13 @@ struct Token {
};
};
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);

View File

@ -15,10 +15,11 @@ CXX_GUARD_START
#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 {

View File

@ -10,6 +10,7 @@
#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);
@ -24,6 +25,13 @@ static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointLis
return 0;
}
static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) {
if (breakpoint->condition) {
parseFree(breakpoint->condition);
free(breakpoint->condition);
}
}
static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {
struct ARMDebugger* debugger = (struct ARMDebugger*) d;
int instructionLength;
@ -37,6 +45,13 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {
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 ARMDebuggerDeinit(struct mDebuggerPlatform* platform);
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 @@ struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) {
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 @@ void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) {
}
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 addre
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 @@ static void ARMDebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t add
size_t i;
for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) {
if (ARMDebugBreakpointListGetPointer(breakpoints, i)->address == address) {
_destroyBreakpoint(ARMDebugBreakpointListGetPointer(breakpoints, i));
ARMDebugBreakpointListShift(breakpoints, i, 1);
}
}

View File

@ -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 @@ static void _setBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
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* CLIDVParse(struct CLIDebugger* debugger, const char* stri
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 @@ struct CLIDebugVector* CLIDVParse(struct CLIDebugger* debugger, const char* stri
}
}
parseFree(tree.lhs);
parseFree(tree.rhs);
parseFree(&tree);
lexFree(&lv);
LexVectorDeinit(&lv);

View File

@ -166,13 +166,20 @@ static void _lexValue(struct LexVector* lv, char token, uint32_t next, enum LexS
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 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) {
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 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) {
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 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) {
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 @@ size_t lexExpression(struct LexVector* lv, const char* string, size_t length) {
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 @@ void parseFree(struct ParseTree* 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) {

View File

@ -11,7 +11,7 @@
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 @@ M_TEST_DEFINE(lexNestedParentheticalExpression) {
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 @@ M_TEST_SUITE_DEFINE_SETUP_TEARDOWN(Lexer,
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))

View File

@ -16,7 +16,7 @@ struct LPTest {
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_SETUP(Parser) {
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);

View File

@ -6,6 +6,7 @@
#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>
@ -23,6 +24,13 @@ static struct LR35902DebugBreakpoint* _lookupBreakpoint(struct LR35902DebugBreak
return 0;
}
static void _destroyBreakpoint(struct LR35902DebugBreakpoint* breakpoint) {
if (breakpoint->condition) {
parseFree(breakpoint->condition);
free(breakpoint->condition);
}
}
static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {
struct LR35902Debugger* debugger = (struct LR35902Debugger*) d;
struct LR35902DebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->pc);
@ -32,6 +40,13 @@ static void LR35902DebuggerCheckBreakpoints(struct mDebuggerPlatform* d) {
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 LR35902DebuggerDeinit(struct mDebuggerPlatform* platform);
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 @@ struct mDebuggerPlatform* LR35902DebuggerPlatformCreate(void) {
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 LR35902DebuggerInit(void* cpu, struct mDebuggerPlatform* platform) {
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 @@ static void LR35902DebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t a
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 @@ static void LR35902DebuggerClearBreakpoint(struct mDebuggerPlatform* d, uint32_t
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);
}
}