all repos — mgba @ f8ced528f991446b7c8c64ad013ea2f1a3024cc1

mGBA Game Boy Advance Emulator

Merge pull request #1831 from ahigerd/stack-traces

Support for tracking stack traces
Vicki Pfau jeffrey@endrift.com
Mon, 27 Jul 2020 21:35:23 -0700
commit

f8ced528f991446b7c8c64ad013ea2f1a3024cc1

parent

588ca83855d19f3a959eb380261a65bef3b8e1e1

M CHANGESCHANGES

@@ -3,6 +3,7 @@ Features:

- e-Reader card scanning - Add WebP and APNG recording - Support for unlicensed Pokemon Jade/Diamond Game Boy mapper + - Stack tracing tools in ARM debugger (by ahigerd) Emulation fixes: - ARM: Fix ALU reading PC after shifting - ARM: Fix STR storing PC after address calculation
M include/mgba/debugger/debugger.hinclude/mgba/debugger/debugger.h

@@ -13,6 +13,7 @@

#include <mgba/core/cpu.h> #include <mgba/core/log.h> #include <mgba-util/vector.h> +#include <mgba/internal/debugger/stack-trace.h> mLOG_DECLARE_CATEGORY(DEBUGGER);

@@ -50,7 +51,8 @@ DEBUGGER_ENTER_MANUAL,

DEBUGGER_ENTER_ATTACHED, DEBUGGER_ENTER_BREAKPOINT, DEBUGGER_ENTER_WATCHPOINT, - DEBUGGER_ENTER_ILLEGAL_OP + DEBUGGER_ENTER_ILLEGAL_OP, + DEBUGGER_ENTER_STACK }; struct mDebuggerEntryInfo {

@@ -67,6 +69,10 @@ struct {

uint32_t opcode; enum mBreakpointType breakType; } bp; + + struct { + enum mStackTraceMode traceType; + } st; } type; ssize_t pointId; };

@@ -114,6 +120,10 @@

bool (*getRegister)(struct mDebuggerPlatform*, const char* name, int32_t* value); bool (*setRegister)(struct mDebuggerPlatform*, const char* name, int32_t value); bool (*lookupIdentifier)(struct mDebuggerPlatform*, const char* name, int32_t* value, int* segment); + + uint32_t (*getStackTraceMode)(struct mDebuggerPlatform*); + void (*setStackTraceMode)(struct mDebuggerPlatform*, uint32_t mode); + bool (*updateStackTrace)(struct mDebuggerPlatform* d); }; struct mDebugger {

@@ -123,6 +133,7 @@ enum mDebuggerState state;

enum mDebuggerType type; struct mCore* core; struct mScriptBridge* bridge; + struct mStackTrace stackTrace; void (*init)(struct mDebugger*); void (*deinit)(struct mDebugger*);
M include/mgba/internal/arm/arm.hinclude/mgba/internal/arm/arm.h

@@ -70,7 +70,7 @@ struct ARMCore;

union PSR { struct { -#if defined(__BIG_ENDIAN__) +#ifdef __BIG_ENDIAN__ unsigned n : 1; unsigned z : 1; unsigned c : 1;

@@ -94,7 +94,7 @@ #endif

}; struct { -#if defined(__BIG_ENDIAN__) +#ifdef __BIG_ENDIAN__ uint8_t flags; uint8_t status; uint8_t extension;

@@ -147,10 +147,21 @@

void (*hitStub)(struct ARMCore* cpu, uint32_t opcode); }; +#define ARM_REGISTER_FILE struct { \ + int32_t gprs[16]; \ + union PSR cpsr; \ + union PSR spsr; \ +} + +struct ARMRegisterFile { + ARM_REGISTER_FILE; +}; + struct ARMCore { - int32_t gprs[16]; - union PSR cpsr; - union PSR spsr; + union { + struct ARMRegisterFile regs; + ARM_REGISTER_FILE; + }; int32_t cycles; int32_t nextEvent;

@@ -174,6 +185,7 @@

size_t numComponents; struct mCPUComponent** components; }; +#undef ARM_REGISTER_FILE void ARMInit(struct ARMCore* cpu); void ARMDeinit(struct ARMCore* cpu);
M include/mgba/internal/arm/debugger/debugger.hinclude/mgba/internal/arm/debugger/debugger.h

@@ -36,6 +36,7 @@ struct mWatchpointList watchpoints;

struct ARMMemory originalMemory; ssize_t nextId; + uint32_t stackTraceMode; void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*);
M include/mgba/internal/arm/decoder.hinclude/mgba/internal/arm/decoder.h

@@ -14,7 +14,7 @@ #include <mgba/internal/arm/arm.h>

// Bit 0: a register is involved with this operand // Bit 1: an immediate is invovled with this operand -// Bit 2: a memory access is invovled with this operand +// Bit 2: a memory access is involved with this operand // Bit 3: the destination of this operand is affected by this opcode // Bit 4: this operand is shifted by a register // Bit 5: this operand is shifted by an immediate
M include/mgba/internal/arm/isa-inlines.hinclude/mgba/internal/arm/isa-inlines.h

@@ -99,15 +99,12 @@ ARMSetPrivilegeMode(cpu, cpu->cpsr.priv);

cpu->irqh.readCPSR(cpu); } +static inline uint32_t _ARMInstructionLength(struct ARMCore* cpu) { + return cpu->cpsr.t == MODE_ARM ? WORD_SIZE_ARM : WORD_SIZE_THUMB; +} + static inline uint32_t _ARMPCAddress(struct ARMCore* cpu) { - int instructionLength; - enum ExecutionMode mode = cpu->cpsr.t; - if (mode == MODE_ARM) { - instructionLength = WORD_SIZE_ARM; - } else { - instructionLength = WORD_SIZE_THUMB; - } - return cpu->gprs[ARM_PC] - instructionLength * 2; + return cpu->gprs[ARM_PC] - _ARMInstructionLength(cpu) * 2; } #endif
A include/mgba/internal/debugger/stack-trace.h

@@ -0,0 +1,56 @@

+/* Copyright (c) 2013-2020 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/. */ +#ifndef STACK_TRACE_H +#define STACK_TRACE_H + +#include <mgba-util/common.h> + +CXX_GUARD_START + +#include <mgba/core/cpu.h> +#include <mgba/core/log.h> +#include <mgba-util/vector.h> + +enum mStackTraceMode { + STACK_TRACE_DISABLED = 0, + STACK_TRACE_ENABLED = 1, + STACK_TRACE_BREAK_ON_RETURN = 2, + STACK_TRACE_BREAK_ON_CALL = 4, + STACK_TRACE_BREAK_ON_BOTH = STACK_TRACE_BREAK_ON_RETURN | STACK_TRACE_BREAK_ON_CALL +}; + +struct mStackFrame { + uint32_t callAddress; + uint32_t entryAddress; + uint32_t frameBaseAddress; + void* regs; + bool finished; + bool breakWhenFinished; + bool interrupt; +}; + +DECLARE_VECTOR(mStackFrames, struct mStackFrame); + +struct mStackTrace { + struct mStackFrames stack; + size_t registersSize; + + void (*formatRegisters)(struct mStackFrame* frame, char* out, size_t* length); +}; + +void mStackTraceInit(struct mStackTrace* stack, size_t registersSize); +void mStackTraceDeinit(struct mStackTrace* stack); + +void mStackTraceClear(struct mStackTrace* stack); +size_t mStackTraceGetDepth(struct mStackTrace* stack); +struct mStackFrame* mStackTracePush(struct mStackTrace* stack, uint32_t pc, uint32_t destAddress, uint32_t sp, void* regs); +struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, uint32_t frame); +void mStackTraceFormatFrame(struct mStackTrace* stack, uint32_t frame, char* out, size_t* length); +void mStackTracePop(struct mStackTrace* stack); + +CXX_GUARD_END + +#endif
M include/mgba/internal/sm83/sm83.hinclude/mgba/internal/sm83/sm83.h

@@ -82,6 +82,14 @@ uint8_t LOW; \

}; \ uint16_t HIGH ## LOW; \ } + +#define SM83_AF_REGISTER union { \ + struct { \ + uint8_t a; \ + union FlagRegister f; \ + }; \ + uint16_t af; \ + } #else #define SM83_REGISTER_PAIR(HIGH, LOW) union { \ struct { \

@@ -90,28 +98,38 @@ uint8_t HIGH; \

}; \ uint16_t HIGH ## LOW; \ } + +#define SM83_AF_REGISTER union { \ + struct { \ + union FlagRegister f; \ + uint8_t a; \ + }; \ + uint16_t af; \ + } #endif +#define SM83_REGISTER_FILE struct { \ + SM83_AF_REGISTER; \ + SM83_REGISTER_PAIR(b, c); \ + SM83_REGISTER_PAIR(d, e); \ + SM83_REGISTER_PAIR(h, l); \ + uint16_t sp; \ + uint16_t pc; \ +} + +struct SM83RegisterFile { +#pragma pack(push, 1) + SM83_REGISTER_FILE; +#pragma pack(pop) +}; + struct SM83Core { #pragma pack(push, 1) union { - struct { -#ifdef __BIG_ENDIAN__ - uint8_t a; - union FlagRegister f; -#else - union FlagRegister f; - uint8_t a; -#endif - }; - uint16_t af; + struct SM83RegisterFile regs; + SM83_REGISTER_FILE; }; #pragma pack(pop) - SM83_REGISTER_PAIR(b, c); - SM83_REGISTER_PAIR(d, e); - SM83_REGISTER_PAIR(h, l); - uint16_t sp; - uint16_t pc; uint16_t index;

@@ -134,6 +152,7 @@

size_t numComponents; struct mCPUComponent** components; }; +#undef SM83_REGISTER_FILE void SM83Init(struct SM83Core* cpu); void SM83Deinit(struct SM83Core* cpu);
M src/arm/debugger/debugger.csrc/arm/debugger/debugger.c

@@ -11,9 +11,103 @@ #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> +#include <mgba/internal/debugger/stack-trace.h> +#include <mgba-util/math.h> DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); +static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uint32_t pc) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct ARMCore* cpu = debugger->cpu; + struct ARMInstructionInfo info; + uint32_t instruction = cpu->prefetch[0]; + struct mStackTrace* stack = &d->p->stackTrace; + bool interrupt = false; + ARMDecodeARM(instruction, &info); + + if (_ARMModeHasSPSR(cpu->cpsr.priv)) { + struct mStackFrame* irqFrame = mStackTraceGetFrame(stack, 0); + // TODO: uint32_t ivtBase = ARMControlRegIsVE(cpu->cp15.r1.c0) ? 0xFFFF0000 : 0x00000000; + uint32_t ivtBase = 0x00000000; + if (ivtBase <= pc && pc < ivtBase + 0x20 && !(irqFrame && _ARMModeHasSPSR(((struct ARMRegisterFile*) irqFrame->regs)->cpsr.priv))) { + // TODO: Potential enhancement opportunity: add break-on-exception mode + irqFrame = mStackTracePush(stack, pc, pc, cpu->gprs[ARM_SP], &cpu->regs); + irqFrame->interrupt = true; + interrupt = true; + } + } + + if (info.branchType == ARM_BRANCH_NONE && !interrupt) { + return false; + } + + struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); + bool isCall = (info.branchType & ARM_BRANCH_LINKED); + uint32_t destAddress; + + if (frame && frame->finished) { + mStackTracePop(stack); + frame = NULL; + } + + if (interrupt && info.branchType == ARM_BRANCH_NONE) { + // The stack frame was already pushed up above, so there's no + // action necessary here, but we still want to check for a + // breakpoint down below. + // + // The first instruction could possibly be a call, which would + // need ANOTHER stack frame, so only skip if it's not. + } else if (info.operandFormat & ARM_OPERAND_MEMORY_1) { + // This is most likely ldmia ..., {..., pc}, which is a function return. + // To find which stack slot holds the return address, count the number of set bits. + int regCount = popcount32(info.op1.immediate); + uint32_t baseAddress = cpu->gprs[info.memory.baseReg] + ((regCount - 1) << 2); + destAddress = cpu->memory.load32(cpu, baseAddress, NULL); + } else if (info.operandFormat & ARM_OPERAND_IMMEDIATE_1) { + if (!isCall) { + return false; + } + destAddress = info.op1.immediate + cpu->gprs[ARM_PC]; + } else if (info.operandFormat & ARM_OPERAND_REGISTER_1) { + if (!isCall && info.op1.reg != ARM_LR && !(_ARMModeHasSPSR(cpu->cpsr.priv) && info.op1.reg == ARM_PC)) { + return false; + } + destAddress = cpu->gprs[info.op1.reg]; + } else { + mLOG(DEBUGGER, ERROR, "Unknown branch operand in stack trace"); + return false; + } + + if (info.branchType & ARM_BRANCH_INDIRECT) { + destAddress = cpu->memory.load32(cpu, destAddress, NULL); + } + + if (isCall) { + int instructionLength = _ARMInstructionLength(debugger->cpu); + frame = mStackTracePush(stack, pc, destAddress + instructionLength, cpu->gprs[ARM_SP], &cpu->regs); + if (!(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_CALL)) { + return false; + } + } else { + frame = mStackTraceGetFrame(stack, 0); + if (!frame) { + return false; + } + if (!frame->breakWhenFinished && !(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_RETURN)) { + mStackTracePop(stack); + return false; + } + frame->finished = true; + } + struct mDebuggerEntryInfo debuggerInfo = { + .address = pc, + .type.st.traceType = isCall ? STACK_TRACE_BREAK_ON_CALL : STACK_TRACE_BREAK_ON_RETURN, + .pointId = 0 + }; + mDebuggerEnter(d->p, DEBUGGER_ENTER_STACK, &debuggerInfo); + return true; +} + static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointList* breakpoints, uint32_t address) { size_t i; for (i = 0; i < ARMDebugBreakpointListSize(breakpoints); ++i) {

@@ -40,14 +134,12 @@ }

static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; - int instructionLength; - enum ExecutionMode mode = debugger->cpu->cpsr.t; - if (mode == MODE_ARM) { - instructionLength = WORD_SIZE_ARM; - } else { - instructionLength = WORD_SIZE_THUMB; + int instructionLength = _ARMInstructionLength(debugger->cpu); + uint32_t pc = debugger->cpu->gprs[ARM_PC] - instructionLength; + if (debugger->stackTraceMode != STACK_TRACE_DISABLED && ARMDebuggerUpdateStackTraceInternal(d, pc)) { + return; } - struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, debugger->cpu->gprs[ARM_PC] - instructionLength); + struct ARMDebugBreakpoint* breakpoint = _lookupBreakpoint(&debugger->breakpoints, pc); if (!breakpoint) { return; }

@@ -79,8 +171,13 @@ static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchpointList*);

static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform*); static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform*); static void ARMDebuggerTrace(struct mDebuggerPlatform*, char* out, size_t* length); +static void ARMDebuggerFormatRegisters(struct ARMRegisterFile* regs, char* out, size_t* length); +static void ARMDebuggerFrameFormatRegisters(struct mStackFrame* frame, char* out, size_t* length); static bool ARMDebuggerGetRegister(struct mDebuggerPlatform*, const char* name, int32_t* value); static bool ARMDebuggerSetRegister(struct mDebuggerPlatform*, const char* name, int32_t value); +static uint32_t ARMDebuggerGetStackTraceMode(struct mDebuggerPlatform*); +static void ARMDebuggerSetStackTraceMode(struct mDebuggerPlatform*, uint32_t); +static bool ARMDebuggerUpdateStackTrace(struct mDebuggerPlatform* d); struct mDebuggerPlatform* ARMDebuggerPlatformCreate(void) { struct mDebuggerPlatform* platform = (struct mDebuggerPlatform*) malloc(sizeof(struct ARMDebugger));

@@ -97,6 +194,9 @@ platform->hasBreakpoints = ARMDebuggerHasBreakpoints;

platform->trace = ARMDebuggerTrace; platform->getRegister = ARMDebuggerGetRegister; platform->setRegister = ARMDebuggerSetRegister; + platform->getStackTraceMode = ARMDebuggerGetStackTraceMode; + platform->setStackTraceMode = ARMDebuggerSetStackTraceMode; + platform->updateStackTrace = ARMDebuggerUpdateStackTrace; return platform; }

@@ -105,9 +205,13 @@ struct ARMDebugger* debugger = (struct ARMDebugger*) platform;

debugger->cpu = cpu; debugger->originalMemory = debugger->cpu->memory; debugger->nextId = 1; + debugger->stackTraceMode = STACK_TRACE_DISABLED; ARMDebugBreakpointListInit(&debugger->breakpoints, 0); ARMDebugBreakpointListInit(&debugger->swBreakpoints, 0); mWatchpointListInit(&debugger->watchpoints, 0); + struct mStackTrace* stack = &platform->p->stackTrace; + mStackTraceInit(stack, sizeof(struct ARMRegisterFile)); + stack->formatRegisters = ARMDebuggerFrameFormatRegisters; } void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) {

@@ -133,6 +237,7 @@ _destroyWatchpoint(mWatchpointListGetPointer(&debugger->watchpoints, i));

} ARMDebugBreakpointListDeinit(&debugger->swBreakpoints); mWatchpointListDeinit(&debugger->watchpoints); + mStackTraceDeinit(&platform->p->stackTrace); } static void ARMDebuggerEnter(struct mDebuggerPlatform* platform, enum mDebuggerEntryReason reason, struct mDebuggerEntryInfo* info) {

@@ -261,7 +366,7 @@ *b = hw->d;

++i; } else if (sw) { *b = sw->d; - ++s; + ++s; } else { abort(); // Should be unreachable }

@@ -270,7 +375,7 @@ }

static bool ARMDebuggerHasBreakpoints(struct mDebuggerPlatform* d) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; - return ARMDebugBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints); + return ARMDebugBreakpointListSize(&debugger->breakpoints) || mWatchpointListSize(&debugger->watchpoints) || debugger->stackTraceMode != STACK_TRACE_DISABLED; } static ssize_t ARMDebuggerSetWatchpoint(struct mDebuggerPlatform* d, const struct mWatchpoint* info) {

@@ -320,12 +425,23 @@ ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: "));

} } - *length = snprintf(out, *length, "%08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X cpsr: %08X | %s", - cpu->gprs[0], cpu->gprs[1], cpu->gprs[2], cpu->gprs[3], - cpu->gprs[4], cpu->gprs[5], cpu->gprs[6], cpu->gprs[7], - cpu->gprs[8], cpu->gprs[9], cpu->gprs[10], cpu->gprs[11], - cpu->gprs[12], cpu->gprs[13], cpu->gprs[14], cpu->gprs[15], - cpu->cpsr.packed, disassembly); + size_t regStringLen = *length; + ARMDebuggerFormatRegisters(&cpu->regs, out, &regStringLen); + regStringLen += snprintf(out + regStringLen, *length - regStringLen, " | %s", disassembly); + *length = regStringLen; +} + +static void ARMDebuggerFormatRegisters(struct ARMRegisterFile* regs, char* out, size_t* length) { + *length = snprintf(out, *length, "%08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X %08X cpsr: %08X", + regs->gprs[0], regs->gprs[1], regs->gprs[2], regs->gprs[3], + regs->gprs[4], regs->gprs[5], regs->gprs[6], regs->gprs[7], + regs->gprs[8], regs->gprs[9], regs->gprs[10], regs->gprs[11], + regs->gprs[12], regs->gprs[13], regs->gprs[14], regs->gprs[15], + regs->cpsr.packed); +} + +static void ARMDebuggerFrameFormatRegisters(struct mStackFrame* frame, char* out, size_t* length) { + ARMDebuggerFormatRegisters(frame->regs, out, length); } bool ARMDebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) {

@@ -403,3 +519,28 @@ return true;

} return false; } + +static uint32_t ARMDebuggerGetStackTraceMode(struct mDebuggerPlatform* d) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + return debugger->stackTraceMode; +} + +static void ARMDebuggerSetStackTraceMode(struct mDebuggerPlatform* d, uint32_t mode) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + struct mStackTrace* stack = &d->p->stackTrace; + if (mode == STACK_TRACE_DISABLED && debugger->stackTraceMode != STACK_TRACE_DISABLED) { + mStackTraceClear(stack); + } + debugger->stackTraceMode = mode; +} + +static bool ARMDebuggerUpdateStackTrace(struct mDebuggerPlatform* d) { + struct ARMDebugger* debugger = (struct ARMDebugger*) d; + int instructionLength = _ARMInstructionLength(debugger->cpu); + uint32_t pc = debugger->cpu->gprs[ARM_PC] - instructionLength; + if (debugger->stackTraceMode != STACK_TRACE_DISABLED) { + return ARMDebuggerUpdateStackTraceInternal(d, pc); + } else { + return false; + } +}
M src/debugger/CMakeLists.txtsrc/debugger/CMakeLists.txt

@@ -3,7 +3,8 @@ set(SOURCE_FILES

cli-debugger.c debugger.c parser.c - symbols.c) + symbols.c + stack-trace.c) set(TEST_FILES test/lexer.c

@@ -13,4 +14,4 @@ source_group("Debugger" FILES ${SOURCE_FILES})

source_group("Debugger tests" FILES ${TEST_FILES}) export_directory(DEBUGGER SOURCE_FILES) -export_directory(DEBUGGER_TEST TEST_FILES)+export_directory(DEBUGGER_TEST TEST_FILES)
M src/debugger/cli-debugger.csrc/debugger/cli-debugger.c

@@ -69,12 +69,17 @@ static void _dumpWord(struct CLIDebugger*, struct CLIDebugVector*);

#ifdef ENABLE_SCRIPTING static void _source(struct CLIDebugger*, struct CLIDebugVector*); #endif +static void _backtrace(struct CLIDebugger*, struct CLIDebugVector*); +static void _finish(struct CLIDebugger*, struct CLIDebugVector*); +static void _setStackTraceMode(struct CLIDebugger*, struct CLIDebugVector*); static struct CLIDebuggerCommandSummary _debuggerCommands[] = { + { "backtrace", _backtrace, "i", "Print backtrace of all or specified frames" }, { "break", _setBreakpoint, "Is", "Set a breakpoint" }, { "continue", _continue, "", "Continue execution" }, { "delete", _clearBreakpoint, "I", "Delete a breakpoint or watchpoint" }, { "disassemble", _disassemble, "Ii", "Disassemble instructions" }, + { "finish", _finish, "", "Execute until current stack frame returns" }, { "help", _printHelp, "S", "Print help" }, { "listb", _listBreakpoints, "", "List breakpoints" }, { "listw", _listWatchpoints, "", "List watchpoints" },

@@ -87,6 +92,7 @@ { "reset", _reset, "", "Reset the emulation" },

{ "r/1", _readByte, "I", "Read a byte from a specified offset" }, { "r/2", _readHalfword, "I", "Read a halfword from a specified offset" }, { "r/4", _readWord, "I", "Read a word from a specified offset" }, + { "stack", _setStackTraceMode, "S", "Changes the stack tracing mode" }, { "status", _printStatus, "", "Print the current status" }, { "trace", _trace, "Is", "Trace a number of instructions" }, { "w/1", _writeByte, "II", "Write a byte at a specified offset" },

@@ -111,6 +117,7 @@ };

static struct CLIDebuggerCommandAlias _debuggerCommandAliases[] = { { "b", "break" }, + { "bt", "backtrace" }, { "c", "continue" }, { "d", "delete" }, { "dis", "disassemble" },

@@ -153,6 +160,18 @@ sigaction(SIGTRAP, &osa, 0);

} #endif +static bool CLIDebuggerCheckTraceMode(struct CLIDebugger* debugger, bool requireEnabled) { + struct mDebuggerPlatform* platform = debugger->d.platform; + if (!platform->getStackTraceMode) { + debugger->backend->printf(debugger->backend, "Stack tracing is not supported by this platform.\n"); + return false; + } else if (requireEnabled && platform->getStackTraceMode(platform) == STACK_TRACE_DISABLED) { + debugger->backend->printf(debugger->backend, "Stack tracing is not enabled.\n"); + return false; + } + return true; +} + static void _continue(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); debugger->d.state = debugger->traceRemaining != 0 ? DEBUGGER_CALLBACK : DEBUGGER_RUNNING;

@@ -160,7 +179,11 @@ }

static void _next(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); + struct mDebuggerPlatform* platform = debugger->d.platform; debugger->d.core->step(debugger->d.core); + if (platform->getStackTraceMode && platform->getStackTraceMode(platform) != STACK_TRACE_DISABLED) { + platform->updateStackTrace(platform); + } _printStatus(debugger, 0); }

@@ -327,6 +350,7 @@ }

static void _reset(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { UNUSED(dv); + mStackTraceClear(&debugger->d.stackTrace); debugger->d.core->reset(debugger->d.core); _printStatus(debugger, 0); }

@@ -979,7 +1003,7 @@ if (info) {

if (info->pointId > 0) { cliDebugger->backend->printf(cliDebugger->backend, "Hit breakpoint %" PRIz "i at 0x%08X\n", info->pointId, info->address); } else { - cliDebugger->backend->printf(cliDebugger->backend, "Hit unknown breakpoint at 0x%08X\n", info->address); + cliDebugger->backend->printf(cliDebugger->backend, "Hit unknown breakpoint at 0x%08X\n", info->address); } } else { cliDebugger->backend->printf(cliDebugger->backend, "Hit breakpoint\n");

@@ -1002,6 +1026,18 @@ cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode at 0x%08X: 0x%08X\n", info->address, info->type.bp.opcode);

} else { cliDebugger->backend->printf(cliDebugger->backend, "Hit illegal opcode\n"); } + break; + case DEBUGGER_ENTER_STACK: + if (info) { + if (info->type.st.traceType == STACK_TRACE_BREAK_ON_CALL) { + cliDebugger->backend->printf(cliDebugger->backend, "Hit function call at at 0x%08X\n", info->address); + } else { + cliDebugger->backend->printf(cliDebugger->backend, "Hit function return at at 0x%08X\n", info->address); + } + } else { + cliDebugger->backend->printf(cliDebugger->backend, "Hit function call or return\n"); + } + _backtrace(cliDebugger, NULL); break; } }

@@ -1131,3 +1167,68 @@ debugger->backend->lineAppend(debugger->backend, name);

debugger->backend->lineAppend(debugger->backend, " "); return true; } + +static void _backtrace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!CLIDebuggerCheckTraceMode(debugger, true)) { + return; + } + struct mStackTrace* stack = &debugger->d.stackTrace; + ssize_t frames = mStackTraceGetDepth(stack); + if (dv && dv->type == CLIDV_INT_TYPE && dv->intValue < frames) { + frames = dv->intValue; + } + ssize_t i; + for (i = 0; i < frames; ++i) { + char trace[1024]; + size_t traceSize = sizeof(trace) - 2; + mStackTraceFormatFrame(stack, i, trace, &traceSize); + debugger->backend->printf(debugger->backend, "%s", trace); + } +} + +static void _finish(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + UNUSED(dv); + if (!CLIDebuggerCheckTraceMode(debugger, true)) { + return; + } + struct mStackTrace* stack = &debugger->d.stackTrace; + struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); + if (!frame) { + debugger->backend->printf(debugger->backend, "No current stack frame.\n"); + return; + } + frame->breakWhenFinished = true; + _continue(debugger, dv); +} + +static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + if (!CLIDebuggerCheckTraceMode(debugger, false)) { + return; + } + if (!dv) { + debugger->backend->printf(debugger->backend, "off disable stack tracing (default)\n"); + debugger->backend->printf(debugger->backend, "trace-only enable stack tracing\n"); + debugger->backend->printf(debugger->backend, "break-call break on function calls\n"); + debugger->backend->printf(debugger->backend, "break-return break on function returns\n"); + debugger->backend->printf(debugger->backend, "break-all break on function calls and returns\n"); + return; + } + if (dv->type != CLIDV_CHAR_TYPE) { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + return; + } + struct mDebuggerPlatform* platform = debugger->d.platform; + if (strcmp(dv->charValue, "off") == 0) { + platform->setStackTraceMode(platform, STACK_TRACE_DISABLED); + } else if (strcmp(dv->charValue, "trace-only") == 0) { + platform->setStackTraceMode(platform, STACK_TRACE_ENABLED); + } else if (strcmp(dv->charValue, "break-call") == 0) { + platform->setStackTraceMode(platform, STACK_TRACE_BREAK_ON_CALL); + } else if (strcmp(dv->charValue, "break-return") == 0) { + platform->setStackTraceMode(platform, STACK_TRACE_BREAK_ON_RETURN); + } else if (strcmp(dv->charValue, "break-all") == 0) { + platform->setStackTraceMode(platform, STACK_TRACE_BREAK_ON_BOTH); + } else { + debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS); + } +}
A src/debugger/stack-trace.c

@@ -0,0 +1,100 @@

+/* Copyright (c) 2013-2020 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 <mgba/internal/debugger/stack-trace.h> + +#include <mgba/core/core.h> + +#define CHECK_LENGTH() \ + if (written >= *length) { \ + *length = written; \ + return; \ + } + +DEFINE_VECTOR(mStackFrames, struct mStackFrame); + +void mStackTraceInit(struct mStackTrace* stack, size_t registersSize) { + mStackFramesInit(&stack->stack, 0); + stack->registersSize = registersSize; +} + +void mStackTraceDeinit(struct mStackTrace* stack) { + mStackTraceClear(stack); + mStackFramesDeinit(&stack->stack); +} + +void mStackTraceClear(struct mStackTrace* stack) { + ssize_t i = mStackTraceGetDepth(stack) - 1; + while (i >= 0) { + free(mStackTraceGetFrame(stack, i)->regs); + --i; + } + mStackFramesClear(&stack->stack); +} + +size_t mStackTraceGetDepth(struct mStackTrace* stack) { + return mStackFramesSize(&stack->stack); +} + +struct mStackFrame* mStackTracePush(struct mStackTrace* stack, uint32_t pc, uint32_t destAddress, uint32_t sp, void* regs) { + struct mStackFrame* frame = mStackFramesAppend(&stack->stack); + frame->callAddress = pc; + frame->entryAddress = destAddress; + frame->frameBaseAddress = sp; + frame->regs = malloc(stack->registersSize); + frame->finished = false; + frame->breakWhenFinished = false; + frame->interrupt = false; + memcpy(frame->regs, regs, stack->registersSize); + return frame; +} + +struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, uint32_t frame) { + size_t depth = mStackTraceGetDepth(stack); + if (frame >= depth) { + return NULL; + } + return mStackFramesGetPointer(&stack->stack, depth - frame - 1); +} + +void mStackTraceFormatFrame(struct mStackTrace* stack, uint32_t frame, char* out, size_t* length) { + struct mStackFrame* stackFrame = mStackTraceGetFrame(stack, frame); + struct mStackFrame* prevFrame = mStackTraceGetFrame(stack, frame + 1); + size_t written = snprintf(out, *length, "#%d ", frame); + CHECK_LENGTH(); + if (prevFrame) { + written += snprintf(out + written, *length - written, "%08X ", prevFrame->entryAddress); + CHECK_LENGTH(); + } + if (!stackFrame) { + written += snprintf(out + written, *length - written, "no stack frame available)\n"); + *length = written; + return; + } else if (stack->formatRegisters) { + written += snprintf(out + written, *length - written, "("); + CHECK_LENGTH(); + char buffer[1024]; + size_t formattedSize = sizeof(buffer) - 2; + stack->formatRegisters(stackFrame, buffer, &formattedSize); + written += snprintf(out + written, *length - written, "%s)\n ", buffer); + CHECK_LENGTH(); + } + if (prevFrame) { + int32_t offset = stackFrame->callAddress - prevFrame->entryAddress; + written += snprintf(out + written, *length - written, "at %08X [%08X+%d]\n", stackFrame->callAddress, prevFrame->entryAddress, offset); + } else { + written += snprintf(out + written, *length - written, "at %08X\n", stackFrame->callAddress); + } + *length = written; +} + +void mStackTracePop(struct mStackTrace* stack) { + size_t depth = mStackTraceGetDepth(stack); + if (depth > 0) { + struct mStackFrame* frame = mStackFramesGetPointer(&stack->stack, depth - 1); + free(frame->regs); + mStackFramesResize(&stack->stack, -1); + } +}
M src/sm83/debugger/debugger.csrc/sm83/debugger/debugger.c

@@ -90,6 +90,9 @@ platform->d.hasBreakpoints = SM83DebuggerHasBreakpoints;

platform->d.trace = SM83DebuggerTrace; platform->d.getRegister = SM83DebuggerGetRegister; platform->d.setRegister = SM83DebuggerSetRegister; + platform->d.getStackTraceMode = NULL; + platform->d.setStackTraceMode = NULL; + platform->d.updateStackTrace = NULL; platform->printStatus = NULL; return &platform->d; }