From 238c68f080cd13f3c8ca8b11f744216e4f296c78 Mon Sep 17 00:00:00 2001 From: Adam Higerd Date: Mon, 27 Jul 2020 03:45:57 -0500 Subject: [PATCH] Stack trace: WIP backtrace implementation --- include/mgba/internal/arm/debugger/debugger.h | 2 +- include/mgba/internal/arm/decoder.h | 2 +- include/mgba/internal/debugger/stack-trace.h | 6 +- src/arm/debugger/debugger.c | 62 ++++++++++++++----- src/debugger/cli-debugger.c | 45 +++++++++++++- src/debugger/stack-trace.c | 49 +++++++++++++-- 6 files changed, 141 insertions(+), 25 deletions(-) diff --git a/include/mgba/internal/arm/debugger/debugger.h b/include/mgba/internal/arm/debugger/debugger.h index 1850478f9..e3615049c 100644 --- a/include/mgba/internal/arm/debugger/debugger.h +++ b/include/mgba/internal/arm/debugger/debugger.h @@ -36,7 +36,7 @@ struct ARMDebugger { struct ARMMemory originalMemory; ssize_t nextId; - enum mStackTraceMode stackTraceMode; + uint32_t stackTraceMode; void (*entered)(struct mDebugger*, enum mDebuggerEntryReason, struct mDebuggerEntryInfo*); diff --git a/include/mgba/internal/arm/decoder.h b/include/mgba/internal/arm/decoder.h index 4f6322b28..9d6481d99 100644 --- a/include/mgba/internal/arm/decoder.h +++ b/include/mgba/internal/arm/decoder.h @@ -14,7 +14,7 @@ CXX_GUARD_START // 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 diff --git a/include/mgba/internal/debugger/stack-trace.h b/include/mgba/internal/debugger/stack-trace.h index afb51e160..47122a604 100644 --- a/include/mgba/internal/debugger/stack-trace.h +++ b/include/mgba/internal/debugger/stack-trace.h @@ -24,7 +24,6 @@ enum mStackTraceMode { }; struct mStackFrame { - uint32_t instruction; uint32_t callAddress; uint32_t entryAddress; uint32_t frameBaseAddress; @@ -47,8 +46,9 @@ void mStackTraceDeinit(struct mStackTrace* stack); void mStackTraceClear(struct mStackTrace* stack); size_t mStackTraceGetDepth(struct mStackTrace* stack); -struct mStackFrame* mStackTracePush(struct mStackTrace* stack, uint32_t instruction, uint32_t pc, uint32_t destAddress, uint32_t sp, void* regs); -struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, size_t frame); +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 diff --git a/src/arm/debugger/debugger.c b/src/arm/debugger/debugger.c index a4dc9a30c..641d3597f 100644 --- a/src/arm/debugger/debugger.c +++ b/src/arm/debugger/debugger.c @@ -12,16 +12,21 @@ #include #include #include +#include DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); static bool _updateStackTrace(struct mDebuggerPlatform* d, uint32_t pc) { struct ARMDebugger* debugger = (struct ARMDebugger*) d; struct ARMCore* cpu = debugger->cpu; - struct mStackTrace* stack = &d->p->stackTrace; struct ARMInstructionInfo info; uint32_t instruction = cpu->prefetch[0]; ARMDecodeARM(instruction, &info); + if (info.branchType == ARM_BRANCH_NONE) { + return false; + } + + struct mStackTrace* stack = &d->p->stackTrace; struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); bool isCall = (info.branchType & ARM_BRANCH_LINKED); uint32_t destAddress; @@ -31,7 +36,24 @@ static bool _updateStackTrace(struct mDebuggerPlatform* d, uint32_t pc) { frame = NULL; } - if (info.operandFormat & ARM_OPERAND_IMMEDIATE_1) { + if (info.operandFormat & ARM_OPERAND_MEMORY_1) { + if (!isCall) { + return false; + } + // count number of set bits, gcc/clang will convert to intrinsics + int regCount = 0; + uint32_t reglist = info.op1.immediate & 0xf; + printf("%d\n", reglist); + fflush(NULL); + while (reglist != 0) { + reglist &= reglist - 1; + regCount++; + } + printf("->%d\n", regCount); + fflush(NULL); + 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; } @@ -46,11 +68,11 @@ static bool _updateStackTrace(struct mDebuggerPlatform* d, uint32_t pc) { } if (info.branchType & ARM_BRANCH_INDIRECT) { - destAddress = cpu->memory.load32(cpu, destAddress, 0); + destAddress = cpu->memory.load32(cpu, destAddress, NULL); } if (isCall) { - frame = mStackTracePush(stack, instruction, pc, destAddress, cpu->gprs[ARM_SP], &cpu->regs); + frame = mStackTracePush(stack, pc, destAddress, cpu->gprs[ARM_SP], &cpu->regs); if (!(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_CALL)) { return false; } @@ -107,10 +129,11 @@ static void ARMDebuggerCheckBreakpoints(struct mDebuggerPlatform* d) { } else { instructionLength = WORD_SIZE_THUMB; } - if (_updateStackTrace(d, debugger->cpu->gprs[ARM_PC] - instructionLength)) { + uint32_t pc = debugger->cpu->gprs[ARM_PC] - instructionLength; + if (debugger->stackTraceMode != STACK_TRACE_DISABLED && _updateStackTrace(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; } @@ -142,6 +165,8 @@ static void ARMDebuggerListWatchpoints(struct mDebuggerPlatform*, struct mWatchp 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* delimiter, char* disassembly, 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*); @@ -172,13 +197,13 @@ void ARMDebuggerInit(void* cpu, struct mDebuggerPlatform* 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)); - // TODO - stack->formatRegisters = NULL; + stack->formatRegisters = ARMDebuggerFrameFormatRegisters; } void ARMDebuggerDeinit(struct mDebuggerPlatform* platform) { @@ -393,12 +418,20 @@ static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* len } } - *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); + ARMDebuggerFormatRegisters(&cpu->regs, " | ", disassembly, out, length); +} + +static void ARMDebuggerFormatRegisters(struct ARMRegisterFile* regs, char* delimiter, char* disassembly, 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%s%s", + 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, delimiter, disassembly); +} + +static void ARMDebuggerFrameFormatRegisters(struct mStackFrame* frame, char* out, size_t* length) { + ARMDebuggerFormatRegisters((struct ARMRegisterFile*)&frame->regs, "", "", out, length); } bool ARMDebuggerGetRegister(struct mDebuggerPlatform* d, const char* name, int32_t* value) { @@ -489,4 +522,5 @@ static void ARMDebuggerSetStackTraceMode(struct mDebuggerPlatform* d, uint32_t m mStackTraceClear(stack); } debugger->stackTraceMode = mode; + printf("SetStackTraceMode %d\n", mode); } diff --git a/src/debugger/cli-debugger.c b/src/debugger/cli-debugger.c index ddac46952..56114bdae 100644 --- a/src/debugger/cli-debugger.c +++ b/src/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 @@ static struct CLIDebuggerCommandSummary _debuggerCommands[] = { { "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" }, + { "stackmode", _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 CLIDebuggerCommandSummary _debuggerCommands[] = { static struct CLIDebuggerCommandAlias _debuggerCommandAliases[] = { { "b", "break" }, + { "bt", "backtrace" }, { "c", "continue" }, { "d", "delete" }, { "dis", "disassemble" }, @@ -979,7 +986,7 @@ static void _reportEntry(struct mDebugger* debugger, enum mDebuggerEntryReason r 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"); @@ -1131,3 +1138,39 @@ bool CLIDebuggerTabComplete(struct CLIDebugger* debugger, const char* token, boo debugger->backend->lineAppend(debugger->backend, " "); return true; } + +static void _backtrace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + struct mDebuggerPlatform* platform = debugger->d.platform; + if (!platform->getStackTraceMode) { + debugger->backend->printf(debugger->backend, "Stack tracing is not supported by this platform.\n"); + return; + } else if (platform->getStackTraceMode(platform) == STACK_TRACE_DISABLED) { + debugger->backend->printf(debugger->backend, "Stack tracing is not enabled.\n"); + return; + } + struct mStackTrace* stack = &debugger->d.stackTrace; + size_t frames = mStackTraceGetDepth(stack); + if (dv && dv->type == CLIDV_INT_TYPE && dv->intValue < frames) { + frames = dv->intValue; + } + size_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) { +} + +static void _setStackTraceMode(struct CLIDebugger* debugger, struct CLIDebugVector* dv) { + printf("setStackTraceMode\n"); + struct mDebuggerPlatform* platform = debugger->d.platform; + if (!platform->setStackTraceMode) { + debugger->backend->printf(debugger->backend, "Stack tracing is not supported by this platform.\n"); + return; + } + platform->setStackTraceMode(platform, STACK_TRACE_ENABLED); +} diff --git a/src/debugger/stack-trace.c b/src/debugger/stack-trace.c index 22e2a470e..3050c2904 100644 --- a/src/debugger/stack-trace.c +++ b/src/debugger/stack-trace.c @@ -7,10 +7,17 @@ #include +#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) { @@ -24,15 +31,15 @@ void mStackTraceClear(struct mStackTrace* stack) { 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 instruction, uint32_t pc, uint32_t destAddress, uint32_t sp, void* regs) { +struct mStackFrame* mStackTracePush(struct mStackTrace* stack, uint32_t pc, uint32_t destAddress, uint32_t sp, void* regs) { struct mStackFrame* frame = mStackFramesAppend(&stack->stack); - frame->instruction = instruction; frame->callAddress = pc; frame->entryAddress = destAddress; frame->frameBaseAddress = sp; @@ -43,7 +50,7 @@ struct mStackFrame* mStackTracePush(struct mStackTrace* stack, uint32_t instruct return frame; } -struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, size_t frame) { +struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, uint32_t frame) { size_t depth = mStackTraceGetDepth(stack); if (frame >= depth) { return NULL; @@ -51,12 +58,44 @@ struct mStackFrame* mStackTraceGetFrame(struct mStackTrace* stack, size_t frame) 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(); + size_t formattedSize = *length - written; + stack->formatRegisters(stackFrame, out + written, &formattedSize); + written += formattedSize; + CHECK_LENGTH(); + written += snprintf(out + written, *length - written, ")\n "); + 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) { + if (depth < 1) { return; } struct mStackFrame* frame = mStackFramesGetPointer(&stack->stack, depth - 1); free(frame->regs); - mStackFramesResize(&stack->stack, depth - 1); + mStackFramesResize(&stack->stack, -1); }