Stack traces: add detection for other calling conventions and stack manipulation
Adam Higerd chighland@gmail.com
Thu, 30 Jul 2020 18:59:12 -0500
5 files changed,
141 insertions(+),
30 deletions(-)
M
include/mgba/internal/arm/decoder-inlines.h
→
include/mgba/internal/arm/decoder-inlines.h
@@ -22,4 +22,16 @@ info->sInstructionCycles = 0; \
info->nInstructionCycles = 1; \ info->nDataCycles = 1; +static inline bool ARMInstructionIsBranch(enum ARMMnemonic mnemonic) { + switch (mnemonic) { + case ARM_MN_B: + case ARM_MN_BL: + case ARM_MN_BX: + // TODO: case: ARM_MN_BLX: + return true; + default: + return false; + } +} + #endif
M
include/mgba/internal/arm/isa-inlines.h
→
include/mgba/internal/arm/isa-inlines.h
@@ -107,4 +107,39 @@ static inline uint32_t _ARMPCAddress(struct ARMCore* cpu) {
return cpu->gprs[ARM_PC] - _ARMInstructionLength(cpu) * 2; } +static inline bool ARMTestCondition(struct ARMCore* cpu, unsigned condition) { + switch (condition) { + case 0x0: + return ARM_COND_EQ; + case 0x1: + return ARM_COND_NE; + case 0x2: + return ARM_COND_CS; + case 0x3: + return ARM_COND_CC; + case 0x4: + return ARM_COND_MI; + case 0x5: + return ARM_COND_PL; + case 0x6: + return ARM_COND_VS; + case 0x7: + return ARM_COND_VC; + case 0x8: + return ARM_COND_HI; + case 0x9: + return ARM_COND_LS; + case 0xA: + return ARM_COND_GE; + case 0xB: + return ARM_COND_LT; + case 0xC: + return ARM_COND_GT; + case 0xD: + return ARM_COND_LE; + default: + return true; + } +} + #endif
M
src/arm/debugger/debugger.c
→
src/arm/debugger/debugger.c
@@ -8,6 +8,7 @@
#include <mgba/core/core.h> #include <mgba/internal/arm/arm.h> #include <mgba/internal/arm/decoder.h> +#include <mgba/internal/arm/decoder-inlines.h> #include <mgba/internal/arm/isa-inlines.h> #include <mgba/internal/arm/debugger/memory-debugger.h> #include <mgba/internal/debugger/parser.h>@@ -15,15 +16,57 @@ #include <mgba/internal/debugger/stack-trace.h>
#include <mgba-util/math.h> DEFINE_VECTOR(ARMDebugBreakpointList, struct ARMDebugBreakpoint); + +static bool ARMDecodeCombined(struct ARMCore* cpu, struct ARMInstructionInfo* info) { + if (cpu->executionMode == MODE_ARM) { + ARMDecodeARM(cpu->prefetch[0], info); + return true; + } else { + struct ARMInstructionInfo info2; + ARMDecodeThumb(cpu->prefetch[0], info); + ARMDecodeThumb(cpu->prefetch[1], &info2); + return ARMDecodeThumbCombine(info, &info2, info); + } +} 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; + + struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); + if (frame && frame->frameBaseAddress < (uint32_t) cpu->gprs[ARM_SP]) { + // The stack frame has been popped off the stack. This means the function + // has been returned from, or that the stack pointer has been otherwise + // manipulated. Either way, the function is done executing. + bool shouldBreak = debugger->stackTraceMode & STACK_TRACE_BREAK_ON_RETURN; + do { + shouldBreak = shouldBreak || frame->breakWhenFinished; + mStackTracePop(stack); + frame = mStackTraceGetFrame(stack, 0); + } while (frame && frame->frameBaseAddress < (uint32_t) cpu->gprs[ARM_SP]); + if (shouldBreak) { + struct mDebuggerEntryInfo debuggerInfo = { + .address = pc, + .type.st.traceType = STACK_TRACE_BREAK_ON_RETURN, + .pointId = 0 + }; + mDebuggerEnter(d->p, DEBUGGER_ENTER_STACK, &debuggerInfo); + return true; + } else { + return false; + } + } + bool interrupt = false; - ARMDecodeARM(instruction, &info); + bool isWideInstruction = ARMDecodeCombined(cpu, &info); + if (!isWideInstruction && info.mnemonic == ARM_MN_BL) { + return false; + } + if (!ARMTestCondition(cpu, info.condition)) { + return false; + } if (_ARMModeHasSPSR(cpu->cpsr.priv)) { struct mStackFrame* irqFrame = mStackTraceGetFrame(stack, 0);@@ -41,15 +84,9 @@ if (info.branchType == ARM_BRANCH_NONE && !interrupt) {
return false; } - struct mStackFrame* frame = mStackTraceGetFrame(stack, 0); - bool isCall = (info.branchType & ARM_BRANCH_LINKED); + bool isCall = interrupt || (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@@ -57,6 +94,7 @@ // breakpoint down below.
// // The first instruction could possibly be a call, which would // need ANOTHER stack frame, so only skip if it's not. + destAddress = pc; } 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.@@ -69,10 +107,39 @@ 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; + if (isCall) { + destAddress = cpu->gprs[info.op1.reg]; + } else { + bool isExceptionReturn = _ARMModeHasSPSR(cpu->cpsr.priv) && info.affectsCPSR && info.op1.reg == ARM_PC; + bool isMovPcLr = (info.operandFormat & ARM_OPERAND_REGISTER_2) && info.op1.reg == ARM_PC && info.op2.reg == ARM_LR; + bool isBranch = ARMInstructionIsBranch(info.mnemonic); + int reg = (isBranch ? info.op1.reg : info.op2.reg); + destAddress = cpu->gprs[reg]; + if (isBranch || (info.op1.reg == ARM_PC && !isMovPcLr)) { + // ARMv4 doesn't have the BLX opcode, so it uses an assignment to LR before a BX for that purpose. + struct ARMInstructionInfo prevInfo; + if (cpu->executionMode == MODE_ARM) { + ARMDecodeARM(cpu->memory.load32(cpu, pc - 4, NULL), &prevInfo); + } else { + ARMDecodeThumb(cpu->memory.load16(cpu, pc - 2, NULL), &prevInfo); + } + if ((prevInfo.operandFormat & (ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1)) == (ARM_OPERAND_REGISTER_1 | ARM_OPERAND_AFFECTED_1) && prevInfo.op1.reg == ARM_LR) { + isCall = true; + } else if ((isBranch ? info.op1.reg : info.op2.reg) == ARM_LR) { + isBranch = true; + } else if (frame && frame->frameBaseAddress == (uint32_t) cpu->gprs[ARM_SP]) { + // A branch to something that isn't LR isn't a standard function return, but it might potentially + // be a nonstandard one. As a heuristic, if the stack pointer and the destination address match + // where we came from, consider it to be a function return. + isBranch = (destAddress > frame->callAddress + 1 && destAddress <= frame->callAddress + 5); + } else { + isBranch = false; + } + } + if (!isCall && !isBranch && !isExceptionReturn && !isMovPcLr) { + return false; + } } - destAddress = cpu->gprs[info.op1.reg]; } else { mLOG(DEBUGGER, ERROR, "Unknown branch operand in stack trace"); return false;@@ -83,21 +150,16 @@ destAddress = cpu->memory.load32(cpu, destAddress, NULL);
} if (isCall) { - int instructionLength = _ARMInstructionLength(debugger->cpu); + int instructionLength = isWideInstruction ? WORD_SIZE_ARM : WORD_SIZE_THUMB; 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); + mStackTracePop(stack); + if (!(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_RETURN)) { return false; } - frame->finished = true; } struct mDebuggerEntryInfo debuggerInfo = { .address = pc,@@ -404,21 +466,18 @@
char disassembly[64]; struct ARMInstructionInfo info; + bool isWideInstruction = ARMDecodeCombined(cpu, &info); if (cpu->executionMode == MODE_ARM) { uint32_t instruction = cpu->prefetch[0]; sprintf(disassembly, "%08X: ", instruction); - ARMDecodeARM(instruction, &info); ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); } else { - struct ARMInstructionInfo info2; - struct ARMInstructionInfo combined; uint16_t instruction = cpu->prefetch[0]; - uint16_t instruction2 = cpu->prefetch[1]; ARMDecodeThumb(instruction, &info); - ARMDecodeThumb(instruction2, &info2); - if (ARMDecodeThumbCombine(&info, &info2, &combined)) { + if (isWideInstruction) { + uint16_t instruction2 = cpu->prefetch[1]; sprintf(disassembly, "%04X%04X: ", instruction, instruction2); - ARMDisassemble(&combined, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); + ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: ")); } else { sprintf(disassembly, " %04X: ", instruction); ARMDisassemble(&info, cpu->gprs[ARM_PC], disassembly + strlen("00000000: "), sizeof(disassembly) - strlen("00000000: "));
M
src/debugger/gdb-stub.c
→
src/debugger/gdb-stub.c
@@ -82,6 +82,7 @@ case DEBUGGER_ENTER_ILLEGAL_OP:
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL); break; case DEBUGGER_ENTER_ATTACHED: + case DEBUGGER_ENTER_STACK: return; } _sendMessage(stub);
M
src/debugger/stack-trace.c
→
src/debugger/stack-trace.c
@@ -65,7 +65,7 @@ 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); + written += snprintf(out + written, *length - written, "0x%08X ", prevFrame->entryAddress); CHECK_LENGTH(); } if (!stackFrame) {@@ -83,9 +83,13 @@ 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); + if (offset >= 0) { + written += snprintf(out + written, *length - written, "at 0x%08X [0x%08X+%d]\n", stackFrame->callAddress, prevFrame->entryAddress, offset); + } else { + written += snprintf(out + written, *length - written, "at 0x%08X\n", stackFrame->callAddress); + } } else { - written += snprintf(out + written, *length - written, "at %08X\n", stackFrame->callAddress); + written += snprintf(out + written, *length - written, "at 0x%08X\n", stackFrame->callAddress); } *length = written; }