diff --git a/Core/Core.cpp b/Core/Core.cpp index 67dffa8586..5e32d29af4 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -272,6 +272,8 @@ void Core_ProcessStepping() { return; } + // We're not inside jit now, so it's safe to clear the breakpoints. + CBreakPoints::ClearTemporaryBreakPoints(); host->UpdateDisassembly(); host->UpdateMemView(); diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 597d2a8f2d..0223a2f6fc 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -488,17 +488,20 @@ u32 CBreakPoints::CheckSkipFirst() return 0; } -const std::vector CBreakPoints::GetMemCheckRanges() -{ +const std::vector CBreakPoints::GetMemCheckRanges(bool write) { std::vector ranges = memChecks_; - for (auto it = memChecks_.begin(), end = memChecks_.end(); it != end; ++it) - { - MemCheck check = *it; + for (const auto &check : memChecks_) { + if (!(check.cond & MEMCHECK_READ) && !write) + continue; + if (!(check.cond & MEMCHECK_WRITE) && write) + continue; + + MemCheck copy = check; // Toggle the cached part of the address. - check.start ^= 0x40000000; - if (check.end != 0) - check.end ^= 0x40000000; - ranges.push_back(check); + copy.start ^= 0x40000000; + if (copy.end != 0) + copy.end ^= 0x40000000; + ranges.push_back(copy); } return ranges; diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index 2457e45aca..8cd7acdc4d 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -156,7 +156,7 @@ public: static u32 CheckSkipFirst(); // Includes uncached addresses. - static const std::vector GetMemCheckRanges(); + static const std::vector GetMemCheckRanges(bool write); static const std::vector GetMemChecks(); static const std::vector GetBreakpoints(); diff --git a/Core/MIPS/ARM64/Arm64CompFPU.cpp b/Core/MIPS/ARM64/Arm64CompFPU.cpp index e608baa6d4..55298ae577 100644 --- a/Core/MIPS/ARM64/Arm64CompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompFPU.cpp @@ -81,6 +81,7 @@ void Arm64Jit::Comp_FPU3op(MIPSOpcode op) { void Arm64Jit::Comp_FPULS(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); // Surprisingly, these work fine alraedy. @@ -88,7 +89,6 @@ void Arm64Jit::Comp_FPULS(MIPSOpcode op) int ft = _FT; MIPSGPReg rs = _RS; // u32 addr = R(rs) + offset; - // logBlocks = 1; std::vector skips; switch (op >> 26) { case 49: //FI(ft) = Memory::Read_U32(addr); break; //lwc1 diff --git a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp index 4e5f42f053..7b962f0e95 100644 --- a/Core/MIPS/ARM64/Arm64CompLoadStore.cpp +++ b/Core/MIPS/ARM64/Arm64CompLoadStore.cpp @@ -111,6 +111,7 @@ namespace MIPSComp { void Arm64Jit::Comp_ITypeMemLR(MIPSOpcode op, bool load) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFF); MIPSGPReg rt = _RT; MIPSGPReg rs = _RS; @@ -119,6 +120,7 @@ namespace MIPSComp { if (!js.inDelaySlot) { // Optimisation: Combine to single unaligned load/store bool isLeft = (o == 34 || o == 42); + CheckMemoryBreakpoint(1); MIPSOpcode nextOp = GetOffsetInstruction(1); // Find a matching shift in opposite direction with opposite offset. if (nextOp == (isLeft ? (op.encoding + (4 << 26) - 3) : (op.encoding - (4 << 26) + 3))) { @@ -255,6 +257,7 @@ namespace MIPSComp { void Arm64Jit::Comp_ITypeMem(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int offset = (signed short)(op & 0xFFFF); bool load = false; diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 0277df28e7..7799dfbfa4 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -202,6 +202,7 @@ namespace MIPSComp { void Arm64Jit::Comp_SV(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); s32 offset = (signed short)(op & 0xFFFC); int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5); @@ -275,6 +276,7 @@ namespace MIPSComp { void Arm64Jit::Comp_SVQ(MIPSOpcode op) { CONDITIONAL_DISABLE; + CheckMemoryBreakpoint(); int imm = (signed short)(op&0xFFFC); int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5); diff --git a/Core/MIPS/ARM64/Arm64Jit.cpp b/Core/MIPS/ARM64/Arm64Jit.cpp index 46b1a9bf3e..afadca29e0 100644 --- a/Core/MIPS/ARM64/Arm64Jit.cpp +++ b/Core/MIPS/ARM64/Arm64Jit.cpp @@ -28,6 +28,7 @@ #include "Core/Config.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/SymbolMap.h" #include "Core/MemMap.h" @@ -44,7 +45,7 @@ using namespace Arm64JitConstants; -void DisassembleArm64Print(const u8 *data, int size) { +static void DisassembleArm64Print(const u8 *data, int size) { std::vector lines = DisassembleArm64(data, size); for (auto s : lines) { ILOG("%s", s.c_str()); @@ -60,6 +61,33 @@ void DisassembleArm64Print(const u8 *data, int size) { ILOG("===");*/ } +static u32 JitBreakpoint() { + // Should we skip this breakpoint? + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + BreakAction result = CBreakPoints::ExecBreakPoint(currentMIPS->pc); + if ((result & BREAK_ACTION_PAUSE) == 0) + return 0; + + return 1; +} + +static u32 JitMemCheck(u32 pc) { + if (CBreakPoints::CheckSkipFirst() == currentMIPS->pc) + return 0; + + // Note: pc may be the delay slot. + const auto op = Memory::Read_Instruction(pc, true); + s32 offset = (s16)(op & 0xFFFF); + if (MIPSGetInfo(op) & IS_VFPU) + offset &= 0xFFFC; + u32 addr = currentMIPS->r[MIPS_GET_RS(op)] + offset; + + CBreakPoints::ExecOpMemCheck(addr, pc); + return coreState == CORE_RUNNING || coreState == CORE_NEXTFRAME ? 0 : 1; +} + namespace MIPSComp { using namespace Arm64Gen; @@ -80,6 +108,10 @@ Arm64Jit::Arm64Jit(MIPSState *mips) : blocks(mips, this), gpr(mips, &js, &jo), f GenerateFixedCode(jo); js.startDefaultPrefix = mips_->HasDefaultPrefix(); js.currentRoundingFunc = convertS0ToSCRATCH1[mips_->fcr31 & 3]; + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } Arm64Jit::~Arm64Jit() { @@ -99,6 +131,10 @@ void Arm64Jit::DoState(PointerWrap &p) { } // Note: we can't update the currentRoundingFunc here because fcr31 wasn't loaded yet. + + // The debugger sets this so that "go" on a breakpoint will actually... go. + // But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks. + CBreakPoints::SetSkipFirst(0); } void Arm64Jit::UpdateFCR31() { @@ -151,12 +187,16 @@ void Arm64Jit::EatInstruction(MIPSOpcode op) { ERROR_LOG_REPORT_ONCE(ateInDelaySlot, JIT, "Ate an instruction inside a delay slot."); } + CheckJitBreakpoint(GetCompilerPC() + 4, 0); js.numInstructions++; js.compilerPC += 4; js.downcountAmount += MIPSGetInstructionCycleEstimate(op); } void Arm64Jit::CompileDelaySlot(int flags) { + // Need to offset the downcount which was already incremented for the branch + delay slot. + CheckJitBreakpoint(GetCompilerPC() + 4, -2); + // preserve flag around the delay slot! Maybe this is not always necessary on ARM where // we can (mostly) control whether we set the flag or not. Of course, if someone puts an slt in to the // delay slot, we're screwed. @@ -285,8 +325,10 @@ const u8 *Arm64Jit::DoJit(u32 em_address, JitBlock *b) { js.numInstructions = 0; while (js.compiling) { gpr.SetCompilerPC(GetCompilerPC()); // Let it know for log messages + // Jit breakpoints are quite fast, so let's do them in release too. + CheckJitBreakpoint(GetCompilerPC(), 0); + MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC()); - js.downcountAmount += MIPSGetInstructionCycleEstimate(inst); MIPSCompileOp(inst, this); @@ -473,7 +515,18 @@ void Arm64Jit::Comp_ReplacementFunc(MIPSOpcode op) return; } - if (entry->flags & REPFLAG_DISABLED) { + u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC()); + bool disabled = (entry->flags & REPFLAG_DISABLED) != 0; + if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) { + // We don't need to disable hooks, the code will still run. + if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) { + // Any breakpoint at the func entry was already tripped, so we can still run the replacement. + // That's a common case - just to see how often the replacement hits. + disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32)); + } + } + + if (disabled) { MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this); } else if (entry->jitReplaceFunc) { MIPSReplaceFunc repl = entry->jitReplaceFunc; @@ -646,6 +699,60 @@ void Arm64Jit::WriteSyscallExit() { B((const void *)dispatcherCheckCoreState); } +bool Arm64Jit::CheckJitBreakpoint(u32 addr, int downcountOffset) { + if (CBreakPoints::IsAddressBreakPoint(addr)) { + MRS(FLAGTEMPREG, FIELD_NZCV); + FlushAll(); + MOVI2R(SCRATCH1, GetCompilerPC()); + MovToPC(SCRATCH1); + RestoreRoundingMode(); + QuickCallFunction(SCRATCH1_64, &JitBreakpoint); + + // If 0, the conditional breakpoint wasn't taken. + CMPI2R(W0, 0); + FixupBranch skip = B(CC_EQ); + WriteDownCount(downcountOffset); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(FIELD_NZCV, FLAGTEMPREG); + return true; + } + + return false; +} + +bool Arm64Jit::CheckMemoryBreakpoint(int instructionOffset) { + if (CBreakPoints::HasMemChecks()) { + int off = instructionOffset + (js.inDelaySlot ? 1 : 0); + + MRS(FLAGTEMPREG, FIELD_NZCV); + FlushAll(); + RestoreRoundingMode(); + MOVI2R(W0, GetCompilerPC()); + MovToPC(W0); + if (off != 0) + ADDI2R(W0, W0, off * 4); + QuickCallFunction(SCRATCH2_64, &JitMemCheck); + + // If 0, the breakpoint wasn't tripped. + CMPI2R(W0, 0); + FixupBranch skip = B(CC_EQ); + WriteDownCount(-1 - off); + ApplyRoundingMode(); + B((const void *)dispatcherCheckCoreState); + SetJumpTarget(skip); + + ApplyRoundingMode(); + _MSR(FIELD_NZCV, FLAGTEMPREG); + return true; + } + + return false; +} + void Arm64Jit::Comp_DoNothing(MIPSOpcode op) { } MIPSOpcode Arm64Jit::GetOriginalOp(MIPSOpcode op) { diff --git a/Core/MIPS/ARM64/Arm64Jit.h b/Core/MIPS/ARM64/Arm64Jit.h index 3d84451384..f504e4cb58 100644 --- a/Core/MIPS/ARM64/Arm64Jit.h +++ b/Core/MIPS/ARM64/Arm64Jit.h @@ -214,6 +214,8 @@ private: void WriteExit(u32 destination, int exit_num); void WriteExitDestInR(Arm64Gen::ARM64Reg Reg); void WriteSyscallExit(); + bool CheckJitBreakpoint(u32 addr, int downcountOffset); + bool CheckMemoryBreakpoint(int instructionOffset = 0); // Utility compilation functions void BranchFPFlag(MIPSOpcode op, CCFlags cc, bool likely); diff --git a/Core/MIPS/x86/JitSafeMem.cpp b/Core/MIPS/x86/JitSafeMem.cpp index 56374ed828..104b6579a0 100644 --- a/Core/MIPS/x86/JitSafeMem.cpp +++ b/Core/MIPS/x86/JitSafeMem.cpp @@ -376,17 +376,10 @@ void JitSafeMem::MemCheckImm(MemoryOpType type) void JitSafeMem::MemCheckAsm(MemoryOpType type) { - const auto memchecks = CBreakPoints::GetMemCheckRanges(); - bool possible = false; + const auto memchecks = CBreakPoints::GetMemCheckRanges(type == MEM_WRITE); + bool possible = !memchecks.empty(); for (auto it = memchecks.begin(), end = memchecks.end(); it != end; ++it) { - if (!(it->cond & MEMCHECK_READ) && type == MEM_READ) - continue; - if (!(it->cond & MEMCHECK_WRITE) && type == MEM_WRITE) - continue; - - possible = true; - FixupBranch skipNext, skipNextRange; if (it->end != 0) { diff --git a/Windows/Debugger/Debugger_Disasm.cpp b/Windows/Debugger/Debugger_Disasm.cpp index 5d187b915f..bf5f9c7995 100644 --- a/Windows/Debugger/Debugger_Disasm.cpp +++ b/Windows/Debugger/Debugger_Disasm.cpp @@ -809,7 +809,6 @@ void CDisasm::SetDebugMode(bool _bDebug, bool switchPC) if (_bDebug && GetUIState() == UISTATE_INGAME && PSP_IsInited()) { Core_WaitInactive(TEMP_BREAKPOINT_WAIT_MS); - CBreakPoints::ClearTemporaryBreakPoints(); breakpointList->reloadBreakpoints(); threadList->reloadThreads(); stackTraceView->loadStackTrace(); @@ -913,4 +912,4 @@ void CDisasm::UpdateDialog(bool _bComplete) // redraw. all others are updated manually InvalidateRect (GetDlgItem(m_hDlg, IDC_DEBUGMEMVIEW), NULL, TRUE); UpdateWindow (GetDlgItem(m_hDlg, IDC_DEBUGMEMVIEW)); -} \ No newline at end of file +}