// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "ppsspp_config.h" #if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64) #include "Core/Config.h" #include "Core/Debugger/Breakpoints.h" #include "Core/MemMap.h" #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/MIPS/x86/Jit.h" #include "Core/MIPS/x86/JitSafeMem.h" #include "Core/System.h" namespace MIPSComp { using namespace Gen; using namespace X64JitConstants; void JitMemCheck(u32 addr, int size, int isWrite) { // Should we skip this breakpoint? if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) return; // Did we already hit one? if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME) return; CBreakPoints::ExecMemCheckJitBefore(addr, isWrite == 1, size, currentMIPS->pc); } void JitMemCheckCleanup() { CBreakPoints::ExecMemCheckJitCleanup(); } JitSafeMem::JitSafeMem(Jit *jit, MIPSGPReg raddr, s32 offset, u32 alignMask) : jit_(jit), raddr_(raddr), offset_(offset), needsCheck_(false), needsSkip_(false), alignMask_(alignMask) { // Mask out the kernel RAM bit, because we'll end up with a negative offset to MEMBASEREG. if (jit_->gpr.IsImm(raddr_)) iaddr_ = (jit_->gpr.GetImm(raddr_) + offset_) & 0x7FFFFFFF; else iaddr_ = (u32) -1; fast_ = g_Config.bFastMemory || raddr == MIPS_REG_SP; // If raddr_ is going to get loaded soon, load it now for more optimal code. // We assume that it was already locked. const int LOOKAHEAD_OPS = 3; if (!jit_->gpr.R(raddr_).IsImm() && MIPSAnalyst::IsRegisterUsed(raddr_, jit_->GetCompilerPC() + 4, LOOKAHEAD_OPS)) jit_->gpr.MapReg(raddr_, true, false); } bool JitSafeMem::PrepareWrite(OpArg &dest, int size) { size_ = size; // If it's an immediate, we can do the write if valid. if (iaddr_ != (u32) -1) { if (ImmValid()) { MemCheckImm(MEM_WRITE); u32 addr = (iaddr_ & alignMask_); #ifdef MASKED_PSP_MEMORY addr &= Memory::MEMVIEW32_MASK; #endif #if PPSSPP_ARCH(32BIT) dest = M(Memory::base + addr); // 32-bit only #else dest = MDisp(MEMBASEREG, addr); #endif return true; } else return false; } // Otherwise, we always can do the write (conditionally.) else dest = PrepareMemoryOpArg(MEM_WRITE); return true; } bool JitSafeMem::PrepareRead(OpArg &src, int size) { size_ = size; if (iaddr_ != (u32) -1) { if (ImmValid()) { MemCheckImm(MEM_READ); u32 addr = (iaddr_ & alignMask_); #ifdef MASKED_PSP_MEMORY addr &= Memory::MEMVIEW32_MASK; #endif #if PPSSPP_ARCH(32BIT) src = M(Memory::base + addr); // 32-bit only #else src = MDisp(MEMBASEREG, addr); #endif return true; } else return false; } else src = PrepareMemoryOpArg(MEM_READ); return true; } OpArg JitSafeMem::NextFastAddress(int suboffset) { if (iaddr_ != (u32) -1) { u32 addr = (iaddr_ + suboffset) & alignMask_; #ifdef MASKED_PSP_MEMORY addr &= Memory::MEMVIEW32_MASK; #endif #if PPSSPP_ARCH(32BIT) return M(Memory::base + addr); // 32-bit only #else return MDisp(MEMBASEREG, addr); #endif } _dbg_assert_msg_(JIT, (suboffset & alignMask_) == suboffset, "suboffset must be aligned"); #if PPSSPP_ARCH(32BIT) return MDisp(xaddr_, (u32) Memory::base + offset_ + suboffset); #else return MComplex(MEMBASEREG, xaddr_, SCALE_1, offset_ + suboffset); #endif } OpArg JitSafeMem::PrepareMemoryOpArg(MemoryOpType type) { // We may not even need to move into EAX as a temporary. bool needTemp = alignMask_ != 0xFFFFFFFF; #ifdef MASKED_PSP_MEMORY bool needMask = true; // raddr_ != MIPS_REG_SP; // Commented out this speedhack due to low impact // We always mask on 32 bit in fast memory mode. needTemp = needTemp || (fast_ && needMask); #endif if (jit_->gpr.R(raddr_).IsSimpleReg() && !needTemp) { jit_->gpr.MapReg(raddr_, true, false); xaddr_ = jit_->gpr.RX(raddr_); } else { jit_->MOV(32, R(EAX), jit_->gpr.R(raddr_)); xaddr_ = EAX; } MemCheckAsm(type); if (!fast_) { // Is it in physical ram? jit_->CMP(32, R(xaddr_), Imm32(PSP_GetKernelMemoryBase() - offset_)); tooLow_ = jit_->J_CC(CC_B); jit_->CMP(32, R(xaddr_), Imm32(PSP_GetUserMemoryEnd() - offset_ - (size_ - 1))); tooHigh_ = jit_->J_CC(CC_AE); // We may need to jump back up here. safe_ = jit_->GetCodePtr(); } else { #ifdef MASKED_PSP_MEMORY if (needMask) { jit_->AND(32, R(EAX), Imm32(Memory::MEMVIEW32_MASK)); } #endif } // TODO: This could be more optimal, but the common case is that we want xaddr_ not to include offset_. // Since we need to align them after add, we add and subtract. if (alignMask_ != 0xFFFFFFFF) { jit_->ADD(32, R(xaddr_), Imm32(offset_)); jit_->AND(32, R(xaddr_), Imm32(alignMask_)); jit_->SUB(32, R(xaddr_), Imm32(offset_)); } #if PPSSPP_ARCH(32BIT) return MDisp(xaddr_, (u32) Memory::base + offset_); #else return MComplex(MEMBASEREG, xaddr_, SCALE_1, offset_); #endif } void JitSafeMem::PrepareSlowAccess() { // Skip the fast path (which the caller wrote just now.) skip_ = jit_->J(true); needsSkip_ = true; jit_->SetJumpTarget(tooLow_); jit_->SetJumpTarget(tooHigh_); // Might also be the scratchpad. jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryBase() - offset_)); FixupBranch tooLow = jit_->J_CC(CC_B); jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryEnd() - offset_ - (size_ - 1))); jit_->J_CC(CC_B, safe_); jit_->SetJumpTarget(tooLow); } bool JitSafeMem::PrepareSlowWrite() { // If it's immediate, we only need a slow write on invalid. if (iaddr_ != (u32) -1) return !fast_ && !ImmValid(); if (!fast_) { PrepareSlowAccess(); return true; } else return false; } void JitSafeMem::DoSlowWrite(const void *safeFunc, const OpArg& src, int suboffset) { if (iaddr_ != (u32) -1) jit_->MOV(32, R(EAX), Imm32((iaddr_ + suboffset) & alignMask_)); else { jit_->LEA(32, EAX, MDisp(xaddr_, offset_ + suboffset)); if (alignMask_ != 0xFFFFFFFF) jit_->AND(32, R(EAX), Imm32(alignMask_)); } #if PPSSPP_ARCH(32BIT) jit_->PUSH(EDX); #endif if (!src.IsSimpleReg(EDX)) { jit_->MOV(32, R(EDX), src); } if (!g_Config.bIgnoreBadMemAccess) { jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); } // This is a special jit-ABI'd function. jit_->CALL(safeFunc); #if PPSSPP_ARCH(32BIT) jit_->POP(EDX); #endif needsCheck_ = true; } bool JitSafeMem::PrepareSlowRead(const void *safeFunc) { if (!fast_) { if (iaddr_ != (u32) -1) { // No slow read necessary. if (ImmValid()) return false; jit_->MOV(32, R(EAX), Imm32(iaddr_ & alignMask_)); } else { PrepareSlowAccess(); jit_->LEA(32, EAX, MDisp(xaddr_, offset_)); if (alignMask_ != 0xFFFFFFFF) jit_->AND(32, R(EAX), Imm32(alignMask_)); } if (!g_Config.bIgnoreBadMemAccess) { jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); } // This is a special jit-ABI'd function. jit_->CALL(safeFunc); needsCheck_ = true; return true; } else return false; } void JitSafeMem::NextSlowRead(const void *safeFunc, int suboffset) { _dbg_assert_msg_(JIT, !fast_, "NextSlowRead() called in fast memory mode?"); // For simplicity, do nothing for 0. We already read in PrepareSlowRead(). if (suboffset == 0) return; if (jit_->gpr.IsImm(raddr_)) { _dbg_assert_msg_(JIT, !Memory::IsValidAddress(iaddr_ + suboffset), "NextSlowRead() for an invalid immediate address?"); jit_->MOV(32, R(EAX), Imm32((iaddr_ + suboffset) & alignMask_)); } // For GPR, if xaddr_ was the dest register, this will be wrong. Don't use in GPR. else { jit_->LEA(32, EAX, MDisp(xaddr_, offset_ + suboffset)); if (alignMask_ != 0xFFFFFFFF) jit_->AND(32, R(EAX), Imm32(alignMask_)); } if (!g_Config.bIgnoreBadMemAccess) { jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); } // This is a special jit-ABI'd function. jit_->CALL(safeFunc); } bool JitSafeMem::ImmValid() { return iaddr_ != (u32) -1 && Memory::IsValidAddress(iaddr_) && Memory::IsValidAddress(iaddr_ + size_ - 1); } void JitSafeMem::Finish() { // Memory::Read_U32/etc. may have tripped coreState. if (needsCheck_ && !g_Config.bIgnoreBadMemAccess) jit_->js.afterOp |= JitState::AFTER_CORE_STATE; if (needsSkip_) jit_->SetJumpTarget(skip_); for (auto it = skipChecks_.begin(), end = skipChecks_.end(); it != end; ++it) jit_->SetJumpTarget(*it); } void JitSafeMem::MemCheckImm(MemoryOpType type) { MemCheck check; if (CBreakPoints::GetMemCheckInRange(iaddr_, size_, &check)) { if (!(check.cond & MEMCHECK_READ) && type == MEM_READ) return; if (!(check.cond & MEMCHECK_WRITE) && type == MEM_WRITE) return; jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); jit_->CallProtectedFunction(&JitMemCheck, iaddr_, size_, type == MEM_WRITE ? 1 : 0); // CORE_RUNNING is <= CORE_NEXTFRAME. if (jit_->RipAccessible((const void *)&coreState)) { jit_->CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME)); // rip accessible } else { // We can't safely overwrite any register, so push. This is only while debugging. jit_->PUSH(RAX); jit_->MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState)); jit_->CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME)); jit_->POP(RAX); } skipChecks_.push_back(jit_->J_CC(CC_G, true)); jit_->js.afterOp |= JitState::AFTER_CORE_STATE | JitState::AFTER_REWIND_PC_BAD_STATE | JitState::AFTER_MEMCHECK_CLEANUP; } } void JitSafeMem::MemCheckAsm(MemoryOpType type) { const auto memchecks = CBreakPoints::GetMemCheckRanges(type == MEM_WRITE); bool possible = !memchecks.empty(); for (auto it = memchecks.begin(), end = memchecks.end(); it != end; ++it) { FixupBranch skipNext, skipNextRange; if (it->end != 0) { jit_->CMP(32, R(xaddr_), Imm32(it->start - offset_ - size_)); skipNext = jit_->J_CC(CC_BE); jit_->CMP(32, R(xaddr_), Imm32(it->end - offset_)); skipNextRange = jit_->J_CC(CC_AE); } else { jit_->CMP(32, R(xaddr_), Imm32(it->start - offset_)); skipNext = jit_->J_CC(CC_NE); } // Keep the stack 16-byte aligned, just PUSH/POP 4 times. for (int i = 0; i < 4; ++i) jit_->PUSH(xaddr_); jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); jit_->ADD(32, R(xaddr_), Imm32(offset_)); jit_->CallProtectedFunction(&JitMemCheck, R(xaddr_), size_, type == MEM_WRITE ? 1 : 0); for (int i = 0; i < 4; ++i) jit_->POP(xaddr_); jit_->SetJumpTarget(skipNext); if (it->end != 0) jit_->SetJumpTarget(skipNextRange); } if (possible) { // CORE_RUNNING is <= CORE_NEXTFRAME. if (jit_->RipAccessible((const void *)&coreState)) { jit_->CMP(32, M(&coreState), Imm32(CORE_NEXTFRAME)); // rip accessible } else { // We can't safely overwrite any register, so push. This is only while debugging. jit_->PUSH(RAX); jit_->MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState)); jit_->CMP(32, MatR(RAX), Imm32(CORE_NEXTFRAME)); jit_->POP(RAX); } skipChecks_.push_back(jit_->J_CC(CC_G, true)); jit_->js.afterOp |= JitState::AFTER_CORE_STATE | JitState::AFTER_REWIND_PC_BAD_STATE | JitState::AFTER_MEMCHECK_CLEANUP; } } static const int FUNCS_ARENA_SIZE = 512 * 1024; void JitSafeMemFuncs::Init(ThunkManager *thunks) { using namespace Gen; AllocCodeSpace(FUNCS_ARENA_SIZE); thunks_ = thunks; BeginWrite(); readU32 = GetCodePtr(); CreateReadFunc(32, (const void *)&Memory::Read_U32); readU16 = GetCodePtr(); CreateReadFunc(16, (const void *)&Memory::Read_U16); readU8 = GetCodePtr(); CreateReadFunc(8, (const void *)&Memory::Read_U8); writeU32 = GetCodePtr(); CreateWriteFunc(32, (const void *)&Memory::Write_U32); writeU16 = GetCodePtr(); CreateWriteFunc(16, (const void *)&Memory::Write_U16); writeU8 = GetCodePtr(); CreateWriteFunc(8, (const void *)&Memory::Write_U8); EndWrite(); } void JitSafeMemFuncs::Shutdown() { ResetCodePtr(0); FreeCodeSpace(); } // Mini ABI: // Read funcs take address in EAX, return in RAX. // Write funcs take address in EAX, data in RDX. // On x86-32, Write funcs also have an extra 4 bytes on the stack. void JitSafeMemFuncs::CreateReadFunc(int bits, const void *fallbackFunc) { CheckDirectEAX(); // Since we were CALLed, we need to align the stack before calling C++. #if PPSSPP_ARCH(32BIT) SUB(32, R(ESP), Imm8(16 - 4)); ABI_CallFunctionA(thunks_->ProtectFunction(fallbackFunc, 1), R(EAX)); ADD(32, R(ESP), Imm8(16 - 4)); #else SUB(64, R(RSP), Imm8(0x28)); ABI_CallFunctionA(thunks_->ProtectFunction(fallbackFunc, 1), R(EAX)); ADD(64, R(RSP), Imm8(0x28)); #endif RET(); StartDirectAccess(); #if PPSSPP_ARCH(32BIT) MOVZX(32, bits, EAX, MDisp(EAX, (u32)Memory::base)); #else MOVZX(32, bits, EAX, MRegSum(MEMBASEREG, EAX)); #endif RET(); } void JitSafeMemFuncs::CreateWriteFunc(int bits, const void *fallbackFunc) { CheckDirectEAX(); // Since we were CALLed, we need to align the stack before calling C++. #if PPSSPP_ARCH(32BIT) // 4 for return, 4 for saved reg on stack. SUB(32, R(ESP), Imm8(16 - 4 - 4)); ABI_CallFunctionAA(thunks_->ProtectFunction(fallbackFunc, 2), R(EDX), R(EAX)); ADD(32, R(ESP), Imm8(16 - 4 - 4)); #else SUB(64, R(RSP), Imm8(0x28)); ABI_CallFunctionAA(thunks_->ProtectFunction(fallbackFunc, 2), R(EDX), R(EAX)); ADD(64, R(RSP), Imm8(0x28)); #endif RET(); StartDirectAccess(); #if PPSSPP_ARCH(32BIT) MOV(bits, MDisp(EAX, (u32)Memory::base), R(EDX)); #else MOV(bits, MRegSum(MEMBASEREG, EAX), R(EDX)); #endif RET(); } void JitSafeMemFuncs::CheckDirectEAX() { // Clear any cache/kernel bits. AND(32, R(EAX), Imm32(0x3FFFFFFF)); CMP(32, R(EAX), Imm32(PSP_GetUserMemoryEnd())); FixupBranch tooHighRAM = J_CC(CC_AE); CMP(32, R(EAX), Imm32(PSP_GetKernelMemoryBase())); skips_.push_back(J_CC(CC_AE)); CMP(32, R(EAX), Imm32(PSP_GetVidMemEnd())); FixupBranch tooHighVid = J_CC(CC_AE); CMP(32, R(EAX), Imm32(PSP_GetVidMemBase())); skips_.push_back(J_CC(CC_AE)); CMP(32, R(EAX), Imm32(PSP_GetScratchpadMemoryEnd())); FixupBranch tooHighScratch = J_CC(CC_AE); CMP(32, R(EAX), Imm32(PSP_GetScratchpadMemoryBase())); skips_.push_back(J_CC(CC_AE)); SetJumpTarget(tooHighRAM); SetJumpTarget(tooHighVid); SetJumpTarget(tooHighScratch); } void JitSafeMemFuncs::StartDirectAccess() { for (auto it = skips_.begin(), end = skips_.end(); it != end; ++it) { SetJumpTarget(*it); } skips_.clear(); } }; #endif // PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)