Skip to content

Commit

Permalink
Merge pull request #18053 from unknownbrackets/x86-jit-debug
Browse files Browse the repository at this point in the history
x86jit: Handle breakpoints
  • Loading branch information
hrydgard committed Sep 3, 2023
2 parents 14c658c + 60bcc5b commit 4694a11
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Core/Debugger/Breakpoints.cpp
Expand Up @@ -311,7 +311,7 @@ BreakAction CBreakPoints::ExecBreakPoint(u32 addr) {
std::unique_lock<std::mutex> guard(breakPointsMutex_);
size_t bp = FindBreakpoint(addr, false);
if (bp != INVALID_BREAKPOINT) {
BreakPoint info = breakPoints_[bp];
const BreakPoint &info = breakPoints_[bp];
guard.unlock();

if (info.hasCond) {
Expand Down
48 changes: 34 additions & 14 deletions Core/MIPS/IR/IRFrontend.cpp
Expand Up @@ -340,16 +340,27 @@ void IRFrontend::CheckBreakpoint(u32 addr) {
if (CBreakPoints::IsAddressBreakPoint(addr)) {
FlushAll();

if (GetCompilerPC() != js.blockStart)
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));

RestoreRoundingMode();
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
// 0 because we normally execute before increasing.
// TODO: In likely branches, downcount will be incorrect.
int downcountOffset = js.inDelaySlot && js.downcountAmount >= 2 ? -2 : 0;
// At this point, downcount HAS the delay slot, but not the instruction itself.
int downcountOffset = 0;
if (js.inDelaySlot) {
MIPSOpcode branchOp = Memory::Read_Opcode_JIT(GetCompilerPC());
MIPSOpcode delayOp = Memory::Read_Opcode_JIT(addr);
downcountOffset = -MIPSGetInstructionCycleEstimate(delayOp);
if ((MIPSGetInfo(branchOp) & LIKELY) != 0) {
// Okay, we're in a likely branch. Also negate the branch cycles.
downcountOffset += -MIPSGetInstructionCycleEstimate(branchOp);
}
}
int downcountAmount = js.downcountAmount + downcountOffset;
ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));
if (downcountAmount != 0)
ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));
// Note that this means downcount can't be metadata on the block.
js.downcountAmount = -downcountOffset;
ir.Write(IROp::Breakpoint);
ir.Write(IROp::Breakpoint, 0, ir.AddConstant(addr));
ApplyRoundingMode();

js.hadBreakpoints = true;
Expand All @@ -360,19 +371,28 @@ void IRFrontend::CheckMemoryBreakpoint(int rs, int offset) {
if (CBreakPoints::HasMemChecks()) {
FlushAll();

if (GetCompilerPC() != js.blockStart)
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));

RestoreRoundingMode();
ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));
// 0 because we normally execute before increasing.
int downcountOffset = js.inDelaySlot ? -2 : -1;
// TODO: In likely branches, downcount will be incorrect. This might make resume fail.
if (js.downcountAmount == 0) {
downcountOffset = 0;
// At this point, downcount HAS the delay slot, but not the instruction itself.
int downcountOffset = 0;
if (js.inDelaySlot) {
// We assume delay slot in compilerPC + 4.
MIPSOpcode branchOp = Memory::Read_Opcode_JIT(GetCompilerPC());
MIPSOpcode delayOp = Memory::Read_Opcode_JIT(GetCompilerPC() + 4);
downcountOffset = -MIPSGetInstructionCycleEstimate(delayOp);
if ((MIPSGetInfo(branchOp) & LIKELY) != 0) {
// Okay, we're in a likely branch. Also negate the branch cycles.
downcountOffset += -MIPSGetInstructionCycleEstimate(branchOp);
}
}
int downcountAmount = js.downcountAmount + downcountOffset;
ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));
if (downcountAmount != 0)
ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));
// Note that this means downcount can't be metadata on the block.
js.downcountAmount = -downcountOffset;
ir.Write(IROp::MemoryCheck, 0, rs, ir.AddConstant(offset));
ir.Write(IROp::MemoryCheck, js.inDelaySlot ? 4 : 0, rs, ir.AddConstant(offset));
ApplyRoundingMode();

js.hadBreakpoints = true;
Expand Down
4 changes: 2 additions & 2 deletions Core/MIPS/IR/IRInst.cpp
Expand Up @@ -166,8 +166,8 @@ static const IRMeta irMeta[] = {
{ IROp::SetPC, "SetPC", "_G" },
{ IROp::SetPCConst, "SetPC", "_C" },
{ IROp::CallReplacement, "CallRepl", "_C", IRFLAG_BARRIER },
{ IROp::Breakpoint, "Breakpoint", "", IRFLAG_BARRIER },
{ IROp::MemoryCheck, "MemoryCheck", "_GC", IRFLAG_BARRIER },
{ IROp::Breakpoint, "Breakpoint", "_C", IRFLAG_BARRIER },
{ IROp::MemoryCheck, "MemoryCheck", "IGC", IRFLAG_BARRIER },

{ IROp::ValidateAddress8, "ValidAddr8", "_GC", IRFLAG_BARRIER },
{ IROp::ValidateAddress16, "ValidAddr16", "_GC", IRFLAG_BARRIER },
Expand Down
26 changes: 18 additions & 8 deletions Core/MIPS/IR/IRInterpreter.cpp
Expand Up @@ -60,20 +60,30 @@ alignas(16) static const uint32_t lowBytesMask[4] = {
0x000000FF, 0x000000FF, 0x000000FF, 0x000000FF,
};

u32 RunBreakpoint(u32 pc) {
u32 IRRunBreakpoint(u32 pc) {
// Should we skip this breakpoint?
if (CBreakPoints::CheckSkipFirst() == pc)
uint32_t skipFirst = CBreakPoints::CheckSkipFirst();
if (skipFirst == pc || skipFirst == currentMIPS->pc)
return 0;

CBreakPoints::ExecBreakPoint(currentMIPS->pc);
// Did we already hit one?
if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME)
return 1;

CBreakPoints::ExecBreakPoint(pc);
return coreState != CORE_RUNNING ? 1 : 0;
}

u32 RunMemCheck(u32 pc, u32 addr) {
u32 IRRunMemCheck(u32 pc, u32 addr) {
// Should we skip this breakpoint?
if (CBreakPoints::CheckSkipFirst() == pc)
uint32_t skipFirst = CBreakPoints::CheckSkipFirst();
if (skipFirst == pc || skipFirst == currentMIPS->pc)
return 0;

// Did we already hit one?
if (coreState != CORE_RUNNING && coreState != CORE_NEXTFRAME)
return 1;

CBreakPoints::ExecOpMemCheck(addr, pc);
return coreState != CORE_RUNNING ? 1 : 0;
}
Expand Down Expand Up @@ -1043,7 +1053,7 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
break;

case IROp::Downcount:
mips->downcount -= inst->constant;
mips->downcount -= (int)inst->constant;
break;

case IROp::SetPC:
Expand Down Expand Up @@ -1100,14 +1110,14 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst, int count) {
break;

case IROp::Breakpoint:
if (RunBreakpoint(mips->pc)) {
if (IRRunBreakpoint(inst->constant)) {
CoreTiming::ForceCheck();
return mips->pc;
}
break;

case IROp::MemoryCheck:
if (RunMemCheck(mips->pc, mips->r[inst->src1] + inst->constant)) {
if (IRRunMemCheck(mips->pc + inst->dest, mips->r[inst->src1] + inst->constant)) {
CoreTiming::ForceCheck();
return mips->pc;
}
Expand Down
3 changes: 3 additions & 0 deletions Core/MIPS/IR/IRInterpreter.h
Expand Up @@ -20,4 +20,7 @@ inline static u32 ReverseBits32(u32 v) {
return v;
}

u32 IRRunBreakpoint(u32 pc);
u32 IRRunMemCheck(u32 pc, u32 addr);

u32 IRInterpret(MIPSState *ms, const IRInst *inst, int count);
19 changes: 18 additions & 1 deletion Core/MIPS/x86/X64IRCompSystem.cpp
Expand Up @@ -23,6 +23,7 @@
#include "Core/HLE/HLE.h"
#include "Core/HLE/ReplaceTables.h"
#include "Core/MemMap.h"
#include "Core/MIPS/IR/IRInterpreter.h"
#include "Core/MIPS/x86/X64IRJit.h"
#include "Core/MIPS/x86/X64IRRegCache.h"

Expand Down Expand Up @@ -88,9 +89,25 @@ void X64JitBackend::CompIR_Breakpoint(IRInst inst) {

switch (inst.op) {
case IROp::Breakpoint:
FlushAll();
// Note: the constant could be a delay slot.
ABI_CallFunctionC((const void *)&IRRunBreakpoint, inst.constant);
TEST(32, R(EAX), R(EAX));
J_CC(CC_NZ, dispatcherCheckCoreState_, true);
break;

case IROp::MemoryCheck:
CompIR_Generic(inst);
{
X64Reg addrBase = regs_.MapGPR(inst.src1);
FlushAll();
LEA(32, addrBase, MDisp(addrBase, inst.constant));
MovFromPC(SCRATCH1);
LEA(32, SCRATCH1, MDisp(SCRATCH1, inst.dest));
ABI_CallFunctionRR((const void *)&IRRunMemCheck, SCRATCH1, addrBase);
TEST(32, R(EAX), R(EAX));
J_CC(CC_NZ, dispatcherCheckCoreState_, true);
break;
}

default:
INVALIDOP;
Expand Down

0 comments on commit 4694a11

Please sign in to comment.