Commit Graph
28 Commits
Author SHA1 Message Date
Henrik RydgårdandClaude Opus 5 6bcc376c06 Accept all four broadcaster names, and alias memory reads to uintValue
broadcast.config.set rejected "game" and "stepping" as unsupported, though both
are documented and both are real broadcasters. The valid keys are whatever
already exists in the client's disallowed map, and that map starts empty and
only grows as a side effect of operator[] the first time each category
broadcasts - so which keys were accepted depended on what had happened to fire
yet. "logger" and "input" work because the broadcast loop touches them every
lap; "game" and "stepping" only appear once one actually occurs. Seed all four
at connection setup. Unknown keys are still refused, which is the useful half
of the old behaviour.

The numeric memory reads answered with "value" while cpu.getReg and
cpu.getAllRegs answer with "uintValue". Nothing marks which is which, so a
client that guesses gets a missing key - and one that defaults a missing key to
zero silently reports plausible nonsense, which cost real time during the
CrossCraft investigation (an empty vtable that wasn't). Write both names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-18 09:32:04 +02:00
Henrik RydgårdandClaude Opus 5 75ff0d406c Rename Memory::Lock() to Core_LockAgainstShutdown(), move it to Core
It stopped being about memory when CPU_Shutdown started holding it across the
whole teardown - it's what keeps kernel objects, the symbol map and the memory
map from being freed while another thread reads them. The old name invited the
reading that it locks memory *access*, which it has never done.

Memory::Reinit() now holds it across both halves rather than relying on
Memory::Shutdown()'s own acquire: between Shutdown() and Init() there is no
memory map at all, and a reader could slip into that gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 13:11:16 +02:00
Henrik RydgårdandClaude Opus 5 81511e7a08 Fix deadlock between the Win32 debugger's paint handlers and the CPU thread
Reported hang: the CPU thread held g_frameMutex (NativeFrame) and blocked on
g_shutdownLock inside a queued memory.read, while the GUI thread held
g_shutdownLock (CtrlMemView::onPaint) and blocked on g_frameMutex. Textbook
ABBA.

The CPU thread's order is structural - NativeFrame wraps everything below it in
g_frameMutex, and both Core_ProcessCPUQueue() and runImDebugger() ->
DisassembleRange() lock memory from under there - so the GUI side is the one
that has to match. Swaps the three handlers that had it backwards
(CtrlMemView::onPaint, CtrlDisAsmView::onPaint, CtrlStackTraceView::
loadStackTrace) to take g_frameMutex first. They already took both locks, so
this is ordering only, and g_shutdownLock is recursive so nesting is fine.

Also drops the Memory::MemoryInitedLock from the WebSocket LockMemory(), which
is what made the CPU thread want that lock in the first place. It was guarding
against another thread tearing down the memory system, but that doesn't happen:
Memory::Shutdown() is only reached via CPU_Shutdown() <- PSP_Shutdown(), whose
callers all run on the CPU thread, and Memory::Reinit() runs from
Memory::DoState() on savestate load, likewise. WebSocket.cpp additionally holds
lifecycleLock across the whole handler and takes it on STOPPING.

Note this second part isn't sufficient on its own - ImMemView's copy-disassembly
path also locks memory from inside the frame span - which is why the ordering
fix is the real one.

Not removing Memory::Lock() from the Win32 paint handlers: teardown isn't fully
inside the g_frameMutex span yet. EmuScreen::render()'s PSP_Shutdown() is, but
the ones in EmuScreen::sendMessage() (game reset, loading a new game) run from
g_screenManager->sendMessage(), above where NativeFrame takes the guard. Closing
that is the prerequisite, and is left for later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 00:29:44 +02:00
Henrik RydgårdandClaude Opus 5 6a05ef290d Fix four WebSocket debugger bugs found while stress testing
memory.readString could kill the connection: it copied raw emulated memory
straight into a JSON string, so any address not holding valid UTF-8 produced an
invalid WebSocket text frame.

hle.data.remove wiped the name of a function sharing the address. Labels are
shared between data and function symbols, so removing the data label left the
function showing up in hle.func.list with an empty name.

hle.data.add silently did nothing outside a loaded module. GetModuleIndex()
returns -1 for e.g. a heap or stack address, and symbols under that index never
reach the active maps - so the add reported success while the symbol was
invisible to list, and rename/remove then failed with "No data symbol found".
Falls back to module index 0 ("no module, absolute address"), which is the right
answer for a label the user put somewhere after a memory.search.

hle.thread.list reported the thread's stack base address in a field called
initialStackSize. Renamed to initialStack, matching the SceKernelThreadInfo
field it comes from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
2026-08-17 00:25:39 +02:00
Henrik Rydgård 9f90512ef6 Make instruction cache invalidation (for us, jit cache invalidation) clearer 2026-08-16 16:59:52 +02:00
Henrik Rydgård a49f4523cb Correct when we process the stepping queue. Also, the jitLock mutex is no longer needed. 2026-08-16 13:33:26 +02:00
Henrik Rydgård 3bd41376da More memory cleanup 2026-08-11 20:12:10 +02:00
Henrik Rydgård d8edeb7649 Interpreter: Add correct alignment checks to loads/stores, cleanup 2026-08-11 10:28:48 +02:00
Henrik Rydgård 66c8bfbcb2 Improve semantics 2026-08-10 10:11:24 +02:00
Henrik RydgårdandClaude Sonnet 5 03dcfd3931 WebSocket debugger: fail fast on invalid memory ranges, move base64 off the CPU thread
memory.read_u8/u16/u32/read/readString/write_u8/u16/u32/write/search all
validated their address/size parameters (and, for search, the rest of its
param parsing) after already queuing onto the CPU thread. None of that
depends on CPU-thread-owned state, so do it upfront instead and fail fast
without a round trip through the queue for requests we already know are
invalid.

Also, for memory.read and memory.readString, only the raw memory copy
(which needs replacements/emuhacks disabled) now happens on the CPU
thread - the base64 encoding itself happens back on the WebSocket thread
afterward, so a large read no longer blocks the CPU thread's frame pump
for the encoding work too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
2026-08-08 17:22:12 +02:00
Henrik RydgårdandClaude Sonnet 5 75b9df8384 Debugger: Route MemorySubscriber through the CPU thread, drop the forced-pause dance
Every memory.read*/write*/search endpoint used to call LockMemoryAndCPU(),
which - unless the CPU was already stepping - busy-waited for coreState to
settle, force-paused a running game with Core_Break(), and blocked on
Core_WaitInactive() before touching memory, just to get exclusive access
from the WebSocket handler thread. It also took MIPSComp::jitLock around
saving/restoring emuhack ops for the same reason.

Now the whole body of each handler runs inside Core_RunOnCPUThread(), so
none of that is needed for CPU-thread exclusivity: reads/writes happen
inline on the CPU thread itself, whether the game is running or stepping,
without ever pausing it. Confirmed live that memory reads/search now
complete while coreState stays CORE_RUNNING_CPU throughout - no more
stepping flicker on every debugger memory poll.

Kept Memory::MemoryInitedLock (guards against Memory::Shutdown() racing in
from a different thread, e.g. the UI thread stopping the game - unrelated
to the WebSocket-thread-vs-CPU-thread problem) and MIPSComp::jitLock around
the emuhack save/restore (guards against a UI-triggered CPU core switch,
also a different thread than the one Core_RunOnCPUThread targets).

Same caveats as previous conversions: memory.read for a very large 'size'
now base64-encodes on the CPU thread itself, and memory.search still has no
size cap - both will now block the CPU thread's own frame pump for their
duration on a large enough request. Noted inline, not fixed here.

Replaced remaining `auto` locals with concrete types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Hqm11k99viLfbJm2MkH4BH
2026-08-08 12:15:04 +02:00
Henrik RydgårdandClaude Opus 5 074c8ac523 Add memory.search and hle.data.* to the WebSocket debugger
Reverse-engineering workflows need to (1) find where an unknown value lives
in memory and (2) label what's found, neither of which the debugger API
could do before:

- memory.search (MemorySubscriber.cpp): Cheat-Engine-style scan of a memory
  range for a u8/u16/u32/float value, or a byte pattern with an optional
  wildcard mask.
- hle.data.list/add/remove/rename (HLESubscriber.cpp): manage ST_DATA
  symbols (structs, tables, buffers), mirroring the existing hle.func.*
  commands for functions. Needed a new SymbolMap::RemoveData, since only
  RemoveFunction existed - added following the same pattern.

Verified live against a running PPSSPP instance (game.status, cpu.stepping,
memory.search in u32/bytes/masked-bytes modes, and the full
add/list/rename/remove data-symbol lifecycle) via Tools/wsdbg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XDNwPPuidmNxQGRJxBuRL6
2026-07-26 21:17:09 +02:00
Henrik Rydgård 5f7a937466 Rename ValidSize to ClampValidSizeAt 2025-12-30 20:31:07 +01:00
Silvris 6d5bf32796 initial tests 2025-09-19 02:40:13 -05:00
Henrik Rydgård 31cf5771f4 Turn the break reason into an enum, fix some minor issues 2025-02-19 16:01:11 -06:00
Henrik Rydgård d3e9398cb3 Split Core_EnableStepping into Core_Break and Core_Resume 2024-11-03 17:53:42 +01:00
Unknown W. Brackets 265a3cf7a0 Debugger: Fix a dumb typo. 2023-01-18 21:42:36 -08:00
Unknown W. Brackets 5b74937780 Debugger: Make sure we don't restore twice.
Aside from the lock, we also don't want to restore emuhacks/replacements
or resume from stepping early.
2023-01-04 23:48:14 -08:00
Unknown W. Brackets a937e5dce3 Debugger: Fix mem lock with optimizations disabled. 2023-01-04 21:41:59 -08:00
Unknown W. Brackets 1233d1d376 Reporting: Disable when debugger methods used.
This won't catch all means of hacking memory, but will catch some.
Trying to reduce noise in reporting from debugging.
2022-07-30 18:21:32 -07:00
Unknown W. Brackets b8ab7f39df jit: Lock around changes to the jit pointer. 2021-11-28 10:04:22 -08:00
Unknown W. Brackets 2bd13c5d9d Debugger: Track reason for entering stepping. 2021-10-23 16:56:15 -07:00
Unknown W. Brackets 9cfaac6d90 Debugger: Invalidate when using memory APIs. 2021-02-13 09:50:04 -08:00
Unknown W. Brackets 3325a27811 Debugger: Cleanup some syntax. 2020-12-28 11:13:54 -08:00
Unknown W. Brackets b5a6693019 Debugger: Add helper to read strings.
This is common, so let's expose as an API.
2020-12-28 11:13:26 -08:00
Unknown W. Brackets 7e5052bc9e Debugger: Add API to read/write chunks of memory.
Using base64 at this point to keep JSON.
2020-12-28 10:30:58 -08:00
Unknown W. Brackets f19d957c42 Debugger: Cleanup memory.read/write validation.
Required parameters fail automatically, better to keep the error
consistent.  This was sending two errors.

Additionally, don't allow it when no game is started yet.
2020-12-28 09:31:30 -08:00
parport0 fd62d30773 Debugger: Add memory.read_*, memory write_* 2020-07-26 01:18:16 +03:00