mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-15 02:29:51 +01:00
WebSocket debugger: runUntilTime that's already there, and across loads
A target time that had already passed, relativeUs 0 for one, failed without a stop event, so a client waiting for cpu.stepping waited until its own timeout. Now it doesn't run, and reports the stop right away. A savestate loaded during the run kept the deadline's absolute time, which belongs to the timeline the load replaced: a state from later in the game stopped the run at once, one from earlier ran it for as long as the gap. Now the deadline keeps the time it had left. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df72f3be81
commit
93b7dacc1e
5 files changed
+35
-9
No files matched your search
@@ -712,6 +712,22 @@ void Core_Break(BreakReason reason, u32 relatedAddress, const BreakpointHit *hit
|
||||
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
|
||||
}
|
||||
|
||||
void Core_ReportBreakWithoutRunning(BreakReason reason, u32 relatedAddress) {
|
||||
if (coreState != CORE_STEPPING_CPU) {
|
||||
return;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(g_stepMutex);
|
||||
if (!g_cpuStepCommand.empty()) {
|
||||
// A step is about to run, and its own stop will be reported.
|
||||
return;
|
||||
}
|
||||
g_breakReason = reason;
|
||||
g_breakHit = BreakpointHit{};
|
||||
g_cpuStepCommand.reason = reason;
|
||||
g_cpuStepCommand.relatedAddr = relatedAddress;
|
||||
steppingCounter++;
|
||||
}
|
||||
|
||||
// Free-threaded (or at least should be)
|
||||
void Core_Resume() {
|
||||
// If the current PC is on a breakpoint, the user doesn't want to do nothing.
|
||||
|
||||
@@ -117,6 +117,9 @@ struct BreakpointHit {
|
||||
// hit is optional detail for the breakpoint kinds, forwarded to the debugger. Only stored when
|
||||
// the break actually takes effect, so a rejected Core_Break() can't leave a stale one behind.
|
||||
void Core_Break(BreakReason reason, u32 relatedAddress = 0, const BreakpointHit *hit = nullptr);
|
||||
// While stepping: reports a stop for reason without running, for a run whose stop condition already
|
||||
// holds (cpu.runUntilTime with a time that's already passed), so a debugger waiting for the stop gets one.
|
||||
void Core_ReportBreakWithoutRunning(BreakReason reason, u32 relatedAddress = 0);
|
||||
|
||||
// Resumes execution. Works both when stepping the CPU and the GE.
|
||||
void Core_Resume();
|
||||
|
||||
+6
-1
@@ -567,6 +567,9 @@ void DoState(PointerWrap &p) {
|
||||
return;
|
||||
// States are saved between slices.
|
||||
otherCPUsRan = 0;
|
||||
// A debugger's run-until deadline keeps the time it had left, not its absolute time, which
|
||||
// belongs to the timeline being replaced.
|
||||
const s64 deadlineLeftUs = p.mode == PointerWrap::MODE_READ && breakDeadlineUs ? breakDeadlineUs - (s64)GetGlobalTimeUs() : 0;
|
||||
|
||||
int n = (int)event_types.size();
|
||||
int current = n;
|
||||
@@ -632,7 +635,9 @@ void DoState(PointerWrap &p) {
|
||||
lastGlobalTimeUs = 0;
|
||||
}
|
||||
if (p.mode == PointerWrap::MODE_READ) {
|
||||
// A debugger's run-until deadline is in emulated us, and the ticks it maps to just changed.
|
||||
if (breakDeadlineUs) {
|
||||
breakDeadlineUs = (s64)GetGlobalTimeUs() + std::max(deadlineLeftUs, (s64)0);
|
||||
}
|
||||
RecomputeBreakDeadline();
|
||||
}
|
||||
|
||||
|
||||
@@ -293,6 +293,9 @@ void WebSocketSteppingState::RunUntil(DebuggerRequest &req) {
|
||||
// - us: emulated time right now.
|
||||
// A cpu.stepping event follows once it gets there. Note that anything else that stops the CPU
|
||||
// first - a breakpoint, an exception - cancels the deadline, same as it cancels a step.
|
||||
// A time that has already passed (relativeUs 0, say) doesn't run at all: the response and the
|
||||
// cpu.stepping event come right away. A savestate loaded during the run moves the deadline along
|
||||
// with the clock, so it still runs for the time that was left.
|
||||
void WebSocketSteppingState::RunUntilTime(DebuggerRequest &req) {
|
||||
if (!currentDebugMIPS->isAlive()) {
|
||||
return req.Fail("CPU not started");
|
||||
@@ -318,15 +321,14 @@ void WebSocketSteppingState::RunUntilTime(DebuggerRequest &req) {
|
||||
const u64 nowUs = CoreTiming::GetGlobalTimeUs();
|
||||
const u64 targetUs = absolute ? (u64)requested : nowUs + (u64)requested;
|
||||
if (targetUs <= nowUs) {
|
||||
req.Fail("Target time has already passed");
|
||||
return;
|
||||
// Already there. A client waiting for the stop would otherwise never get one.
|
||||
Core_ReportBreakWithoutRunning(BreakReason::RunUntilTime, currentMIPS->pc);
|
||||
} else {
|
||||
CoreTiming::SetBreakDeadlineUs(targetUs);
|
||||
PrepareResume();
|
||||
Core_Resume();
|
||||
}
|
||||
|
||||
CoreTiming::SetBreakDeadlineUs(targetUs);
|
||||
|
||||
PrepareResume();
|
||||
Core_Resume();
|
||||
|
||||
JsonWriter &json = req.Respond();
|
||||
json.writeFloat("targetUs", (double)targetUs);
|
||||
json.writeFloat("us", (double)nowUs);
|
||||
|
||||
@@ -273,7 +273,7 @@ file - this is just an index.
|
||||
|---|---|---|
|
||||
| Game/version | `game.reset`, `game.status`, `game.state.load` (queue loading a `.ppst` file given as `path`, or a `slot` of the running game (any slot in headless, otherwise one the app's slot count shows), applied at the end of the next frame the CPU runs; with `wait: true` the response comes once it's done, or as an error if it failed), `game.state.save` (the same for saving one), `game.state.rewind` (the same for the newest rewind snapshot, which it keeps, so only a rewind within 0.8 seconds of the last steps further back; needs `RewindSnapshotInterval` set, and the `wait` response's `metadata` says how far back that is in emulated time), `game.state.verify` (the same for checking that saving the current state and reading it back agree; all but this one are refused in a session booted into the XMB, whose discs come and go), `game.speed.get/set` (emulation speed - unlimited fast-forward, or a percentage of 60 FPS; see below), `game.umd.insert` / `game.umd.eject` (put a disc image, given as `path`, in an empty drive, or take it out: for the XMB booted with `--vsh` and no game, which then lists the disc and can launch it), `version` | `GameSubscriber.cpp` |
|
||||
| CPU core | `cpu.stepping`, `cpu.resume`, `cpu.status` (reports `ticks` plus `us`, emulated microseconds, and `clockHz` - use `us` to line up with wall-clock timings, since games change the clock frequency and the ticks-per-second ratio isn't fixed), `cpu.getAllRegs`, `cpu.getReg`, `cpu.setReg`, `cpu.evaluate` | `CPUCoreSubscriber.cpp` |
|
||||
| Stepping | `cpu.stepInto`, `cpu.stepOver`, `cpu.stepOut`, `cpu.runUntil`, `cpu.runUntilTime` (run until a point in emulated time - `us` absolute or `relativeUs` from now - and break there; this is how to get a scripted repro reproducibly "N seconds into the game" instead of polling `cpu.status` in a loop), `cpu.nextHLE` | `SteppingSubscriber.cpp` |
|
||||
| Stepping | `cpu.stepInto`, `cpu.stepOver`, `cpu.stepOut`, `cpu.runUntil`, `cpu.runUntilTime` (run until a point in emulated time - `us` absolute or `relativeUs` from now - and break there; this is how to get a scripted repro reproducibly "N seconds into the game" instead of polling `cpu.status` in a loop; a time already passed, such as `relativeUs` 0, stops right away without running, and a savestate loaded during the run keeps the time that was left), `cpu.nextHLE` | `SteppingSubscriber.cpp` |
|
||||
| Breakpoints | `cpu.breakpoint.add/update/remove/list`, `memory.breakpoint.add/update/remove/list`, `cpu.regBreakpoint.add/update/remove/list` (break when a register is written to, by any instruction anywhere - currently GPRs only; interpreter-only, no effect under a JIT backend) | `BreakpointSubscriber.cpp` |
|
||||
| Memory read/write | `memory.read_u8/u16/u32`, `memory.read`, `memory.readString`, `memory.write_u8/u16/u32`, `memory.write`. The numeric ones report the result as both `value` and `uintValue` - the latter is what `cpu.getReg`/`cpu.getAllRegs` call it, so a client can read either without caring which event answered | `MemorySubscriber.cpp` |
|
||||
| Memory search | `memory.search` - scan a range for a `u8`/`u16`/`u32`/`float` value or a `bytes` pattern (with an optional wildcard mask), for narrowing down where an unknown value lives (Cheat Engine style) | `MemorySubscriber.cpp` |
|
||||
|
||||
Reference in new issue
Block a user