From 6c8ee35826fe19b0cb45286fe28018cebcbdf248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 19 Dec 2020 20:26:02 +0100 Subject: [PATCH] Crash screen: Add a very dangerous and inaccurate "Resume" function. Not sure if there's much point really but maybe.. --- Core/MemFault.cpp | 26 ++++++++++++++++++++++++-- Core/MemFault.h | 8 ++++++++ UI/EmuScreen.cpp | 24 +++++++++++++++++++++--- UI/EmuScreen.h | 5 +++-- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp index 0f4393b3a0..3148f1a80e 100644 --- a/Core/MemFault.cpp +++ b/Core/MemFault.cpp @@ -17,6 +17,9 @@ #include "ppsspp_config.h" +#include +#include + #include "Common/MachineContext.h" #if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) @@ -37,9 +40,13 @@ namespace Memory { static int64_t g_numReportedBadAccesses = 0; +const uint8_t *g_lastCrashAddress; +std::unordered_set g_ignoredAddresses; void MemFault_Init() { g_numReportedBadAccesses = 0; + g_lastCrashAddress = nullptr; + g_ignoredAddresses.clear(); } #ifdef MACHINE_CONTEXT_SUPPORTED @@ -67,10 +74,21 @@ static bool DisassembleNativeAt(const uint8_t *codePtr, int instructionSize, std return false; } +bool MemFault_MayBeResumable() { + return g_lastCrashAddress != nullptr; +} + +void MemFault_IgnoreLastCrash() { + g_ignoredAddresses.insert(g_lastCrashAddress); +} + bool HandleFault(uintptr_t hostAddress, void *ctx) { SContext *context = (SContext *)ctx; const uint8_t *codePtr = (uint8_t *)(context->CTX_PC); + // We set this later if we think it can be resumed from. + g_lastCrashAddress = nullptr; + // TODO: Check that codePtr is within the current JIT space. bool inJitSpace = MIPSComp::jit && MIPSComp::jit->CodeInRange(codePtr); if (!inJitSpace) { @@ -91,6 +109,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { return false; } + // OK, a guest executable did a bad access. Take care of it. uint32_t guestAddress = hostAddress - baseAddress; @@ -149,7 +168,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { type = MemoryExceptionType::UNKNOWN; } - if (success && g_Config.bIgnoreBadMemAccess) { + if (success && (g_Config.bIgnoreBadMemAccess || g_ignoredAddresses.find(codePtr) != g_ignoredAddresses.end())) { if (!info.isMemoryWrite) { // It was a read. Fill the destination register with 0. // TODO @@ -165,7 +184,10 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { uint32_t approximatePC = currentMIPS->pc; Core_MemoryExceptionInfo(guestAddress, approximatePC, type, infoString); - // Redirect execution to a crash handler that will exit the game immediately. + // There's a small chance we can resume from this type of crash. + g_lastCrashAddress = codePtr; + + // Redirect execution to a crash handler that will switch to CoreState::CORE_RUNTIME_ERROR immediately. context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler(); ERROR_LOG(MEMMAP, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str()); } diff --git a/Core/MemFault.h b/Core/MemFault.h index 40472a50da..4de4cbdf6b 100644 --- a/Core/MemFault.h +++ b/Core/MemFault.h @@ -6,6 +6,14 @@ namespace Memory { void MemFault_Init(); +// Used by the hacky "Resume" button. +// +// TODO: Add a way to actually resume to the next instruction, +// rather than just jumping into the dispatcher again and hoping for the best. That will be +// a little tricky though, with per-backend work. +bool MemFault_MayBeResumable(); +void MemFault_IgnoreLastCrash(); + // Called by exception handlers. We simply filter out accesses to PSP RAM and otherwise // just leave it as-is. bool HandleFault(uintptr_t hostAddress, void *context); diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index 4f6fd01540..f6be8fab9e 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -49,6 +49,7 @@ #include "Core/Core.h" #include "Core/Host.h" #include "Core/KeyMap.h" +#include "Core/MemFault.h" #include "Core/Reporting.h" #include "Core/System.h" #include "GPU/GPUState.h" @@ -1025,6 +1026,9 @@ void EmuScreen::CreateViews() { if (g_Config.bShowDeveloperMenu) { root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools); } + resumeButton_ = root_->Add(new Button(dev->T("Resume"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 60, true))); + resumeButton_->OnClick.Handle(this, &EmuScreen::OnResume); + resumeButton_->SetVisibility(V_GONE); cardboardDisableButton_ = root_->Add(new Button(sc->T("Cardboard VR OFF"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 30, true))); cardboardDisableButton_->OnClick.Handle(this, &EmuScreen::OnDisableCardboard); @@ -1135,15 +1139,29 @@ UI::EventReturn EmuScreen::OnDisableCardboard(UI::EventParams ¶ms) { return UI::EVENT_DONE; } -UI::EventReturn EmuScreen::OnChat(UI::EventParams& params) { - if (chatButtons->GetVisibility() == UI::V_VISIBLE) chatButtons->SetVisibility(UI::V_GONE); +UI::EventReturn EmuScreen::OnChat(UI::EventParams ¶ms) { + if (chatButtons->GetVisibility() == UI::V_VISIBLE) { + chatButtons->SetVisibility(UI::V_GONE); + } screenManager()->push(new ChatMenu()); return UI::EVENT_DONE; } +UI::EventReturn EmuScreen::OnResume(UI::EventParams ¶ms) { + if (coreState == CoreState::CORE_RUNTIME_ERROR) { + // Force it! + Memory::MemFault_IgnoreLastCrash(); + coreState = CoreState::CORE_RUNNING; + } + return UI::EVENT_DONE; +} + void EmuScreen::update() { + using namespace UI; + UIScreen::update(); - onScreenMessagesView_->SetVisibility(g_Config.bShowOnScreenMessages ? UI::Visibility::V_VISIBLE : UI::Visibility::V_GONE); + onScreenMessagesView_->SetVisibility(g_Config.bShowOnScreenMessages ? V_VISIBLE : V_GONE); + resumeButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR && Memory::MemFault_MayBeResumable() ? V_VISIBLE : V_GONE); if (bootPending_) { bootGame(gamePath_); diff --git a/UI/EmuScreen.h b/UI/EmuScreen.h index 69befa43b0..3c8492f044 100644 --- a/UI/EmuScreen.h +++ b/UI/EmuScreen.h @@ -49,13 +49,13 @@ public: bool key(const KeyInput &key) override; bool axis(const AxisInput &axis) override; -protected: +private: void CreateViews() override; UI::EventReturn OnDevTools(UI::EventParams ¶ms); UI::EventReturn OnDisableCardboard(UI::EventParams ¶ms); UI::EventReturn OnChat(UI::EventParams ¶ms); + UI::EventReturn OnResume(UI::EventParams ¶ms); -private: void bootGame(const std::string &filename); bool bootAllowStorage(const std::string &filename); void bootComplete(); @@ -105,6 +105,7 @@ private: UI::VisibilityTween *loadingViewVisible_ = nullptr; UI::Spinner *loadingSpinner_ = nullptr; UI::TextView *loadingTextView_ = nullptr; + UI::Button *resumeButton_ = nullptr; UI::Button *cardboardDisableButton_ = nullptr; OnScreenMessagesView *onScreenMessagesView_ = nullptr;