#include "ppsspp_config.h"
#include <cstdint>
#include <unordered_set>
#include <mutex>
#include <sstream>
#include "Common/StringUtils.h"
#include "Common/MachineContext.h"
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
#include "Common/x64Analyzer.h"
#elif PPSSPP_ARCH(ARM64)
#include "Core/Util/DisArm64.h"
#elif PPSSPP_ARCH(ARM)
#include "ext/disarm.h"
#endif
#include "Common/Log.h"
#include "Core/Config.h"
#include "Core/Core.h"
#include "Core/MemFault.h"
#include "Core/MemMap.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/Debugger/SymbolMap.h"
#include "Core/MIPS/MIPSStackWalk.h"
#include "Core/MIPS/MIPSDebugInterface.h"
#include "Core/HLE/sceKernelThread.h"
namespace Memory {
static int64_t g_numReportedBadAccesses = 0;
const uint8_t *g_lastCrashAddress;
MemoryExceptionType g_lastMemoryExceptionType;
static bool inCrashHandler = false;
std::unordered_set<const uint8_t *> g_ignoredAddresses;
void MemFault_Init() {
g_numReportedBadAccesses = 0;
g_lastCrashAddress = nullptr;
g_lastMemoryExceptionType = MemoryExceptionType::NONE;
g_ignoredAddresses.clear();
}
bool MemFault_MayBeResumable() {
return g_lastCrashAddress != nullptr;
}
void MemFault_IgnoreLastCrash() {
g_ignoredAddresses.insert(g_lastCrashAddress);
}
#ifdef MACHINE_CONTEXT_SUPPORTED
static bool DisassembleNativeAt(const uint8_t *codePtr, int instructionSize, std::string *dest) {
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
auto lines = DisassembleX86(codePtr, instructionSize);
if (!lines.empty()) {
*dest = lines[0];
return true;
}
#elif PPSSPP_ARCH(ARM64)
auto lines = DisassembleArm64(codePtr, instructionSize);
if (!lines.empty()) {
*dest = lines[0];
return true;
}
#elif PPSSPP_ARCH(ARM)
auto lines = DisassembleArm2(codePtr, instructionSize);
if (!lines.empty()) {
*dest = lines[0];
return true;
}
#elif PPSSPP_ARCH(RISCV64)
auto lines = DisassembleRV64(codePtr, instructionSize);
if (!lines.empty()) {
*dest = lines[0];
return true;
}
#elif PPSSPP_ARCH(LOONGARCH64)
auto lines = DisassembleLA64(codePtr, instructionSize);
if (!lines.empty()) {
*dest = lines[0];
return true;
}
#endif
return false;
}
bool HandleFault(uintptr_t hostAddress, void *ctx) {
if (inCrashHandler)
return false;
inCrashHandler = true;
SContext *context = (SContext *)ctx;
const uint8_t *codePtr = (uint8_t *)(context->CTX_PC);
std::lock_guard<std::recursive_mutex> guard(MIPSComp::jitLock);
g_lastCrashAddress = nullptr;
bool inJitSpace = MIPSComp::jit && MIPSComp::jit->CodeInRange(codePtr);
if (!inJitSpace) {
inCrashHandler = false;
return false;
}
uintptr_t baseAddress = (uintptr_t)base;
#ifdef MASKED_PSP_MEMORY
const uintptr_t addressSpaceSize = 0x40000000ULL;
#else
const uintptr_t addressSpaceSize = 0x100000000ULL;
#endif
bool invalidHostAddress = hostAddress == (uintptr_t)0xFFFFFFFFFFFFFFFFULL;
if (hostAddress < baseAddress || hostAddress >= baseAddress + addressSpaceSize) {
if (!invalidHostAddress) {
inCrashHandler = false;
return false;
}
}
uint32_t guestAddress = invalidHostAddress ? 0xFFFFFFFFUL : (uint32_t)(hostAddress - baseAddress);
bool success = false;
MemoryExceptionType type = MemoryExceptionType::NONE;
int instructionSize = 4;
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
instructionSize = 15;
LSInstructionInfo info{};
success = X86AnalyzeMOV(codePtr, info);
if (success)
instructionSize = info.instructionSize;
#elif PPSSPP_ARCH(ARM64)
uint32_t word;
memcpy(&word, codePtr, 4);
Arm64LSInstructionInfo info{};
success = Arm64AnalyzeLoadStore((uint64_t)codePtr, word, &info);
#elif PPSSPP_ARCH(ARM)
uint32_t word;
memcpy(&word, codePtr, 4);
ArmLSInstructionInfo info{};
success = ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info);
#elif PPSSPP_ARCH(RISCV64)
struct RiscVLSInstructionInfo {
int instructionSize;
bool isIntegerLoadStore;
bool isFPLoadStore;
int size;
bool isMemoryWrite;
};
uint32_t word;
memcpy(&word, codePtr, 4);
RiscVLSInstructionInfo info{};
info.instructionSize = (word & 3) == 3 ? 4 : 2;
instructionSize = info.instructionSize;
success = true;
switch (word & 0x7F) {
case 3:
info.isIntegerLoadStore = true;
info.size = 1 << ((word >> 12) & 3);
break;
case 7:
info.isFPLoadStore = true;
info.size = 1 << ((word >> 12) & 3);
break;
case 35:
info.isIntegerLoadStore = true;
info.isMemoryWrite = true;
info.size = 1 << ((word >> 12) & 3);
break;
case 39:
info.isFPLoadStore = true;
info.isMemoryWrite = true;
info.size = 1 << ((word >> 12) & 3);
break;
default:
switch (word & 0x6003) {
case 0x4000:
case 0x4002:
case 0x6000:
case 0x6002:
info.isIntegerLoadStore = true;
info.size = (word & 0x2000) != 0 ? 8 : 4;
info.isMemoryWrite = (word & 0x8000) != 0;
break;
case 0x2000:
case 0x2002:
info.isFPLoadStore = true;
info.size = 8;
info.isMemoryWrite = (word & 0x8000) != 0;
break;
default:
success = false;
break;
}
break;
}
#endif
if (MIPSComp::jit && MIPSComp::jit->IsAtDispatchFetch(codePtr)) {
u32 targetAddr = currentMIPS->pc;
#if PPSSPP_ARCH(AMD64) && PPSSPP_PLATFORM(WINDOWS)
targetAddr = (uint32_t)context->Rax;
#endif
Core_ExecException(targetAddr, currentMIPS->pc, ExecExceptionType::JUMP);
uintptr_t crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
if (crashHandler != 0) {
context->CTX_PC = crashHandler;
ERROR_LOG(Log::MemMap, "Bad execution access detected, halting: %08x (last known pc %08x, host: %p)", targetAddr, currentMIPS->pc, (void *)hostAddress);
inCrashHandler = false;
return true;
}
type = MemoryExceptionType::UNKNOWN;
} else if (success) {
if (info.isMemoryWrite) {
type = MemoryExceptionType::WRITE_WORD;
} else {
type = MemoryExceptionType::READ_WORD;
}
} else {
type = MemoryExceptionType::UNKNOWN;
}
g_lastMemoryExceptionType = type;
bool handled = true;
if (success && (g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) {
if (!info.isMemoryWrite) {
}
context->CTX_PC += info.instructionSize;
g_numReportedBadAccesses++;
if (g_numReportedBadAccesses < 100) {
ERROR_LOG(Log::MemMap, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress);
}
} else {
std::string infoString = "";
std::string temp;
if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(codePtr, temp)) {
infoString += temp + "\n";
}
temp.clear();
if (DisassembleNativeAt(codePtr, instructionSize, &temp)) {
infoString += temp + "\n";
}
uint32_t approximatePC = currentMIPS->pc;
Core_MemoryExceptionInfo(guestAddress, 0, approximatePC, type, infoString, true);
g_lastCrashAddress = codePtr;
uintptr_t crashHandler = 0;
if (MIPSComp::jit)
crashHandler = (uintptr_t)MIPSComp::jit->GetCrashHandler();
if (crashHandler != 0)
context->CTX_PC = crashHandler;
else
handled = false;
ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str());
}
inCrashHandler = false;
return handled;
}
#else
bool HandleFault(uintptr_t hostAddress, void *ctx) {
ERROR_LOG(Log::MemMap, "Exception handling not supported");
return false;
}
#endif
}
std::vector<MIPSStackWalk::StackFrame> WalkCurrentStack(int threadID) {
DebugInterface *cpuDebug = currentDebugMIPS;
auto threads = GetThreadsInfo();
uint32_t entry = cpuDebug->GetPC();
uint32_t stackTop = 0;
for (const DebugThreadInfo &th : threads) {
if ((threadID == -1 && th.isCurrent) || th.id == threadID) {
entry = th.entrypoint;
stackTop = th.initialStack;
break;
}
}
uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA);
uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP);
return MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop);
}
std::string FormatStackTrace(const std::vector<MIPSStackWalk::StackFrame> &frames) {
std::stringstream str;
for (const auto &frame : frames) {
std::string desc = g_symbolMap->GetDescription(frame.entry);
str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frame.entry, frame.pc - frame.entry, frame.pc, frame.sp);
}
return str.str();
}