diff --git a/CMakeLists.txt b/CMakeLists.txt index 95f44d9753..cfc00885ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -934,6 +934,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/MIPS/MIPSInt.h Core/MIPS/MIPSIntVFPU.cpp Core/MIPS/MIPSIntVFPU.h + Core/MIPS/MIPSStackWalk.cpp + Core/MIPS/MIPSStackWalk.h Core/MIPS/MIPSTables.cpp Core/MIPS/MIPSTables.h Core/MIPS/MIPSVFPUUtils.cpp diff --git a/Core/CMakeLists.txt b/Core/CMakeLists.txt index 90dbf87d39..dad4011183 100644 --- a/Core/CMakeLists.txt +++ b/Core/CMakeLists.txt @@ -15,6 +15,7 @@ set(SRCS MIPS/MIPSDisVFPU.cpp MIPS/MIPSInt.cpp MIPS/MIPSIntVFPU.cpp + MIPS/MIPSStackWalk.cpp MIPS/MIPSTables.cpp MIPS/MIPSVFPUUtils.cpp MIPS/JitCommon/JitCommon.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 60a98ee845..604058db6f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -336,6 +336,7 @@ + @@ -487,6 +488,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index e96aaf9a55..3d79f6093d 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -454,6 +454,9 @@ HW + + MIPS + @@ -846,6 +849,9 @@ HW + + MIPS + diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 6ebc42ca29..96b43d03a3 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -3539,6 +3539,8 @@ std::vector GetThreadsInfo() info.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0; info.status = t->nt.status; info.entrypoint = t->nt.entrypoint; + info.initialStack = t->nt.initialStack; + info.stackSize = (u32)t->nt.stackSize; info.priority = t->nt.currentPriority; info.waitType = t->nt.waitType; if(*iter == currentThread) diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 964812df1a..e931f959f4 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -313,8 +313,10 @@ struct DebugThreadInfo SceUID id; char name[KERNELOBJECT_MAX_NAME_LENGTH+1]; u32 status; - int curPC; - int entrypoint; + u32 curPC; + u32 entrypoint; + u32 initialStack; + int stackSize; int priority; WaitType waitType; bool isCurrent; diff --git a/Core/MIPS/MIPSStackWalk.cpp b/Core/MIPS/MIPSStackWalk.cpp new file mode 100644 index 0000000000..a2241e8b2a --- /dev/null +++ b/Core/MIPS/MIPSStackWalk.cpp @@ -0,0 +1,173 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#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; + + // In the worst case, we scan this far above the pc for a entry. + const int MAX_FUNC_SIZE = 32768 * 4; + // After this we assume we're stuck. + const size_t MAX_DEPTH = 1024; + + static u32 GuessEntry(u32 pc) { + SymbolInfo info; + if (symbolMap.GetSymbolInfo(&info, pc)) { + return info.address; + } + return INVALIDTARGET; + } + + bool IsSWInstr(u32 op) { + return (op & MIPSTABLE_IMM_MASK) == 0xAC000000; + } + + bool IsAddImmInstr(u32 op) { + return (op & MIPSTABLE_IMM_MASK) == 0x20000000 || (op & MIPSTABLE_IMM_MASK) == 0x24000000; + } + + bool IsMovRegsInstr(u32 op) { + // TODO: There are more options here. Let's assume addu for now. + if ((op & MIPSTABLE_SPECIAL_MASK) == 0x00000021) { + return _RS == 0 || _RT == 0; + } + return false; + } + + bool ScanForAllocaSignature(u32 pc) { + // In God Eater Burst, for example, after 0880E750, there's what looks like an alloca(). + // It's surrounded by "mov fp, sp" and "mov sp, fp", which is unlikely to be used for other reasons. + + // It ought to be pretty close. + u32 stop = pc - 32 * 4; + for (; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) { + u32 op = Memory::Read_Instruction(pc); + + // We're looking for a "mov fp, sp" close by a "addiu sp, sp, -N". + 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) { + // TODO: Check if found entry is in the same symbol? Might be wrong sometimes... + + int ra_offset = -1; + u32 stop = entry == INVALIDTARGET ? 0 : entry; + for (u32 pc = frame.pc; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) { + u32 op = Memory::Read_Instruction(pc); + + // Here's where they store the ra address. + 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) { + // A positive imm either means alloca() or we went too far. + if (_IMM16 > 0) { + // TODO: Maybe check for any alloca() signature and bail? + continue; + } + if (ScanForAllocaSignature(pc)) { + continue; + } + + frame.entry = pc; + frame.stackSize = -_IMM16; + if (ra_offset != -1) { + 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)) { + // Awesome, found one that looks right. + return true; + } else if (ra != INVALIDTARGET && possibleEntry != INVALIDTARGET) { + // Let's just assume it's a leaf. + frame.entry = possibleEntry; + frame.stackSize = 0; + return true; + } + + // Okay, we failed to get one. Our possibleEntry could be wrong, it often is. + // Let's just scan upward. + u32 newPossibleEntry = frame.pc > threadEntry ? threadEntry : frame.pc - MAX_FUNC_SIZE; + if (ScanForEntry(frame, newPossibleEntry, ra)) { + return true; + } else { + return false; + } + } + + std::vector Walk(u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop) { + std::vector frames; + StackFrame current; + current.pc = pc; + current.sp = sp; + current.entry = INVALIDTARGET; + current.stackSize = -1; + + u32 prevEntry = INVALIDTARGET; + while (pc != threadEntry) { + 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) { + // Recursion, means we're screwed. Let's just give up. + break; + } + prevEntry = current.entry; + + current.pc = ra; + current.sp += current.stackSize; + ra = INVALIDTARGET; + current.entry = INVALIDTARGET; + current.stackSize = -1; + } else { + // Well, we got as far as we could. + current.entry = possibleEntry; + current.stackSize = 0; + frames.push_back(current); + break; + } + } + + return frames; + } +}; \ No newline at end of file diff --git a/Core/MIPS/MIPSStackWalk.h b/Core/MIPS/MIPSStackWalk.h new file mode 100644 index 0000000000..78b3e1dc95 --- /dev/null +++ b/Core/MIPS/MIPSStackWalk.h @@ -0,0 +1,36 @@ +// Copyright (c) 2012- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include "Common/CommonTypes.h" + +namespace MIPSStackWalk { + struct StackFrame { + // Beginning of function symbol (may be estimated.) + u32 entry; + // Next position within function. + u32 pc; + // Value of SP inside this function (assuming no alloca()...) + u32 sp; + // Size of stack in bytes. + int stackSize; + }; + + std::vector Walk(u32 pc, u32 ra, u32 sp, u32 threadEntry, u32 threadStackTop); +}; \ No newline at end of file diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9f99b804fb..fd2c2ebad7 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -282,6 +282,7 @@ LOCAL_SRC_FILES := \ $(SRC)/Core/MIPS/MIPSDisVFPU.cpp \ $(SRC)/Core/MIPS/MIPSInt.cpp.arm \ $(SRC)/Core/MIPS/MIPSIntVFPU.cpp.arm \ + $(SRC)/Core/MIPS/MIPSStackWalk.cpp \ $(SRC)/Core/MIPS/MIPSTables.cpp.arm \ $(SRC)/Core/MIPS/MIPSVFPUUtils.cpp.arm \ $(SRC)/Core/MIPS/MIPSCodeUtils.cpp.arm \