Add range watchpoints.

These are accessible via the following new CLI debugger commands:

 - rw: watchr minAddr maxAddr [cond]
 - r: watchr/r minAddr maxAddr [cond]
 - w: watchr/w minAddr maxAddr [cond]
 - c: watchr/c minAddr maxAddr [cond]

This also makes all watchpoints range watchpoints under the hood.

Preliminary benchmark results:

Time taken to run 10000 frames of Megaman Battle Network 1 (U) with a write watchpoint set at 0x02000000 in milliseconds, 10 runs each:

control (no watchpoint):
[4184, 4185, 4197, 4207, 4220, 4178, 4304, 4226, 4234, 4292]
mean = 4223, stdev = 43.95

old (single address watchpoint):
[4743, 4685, 4679, 4670, 4782, 4704, 4698, 4875, 4746, 4718]
mean = 4730, stdev = 61.67

new (range watchpoint):
[4683, 4691, 4693, 4706, 4782, 4674, 4746, 4768, 4770, 4776]
mean = 4728, stdev = 43.36
This commit is contained in:
bigfarts 2022-08-10 19:59:02 -07:00 committed by Vicki Pfau
parent 5415cd72e2
commit 42f7876731
6 changed files with 96 additions and 12 deletions

View File

@ -1,6 +1,7 @@
0.11.0: (Future)
Features:
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng
- Debugger: Add range watchpoints
Other fixes:
- Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681)
- Qt: Expand criteria for tag branch names (fixes mgba.io/i/2679)

View File

@ -88,8 +88,9 @@ struct mBreakpoint {
struct mWatchpoint {
ssize_t id;
uint32_t address;
int segment;
uint32_t minAddress;
uint32_t maxAddress;
enum mWatchpointType type;
struct ParseTree* condition;
};

View File

@ -92,12 +92,13 @@ CREATE_MULTIPLE_WATCHPOINT_SHIM(storeMultiple, WATCHPOINT_WRITE)
CREATE_SHIM(setActiveRegion, void, (struct ARMCore* cpu, uint32_t address), address)
static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, struct mDebuggerEntryInfo* info, enum mWatchpointType type, uint32_t newValue, int width) {
--width;
struct mWatchpoint* watchpoint;
size_t i;
uint32_t minAddress = address & ~(width - 1);
uint32_t maxAddress = minAddress + width;
for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) {
watchpoint = mWatchpointListGetPointer(&debugger->watchpoints, i);
if (!((watchpoint->address ^ address) & ~width) && watchpoint->type & type) {
if (watchpoint->type & type && watchpoint->minAddress < maxAddress && minAddress < watchpoint->maxAddress) {
if (watchpoint->condition) {
int32_t value;
int segment;
@ -107,7 +108,7 @@ static bool _checkWatchpoints(struct ARMDebugger* debugger, uint32_t address, st
}
uint32_t oldValue;
switch (width + 1) {
switch (width) {
case 1:
oldValue = debugger->originalMemory.load8(debugger->cpu, address, 0);
break;

View File

@ -60,6 +60,10 @@ static void _setReadWriteWatchpoint(struct CLIDebugger*, struct CLIDebugVector*)
static void _setReadWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setWriteWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setWriteChangedWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setReadWriteRangeWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setReadRangeWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setWriteRangeWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _setWriteChangedRangeWatchpoint(struct CLIDebugger*, struct CLIDebugVector*);
static void _listWatchpoints(struct CLIDebugger*, struct CLIDebugVector*);
static void _trace(struct CLIDebugger*, struct CLIDebugVector*);
static void _writeByte(struct CLIDebugger*, struct CLIDebugVector*);
@ -114,6 +118,10 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = {
{ "watch/c", _setWriteChangedWatchpoint, "Is", "Set a change watchpoint" },
{ "watch/r", _setReadWatchpoint, "Is", "Set a read watchpoint" },
{ "watch/w", _setWriteWatchpoint, "Is", "Set a write watchpoint" },
{ "watch-range", _setReadWriteRangeWatchpoint, "IIs", "Set a range watchpoint" },
{ "watch-range/c", _setWriteChangedRangeWatchpoint, "IIs", "Set a change range watchpoint" },
{ "watch-range/r", _setReadRangeWatchpoint, "IIs", "Set a read range watchpoint" },
{ "watch-range/w", _setWriteRangeWatchpoint, "IIs", "Set a write range watchpoint" },
{ "x/1", _dumpByte, "Ii", "Examine bytes at a specified offset" },
{ "x/2", _dumpHalfword, "Ii", "Examine halfwords at a specified offset" },
{ "x/4", _dumpWord, "Ii", "Examine words at a specified offset" },
@ -146,6 +154,10 @@ static struct CLIDebuggerCommandAlias _debuggerCommandAliases[] = {
{ "p/x", "print/x" },
{ "q", "quit" },
{ "w", "watch" },
{ "watchr", "watch-range" },
{ "watchr/c", "watch-range/c" },
{ "watchr/r", "watch-range/r" },
{ "watchr/w", "watch-range/w" },
{ ".", "source" },
{ 0, 0 }
};
@ -647,8 +659,9 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
return;
}
struct mWatchpoint watchpoint = {
.address = dv->intValue,
.segment = dv->segmentValue,
.minAddress = dv->intValue,
.maxAddress = dv->intValue + 1,
.type = type
};
if (dv->next && dv->next->type == CLIDV_CHAR_TYPE) {
@ -666,6 +679,48 @@ static void _setWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector*
}
}
static void _setRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv, enum mWatchpointType type) {
if (!dv || dv->type != CLIDV_INT_TYPE) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
return;
}
if (!dv->next || dv->next->type != CLIDV_INT_TYPE) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
return;
}
if (!debugger->d.platform->setWatchpoint) {
debugger->backend->printf(debugger->backend, "Watchpoints are not supported by this platform.\n");
return;
}
if (dv->intValue >= dv->next->intValue) {
debugger->backend->printf(debugger->backend, "Range watchpoint end is before start. Note that the end of the range is not included.\n");
return;
}
if (dv->segmentValue != dv->next->segmentValue) {
debugger->backend->printf(debugger->backend, "Range watchpoint does not start and end in the same segment.\n");
return;
}
struct mWatchpoint watchpoint = {
.segment = dv->segmentValue,
.minAddress = dv->intValue,
.maxAddress = dv->next->intValue,
.type = type
};
if (dv->next->next && dv->next->next->type == CLIDV_CHAR_TYPE) {
struct ParseTree* tree = _parseTree((const char*[]) { dv->next->next->charValue, NULL });
if (tree) {
watchpoint.condition = tree;
} else {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS);
return;
}
}
ssize_t id = debugger->d.platform->setWatchpoint(debugger->d.platform, &watchpoint);
if (id > 0) {
debugger->backend->printf(debugger->backend, INFO_WATCHPOINT_ADDED, id);
}
}
static void _setReadWriteWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
_setWatchpoint(debugger, dv, WATCHPOINT_RW);
}
@ -682,6 +737,22 @@ static void _setWriteChangedWatchpoint(struct CLIDebugger* debugger, struct CLID
_setWatchpoint(debugger, dv, WATCHPOINT_WRITE_CHANGE);
}
static void _setReadWriteRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
_setRangeWatchpoint(debugger, dv, WATCHPOINT_RW);
}
static void _setReadRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
_setRangeWatchpoint(debugger, dv, WATCHPOINT_READ);
}
static void _setWriteRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
_setRangeWatchpoint(debugger, dv, WATCHPOINT_WRITE);
}
static void _setWriteChangedRangeWatchpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
_setRangeWatchpoint(debugger, dv, WATCHPOINT_WRITE_CHANGE);
}
static void _clearBreakpoint(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
if (!dv || dv->type != CLIDV_INT_TYPE) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
@ -717,9 +788,17 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector
for (i = 0; i < mWatchpointListSize(&watchpoints); ++i) {
struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, i);
if (watchpoint->segment >= 0) {
debugger->backend->printf(debugger->backend, "%" PRIz "i: %02X:%X\n", watchpoint->id, watchpoint->segment, watchpoint->address);
if (watchpoint->maxAddress == watchpoint->minAddress + 1) {
debugger->backend->printf(debugger->backend, "%" PRIz "i: %02X:%X\n", watchpoint->id, watchpoint->segment, watchpoint->minAddress);
} else {
debugger->backend->printf(debugger->backend, "%" PRIz "i: %02X:%X-%X\n", watchpoint->id, watchpoint->segment, watchpoint->minAddress, watchpoint->maxAddress);
}
} else {
debugger->backend->printf(debugger->backend, "%" PRIz "i: 0x%X\n", watchpoint->id, watchpoint->address);
if (watchpoint->maxAddress == watchpoint->minAddress + 1) {
debugger->backend->printf(debugger->backend, "%" PRIz "i: 0x%X\n", watchpoint->id, watchpoint->minAddress);
} else {
debugger->backend->printf(debugger->backend, "%" PRIz "i: 0x%X-0x%X\n", watchpoint->id, watchpoint->minAddress, watchpoint->maxAddress);
}
}
}
mWatchpointListDeinit(&watchpoints);

View File

@ -532,7 +532,7 @@ static void _processQReadCommand(struct GDBStub* stub, const char* message) {
} else if (!strncmp("Xfer:memory-map:read::", message, 22)) {
if (strlen(stub->memoryMapXml) == 0) {
_generateMemoryMapXml(stub, stub->memoryMapXml);
}
}
_processQXferCommand(stub, message + 22, stub->memoryMapXml);
} else if (!strncmp("Supported:", message, 10)) {
_processQSupportedCommand(stub, message + 10);
@ -575,7 +575,8 @@ static void _setBreakpoint(struct GDBStub* stub, const char* message) {
.type = BREAKPOINT_HARDWARE
};
struct mWatchpoint watchpoint = {
.address = address
.minAddress = address,
.maxAddress = address + 1
};
switch (message[0]) {
@ -631,10 +632,11 @@ static void _clearBreakpoint(struct GDBStub* stub, const char* message) {
mWatchpointListInit(&watchpoints, 0);
stub->d.platform->listWatchpoints(stub->d.platform, &watchpoints);
for (index = 0; index < mWatchpointListSize(&watchpoints); ++index) {
if (mWatchpointListGetPointer(&watchpoints, index)->address != address) {
struct mWatchpoint* watchpoint = mWatchpointListGetPointer(&watchpoints, index);
if (address >= watchpoint->minAddress && address < watchpoint->maxAddress) {
continue;
}
stub->d.platform->clearBreakpoint(stub->d.platform, mWatchpointListGetPointer(&watchpoints, index)->id);
stub->d.platform->clearBreakpoint(stub->d.platform, watchpoint->id);
}
mWatchpointListDeinit(&watchpoints);
break;

View File

@ -47,7 +47,7 @@ static bool _checkWatchpoints(struct SM83Debugger* debugger, uint16_t address, s
size_t i;
for (i = 0; i < mWatchpointListSize(&debugger->watchpoints); ++i) {
watchpoint = mWatchpointListGetPointer(&debugger->watchpoints, i);
if (watchpoint->address == address && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address)) && watchpoint->type & type) {
if (watchpoint->type & type && address >= watchpoint->minAddress && address < watchpoint->maxAddress && (watchpoint->segment < 0 || watchpoint->segment == debugger->originalMemory.currentSegment(debugger->cpu, address))) {
if (watchpoint->condition) {
int32_t value;
int segment;