#include "Common/Log.h"
#include "Core/MemMap.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MIPS/MIPSCodeUtils.h"
#include "Core/MIPS/MIPSStackWalk.h"
#define _RS ((op >> 21) & 0x1F)
#define _RT ((op >> 16) & 0x1F)
#define _RD ((op >> 11) & 0x1F)
#define _IMM16 ((signed short)(op & 0xFFFF))
#define MIPSTABLE_IMM_MASK 0xFC000000
#define MIPSTABLE_SPECIAL_MASK 0xFC00003F
namespace MIPSStackWalk {
using namespace MIPSCodeUtils;
const int MAX_FUNC_SIZE = 32768 * 4;
const size_t MAX_DEPTH = 1024;
static u32 GuessEntry(u32 pc) {
SymbolInfo info;
if (g_symbolMap->GetSymbolInfo(&info, pc)) {
return info.address;
}
return INVALIDTARGET;
}
bool IsSWInstr(MIPSOpcode op) {
return (op & MIPSTABLE_IMM_MASK) == 0xAC000000;
}
bool IsAddImmInstr(MIPSOpcode op) {
return (op & MIPSTABLE_IMM_MASK) == 0x20000000 || (op & MIPSTABLE_IMM_MASK) == 0x24000000;
}
bool IsMovRegsInstr(MIPSOpcode op) {
if ((op & MIPSTABLE_SPECIAL_MASK) == 0x00000021) {
return _RS == 0 || _RT == 0;
}
return false;
}
bool ScanForAllocaSignature(u32 pc) {
u32 stop = pc - 32 * 4;
for (; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) {
MIPSOpcode op = Memory::Read_Instruction(pc, true);
if (IsMovRegsInstr(op) && _RD == MIPS_REG_FP && (_RS == MIPS_REG_SP || _RT == MIPS_REG_SP)) {
return true;
}
}
return false;
}
bool ScanForEntry(StackFrame &frame, u32 entry, u32 &ra) {
const u32 LONGEST_FUNCTION = 1024 * 512;
if (entry != INVALIDTARGET && frame.pc == entry) {
frame.entry = entry;
frame.stackSize = 0;
return true;
}
int ra_offset = -1;
const u32 start = frame.pc - 4;
u32 stop = entry;
if (entry == INVALIDTARGET) {
if (start >= PSP_GetUserMemoryBase()) {
stop = PSP_GetUserMemoryBase();
} else if (start >= PSP_GetKernelMemoryBase()) {
stop = PSP_GetKernelMemoryBase();
} else if (start >= PSP_GetScratchpadMemoryBase()) {
stop = PSP_GetScratchpadMemoryBase();
}
}
if (!Memory::IsValidAddress(start)) {
return false;
}
if (stop < start - LONGEST_FUNCTION) {
stop = start - LONGEST_FUNCTION;
}
for (u32 pc = start; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) {
_dbg_assert_(Memory::IsValidAddress(pc));
MIPSOpcode op = Memory::Read_Instruction(pc, true);
if (IsSWInstr(op) && _RT == MIPS_REG_RA && _RS == MIPS_REG_SP) {
ra_offset = _IMM16;
}
if (IsAddImmInstr(op) && _RT == MIPS_REG_SP && _RS == MIPS_REG_SP) {
if (_IMM16 > 0) {
continue;
}
if (ScanForAllocaSignature(pc)) {
continue;
}
frame.entry = pc;
frame.stackSize = -_IMM16;
if (ra_offset != -1 && Memory::IsValidAddress(frame.sp + ra_offset)) {
ra = Memory::Read_U32(frame.sp + ra_offset);
}
return true;
}
}
return false;
}
bool DetermineFrameInfo(StackFrame &frame, u32 possibleEntry, u32 threadEntry, u32 &ra) {
if (ScanForEntry(frame, possibleEntry, ra)) {
return true;
} else if (ra != INVALIDTARGET && possibleEntry != INVALIDTARGET) {
frame.entry = possibleEntry;
frame.stackSize = 0;
return true;
}
u32 newPossibleEntry = frame.pc > threadEntry ? threadEntry : frame.pc - MAX_FUNC_SIZE;
if (ScanForEntry(frame, newPossibleEntry, ra)) {
return true;
} else {
return false;
}
}
std::vector<StackFrame> Walk(const u32 pc, u32 ra, u32 sp, const u32 threadEntry, u32 threadStackTop) {
std::vector<StackFrame> frames;
if (!Memory::IsValidAddress(pc) || !Memory::IsValidAddress(sp) || !Memory::IsValidAddress(ra)) {
return frames;
}
StackFrame current;
current.pc = pc;
current.sp = sp;
current.entry = INVALIDTARGET;
current.stackSize = -1;
u32 prevEntry = INVALIDTARGET;
while (current.pc != threadEntry) {
if (!Memory::IsValidAddress(current.pc)) {
break;
}
u32 possibleEntry = GuessEntry(current.pc);
if (DetermineFrameInfo(current, possibleEntry, threadEntry, ra)) {
frames.push_back(current);
if (current.entry == threadEntry || GuessEntry(current.entry) == threadEntry) {
break;
}
if (current.entry == prevEntry || frames.size() >= MAX_DEPTH) {
break;
}
prevEntry = current.entry;
current.pc = ra;
current.sp += current.stackSize;
ra = INVALIDTARGET;
current.entry = INVALIDTARGET;
current.stackSize = -1;
} else {
current.entry = possibleEntry;
current.stackSize = 0;
frames.push_back(current);
break;
}
}
return frames;
}
};