diff --git a/src/core/pcsxlua.cc b/src/core/pcsxlua.cc index 31c8b044..944772fa 100644 --- a/src/core/pcsxlua.cc +++ b/src/core/pcsxlua.cc @@ -314,6 +314,14 @@ void PCSX::LuaFFI::open_pcsx(Lua L) { regs.pc = pc; auto outcome = g_emulator->m_cpu->RunUntil(ra, cycles); + if (outcome == R3000Acpu::RunUntilResult::Reentered) { + regs.GPR = savedGPR; + regs.pc = savedPC; + return L.error( + "callGuest can't be nested: this one was called from inside another guest call, most likely from " + "a breakpoint invoker that fired during it. An ExecutionFlow event listener is a fine place to " + "call from; the middle of an instruction is not."); + } if (outcome == R3000Acpu::RunUntilResult::Unsupported) { regs.GPR = savedGPR; regs.pc = savedPC; diff --git a/src/core/psxinterpreter.cc b/src/core/psxinterpreter.cc index 8cfada9d..1475d8b4 100644 --- a/src/core/psxinterpreter.cc +++ b/src/core/psxinterpreter.cc @@ -1653,6 +1653,36 @@ void InterpretedCPU::Execute() { PCSX::R3000Acpu::RunUntilResult InterpretedCPU::RunUntil(uint32_t stopPC, uint64_t cycleBudget) { ZoneScoped; + /* Nesting would re-enter execBlock on top of a live one and shred the delayed-load + ping-pong underneath it. The dangerous caller is a breakpoint invoker, which runs from + Debug::process in the middle of execBlock's loop body; an ExecutionFlow event listener + is fine, since those fire from hasToRun() between blocks, which is the same point + UI::shellReached does its own wholesale register surgery from. */ + if (m_inRunUntil) return RunUntilResult::Reentered; + m_inRunUntil = true; + /* Even at a clean boundary a branch may have left a delay slot pending, and the callee + must not inherit it. Park the bookkeeping and hand it back untouched. */ + const bool savedNextIsDelaySlot = m_nextIsDelaySlot; + const bool savedInDelaySlot = m_inDelaySlot; + const unsigned savedCurrentDelayedLoad = m_currentDelayedLoad; + const auto savedDelayedLoad0 = m_delayedLoadInfo[0]; + const auto savedDelayedLoad1 = m_delayedLoadInfo[1]; + m_nextIsDelaySlot = false; + m_inDelaySlot = false; + m_delayedLoadInfo[0].active = false; + m_delayedLoadInfo[1].active = false; + m_delayedLoadInfo[0].pcActive = false; + m_delayedLoadInfo[1].pcActive = false; + + /* exception() honours the first-chance mask by printing a line and calling + g_system->pause(true), which stops the WHOLE emulator - including a -run that some + other part of the harness is relying on. A call that faults is a result here, not an + incident: we report the code, EPC and BadVAddr in the return value and leave the + machine alone. Park the mask for the duration. */ + auto &debugSettings = PCSX::g_emulator->settings.get(); + const uint32_t savedFirstChance = debugSettings.get(); + debugSettings.get() = 0; + const uint64_t deadline = m_regs.cycle + cycleBudget; /* m_inISR is how we hear about an exception: exception() sets it unconditionally, and it covers the cop0 vectors too, which enumerating the four vector addresses wouldn't. @@ -1672,7 +1702,14 @@ PCSX::R3000Acpu::RunUntilResult InterpretedCPU::RunUntil(uint32_t stopPC, uint64 break; } } + debugSettings.get() = savedFirstChance; m_inISR = wasInISR; + m_nextIsDelaySlot = savedNextIsDelaySlot; + m_inDelaySlot = savedInDelaySlot; + m_currentDelayedLoad = savedCurrentDelayedLoad; + m_delayedLoadInfo[0] = savedDelayedLoad0; + m_delayedLoadInfo[1] = savedDelayedLoad1; + m_inRunUntil = false; return result; } diff --git a/src/core/r3000a.h b/src/core/r3000a.h index 93e0a607..40ed4353 100644 --- a/src/core/r3000a.h +++ b/src/core/r3000a.h @@ -263,6 +263,7 @@ class R3000Acpu { OutOfCycles, /* the budget ran out first */ Exception, /* the guest took an exception */ Unsupported, /* this cpu can't do it */ + Reentered, /* called from inside another RunUntil */ }; /* Runs until pc reaches stopPC, the cycle budget is spent, or an exception is taken. Unlike Execute(), this doesn't run branchTest(), so counters, scheduled interrupts @@ -391,6 +392,7 @@ class R3000Acpu { m_regs.scheduleMask = 0; } bool m_inISR = false; + bool m_inRunUntil = false; bool m_nextIsDelaySlot = false; bool m_inDelaySlot = false; struct {