diff --git a/Core/Config.h b/Core/Config.h index c4ba00d2b1ca..1843823047e9 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -97,6 +97,7 @@ struct Config { // Core bool bIgnoreBadMemAccess; + bool bFastMemory; int iCpuCore; bool bCheckForNewVersion; diff --git a/Core/Core.cpp b/Core/Core.cpp index 6a89148be251..cf851e807e33 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -29,6 +29,7 @@ #include "profiler/profiler.h" #include "Common/GraphicsContext.h" +#include "Common/Log.h" #include "Core/Core.h" #include "Core/Config.h" #include "Core/Host.h" @@ -44,7 +45,6 @@ #include "Windows/InputDevice.h" #endif - // Time until we stop considering the core active without user input. // Should this be configurable? 2 hours currently. static const double ACTIVITY_IDLE_TIMEOUT = 2.0 * 3600.0; @@ -63,6 +63,8 @@ static double lastKeepAwake = 0.0; static GraphicsContext *graphicsContext; static bool powerSaving = false; +static ExceptionInfo g_exceptionInfo; + void Core_SetGraphicsContext(GraphicsContext *ctx) { graphicsContext = ctx; PSP_CoreParameter().graphicsContext = graphicsContext; @@ -339,7 +341,8 @@ void Core_Run(GraphicsContext *ctx) { case CORE_POWERUP: case CORE_POWERDOWN: - case CORE_ERROR: + case CORE_BOOT_ERROR: + case CORE_RUNTIME_ERROR: // Exit loop!! Core_StateProcessed(); @@ -364,6 +367,75 @@ void Core_EnableStepping(bool step) { } } +bool Core_NextFrame() { + if (coreState == CORE_RUNNING) { + coreState = CORE_NEXTFRAME; + return true; + } else { + return false; + } +} + int Core_GetSteppingCounter() { return steppingCounter; } + +const char *ExceptionTypeAsString(ExceptionType type) { + switch (type) { + case ExceptionType::MEMORY: return "Invalid Memory Access"; + case ExceptionType::BREAK: return "Break"; + case ExceptionType::BAD_EXEC_ADDR: return "Bad Execution Address"; + default: return "N/A"; + } +} + +const char *MemoryExceptionTypeAsString(MemoryExceptionType type) { + switch (type) { + case MemoryExceptionType::READ_WORD: return "Read Word"; + case MemoryExceptionType::WRITE_WORD: return "Write Word"; + case MemoryExceptionType::READ_BLOCK: return "Read Block"; + case MemoryExceptionType::WRITE_BLOCK: return "Read/Write Block"; + default: + return "N/A"; + } +} + +void Core_MemoryException(u32 address, u32 pc, MemoryExceptionType type) { + const char *desc = MemoryExceptionTypeAsString(type); + // In jit, we only flush PC when bIgnoreBadMemAccess is off. + if (g_Config.iCpuCore == (int)CPUCore::JIT && g_Config.bIgnoreBadMemAccess) { + WARN_LOG(MEMMAP, "%s: Invalid address %08x", desc, address); + } else { + WARN_LOG(MEMMAP, "%s: Invalid address %08x PC %08x LR %08x", desc, address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); + } + + if (!g_Config.bIgnoreBadMemAccess) { + ExceptionInfo &e = g_exceptionInfo; + e = {}; + e.type = ExceptionType::MEMORY; + e.info = ""; + e.memory_type = type; + e.address = address; + e.pc = pc; + Core_EnableStepping(true); + host->SetDebugMode(true); + } +} + +void Core_Break() { + ERROR_LOG(CPU, "BREAK!"); + + ExceptionInfo &e = g_exceptionInfo; + e = {}; + e.type = ExceptionType::BREAK; + e.info = ""; + + if (!g_Config.bIgnoreBadMemAccess) { + Core_EnableStepping(true); + host->SetDebugMode(true); + } +} + +const ExceptionInfo &Core_GetExceptionInfo() { + return g_exceptionInfo; +} diff --git a/Core/Core.h b/Core/Core.h index 69749f0d7032..ef428595bd94 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -32,6 +32,8 @@ void Core_SetGraphicsContext(GraphicsContext *ctx); // called from gui void Core_EnableStepping(bool step); + +bool Core_NextFrame(); void Core_DoSingleStep(); void Core_UpdateSingleStep(); void Core_ProcessStepping(); @@ -76,3 +78,35 @@ void Core_NotifyActivity(); void Core_SetPowerSaving(bool mode); bool Core_GetPowerSaving(); + +enum class MemoryExceptionType { + READ_WORD, + WRITE_WORD, + READ_BLOCK, + WRITE_BLOCK, +}; + +void Core_MemoryException(u32 address, u32 pc, MemoryExceptionType type); +void Core_Break(); + +enum class ExceptionType { + NONE, + MEMORY, + BREAK, + BAD_EXEC_ADDR, +}; + +struct ExceptionInfo { + ExceptionType type; + std::string info; + + // Memory exception info + MemoryExceptionType memory_type; + uint32_t pc; + uint32_t address; +}; + +const ExceptionInfo &Core_GetExceptionInfo(); + +const char *ExceptionTypeAsString(ExceptionType type); +const char *MemoryExceptionTypeAsString(MemoryExceptionType type); diff --git a/Core/HLE/HLE.cpp b/Core/HLE/HLE.cpp index a19c17080066..d1dadad229a1 100644 --- a/Core/HLE/HLE.cpp +++ b/Core/HLE/HLE.cpp @@ -469,7 +469,7 @@ void HLEReturnFromMipsCall() { if ((stackData->nextOff & 0x0000000F) != 0 || !Memory::IsValidAddress(sp + stackData->nextOff)) { ERROR_LOG(HLE, "Corrupt stack on HLE mips call return: %08x", stackData->nextOff); - Core_UpdateState(CORE_ERROR); + Core_UpdateState(CORE_RUNTIME_ERROR); return; } @@ -482,9 +482,10 @@ void HLEReturnFromMipsCall() { while ((finalMarker->nextOff & 0x0000000F) == 0 && Memory::IsValidAddress(finalMarker.ptr + finalMarker->nextOff)) { finalMarker.ptr += finalMarker->nextOff; } + if (finalMarker->nextOff != 0xFFFFFFFF) { ERROR_LOG(HLE, "Corrupt stack on HLE mips call return action: %08x", finalMarker->nextOff); - Core_UpdateState(CORE_ERROR); + Core_UpdateState(CORE_RUNTIME_ERROR); return; } diff --git a/Core/HLE/proAdhoc.h b/Core/HLE/proAdhoc.h index 90f5dfe4dccb..1480c030260b 100644 --- a/Core/HLE/proAdhoc.h +++ b/Core/HLE/proAdhoc.h @@ -24,7 +24,6 @@ #include "net/resolve.h" #include "Common/ChunkFile.h" -#include "Core/Config.h" #include "Core/CoreTiming.h" #include "Core/MemMap.h" #include "Core/HLE/HLE.h" diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 56fcf9bfc91d..362ad09ce179 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -42,6 +42,7 @@ #include "Core/CoreParameter.h" #include "Core/Host.h" #include "Core/Reporting.h" +#include "Core/Core.h" #include "Core/System.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" @@ -791,8 +792,7 @@ void __DisplayFlip(int cyclesLate) { const bool fbReallyDirty = gpu->FramebufferReallyDirty(); if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) { // Check first though, might've just quit / been paused. - if (coreState == CORE_RUNNING) { - coreState = CORE_NEXTFRAME; + if (Core_NextFrame()) { gpu->CopyDisplayToOutput(fbReallyDirty); if (fbReallyDirty) { actualFlips++; diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 7865d4cea530..fad7769c0219 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -555,7 +555,7 @@ static VFSFileSystem *flash0System = nullptr; static void __IoManagerThread() { setCurrentThreadName("IO"); - while (ioManagerThreadEnabled && coreState != CORE_ERROR && coreState != CORE_POWERDOWN) { + while (ioManagerThreadEnabled && coreState != CORE_BOOT_ERROR && coreState != CORE_RUNTIME_ERROR && coreState != CORE_POWERDOWN) { ioManager.RunEventsUntil(CoreTiming::GetTicks() + msToCycles(1000)); } } diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index ae804a4520ba..70d1f13ca6b4 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -297,6 +297,12 @@ bool __KernelIsRunning() { return kernelRunning; } +std::string __KernelStateSummary() { + std::string threadSummary = __KernelThreadingSummary(); + return StringFromFormat("%s", threadSummary.c_str()); +} + + void sceKernelExitGame() { INFO_LOG(SCEKERNEL, "sceKernelExitGame"); diff --git a/Core/HLE/sceKernel.h b/Core/HLE/sceKernel.h index 96e94cb76002..692d23ec8992 100644 --- a/Core/HLE/sceKernel.h +++ b/Core/HLE/sceKernel.h @@ -18,6 +18,7 @@ #pragma once #include +#include #include "Common/Common.h" #include "Common/Swap.h" @@ -374,6 +375,9 @@ void __KernelDoState(PointerWrap &p); bool __KernelIsRunning(); bool __KernelLoadExec(const char *filename, SceKernelLoadExecParam *param); +// For crash reporting. +std::string __KernelStateSummary(); + int sceKernelLoadExec(const char *filename, u32 paramPtr); void sceKernelExitGame(); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index bc8f91acecee..2c3b7aef0894 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1837,7 +1837,7 @@ int sceKernelLoadExec(const char *filename, u32 paramPtr) std::string error_string; if (!__KernelLoadExec(exec_filename.c_str(), paramPtr, &error_string)) { ERROR_LOG(SCEMODULE, "sceKernelLoadExec failed: %s", error_string.c_str()); - Core_UpdateState(CORE_ERROR); + Core_UpdateState(CORE_RUNTIME_ERROR); return -1; } if (gpu) { diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index c125764a8f41..d892b2c1eb93 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -372,6 +372,8 @@ class ActionAfterCallback : public PSPAction class PSPThread : public KernelObject { public: + PSPThread() : debug(currentMIPS, context) {} + const char *GetName() override { return nt.name; } const char *GetTypeName() override { return "Thread"; } void GetQuickInfo(char *ptr, int size) override @@ -491,10 +493,6 @@ class PSPThread : public KernelObject { return true; } - PSPThread() : debug(currentMIPS, context) { - currentStack.start = 0; - } - // Can't use a destructor since savestates will call that too. void Cleanup() { @@ -573,14 +571,14 @@ class PSPThread : public KernelObject { } } - NativeThread nt; + NativeThread nt{}; - ThreadWaitInfo waitInfo; - SceUID moduleId; + ThreadWaitInfo waitInfo{}; + SceUID moduleId = -1; - bool isProcessingCallbacks; - u32 currentMipscallId; - SceUID currentCallbackId; + bool isProcessingCallbacks = false; + u32 currentMipscallId = -1; + SceUID currentCallbackId = -1; PSPThreadContext context; KernelThreadDebugInterface debug; @@ -597,7 +595,7 @@ class PSPThread : public KernelObject { // These are stacks that aren't "active" right now, but will pop off once the func returns. std::vector pushedStacks; - StackInfo currentStack; + StackInfo currentStack{}; // For thread end. std::vector waitingThreads; @@ -1184,6 +1182,10 @@ void __KernelThreadingShutdown() { pendingDeleteThreads.clear(); } +std::string __KernelThreadingSummary() { + return StringFromFormat("Cur thread: %s", __GetCurrentThread()->GetName()); +} + const char *__KernelGetThreadName(SceUID threadID) { u32 error; diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index a3fbbf1aeb26..4c3041f65af0 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -155,6 +155,9 @@ void __KernelThreadingInit(); void __KernelThreadingDoState(PointerWrap &p); void __KernelThreadingDoStateLate(PointerWrap &p); void __KernelThreadingShutdown(); + +std::string __KernelThreadingSummary(); + KernelObject *__KernelThreadObject(); KernelObject *__KernelCallbackObject(); diff --git a/Core/HW/AsyncIOManager.h b/Core/HW/AsyncIOManager.h index 41b997b0893a..6bb9be5039e9 100644 --- a/Core/HW/AsyncIOManager.h +++ b/Core/HW/AsyncIOManager.h @@ -91,7 +91,7 @@ class AsyncIOManager : public IOThreadEventQueue { protected: void ProcessEvent(AsyncIOEvent ref) override; bool ShouldExitEventLoop() override { - return coreState == CORE_ERROR || coreState == CORE_POWERDOWN; + return coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR || coreState == CORE_POWERDOWN; } private: @@ -106,4 +106,4 @@ class AsyncIOManager : public IOThreadEventQueue { std::condition_variable resultsWait_; std::set resultsPending_; std::map results_; -}; \ No newline at end of file +}; diff --git a/Core/HW/Camera.cpp b/Core/HW/Camera.cpp index 48d5210432b8..415ac0a19098 100644 --- a/Core/HW/Camera.cpp +++ b/Core/HW/Camera.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Camera.h" +#include "Core/Config.h" void convert_frame(int inw, int inh, unsigned char *inData, AVPixelFormat inFormat, int outw, int outh, unsigned char **outData, int *outLen) { diff --git a/Core/HW/Camera.h b/Core/HW/Camera.h index fbc691bf640e..4e36fbd29644 100644 --- a/Core/HW/Camera.h +++ b/Core/HW/Camera.h @@ -18,7 +18,6 @@ #pragma once #include "ppsspp_config.h" -#include "Core/Config.h" #include "Core/HLE/sceUsbCam.h" #include "Log.h" diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index a4d102e122d6..a15b8e3c9898 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -258,7 +258,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { } else if (ebootType == IdentifiedFileType::PSP_PS1_PBP) { *error_string = "PS1 EBOOTs are not supported by PPSSPP."; - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; return false; } std::string path = fileLoader->Path(); @@ -270,7 +270,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { return Load_PSP_ELF_PBP(fileLoader, error_string); } else { *error_string = "No EBOOT.PBP, misidentified game"; - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; return false; } } @@ -352,7 +352,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { break; } - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; return false; } diff --git a/Core/MIPS/IR/IRInterpreter.cpp b/Core/MIPS/IR/IRInterpreter.cpp index 9a4e49122825..63a1d5718924 100644 --- a/Core/MIPS/IR/IRInterpreter.cpp +++ b/Core/MIPS/IR/IRInterpreter.cpp @@ -947,10 +947,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) { } case IROp::Break: - if (!g_Config.bIgnoreBadMemAccess) { - Core_EnableStepping(true); - host->SetDebugMode(true); - } + Core_Break(); return mips->pc + 4; case IROp::SetCtrlVFPU: diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index e99e86073c24..f81b55ca98de 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -215,7 +215,7 @@ unsigned int MIPSDebugInterface::readMemory(unsigned int address) bool MIPSDebugInterface::isAlive() { - return PSP_IsInited() && coreState != CORE_ERROR && coreState != CORE_POWERDOWN; + return PSP_IsInited() && coreState != CORE_BOOT_ERROR && coreState != CORE_RUNTIME_ERROR && coreState != CORE_POWERDOWN; } bool MIPSDebugInterface::isBreakpoint(unsigned int address) diff --git a/Core/MIPS/MIPSInt.cpp b/Core/MIPS/MIPSInt.cpp index 93372391c898..b8d99c078d69 100644 --- a/Core/MIPS/MIPSInt.cpp +++ b/Core/MIPS/MIPSInt.cpp @@ -152,11 +152,7 @@ namespace MIPSInt void Int_Break(MIPSOpcode op) { Reporting::ReportMessage("BREAK instruction hit"); - ERROR_LOG(CPU, "BREAK!"); - if (!g_Config.bIgnoreBadMemAccess) { - Core_EnableStepping(true); - host->SetDebugMode(true); - } + Core_Break(); PC += 4; } diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index a8050ac0ac0f..a9b24fa8a230 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -747,7 +747,7 @@ void Jit::WriteExitDestInReg(X64Reg reg) { if (g_Config.bIgnoreBadMemAccess) { CMP(32, R(EAX), Imm32(0)); FixupBranch skip = J_CC(CC_NE); - ABI_CallFunctionA((const void *)&Core_UpdateState, Imm32(CORE_ERROR)); + ABI_CallFunctionA((const void *)&Core_UpdateState, Imm32(CORE_RUNTIME_ERROR)); SetJumpTarget(skip); } diff --git a/Core/MemMap.h b/Core/MemMap.h index 28c635c1815a..4f598961f3a6 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -52,8 +52,7 @@ typedef void (*readFn16)(u16&, const u32); typedef void (*readFn32)(u32&, const u32); typedef void (*readFn64)(u64&, const u32); -namespace Memory -{ +namespace Memory { // Base is a pointer to the base of the memory map. Yes, some MMU tricks // are used to set up a full GC or Wii memory map in process memory. on // 32-bit, you have to mask your offsets with 0x3FFFFFFF. This means that @@ -165,16 +164,6 @@ inline u8* GetPointerUnchecked(const u32 address) { #endif } -#ifdef SAFE_MEMORY -u32 ReadUnchecked_U32(const u32 _Address); -// ONLY for use by GUI and fast interpreter -u8 ReadUnchecked_U8(const u32 _Address); -u16 ReadUnchecked_U16(const u32 _Address); -void WriteUnchecked_U8(const u8 _Data, const u32 _Address); -void WriteUnchecked_U16(const u16 _Data, const u32 _Address); -void WriteUnchecked_U32(const u32 _Data, const u32 _Address); -#else - inline u32 ReadUnchecked_U32(const u32 address) { #ifdef MASKED_PSP_MEMORY return *(u32_le *)(base + (address & MEMVIEW32_MASK)); @@ -239,8 +228,6 @@ inline void WriteUnchecked_U8(u8 data, u32 address) { #endif } -#endif - inline float Read_Float(u32 address) { u32 ifloat = Read_U32(address); @@ -274,18 +261,15 @@ inline const char* GetCharPointer(const u32 address) { return (const char *)GetPointer(address); } -inline void MemcpyUnchecked(void *to_data, const u32 from_address, const u32 len) -{ +inline void MemcpyUnchecked(void *to_data, const u32 from_address, const u32 len) { memcpy(to_data, GetPointerUnchecked(from_address), len); } -inline void MemcpyUnchecked(const u32 to_address, const void *from_data, const u32 len) -{ +inline void MemcpyUnchecked(const u32 to_address, const void *from_data, const u32 len) { memcpy(GetPointerUnchecked(to_address), from_data, len); } -inline void MemcpyUnchecked(const u32 to_address, const u32 from_address, const u32 len) -{ +inline void MemcpyUnchecked(const u32 to_address, const u32 from_address, const u32 len) { MemcpyUnchecked(GetPointer(to_address), from_address, len); } @@ -327,7 +311,7 @@ inline bool IsValidRange(const u32 address, const u32 size) { return IsValidAddress(address) && ValidSize(address, size) == size; } -}; +} // namespace Memory template struct PSPPointer @@ -462,44 +446,36 @@ inline u32 PSP_GetKernelMemoryEnd() { return 0x08400000;} // game through sceKernelVolatileMemTryLock. inline u32 PSP_GetUserMemoryBase() { return 0x08800000;} - inline u32 PSP_GetDefaultLoadAddress() { return 0;} -//inline u32 PSP_GetDefaultLoadAddress() { return 0x0898dab0;} inline u32 PSP_GetVidMemBase() { return 0x04000000;} inline u32 PSP_GetVidMemEnd() { return 0x04800000;} template -inline bool operator==(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator==(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr == rhs.ptr; } template -inline bool operator!=(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator!=(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr != rhs.ptr; } template -inline bool operator<(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator<(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr < rhs.ptr; } template -inline bool operator>(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator>(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr > rhs.ptr; } template -inline bool operator<=(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator<=(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr <= rhs.ptr; } template -inline bool operator>=(const PSPPointer &lhs, const PSPPointer &rhs) -{ +inline bool operator>=(const PSPPointer &lhs, const PSPPointer &rhs) { return lhs.ptr >= rhs.ptr; } diff --git a/Core/MemMapFunctions.cpp b/Core/MemMapFunctions.cpp index ac543e36bd27..07f912b198d6 100644 --- a/Core/MemMapFunctions.cpp +++ b/Core/MemMapFunctions.cpp @@ -26,17 +26,7 @@ #include "Core/MIPS/MIPS.h" -namespace Memory -{ - -// ================================= -// From Memmap.cpp -// ---------------- - -// Read and write shortcuts - -// GetPointer must always return an address in the bottom 32 bits of address space, so that 64-bit -// programs don't have problems directly addressing any part of memory. +namespace Memory { u8 *GetPointer(const u32 address) { if ((address & 0x3E000000) == 0x08000000) { @@ -52,16 +42,12 @@ u8 *GetPointer(const u32 address) { // More RAM (remasters, etc.) return GetPointerUnchecked(address); } else { - ERROR_LOG(MEMMAP, "Unknown GetPointer %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); static bool reported = false; if (!reported) { Reporting::ReportMessage("Unknown GetPointer %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); reported = true; } - if (!g_Config.bIgnoreBadMemAccess) { - Core_EnableStepping(true); - host->SetDebugMode(true); - } + Core_MemoryException(address, currentMIPS->pc, MemoryExceptionType::WRITE_BLOCK); return nullptr; } } @@ -69,10 +55,6 @@ u8 *GetPointer(const u32 address) { template inline void ReadFromHardware(T &var, const u32 address) { // TODO: Figure out the fastest order of tests for both read and write (they are probably different). - // TODO: Make sure this represents the mirrors in a correct way. - - // Could just do a base-relative read, too.... TODO - if ((address & 0x3E000000) == 0x08000000) { // RAM var = *((const T*)GetPointerUnchecked(address)); @@ -86,29 +68,18 @@ inline void ReadFromHardware(T &var, const u32 address) { // More RAM (remasters, etc.) var = *((const T*)GetPointerUnchecked(address)); } else { - // In jit, we only flush PC when bIgnoreBadMemAccess is off. - if (g_Config.iCpuCore == (int)CPUCore::JIT && g_Config.bIgnoreBadMemAccess) { - WARN_LOG(MEMMAP, "ReadFromHardware: Invalid address %08x", address); - } else { - WARN_LOG(MEMMAP, "ReadFromHardware: Invalid address %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); - } static bool reported = false; if (!reported) { Reporting::ReportMessage("ReadFromHardware: Invalid address %08x near PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); reported = true; } - if (!g_Config.bIgnoreBadMemAccess) { - Core_EnableStepping(true); - host->SetDebugMode(true); - } + Core_MemoryException(address, currentMIPS->pc, MemoryExceptionType::READ_WORD); var = 0; } } template inline void WriteToHardware(u32 address, const T data) { - // Could just do a base-relative write, too.... TODO - if ((address & 0x3E000000) == 0x08000000) { // RAM *(T*)GetPointerUnchecked(address) = data; @@ -122,32 +93,21 @@ inline void WriteToHardware(u32 address, const T data) { // More RAM (remasters, etc.) *(T*)GetPointerUnchecked(address) = data; } else { - // In jit, we only flush PC when bIgnoreBadMemAccess is off. - if (g_Config.iCpuCore == (int)CPUCore::JIT && g_Config.bIgnoreBadMemAccess) { - WARN_LOG(MEMMAP, "WriteToHardware: Invalid address %08x", address); - } else { - WARN_LOG(MEMMAP, "WriteToHardware: Invalid address %08x PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); - } static bool reported = false; if (!reported) { Reporting::ReportMessage("WriteToHardware: Invalid address %08x near PC %08x LR %08x", address, currentMIPS->pc, currentMIPS->r[MIPS_REG_RA]); reported = true; } - if (!g_Config.bIgnoreBadMemAccess) { - Core_EnableStepping(true); - host->SetDebugMode(true); - } + Core_MemoryException(address, currentMIPS->pc, MemoryExceptionType::WRITE_WORD); } } -// ===================== - bool IsRAMAddress(const u32 address) { if ((address & 0x3E000000) == 0x08000000) { return true; - } else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) { + } else if ((address & 0x3F000000) >= 0x08000000 && (address & 0x3F000000) < 0x08000000 + g_MemorySize) { return true; - } else { + } else { return false; } } @@ -160,102 +120,52 @@ bool IsScratchpadAddress(const u32 address) { return (address & 0xBFFF0000) == 0x00010000 && (address & 0x0000FFFF) < SCRATCHPAD_SIZE; } -u8 Read_U8(const u32 _Address) -{ - u8 _var = 0; - ReadFromHardware(_var, _Address); - return (u8)_var; +u8 Read_U8(const u32 address) { + u8 value = 0; + ReadFromHardware(value, address); + return (u8)value; } -u16 Read_U16(const u32 _Address) -{ - u16_le _var = 0; - ReadFromHardware(_var, _Address); - return (u16)_var; +u16 Read_U16(const u32 address) { + u16_le value = 0; + ReadFromHardware(value, address); + return (u16)value; } -u32 Read_U32(const u32 _Address) -{ - u32_le _var = 0; - ReadFromHardware(_var, _Address); - return _var; +u32 Read_U32(const u32 address) { + u32_le value = 0; + ReadFromHardware(value, address); + return value; } -u64 Read_U64(const u32 _Address) -{ - u64_le _var = 0; - ReadFromHardware(_var, _Address); - return _var; +u64 Read_U64(const u32 address) { + u64_le value = 0; + ReadFromHardware(value, address); + return value; } -u32 Read_U8_ZX(const u32 _Address) -{ - return (u32)Read_U8(_Address); +u32 Read_U8_ZX(const u32 address) { + return (u32)Read_U8(address); } -u32 Read_U16_ZX(const u32 _Address) -{ - return (u32)Read_U16(_Address); +u32 Read_U16_ZX(const u32 address) { + return (u32)Read_U16(address); } -void Write_U8(const u8 _Data, const u32 _Address) -{ - WriteToHardware(_Address, _Data); +void Write_U8(const u8 _Data, const u32 address) { + WriteToHardware(address, _Data); } -void Write_U16(const u16 _Data, const u32 _Address) -{ - WriteToHardware(_Address, _Data); +void Write_U16(const u16 _Data, const u32 address) { + WriteToHardware(address, _Data); } -void Write_U32(const u32 _Data, const u32 _Address) -{ - WriteToHardware(_Address, _Data); +void Write_U32(const u32 _Data, const u32 address) { + WriteToHardware(address, _Data); } -void Write_U64(const u64 _Data, const u32 _Address) -{ - WriteToHardware(_Address, _Data); +void Write_U64(const u64 _Data, const u32 address) { + WriteToHardware(address, _Data); } -#ifdef SAFE_MEMORY - -u8 ReadUnchecked_U8(const u32 _Address) -{ - u8 _var = 0; - ReadFromHardware(_var, _Address); - return _var; -} - -u16 ReadUnchecked_U16(const u32 _Address) -{ - u16_le _var = 0; - ReadFromHardware(_var, _Address); - return _var; -} - -u32 ReadUnchecked_U32(const u32 _Address) -{ - u32_le _var = 0; - ReadFromHardware(_var, _Address); - return _var; -} - -void WriteUnchecked_U8(const u8 _iValue, const u32 _Address) -{ - WriteToHardware(_Address, _iValue); -} - -void WriteUnchecked_U16(const u16 _iValue, const u32 _Address) -{ - WriteToHardware(_Address, _iValue); -} - -void WriteUnchecked_U32(const u32 _iValue, const u32 _Address) -{ - WriteToHardware(_Address, _iValue); -} - -#endif - } // namespace Memory diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index d29f1b3b8985..8f61f3542c1d 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -278,7 +278,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { } else { *error_string = "A PSP game couldn't be found on the disc."; } - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; return false; } @@ -303,7 +303,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { if (success && coreState == CORE_POWERUP) { coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; // TODO: This is a crummy way to communicate the error... PSP_CoreParameter().fileToStart = ""; } @@ -374,7 +374,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { // If root is not a subpath of path, we can't boot the game. if (!startsWith(pathNorm, rootNorm)) { *error_string = "Cannot boot ELF located outside mountRoot."; - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; return false; } @@ -433,7 +433,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { if (success && coreState == CORE_POWERUP) { coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; // TODO: This is a crummy way to communicate the error... PSP_CoreParameter().fileToStart = ""; } @@ -457,7 +457,7 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { if (success && coreState == CORE_POWERUP) { coreState = PSP_CoreParameter().startBreak ? CORE_STEPPING : CORE_RUNNING; } else { - coreState = CORE_ERROR; + coreState = CORE_BOOT_ERROR; // TODO: This is a crummy way to communicate the error... PSP_CoreParameter().fileToStart = ""; } diff --git a/Core/System.cpp b/Core/System.cpp index eff04fbeade6..c8a8b4c769d0 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -97,13 +97,19 @@ bool coreCollectDebugStatsForced = false; // This can be read and written from ANYWHERE. volatile CoreState coreState = CORE_STEPPING; -// Note: intentionally not used for CORE_NEXTFRAME. +// If true, core state has been changed, but JIT has probably not noticed yet. volatile bool coreStatePending = false; + static volatile CPUThreadState cpuThreadState = CPU_THREAD_NOT_RUNNING; static GPUBackend gpuBackend; static std::string gpuBackendDevice; +// Ugly! +static bool pspIsInited = false; +static bool pspIsIniting = false; +static bool pspIsQuitting = false; + void ResetUIState() { globalUIState = UISTATE_MENU; } @@ -328,11 +334,6 @@ void Core_UpdateDebugStats(bool collectStats) { gpuStats.ResetFrame(); } -// Ugly! -static bool pspIsInited = false; -static bool pspIsIniting = false; -static bool pspIsQuitting = false; - bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { if (pspIsIniting || pspIsQuitting) { return false; @@ -433,7 +434,7 @@ void PSP_Shutdown() { // Make sure things know right away that PSP memory, etc. is going away. pspIsQuitting = true; if (coreState == CORE_RUNNING) - Core_UpdateState(CORE_ERROR); + Core_UpdateState(CORE_POWERDOWN); #ifndef MOBILE_DEVICE if (g_Config.bFuncHashMap) { @@ -486,7 +487,7 @@ void PSP_RunLoopWhileState() { void PSP_RunLoopUntil(u64 globalticks) { SaveState::Process(); - if (coreState == CORE_POWERDOWN || coreState == CORE_ERROR) { + if (coreState == CORE_POWERDOWN || coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR) { return; } else if (coreState == CORE_STEPPING) { Core_ProcessStepping(); diff --git a/Core/System.h b/Core/System.h index 43be1e55eb75..0d21b0d1bb05 100644 --- a/Core/System.h +++ b/Core/System.h @@ -101,12 +101,20 @@ void InitSysDirectories(); // RUNNING must be at 0, NEXTFRAME must be at 1. enum CoreState { + // Emulation is running normally. CORE_RUNNING = 0, + // Emulation was running normally, just reached the end of a frame. CORE_NEXTFRAME = 1, - CORE_STEPPING, + // Emulation is paused, CPU thread is sleeping. + CORE_STEPPING, // Can be used for recoverable runtime errors (ignored memory exceptions) + // Core is being powered up. CORE_POWERUP, + // Core is being powered down. CORE_POWERDOWN, - CORE_ERROR, + // An error happened at boot. + CORE_BOOT_ERROR, + // Unrecoverable runtime error. Recoverable errors should use CORE_STEPPING. + CORE_RUNTIME_ERROR, }; extern bool coreCollectDebugStats; diff --git a/Core/ThreadEventQueue.h b/Core/ThreadEventQueue.h index 66f442cdfd82..d365454776d3 100644 --- a/Core/ThreadEventQueue.h +++ b/Core/ThreadEventQueue.h @@ -139,7 +139,7 @@ struct ThreadEventQueue : public B { // Don't run if it's not running, but wait for startup. if (!eventsRunning_) { - if (eventsHaveRun_ || coreState == CORE_ERROR || coreState == CORE_POWERDOWN) { + if (eventsHaveRun_ || coreState == CORE_BOOT_ERROR || coreState == CORE_RUNTIME_ERROR || coreState == CORE_POWERDOWN) { return false; } } diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 98a7bbd9e3f2..79035ba076d7 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -28,10 +28,10 @@ #include "Common/Common.h" #include "Core/Config.h" #include "Core/ConfigValues.h" +#include "Core/Core.h" #include "Core/CoreParameter.h" #include "Core/Host.h" #include "Core/Reporting.h" -#include "Core/System.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferCommon.h" #include "GPU/Common/PostShader.h" @@ -831,7 +831,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { currentRenderVfb_ = 0; if (displayFramebufPtr_ == 0) { - if (coreState == CORE_STEPPING) + if (Core_IsStepping()) VERBOSE_LOG(FRAMEBUF, "Display disabled, displaying only black"); else DEBUG_LOG(FRAMEBUF, "Display disabled, displaying only black"); @@ -925,7 +925,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput(bool reallyDirty) { displayFramebuf_ = vfb; if (vfb->fbo) { - if (coreState == CORE_STEPPING) + if (Core_IsStepping()) VERBOSE_LOG(FRAMEBUF, "Displaying FBO %08x", vfb->fb_address); else DEBUG_LOG(FRAMEBUF, "Displaying FBO %08x", vfb->fb_address); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 2b0b068b6ff1..28c215967051 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -136,7 +136,9 @@ EmuScreen::EmuScreen(const std::string &filename) frameStep_ = false; lastNumFlips = gpuStats.numFlips; startDumping = false; + // Make sure we don't leave it at powerdown after the last game. + // TODO: This really should be handled elsewhere if it isn't. if (coreState == CORE_POWERDOWN) coreState = CORE_STEPPING; @@ -1263,6 +1265,53 @@ static void DrawDebugStats(DrawBuffer *draw2d, const Bounds &bounds) { draw2d->SetFontScale(1.0f, 1.0f); } +static void DrawCrashDump(DrawBuffer *draw2d) { + const ExceptionInfo &info = Core_GetExceptionInfo(); + + FontID ubuntu24("UBUNTU24"); + char statbuf[4096]; + char versionString[256]; + sprintf(versionString, "%s", PPSSPP_GIT_VERSION); + // TODO: Draw a lot more information. Full register set, and so on. + +#ifdef _DEBUG + char build[] = "Debug"; +#else + char build[] = "Release"; +#endif + snprintf(statbuf, sizeof(statbuf), R"(%s +Game ID (Title): %s (%s) +PPSSPP build: %s (%s) +)", + ExceptionTypeAsString(info.type), + g_paramSFO.GetDiscID().c_str(), + g_paramSFO.GetValueString("TITLE").c_str(), + versionString, + build + ); + + draw2d->SetFontScale(.7f, .7f); + int x = 20; + int y = 50; + draw2d->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + y += 100; + + if (info.type == ExceptionType::MEMORY) { + snprintf(statbuf, sizeof(statbuf), R"( +Access: %s at %08x +PC: %08x)", + MemoryExceptionTypeAsString(info.memory_type), + info.address, + info.pc); + draw2d->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); + y += 120; + } + + std::string kernelState = __KernelStateSummary(); + + draw2d->DrawTextShadow(ubuntu24, kernelState.c_str(), x, y, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII); +} + static void DrawAudioDebugStats(DrawBuffer *draw2d, const Bounds &bounds) { FontID ubuntu24("UBUNTU24"); char statbuf[4096] = { 0 }; @@ -1403,23 +1452,39 @@ void EmuScreen::render() { PSP_RunLoopWhileState(); // Hopefully coreState is now CORE_NEXTFRAME - if (coreState == CORE_NEXTFRAME) { - // set back to running for the next frame + switch (coreState) { + case CORE_NEXTFRAME: + // Reached the end of the frame, all good. Set back to running for the next frame coreState = CORE_RUNNING; - } else if (coreState == CORE_STEPPING) { - // If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output. - // This won't work in non-buffered, but that's fine. - thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_Stepping"); - // Just to make sure. - if (PSP_IsInited()) { - gpu->CopyDisplayToOutput(true); + break; + case CORE_STEPPING: + case CORE_RUNTIME_ERROR: + { + // If there's an exception, display information. + const ExceptionInfo &info = Core_GetExceptionInfo(); + if (info.type != ExceptionType::NONE) { + // Clear to blue background screen + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE, 0xFF900000 }, "EmuScreen_RuntimeError"); + // The info is drawn later in renderUI + } else { + // If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output. + // This won't work in non-buffered, but that's fine. + thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_Stepping"); + // Just to make sure. + if (PSP_IsInited()) { + gpu->CopyDisplayToOutput(true); + } } - } else { + break; + } + default: // Didn't actually reach the end of the frame, ran out of the blockTicks cycles. // In this case we need to bind and wipe the backbuffer, at least. // It's possible we never ended up outputted anything - make sure we have the backbuffer cleared thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_NoFrame"); + break; } + checkPowerDown(); PSP_EndHostFrame(); @@ -1464,6 +1529,9 @@ bool EmuScreen::hasVisibleUI() { if (g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler) return true; + // Exception information. + if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) + return true; return false; } @@ -1523,6 +1591,14 @@ void EmuScreen::renderUI() { DrawProfile(*ctx); } #endif + + if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) { + const ExceptionInfo &info = Core_GetExceptionInfo(); + if (info.type != ExceptionType::NONE) { + DrawCrashDump(draw2d); + } + } + ctx->Flush(); } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 0fab4eb865ee..1099158a9ce9 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -779,6 +779,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new ItemHeader(sy->T("Emulation"))); systemSettings->Add(new CheckBox(&g_Config.bFastMemory, sy->T("Fast Memory", "Fast Memory (Unstable)")))->OnClick.Handle(this, &GameSettingsScreen::OnJitAffectingSetting); + systemSettings->Add(new CheckBox(&g_Config.bIgnoreBadMemAccess, sy->T("Ignore bad memory accesses"))); systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, sy->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited()); static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" }; diff --git a/Windows/MainWindowMenu.cpp b/Windows/MainWindowMenu.cpp index b5e1bb0eb005..78801f61456b 100644 --- a/Windows/MainWindowMenu.cpp +++ b/Windows/MainWindowMenu.cpp @@ -1354,7 +1354,7 @@ namespace MainWindow { void UpdateCommands() { static GlobalUIState lastGlobalUIState = UISTATE_PAUSEMENU; - static CoreState lastCoreState = CORE_ERROR; + static CoreState lastCoreState = CORE_BOOT_ERROR; HMENU menu = GetMenu(GetHWND()); EnableMenuItem(menu, ID_DEBUG_LOG, !g_Config.bEnableLogging);