mirror of https://github.com/mgba-emu/mgba.git
Stack traces: add detection for other calling conventions and stack manipulation
This commit is contained in:
parent
89de06a610
commit
8ee4b3c046
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: "));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue