all repos — mgba @ c45d91d311854c824faa1055038c676e1cfc7e7b

mGBA Game Boy Advance Emulator

Debugger: Add software breakpoints, fix cleanup
Jeffrey Pfau jeffrey@endrift.com
Wed, 07 Sep 2016 17:49:27 -0700
commit

c45d91d311854c824faa1055038c676e1cfc7e7b

parent

fff0e9b83cbab7934ecaf0f0e6376677b9109495

M CHANGESCHANGES

@@ -92,6 +92,7 @@ - GBA: Better debug logging if event processing breaks

- 3DS: 3D banner - FFmpeg: Full support for libavcodec 56+ - Util: PNG utils should support 16-bit when applicable + - Debugger: Add software breakpoint support to gdb 0.4.1: (2016-07-11) Bugfixes:
M src/arm/debugger/debugger.csrc/arm/debugger/debugger.c

@@ -1,4 +1,4 @@

-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this

@@ -37,7 +37,8 @@ if (!breakpoint) {

return; } struct mDebuggerEntryInfo info = { - .address = breakpoint->address + .address = breakpoint->address, + .breakType = BREAKPOINT_HARDWARE }; mDebuggerEnter(d->p, DEBUGGER_ENTER_BREAKPOINT, &info); }

@@ -79,6 +80,16 @@ }

void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { struct ARMDebugger* debugger = (struct ARMDebugger*) platform; + if (debugger->clearSoftwareBreakpoint) { + // Clear the stack backwards in case any overlap + size_t b; + for (b = ARMDebugBreakpointListSize(&debugger->swBreakpoints); b; --b) { + struct ARMDebugBreakpoint* breakpoint = ARMDebugBreakpointListGetPointer(&debugger->swBreakpoints, b - 1); + debugger->clearSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, breakpoint->sw.opcode); + } + } + ARMDebuggerRemoveMemoryShim(debugger); + ARMDebugBreakpointListDeinit(&debugger->breakpoints); ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); ARMDebugWatchpointListDeinit(&debugger->watchpoints);

@@ -103,8 +114,8 @@ debugger->setSoftwareBreakpoint(debugger, breakpoint->address, breakpoint->sw.mode, &breakpoint->sw.opcode);

} } } - if (debugger->entered) { - debugger->entered(debugger->d.p, reason, info); + if (debugger->d.p->entered) { + debugger->d.p->entered(debugger->d.p, reason, info); } }

@@ -122,6 +133,26 @@ breakpoint->sw.opcode = opcode;

breakpoint->sw.mode = mode; return true; +} + +void ARMDebuggerClearSoftwareBreakpoint(struct mDebuggerPlatform* d, uint32_t address) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + if (!debugger->clearSoftwareBreakpoint) { + return; + } + + struct ARMDebugBreakpoint* breakpoint = NULL; + // Clear the stack backwards in case any overlap + size_t b; + for (b = ARMDebugBreakpointListSize(&debugger->swBreakpoints); b; --b) { + breakpoint = ARMDebugBreakpointListGetPointer(&debugger->swBreakpoints, b - 1); + if (breakpoint->address == address) { + break; + } + breakpoint = NULL; + } + + debugger->clearSoftwareBreakpoint(debugger, address, breakpoint->sw.mode, breakpoint->sw.opcode); } static void ARMDebuggerSetBreakpoint(struct mDebuggerPlatform* d, uint32_t address) {
M src/arm/debugger/debugger.hsrc/arm/debugger/debugger.h

@@ -39,3 +39,4 @@ };

struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void); bool ARMDebuggerSetSoftwareBreakpoint(struct mDebuggerPlatform* debugger, uint32_t address, enum ExecutionMode mode); +void ARMDebuggerClearSoftwareBreakpoint(struct mDebuggerPlatform* debugger, uint32_t address);
A src/debugger/CMakeLists.txt

@@ -0,0 +1,22 @@

+set(SRC + debugger.c + parser.c + ) + +set(RENDERER_SRC + renderers/software-bg.c + renderers/software-mode0.c + renderers/software-obj.c + renderers/video-software.c + ) + +if(USE_CLI_DEBUGGER) + list(APPEND SRC cli-debugger.c) +endif() + +if(USE_GDB_STUB) + list(APPEND SRC gdb-stub.c) +endif() + +source_group("Debugger source" FILES ${SRC}) +add_internal_library(debugger "${SRC}" "")
M src/debugger/debugger.csrc/debugger/debugger.c

@@ -102,8 +102,8 @@ }

void mDebuggerEnter(struct mDebugger* debugger, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) { debugger->state = DEBUGGER_PAUSED; - if (debugger->entered) { - debugger->entered(debugger, reason, info); + if (debugger->platform->entered) { + debugger->platform->entered(debugger->platform, reason, info); } }
M src/debugger/debugger.hsrc/debugger/debugger.h

@@ -40,6 +40,11 @@ WATCHPOINT_READ = 2,

WATCHPOINT_RW = WATCHPOINT_WRITE | WATCHPOINT_READ }; +enum mBreakpointType { + BREAKPOINT_HARDWARE, + BREAKPOINT_SOFTWARE +}; + enum mDebuggerEntryReason { DEBUGGER_ENTER_MANUAL, DEBUGGER_ENTER_ATTACHED,

@@ -48,6 +53,14 @@ DEBUGGER_ENTER_WATCHPOINT,

DEBUGGER_ENTER_ILLEGAL_OP }; +struct mDebugWatchpoint { + uint32_t address; + enum mWatchpointType type; +}; + +DECLARE_VECTOR(mDebugBreakpointList, struct mDebugBreakpoint); +DECLARE_VECTOR(mDebugWatchpointList, struct mDebugWatchpoint); + extern const char* ERROR_MISSING_ARGS; extern const char* ERROR_OVERFLOW;

@@ -63,6 +76,7 @@ };

struct { uint32_t opcode; + enum mBreakpointType breakType; }; }; };
M src/debugger/gdb-stub.csrc/debugger/gdb-stub.c

@@ -1,10 +1,11 @@

-/* Copyright (c) 2013-2014 Jeffrey Pfau +/* Copyright (c) 2013-2016 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "gdb-stub.h" +#include "arm/debugger/debugger.h" #include "arm/isa-inlines.h" #include "core/core.h" #include "gba/memory.h"

@@ -44,7 +45,11 @@ case DEBUGGER_ENTER_MANUAL:

snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGINT); break; case DEBUGGER_ENTER_BREAKPOINT: - snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGTRAP); // TODO: Use hwbreak/swbreak if gdb supports it + if (stub->supportsHwbreak && stub->supportsSwbreak && info) { + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "T%02x%cwbreak:;", SIGTRAP, info->breakType == BREAKPOINT_SOFTWARE ? 's' : 'h'); + } else { + snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02xk", SIGTRAP); + } break; case DEBUGGER_ENTER_WATCHPOINT: if (info) {

@@ -385,6 +390,35 @@ stub->outgoing[8] = '\0';

_sendMessage(stub); } +static void _processQSupportedCommand(struct GDBStub* stub, const char* message) { + const char* terminator = strrchr(message, '#'); + stub->supportsSwbreak = false; + stub->supportsHwbreak = false; + while (message < terminator) { + const char* end = strchr(message, ';'); + size_t len; + if (end && end < terminator) { + len = end - message; + } else { + len = end - terminator; + } + if (!strncmp(message, "swbreak+", 8)) { + stub->supportsSwbreak = true; + } else if (!strncmp(message, "hwbreak+", 8)) { + stub->supportsHwbreak = true; + } else if (!strncmp(message, "swbreak-", 8)) { + stub->supportsSwbreak = false; + } else if (!strncmp(message, "hwbreak-", 8)) { + stub->supportsHwbreak = false; + } + if (!end) { + break; + } + message = end + 1; + } + strncpy(stub->outgoing, "swbreak+;hwbreak+", GDB_STUB_MAX_LINE - 4); +} + static void _processQReadCommand(struct GDBStub* stub, const char* message) { stub->outgoing[0] = '\0'; if (!strncmp("HostInfo#", message, 9)) {

@@ -401,6 +435,8 @@ } else if (!strncmp("fThreadInfo#", message, 12)) {

strncpy(stub->outgoing, "m1", GDB_STUB_MAX_LINE - 4); } else if (!strncmp("sThreadInfo#", message, 12)) { strncpy(stub->outgoing, "l", GDB_STUB_MAX_LINE - 4); + } else if (!strncmp("Supported:", message, 10)) { + _processQSupportedCommand(stub, message + 10); } _sendMessage(stub); }

@@ -434,36 +470,31 @@ const char* readAddress = &message[2];

unsigned i = 0; uint32_t address = _readHex(readAddress, &i); readAddress += i + 1; - uint32_t kind = _readHex(readAddress, &i); // We don't use this in hardware watchpoints - UNUSED(kind); + uint32_t kind = _readHex(readAddress, &i); switch (message[0]) { - case '0': // Memory breakpoints are not currently supported + case '0': + ARMDebuggerSetSoftwareBreakpoint(stub->d.platform, address, kind == 2 ? MODE_THUMB : MODE_ARM); + break; case '1': stub->d.platform->setBreakpoint(stub->d.platform, address); - strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); - _sendMessage(stub); break; case '2': stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_WRITE); - strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); - _sendMessage(stub); break; case '3': stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_READ); - strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); - _sendMessage(stub); break; case '4': stub->d.platform->setWatchpoint(stub->d.platform, address, WATCHPOINT_RW); - strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); - _sendMessage(stub); break; default: stub->outgoing[0] = '\0'; _sendMessage(stub); - break; + return; } + strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4); + _sendMessage(stub); } static void _clearBreakpoint(struct GDBStub* stub, const char* message) {

@@ -471,7 +502,9 @@ const char* readAddress = &message[2];

unsigned i = 0; uint32_t address = _readHex(readAddress, &i); switch (message[0]) { - case '0': // Memory breakpoints are not currently supported + case '0': + ARMDebuggerClearSoftwareBreakpoint(stub->d.platform, address); + break; case '1': stub->d.platform->clearBreakpoint(stub->d.platform, address); break;
M src/debugger/gdb-stub.hsrc/debugger/gdb-stub.h

@@ -34,6 +34,9 @@ Socket connection;

bool shouldBlock; int untilPoll; + + bool supportsSwbreak; + bool supportsHwbreak; }; void GDBStubCreate(struct GDBStub*);
M src/gba/gba.csrc/gba/gba.c

@@ -799,7 +799,8 @@ switch (immediate) {

case CPU_COMPONENT_DEBUGGER: if (gba->debugger) { struct mDebuggerEntryInfo info = { - .address = _ARMPCAddress(cpu) + .address = _ARMPCAddress(cpu), + .breakType = BREAKPOINT_SOFTWARE }; mDebuggerEnter(gba->debugger->d.p, DEBUGGER_ENTER_BREAKPOINT, &info); }