mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-11 15:13:37 +02:00
System.cpp stamped BootState::Complete unconditionally after InitGPU(), overwriting the Failed that InitGPU sets when GPU_Init() fails - after it has already run CPU_Shutdown(). PSP_InitUpdate then took the success path on a core that no longer existed, down to a null Memory::base, and the first guest access dereferenced it. InitGPU now reports failure and both callers honor it. (The libretro path had the same problem from the other direction: it calls InitGPU after the Failed check.) HandleAssert called g_assertCancelCallback directly on the IDCANCEL path, without the null check its own BreakIntoPSPDebugger() helper does - and EmuScreen clears the callback when a game is unloaded. So any assert after returning to the menu turned "Cancel: skip and break into PPSSPP debugger" into a null jump, from the one button whose entire purpose is surviving the assert. __CheatDoState registered the cheat event type when the savestate had no CwCheat section, but never scheduled it. CoreTiming::DoState has already swapped in the state's event queue by then, which doesn't contain one either - so loading an old savestate silently killed cheats, and the enable/disable polling with them, for the rest of the session. Achievements::ChangeUMD set g_isIdentifying and returned without clearing it when hashing failed, leaving IsBlockingExecution() true forever - EmuScreen stops running the CPU and the game is frozen until restart. Reachable from a disc swap on any ISO whose PARAM.SFO or EBOOT.BIN can't be read. x64Analyzer routed opcode 0x88 into the write path but had no case for it, so it hit the default, logged from inside the crash handler, and failed. 0x88 is exactly what the x64 JIT emits for a guest sb, so MemFault could never skip or ignore a bad byte store the way it can a word one. Handle the 8-bit forms, and drop the 0x8a/0x8b cases in the read path that the same 0xF0 mask made unreachable. Covered by a new CheckAnalyze case, which fails without this change. 314 pspautotests pass, all unit tests pass.
140 lines
4.6 KiB
C++
140 lines
4.6 KiB
C++
#include "ppsspp_config.h"
|
|
|
|
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
|
|
|
|
#include "Common/CPUDetect.h"
|
|
#include "Common/x64Analyzer.h"
|
|
#include "Common/x64Emitter.h"
|
|
#include "Core/MIPS/x86/RegCacheFPU.h"
|
|
#include "Core/MIPS/x86/Jit.h"
|
|
#include "Core/MIPS/JitCommon/JitState.h"
|
|
#include "Core/MIPS/JitCommon/JitCommon.h"
|
|
#include "Core/MIPS/MIPSVFPUUtils.h"
|
|
#include "ext/disarm.h"
|
|
|
|
#include "UnitTest.h"
|
|
|
|
static const u8 *prevStart = NULL;
|
|
|
|
static bool CheckLast(const Gen::XEmitter &emit, const char *comp) {
|
|
auto vec = DisassembleX86(prevStart, (int)(emit.GetCodePointer() - prevStart));
|
|
EXPECT_EQ_STR(vec[0], std::string(comp));
|
|
return true;
|
|
}
|
|
|
|
// Emits nothing itself - runs the x64 crash-handler instruction analyzer (Common/x64Analyzer.cpp)
|
|
// on the instruction most recently emitted (from prevStart to the emitter's current position),
|
|
// and checks that it decoded it the way we expect.
|
|
static bool CheckAnalyze(const Gen::XEmitter &emit, bool expectWrite, InstructionClass expectClass, int expectOperandSize, bool expectZeroExtend = false, bool expectSignExtend = false) {
|
|
LSInstructionInfo info{};
|
|
bool success = X86AnalyzeMOV(prevStart, info);
|
|
EXPECT_TRUE(success);
|
|
EXPECT_EQ_INT(info.instructionSize, (int)(emit.GetCodePointer() - prevStart));
|
|
EXPECT_EQ_INT(info.isMemoryWrite, expectWrite);
|
|
EXPECT_EQ_INT((int)info.instructionClass, (int)expectClass);
|
|
EXPECT_EQ_INT(info.operandSizeInBytes, expectOperandSize);
|
|
EXPECT_EQ_INT(info.zeroExtend, expectZeroExtend);
|
|
EXPECT_EQ_INT(info.signExtend, expectSignExtend);
|
|
return true;
|
|
}
|
|
|
|
static void PrintLast(const Gen::XEmitter &emit) {
|
|
for (const u8 *p = prevStart; p < emit.GetCodePointer(); p++) {
|
|
printf("%02x ", *p);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
bool TestX64Emitter() {
|
|
using namespace Gen;
|
|
|
|
u32 code[512];
|
|
XEmitter emitter((u8 *)code);
|
|
|
|
bool prevAVX = cpu_info.bAVX;
|
|
cpu_info.bAVX = true;
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.VADDSD(XMM0, XMM1, R(XMM7));
|
|
RET(CheckLast(emitter, "vaddsd xmm0, xmm1, xmm7"));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.VMULSD(XMM0, XMM1, R(XMM7));
|
|
RET(CheckLast(emitter, "vmulsd xmm0, xmm1, xmm7"));
|
|
|
|
cpu_info.bAVX = prevAVX;
|
|
|
|
// Exercise Common/x64Analyzer.cpp (used by the JIT crash handler to figure out what a faulting
|
|
// load/store instruction was doing) against instructions written by the emitter, for the most
|
|
// common memory access instructions.
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOV(32, R(EAX), MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 4));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOV(32, MDisp(RCX, 4), R(EAX));
|
|
RET(CheckAnalyze(emitter, true, InstructionClass::GPR, 4));
|
|
|
|
// The 8-bit forms, which the JIT emits for a guest sb/lb. These used to fail to decode
|
|
// entirely, so MemFault couldn't skip or ignore a bad byte access the way it can a word one.
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOV(8, MDisp(RCX, 4), R(AL));
|
|
RET(CheckAnalyze(emitter, true, InstructionClass::GPR, 1));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOV(8, R(AL), MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVZX(32, 8, EAX, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, true, false));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVZX(32, 16, EAX, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, true, false));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVSX(32, 8, EAX, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, false, true));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVSX(32, 16, EAX, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, false, true));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVSS(XMM0, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::FP, 4));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVSS(MDisp(RCX, 4), XMM0);
|
|
RET(CheckAnalyze(emitter, true, InstructionClass::FP, 4));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVUPS(XMM0, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVUPS(MDisp(RCX, 4), XMM0);
|
|
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVAPS(XMM0, MDisp(RCX, 4));
|
|
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
|
|
|
prevStart = emitter.GetCodePointer();
|
|
emitter.MOVAPS(MDisp(RCX, 4), XMM0);
|
|
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
|
|
|
// Just for checking.
|
|
PrintLast(emitter);
|
|
return true;
|
|
}
|
|
|
|
#else
|
|
|
|
bool TestX64Emitter() {
|
|
return true;
|
|
}
|
|
|
|
#endif
|