Commit Graph
16 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5 35a91b757a Move the temporary breakpoint out of the user's breakpoint list
step-over, step-out and run-until plant a one-shot breakpoint at the address
they want execution to return to. Keeping it in breakPoints_ alongside the
user's own meant the two kept colliding:

- Adding a log-only user breakpoint at the same address hijacked the temporary
  one. AddBreakPoint() didn't match across temp-ness so both existed, and then
  ChangeBreakPoint() looked up "the first enabled breakpoint at this address" -
  a log-only breakpoint isn't enabled, so the temporary one won and had its
  action overwritten to log-only. It lost PAUSE and the step never came back.
- RemoveBreakPoint() erased up to two entries per address to catch an
  overlapping temporary one, so deleting either deleted both - including the
  interpreter's cleanup path in CheckExecBreakpoints() taking the user's
  breakpoint with it.
- ExecBreakPoint() handled one breakpoint per address, so with both at the same
  address only one of them did anything: the step completed but the user's log
  line never printed.
- Nothing dropped it when something *else* stopped us first, so an interrupted
  step left a breakpoint armed at an address nobody was waiting for anymore,
  which later fired as a phantom stop.

It's a single TempBreakPoint member now, invisible to the breakpoint lists and
untouched by user edits. One is enough: step over/out and cross-thread step into
all require the CPU to already be stepping and resume it immediately, so only
one can be in flight, and run-until now replaces rather than stacking (two
pending run-untils had no coherent meaning, and the loser stayed armed).

Behavior follows what other debuggers do. Both breakpoints at an address are
evaluated independently and their actions combine, so a log-only breakpoint
logs without stopping and still lets the step finish. Core_Break() drops the
temporary breakpoint on any stop, whatever the reason - the same way gdb deletes
its step-resume breakpoint and lldb discards the thread plan.

Two things to be careful of, both covered by the new TempBreakpoints test:
HasBreakPoints() has to account for it, or the interpreter's checked run loop
and the JIT skip breakpoint checking entirely and a step with no user
breakpoints set never returns; and IsAddressBreakPoint() (user-facing, for the
lists and disassembly markers) is now separate from NeedsBreakCheckAt() (what
the JIT frontends and interpreter ask), since only the latter should see it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 00:29:26 +02:00
Henrik Rydgård c457dbbd2f Debugger breakpoints: Rename "Result" to "Action" 2026-08-16 16:59:54 +02:00
Henrik RydgårdandClaude Sonnet 5 3edea91c8e Debugger: track hit counts on address breakpoints, expose via cpu.breakpoint.list
BreakPoint (cpu.breakpoint.*) had no hit-count tracking at all, unlike
MemCheck (memory.breakpoint.*), which already tracks numHits. This made it
genuinely hard to tell "this breakpoint is never being reached" apart from
"it's being reached but I'm not seeing the log/pause where I'm looking" -
directly informed by repeatedly hitting exactly that ambiguity while
debugging the VSH boot path this session (see docs/VSHBootInvestigation.md).

Added BreakPoint::numHits, incremented in BreakpointManager::ExecBreakPoint()
whenever a breakpoint's address is hit and any condition passes (matching
MemCheck::Apply()'s existing semantics - counts real triggers, not just
"execution passed through here"). Exposed as a new "hits" field in
cpu.breakpoint.list's response.

Verified live via PPSSPPHeadless + wsdbg: hits reads 0 before the CPU
resumes, 1 after the breakpoint fires once. UnitTest.exe all: 49/49 passed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-14 11:04:32 +02:00
Henrik RydgårdandClaude Sonnet 5 db2d248b4a Rename GPRBreakpoint/gprBreakpoint to RegBreakpoint/regBreakpoint
The struct and its API only handle GPR indices today, but the naming
should stay general since this is expected to grow to cover other
register files too (e.g. FPU registers like $f10). Pure rename - no
behavior change:

- Core/Debugger/Breakpoints.{h,cpp}: RegBreakpoint struct, all
  BreakpointManager Add/Remove/Change/Get/Exec/Has/Find*RegBreakpoint*
  methods, regBreakpoints_/regBreakpointMask_ members.
- Core/Core.{h,cpp}: BreakReason::RegBreakpoint, "cpu.regBreakpoint"
  break-reason string.
- Core/Debugger/WebSocket/BreakpointSubscriber.{h,cpp}: WebSocket
  events cpu.gprBreakpoint.* -> cpu.regBreakpoint.*, matching
  Add/Update/Remove/List handlers and params struct.
- Core/MIPS/MIPSTables.cpp: local variable names in the interpreter's
  per-instruction breakpoint check.
- docs/WebSocketDebugger.md updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-13 16:09:51 +02:00
Henrik RydgårdandClaude Sonnet 5 75174af77b Add GPR write breakpoints (break when a register is written, anywhere)
New debugging primitive: break whenever any instruction writes to a
given general-purpose register (0-31), regardless of which address
executes the write. Requested for continuing the reboot.bin trace,
where the actual blocker is "what sets $s3 to this bad value", not
"what happens at a specific address" - existing address/memory
breakpoints can't express that directly.

- GPRBreakpoint (Core/Debugger/Breakpoints.h) mirrors the existing
  BreakPoint/MemCheck shape (result/condition/logFormat/hit count),
  keyed by register index instead of address/range.
- BreakpointManager keeps a u32 bitmask (bit i = register i has an
  active breakpoint) alongside the GPRBreakpoint vector, so the
  interpreter loop can test "would this write trip anything" with a
  single shift+and against a value already cached in a local.
- RunUntilDowncountZeroWithChecks (Core/MIPS/MIPSTables.cpp) computes
  the about-to-be-written register from the current instruction's
  OUT_RT/OUT_RD/OUT_RA flags (GetGPRWriteTarget()) and checks it
  against the mask, same convention as the existing memcheck handling
  right above it (checked before the instruction executes, bails via
  CORE_STEPPING_CPU without running it if tripped).
- New BreakReason::GPRBreakpoint ("cpu.gprBreakpoint") for Core_Break.
- WebSocket API: cpu.gprBreakpoint.add/update/remove/list, accepting
  either a 0-31 'register' index or a case-insensitive 'name' (e.g.
  "s3"), documented in docs/WebSocketDebugger.md.

Interpreter-only for now, deliberately.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-13 11:35:29 +02:00
Henrik Rydgård 06522e91a0 BreakpointSubscriber: apply the same overflow check to breakpoint removal
WebSocketMemoryBreakpointParams::Parse() (used by add/update) checks
for address + size wrapping around before computing the end address,
but memory.breakpoint.remove computed it inline without that check.
Apply the same check for consistency - a crafted size could otherwise
wrap the computed end below address, causing RemoveMemCheck to operate
on an unintended range.
2026-08-11 15:40:30 +02:00
Henrik RydgårdandClaude Sonnet 5 c32df3d931 Debugger: Route BreakpointSubscriber, DisasmSubscriber, and most of
CPUCoreSubscriber through the CPU thread

Continues the pattern started with SteppingSubscriber: route breakpoint,
disassembly, symbol, and register access through Core_RunOnCPUThread()
instead of touching that state directly from the WebSocket handler thread.

Two intentional exceptions, matching the reasoning already used for
cpu.stepInto's "not currently stepping" branch:
- cpu.stepping's Core_Break() call stays unqueued - it's what makes the CPU
  thread start reaching the queue drain point in the first place.
- cpu.status stays unqueued - it's meant to be a cheap, frequently-pollable
  status check, and its "pc" field is already documented as inaccurate unless
  stepping. Matches how SteppingBroadcaster already reads the same state
  directly from the WebSocket thread.

Where a handler's response doesn't depend on anything the queued lambda
computed (plain add/remove endpoints), moved req.Respond() back out after
the Core_RunOnCPUThread() call for readability - the JSON building and
socket write happen later in Finish() regardless of where Respond() is
called, so there's no thread-safety difference either way, just clarity
about what actually needs to run on the CPU thread.

memory.searchDisasm carries the same caveat flagged for memory.search: its
scan range has no size cap, so if the CPU is stepping, a very large range
will now block the CPU thread's own frame pump for the scan's duration
rather than running unqueued on the WebSocket thread as before. Not fixed
here - noted in a comment at the call site.

Replaced remaining `auto` locals in these three files with concrete types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
2026-08-08 11:56:32 +02:00
Nemoumbra 846c265972 Fixed the docs + better wording 2025-08-10 17:44:38 +03:00
Henrik Rydgård 597be1c9bc Stop pretending that DisassemblyManager isn't a singleton - it currently is. 2024-12-12 19:25:04 +01:00
Henrik Rydgård 20a17a0e8d Reorganize DebugInterface etc a bit.
KernelThreadDebugInterface no longer has a useless copy of a MIPSDebugInterface.
2024-12-12 18:54:46 +01:00
Henrik Rydgård 7992ff4627 Make CBreakpoints an object 2024-11-25 00:22:53 +01:00
Unknown W. Brackets 069ff3e58c Debugger: Add memory bp conditions to websockets. 2023-04-12 01:07:56 -07:00
Unknown W. Brackets 4ce2b64ef7 Debugger: Refactor to allow simpler broadcasting.
It's okay to mix a bit for simple ones, I think.
2018-09-01 10:15:22 -07:00
Unknown W. Brackets f0694ba115 Debugger: Add some docs for new APIs. 2018-06-16 15:42:42 -07:00
Unknown W. Brackets b2cc4a0965 Debugger: Add memory breakpoint management. 2018-06-08 06:59:18 -07:00
Unknown W. Brackets 29d93c56c7 Debugger: Initial breakpoint APIs. 2018-06-08 06:59:18 -07:00