Stack traces: add detection for other calling conventions and stack manipulation

This commit is contained in:
Adam Higerd 2020-07-30 18:59:12 -05:00 committed by Vicki Pfau
parent 89de06a610
commit 8ee4b3c046
5 changed files with 141 additions and 30 deletions

View File

@ -22,4 +22,16 @@
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

View File

@ -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

View File

@ -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>
@ -16,14 +17,56 @@
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 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
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 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
//
// 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 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
}
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 @@ static bool ARMDebuggerUpdateStackTraceInternal(struct mDebuggerPlatform* d, uin
}
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) {
mStackTracePop(stack);
if (!(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_RETURN)) {
return false;
}
if (!frame->breakWhenFinished && !(debugger->stackTraceMode & STACK_TRACE_BREAK_ON_RETURN)) {
mStackTracePop(stack);
return false;
}
frame->finished = true;
}
struct mDebuggerEntryInfo debuggerInfo = {
.address = pc,
@ -404,21 +466,18 @@ static void ARMDebuggerTrace(struct mDebuggerPlatform* d, char* out, size_t* len
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: "));

View File

@ -82,6 +82,7 @@ static void _gdbStubEntered(struct mDebugger* debugger, enum mDebuggerEntryReaso
snprintf(stub->outgoing, GDB_STUB_MAX_LINE - 4, "S%02x", SIGILL);
break;
case DEBUGGER_ENTER_ATTACHED:
case DEBUGGER_ENTER_STACK:
return;
}
_sendMessage(stub);

View File

@ -65,7 +65,7 @@ void mStackTraceFormatFrame(struct mStackTrace* stack, uint32_t frame, char* out
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 @@ void mStackTraceFormatFrame(struct mStackTrace* stack, uint32_t frame, char* out
}
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;
}