diff --git a/Common/CodeBlock.h b/Common/CodeBlock.h index f60c4b9305..777fb164a4 100644 --- a/Common/CodeBlock.h +++ b/Common/CodeBlock.h @@ -19,7 +19,7 @@ public: CodeBlockCommon() {} virtual ~CodeBlockCommon() {} - bool IsInSpace(const u8 *ptr) { + bool IsInSpace(const u8 *ptr) const { return (ptr >= region) && (ptr < (region + region_size)); } diff --git a/Common/ExceptionHandlerSetup.cpp b/Common/ExceptionHandlerSetup.cpp index f538efb5a1..d9c3d5c4ad 100644 --- a/Common/ExceptionHandlerSetup.cpp +++ b/Common/ExceptionHandlerSetup.cpp @@ -3,7 +3,6 @@ // Refer to the license.txt file included. #include "Common/ExceptionHandlerSetup.h" - #include #include #include @@ -84,6 +83,7 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler) { return; } + INFO_LOG(SYSTEM, "Installing exception handler"); g_badAccessHandler = badAccessHandler; g_vectoredExceptionHandle = AddVectoredExceptionHandler(TRUE, Handler); } @@ -91,6 +91,7 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler) { void UninstallExceptionHandler() { RemoveVectoredExceptionHandler(g_vectoredExceptionHandle); g_badAccessHandler = nullptr; + INFO_LOG(SYSTEM, "Removed exception handler"); } #elif defined(__APPLE__) && !defined(USE_SIGACTION_ON_APPLE) @@ -186,7 +187,14 @@ static void ExceptionThread(mach_port_t port) { } void InstallExceptionHandler(BadAccessHandler badAccessHandler) { - g_badAccessHandler = badAccessHandler; + if (!g_badAccessHandler) { + g_badAccessHandler = badAccessHandler; + } else { + // The rest of the setup we don't need to do again. + g_badAccessHandler = badAccessHandler; + return; + } + INFO_LOG(SYSTEM, "Installing exception handler"); mach_port_t port; CheckKR("mach_port_allocate", mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)); @@ -275,6 +283,10 @@ static void sigsegv_handler(int sig, siginfo_t* info, void* raw_context) { } void InstallExceptionHandler(BadAccessHandler badAccessHandler) { + if (g_badAccessHandler) { + return; + } + NOTICE_LOG(SYSTEM, "Installed exception handler"); g_badAccessHandler = badAccessHandler; stack_t signal_stack; @@ -308,10 +320,14 @@ void UninstallExceptionHandler() { #ifdef __APPLE__ sigaction(SIGBUS, &old_sa_bus, nullptr); #endif + NOTICE_LOG(SYSTEM, "Uninstalled exception handler"); + g_badAccessHandler = nullptr; } #else // _M_GENERIC or unsupported platform -void InstallExceptionHandler(BadAccessHandler badAccessHandler) { } +void InstallExceptionHandler(BadAccessHandler badAccessHandler) { + ERROR_LOG(SYSTEM, "Exception handler not implemented on this platform, can't install"); +} void UninstallExceptionHandler() { } #endif diff --git a/Common/x64Analyzer.cpp b/Common/x64Analyzer.cpp index 42095b03c9..0d04346c04 100644 --- a/Common/x64Analyzer.cpp +++ b/Common/x64Analyzer.cpp @@ -17,7 +17,7 @@ #include "x64Analyzer.h" -bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info) +bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) { int accessType = 0; diff --git a/Common/x64Analyzer.h b/Common/x64Analyzer.h index d448d4c60f..8ba78005e5 100644 --- a/Common/x64Analyzer.h +++ b/Common/x64Analyzer.h @@ -19,7 +19,7 @@ #include "Common.h" -struct InstructionInfo +struct LSInstructionInfo { int operandSize; //8, 16, 32, 64 int instructionSize; @@ -60,4 +60,4 @@ enum AccessType { OP_ACCESS_WRITE = 1 }; -bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info); +bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info); diff --git a/Core/MIPS/ARM/ArmAsm.cpp b/Core/MIPS/ARM/ArmAsm.cpp index 3a5bcba1a5..fa5a10195d 100644 --- a/Core/MIPS/ARM/ArmAsm.cpp +++ b/Core/MIPS/ARM/ArmAsm.cpp @@ -237,6 +237,7 @@ void ArmJit::GenerateFixedCode() { CMP(R0, 0); B_CC(CC_EQ, outerLoop); + const uint8_t *quitLoop = GetCodePtr(); SetJumpTarget(badCoreState); SaveDowncount(); @@ -251,6 +252,12 @@ void ArmJit::GenerateFixedCode() { POP(9, R4, R5, R6, R7, R8, R9, R10, R11, R_PC); // Returns + crashHandler = GetCodePtr(); + MOVP2R(R0, &coreState); + MOVI2R(R1, CORE_ERROR); + STR(R1, R0, 0); + B(quitLoop); + // Uncomment if you want to see the output... if (disasm) { INFO_LOG(JIT, "THE DISASM ========================"); diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index 80a3bc6ae9..50322a65bc 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -51,6 +51,8 @@ public: void Compile(u32 em_address) override; // Compiles a block at current MIPS PC + const u8 *GetCrashHandler() const override { return crashHandler; } + bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); } bool DescribeCodePtr(const u8 *ptr, std::string &name) override; MIPSOpcode GetOriginalOp(MIPSOpcode op) override; @@ -313,6 +315,8 @@ public: const u8 *restoreRoundingMode; const u8 *applyRoundingMode; + + const u8 *crashHandler; }; } // namespace MIPSComp diff --git a/Core/MIPS/ARM64/Arm64Asm.cpp b/Core/MIPS/ARM64/Arm64Asm.cpp index 755e35f57c..a884f6ac4e 100644 --- a/Core/MIPS/ARM64/Arm64Asm.cpp +++ b/Core/MIPS/ARM64/Arm64Asm.cpp @@ -277,6 +277,7 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) { CMP(SCRATCH1, 0); B(CC_EQ, outerLoop); + const uint8_t *quitLoop = GetCodePtr(); SetJumpTarget(badCoreState); SaveStaticRegisters(); @@ -286,6 +287,12 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) { RET(); + crashHandler = GetCodePtr(); + MOVP2R(SCRATCH1_64, &coreState); + MOVI2R(SCRATCH2, CORE_ERROR); + STR(INDEX_UNSIGNED, SCRATCH2, SCRATCH1_64, 0); + B(quitLoop); + // Generate some integer conversion funcs. // MIPS order! static const RoundingMode roundModes[8] = { ROUND_N, ROUND_Z, ROUND_P, ROUND_M, ROUND_N, ROUND_Z, ROUND_P, ROUND_M }; diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 6ad5921d53..17734ade59 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -52,6 +52,8 @@ public: void Compile(u32 em_address) override; // Compiles a block at current MIPS PC const u8 *DoJit(u32 em_address, JitBlock *b); + const u8 *GetCrashHandler() const override { return crashHandler; } + bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); } bool DescribeCodePtr(const u8 *ptr, std::string &name) override; MIPSOpcode GetOriginalOp(MIPSOpcode op) override; @@ -281,6 +283,8 @@ public: const u8 *applyRoundingMode; const u8 *updateRoundingMode; + const u8 *crashHandler; + int jitStartOffset; // Indexed by FPCR FZ:RN bits for convenience. Uses SCRATCH2. diff --git a/Core/MIPS/IR/IRJit.h b/Core/MIPS/IR/IRJit.h index d378dcfc87..9beabe585a 100644 --- a/Core/MIPS/IR/IRJit.h +++ b/Core/MIPS/IR/IRJit.h @@ -160,7 +160,12 @@ public: void InvalidateCacheAt(u32 em_address, int length = 4) override; void UpdateFCR31() override; + bool CodeInRange(const u8 *ptr) const override { + return false; + } + const u8 *GetDispatcher() const override { return nullptr; } + const u8 *GetCrashHandler() const override { return nullptr; } void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override; void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; diff --git a/Core/MIPS/JitCommon/JitCommon.h b/Core/MIPS/JitCommon/JitCommon.h index bf3023e21d..08063f866e 100644 --- a/Core/MIPS/JitCommon/JitCommon.h +++ b/Core/MIPS/JitCommon/JitCommon.h @@ -121,8 +121,10 @@ namespace MIPSComp { public: virtual ~JitInterface() {} + virtual bool CodeInRange(const u8 *ptr) const = 0; virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; virtual const u8 *GetDispatcher() const = 0; + virtual const u8 *GetCrashHandler() const = 0; virtual JitBlockCache *GetBlockCache() = 0; virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0; virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0; diff --git a/Core/MIPS/x86/Asm.cpp b/Core/MIPS/x86/Asm.cpp index fd1815132d..d927ba8c6d 100644 --- a/Core/MIPS/x86/Asm.cpp +++ b/Core/MIPS/x86/Asm.cpp @@ -206,11 +206,21 @@ void Jit::GenerateFixedCode(JitOptions &jo) { } J_CC(CC_Z, outerLoop, true); + const uint8_t *quitLoop = GetCodePtr(); SetJumpTarget(badCoreState); RestoreRoundingMode(true); ABI_PopAllCalleeSavedRegsAndAdjustStack(); RET(); + crashHandler = GetCodePtr(); + if (RipAccessible((const void *)&coreState)) { + MOV(32, M(&coreState), Imm32(CORE_RUNTIME_ERROR)); + } else { + MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState)); + MOV(32, MatR(RAX), Imm32(CORE_RUNTIME_ERROR)); + } + JMP(quitLoop, true); + // Let's spare the pre-generated code from unprotect-reprotect. endOfPregeneratedCode = AlignCodePage(); EndWrite(); diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 2716f7233a..0dbdf34d7b 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -442,6 +442,8 @@ bool Jit::DescribeCodePtr(const u8 *ptr, std::string &name) { name = "enterDispatcher"; else if (ptr == restoreRoundingMode) name = "restoreRoundingMode"; + else if (ptr == crashHandler) + name = "crashHandler"; else { u32 jitAddr = blocks.GetAddressFromBlockPtr(ptr); diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 0eefe89c40..b065d90522 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -63,6 +63,8 @@ public: void Compile(u32 em_address) override; // Compiles a block at current MIPS PC const u8 *DoJit(u32 em_address, JitBlock *b); + const u8 *GetCrashHandler() const override { return crashHandler; } + bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); } bool DescribeCodePtr(const u8 *ptr, std::string &name) override; void Comp_RunBlock(MIPSOpcode op) override; @@ -325,6 +327,8 @@ private: const u8 *endOfPregeneratedCode; + const u8 *crashHandler; + friend class JitSafeMem; friend class JitSafeMemFuncs; }; diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index af47ce6668..78abbaeacb 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -28,8 +28,20 @@ #include "Common/MemoryUtil.h" #include "Common/MemArena.h" #include "Common/ChunkFile.h" + + +#if defined(PPSSPP_ARCH_AMD64) || defined(PPSSPP_ARCH_X86) #include "Common/MachineContext.h" #include "Common/x64Analyzer.h" +#elif defined(PPSSPP_ARCH_ARM64) +#include "Core/Util/DisArm64.h" +typedef sigcontext SContext; +#define CTX_PC pc +#elif defined(PPSSPP_ARCH_ARM) +#include "ext/disarm.h" +typedef sigcontext SContext; +#define CTX_PC arm_pc +#endif #include "Core/MemMap.h" #include "Core/HDRemaster.h" @@ -43,6 +55,8 @@ #include "Core/ConfigValues.h" #include "Core/HLE/ReplaceTables.h" #include "Core/MIPS/JitCommon/JitBlockCache.h" +#include "Core/MIPS/JitCommon/JitCommon.h" +#include "UI/OnScreenDisplay.h" namespace Memory { @@ -87,6 +101,8 @@ u32 g_PSPModel; std::recursive_mutex g_shutdownLock; +static int64_t g_numReportedBadAccesses = 0; + // We don't declare the IO region in here since its handled by other means. static MemoryView views[] = { @@ -292,6 +308,8 @@ void Init() { INFO_LOG(MEMMAP, "Memory system initialized. Base at %p (RAM at @ %p, uncached @ %p)", base, m_pPhysicalRAM, m_pUncachedRAM); + + g_numReportedBadAccesses = 0; } void Reinit() { @@ -464,7 +482,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { const uint8_t *codePtr = (uint8_t *)(context->CTX_PC); // TODO: Check that codePtr is within the current JIT space. - bool inJitSpace = true; // MIPSComp::jit->IsInSpace(codePtr); + bool inJitSpace = MIPSComp::jit->CodeInRange(codePtr); if (!inJitSpace) { // This is a crash in non-jitted code. Not something we want to handle here, ignore. return false; @@ -486,32 +504,49 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { // OK, a guest executable did a bad access. Take care of it. uint32_t guestAddress = hostAddress - baseAddress; - ERROR_LOG(SYSTEM, "Bad memory access detected and ignored: %08x (%p)", guestAddress, hostAddress); - // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC + + // TODO: Share the struct between the various analyzers, that will allow us to share most of + // the implementations here. #if defined(PPSSPP_ARCH_AMD64) || defined(PPSSPP_ARCH_X86) + // X86, X86-64. Variable instruction size so need to analyze the mov instruction in detail. - InstructionInfo info; - DisassembleMov(codePtr, info); + // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC + LSInstructionInfo info; + X86AnalyzeMOV(codePtr, info); +#elif defined(PPSSPP_ARCH_ARM64) + uint32_t word; + memcpy(&word, codePtr, 4); + // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC + Arm64LSInstructionInfo info; + Arm64AnalyzeLoadStore((uint64_t)codePtr, word, &info); +#elif defined(PPSSPP_ARCH_ARM) + uint32_t word; + memcpy(&word, codePtr, 4); + // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC + ArmLSInstructionInfo info; + ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info); +#endif if (g_Config.bIgnoreBadMemAccess) { if (!info.isMemoryWrite) { - // Must have been a read. Fill the register with 0. + // It was a read. Fill the destination register with 0. // TODO } // Move on to the next instruction. context->CTX_PC += info.instructionSize; + // Fall through to logging. } else { - // Jump to a crash handler. - // TODO - context->CTX_PC += info.instructionSize; + // Jump to a crash handler that will exit the game. + context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler(); + ERROR_LOG(SYSTEM, "Bad memory access detected! %08x (%p) Stopping emulation.", guestAddress, (void *)hostAddress); + return true; } -#else - // ARM, ARM64 : All instructions are always 4 bytes in size. As an initial implementation, - // let's just skip the offending instruction. - context->CTX_PC += 4; -#endif + g_numReportedBadAccesses++; + if (g_numReportedBadAccesses < 100) { + ERROR_LOG(SYSTEM, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress); + } return true; } diff --git a/Core/Util/DisArm64.cpp b/Core/Util/DisArm64.cpp index fd17c54412..c891b5eb05 100644 --- a/Core/Util/DisArm64.cpp +++ b/Core/Util/DisArm64.cpp @@ -265,6 +265,41 @@ static void BranchExceptionAndSystem(uint32_t w, uint64_t addr, Instruction *ins } } +void Arm64AnalyzeLoadStore(uint64_t addr, uint32_t w, Arm64LSInstructionInfo *info) { + *info = {}; + info->instructionSize = 4; + int id = (w >> 25) & 0xF; + switch (id) { + case 4: case 6: case 0xC: case 0xE: + info->isLoadOrStore = true; + break; + default: + ERROR_LOG(CPU, "Tried to disassemble %08x at %p as a load/store instruction", w, (void *)addr); + return; // not the expected instruction + } + + info->size = w >> 30; + info->Rt = (w & 0x1F); + info->Rn = ((w >> 5) & 0x1F); + info->Rm = ((w >> 16) & 0x1F); + int opc = (w >> 22) & 0x3; + if (opc == 0 || opc == 2) { + info->isMemoryWrite = true; + } + + if (((w >> 27) & 7) == 7) { + int V = (w >> 26) & 1; + if (V == 0) { + info->isIntegerLoadStore = true; + } else { + info->isFPLoadStore = true; + } + } else { + info->isPairLoadStore = true; + // TODO + } +} + static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) { int size = w >> 30; int imm9 = SignExtend9((w >> 12) & 0x1FF); diff --git a/Core/Util/DisArm64.h b/Core/Util/DisArm64.h index fc55c6c34b..73b22db4bd 100644 --- a/Core/Util/DisArm64.h +++ b/Core/Util/DisArm64.h @@ -24,3 +24,24 @@ typedef bool (*SymbolCallback)(char *buffer, int bufsize, uint8_t *address); void Arm64Dis(uint64_t addr, uint32_t w, char *output, int bufsize, bool includeWord, SymbolCallback symbolCallback = nullptr); +// Information about a load/store instruction. +struct Arm64LSInstructionInfo { + int instructionSize; + + bool isLoadOrStore; + + bool isIntegerLoadStore; + bool isFPLoadStore; + bool isPairLoadStore; + + int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit + bool isMemoryWrite; + + int Rt; + int Rn; + int Rm; + + // TODO: more. +}; + +void Arm64AnalyzeLoadStore(uint64_t addr, uint32_t op, Arm64LSInstructionInfo *info); diff --git a/ext/disarm.cpp b/ext/disarm.cpp index 90c89c16b5..c3d0662f3f 100644 --- a/ext/disarm.cpp +++ b/ext/disarm.cpp @@ -72,6 +72,7 @@ #include "base/basictypes.h" #include "Common/ArmEmitter.h" +#include "ext/disarm.h" static const char *CCFlagsStr[] = { "EQ", // Equal @@ -759,30 +760,12 @@ static bool DisasmNeon(uint32_t op, char *text) { return false; } +void ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info) { + *info = {}; + info->instructionSize = 4; - - - - - - - - - - - - - - - - - - - - - - - + // TODO +} typedef unsigned int word; diff --git a/ext/disarm.h b/ext/disarm.h index 9bae68f215..138f3e3e69 100644 --- a/ext/disarm.h +++ b/ext/disarm.h @@ -17,18 +17,34 @@ #pragma once +#include -// This stuff only disassembles very old ARM but should be sufficient -// for the basics except for MOVW/MOVT. - - +// This stuff used to only disassemble very old ARM but has now +// been extended to support most (but not all) modern instructions, including NEON. // Disarm itself has the license you can see in the cpp file. // I'm not entirely sure it's 100% gpl compatible but it's nearly // public domain so meh. -// The only changes I've done is C++ compat and replaced the main -// program with this function. - const char *ArmRegName(int r); void ArmDis(unsigned int addr, unsigned int w, char *output, int bufsize, bool includeWord); + +// Information about a load/store instruction. +struct ArmLSInstructionInfo { + int instructionSize; + + bool isIntegerLoadStore; + bool isFPLoadStore; + bool isMultiLoadStore; + + int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit + bool isMemoryWrite; + + int Rt; + int Rn; + int Rm; + + // TODO: more. +}; + +void ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info);