Debugger: add kernel object introspection over the WebSocket API

New events, all read-only (never mutate kernel state, no cleanup/sort calls
- see HLEKernelObjectSubscriber.cpp's header comment):

- hle.object.list: every live kernel object of every type at once (uid,
  type, name, one-line quickInfo), with an optional 'type' filter. Uses
  KernelObjectPool::IterateAll(), a new type-agnostic sibling of the
  existing Iterate<T>().
- hle.eventflag.list/info, hle.mutex.list/info, hle.semaphore.list/info,
  hle.msgpipe.list/info, hle.callback.list/info: per-type full detail
  (all Native* status struct fields plus waiting-thread lists), reading
  straight off the classes exposed in the previous commit.

Also adds JsonWriter::DictScope/ArrayScope (Common/Data/Format/JSONWriter.h)
- RAII push/pop for pushDict()/pushArray(), used throughout the new
  handlers. A forgotten or early-returned pop() previously just produced
  silently malformed JSON; with 11 new handlers each writing a handful of
  nested arrays/dicts, that seemed worth fixing at the API level rather
  than trusting every call site to pair things up by hand. Existing
  handlers are untouched - this is purely additive.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
Henrik RydgårdandClaude Sonnet 5 committed 2026-08-17 16:01:23 +02:00
1 parent 21b7ce7392
commit 83b1d13c88
11 files changed
+530

No files matched your search

+26
View File
@@ -57,6 +57,32 @@ public:
return result;
}
// RAII helpers: push in the constructor, pop in the destructor. Prefer these over manually
// paired pushDict()/pushArray() + pop() calls - a forgotten or early-returned pop() silently
// produces malformed JSON (extra or missing closing brace/bracket) rather than a compile or
// even runtime error, and that gets easy to miss once nesting goes a few levels deep (e.g.
// an array of per-item dicts, each with their own nested array).
class DictScope {
public:
explicit DictScope(JsonWriter &w) : w_(w) { w_.pushDict(); }
DictScope(JsonWriter &w, const std::string &name) : w_(w) { w_.pushDict(name); }
~DictScope() { w_.pop(); }
DictScope(const DictScope &) = delete;
DictScope &operator=(const DictScope &) = delete;
private:
JsonWriter &w_;
};
class ArrayScope {
public:
explicit ArrayScope(JsonWriter &w) : w_(w) { w_.pushArray(); }
ArrayScope(JsonWriter &w, const std::string &name) : w_(w) { w_.pushArray(name); }
~ArrayScope() { w_.pop(); }
ArrayScope(const ArrayScope &) = delete;
ArrayScope &operator=(const ArrayScope &) = delete;
private:
JsonWriter &w_;
};
enum {
NORMAL = 0,
PRETTY = 1,
+2
View File
@@ -313,6 +313,8 @@ add_library(Core STATIC
Debugger/WebSocket/GPURecordSubscriber.h
Debugger/WebSocket/GPUStatsSubscriber.cpp
Debugger/WebSocket/GPUStatsSubscriber.h
Debugger/WebSocket/HLEKernelObjectSubscriber.cpp
Debugger/WebSocket/HLEKernelObjectSubscriber.h
Debugger/WebSocket/HLESubscriber.cpp
Debugger/WebSocket/HLESubscriber.h
Debugger/WebSocket/InputBroadcaster.cpp
+2
View File
@@ -452,6 +452,7 @@
<ClCompile Include="Debugger\WebSocket\GPUDisasmSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\GPURecordSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\GPUStatsSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\HLEKernelObjectSubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="Debugger\WebSocket\InputBroadcaster.cpp" />
<ClCompile Include="Debugger\WebSocket\InputSubscriber.cpp" />
@@ -992,6 +993,7 @@
<ClInclude Include="Debugger\WebSocket\GPUDisasmSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GPURecordSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\GPUStatsSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\HLEKernelObjectSubscriber.h" />
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="Debugger\WebSocket\InputBroadcaster.h" />
<ClInclude Include="Debugger\WebSocket\InputSubscriber.h" />
+6
View File
@@ -754,6 +754,9 @@
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\HLEKernelObjectSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp">
<Filter>Debugger\WebSocket</Filter>
</ClCompile>
@@ -2031,6 +2034,9 @@
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\HLEKernelObjectSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h">
<Filter>Debugger\WebSocket</Filter>
</ClInclude>
+2
View File
@@ -61,6 +61,7 @@
#include "Core/Debugger/WebSocket/GPUDisasmSubscriber.h"
#include "Core/Debugger/WebSocket/GPURecordSubscriber.h"
#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"
#include "Core/Debugger/WebSocket/HLEKernelObjectSubscriber.h"
#include "Core/Debugger/WebSocket/HLESubscriber.h"
#include "Core/Debugger/WebSocket/InputSubscriber.h"
#include "Core/Debugger/WebSocket/LogConfigSubscriber.h"
@@ -80,6 +81,7 @@ static const std::vector<SubscriberInit> subscribers({
&WebSocketGPUDisasmInit,
&WebSocketGPURecordInit,
&WebSocketGPUStatsInit,
&WebSocketHLEKernelObjectInit,
&WebSocketHLEInit,
&WebSocketInputInit,
&WebSocketLogConfigInit,
@@ -0,0 +1,451 @@
// Copyright (c) 2026- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// Introspection for kernel synchronization objects (event flags, mutexes, semaphores, message
// pipes, callbacks) plus a generic overview across every kernel object type at once. These read
// straight off the live KernelObject-derived classes (exposed in their respective sceKernel*.h
// headers for exactly this purpose) rather than going through a kernel-side accessor function -
// deliberately: nothing here ever mutates kernel state, only reads already-public fields, so
// there's no reason to route through the owning module. That also means "waiting threads" lists
// below are shown exactly as the kernel currently has them - a thread that's actually done
// waiting but hasn't been pruned by the next real syscall on that object yet may still appear.
#include "Core/Core.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/sceKernelEventFlag.h"
#include "Core/HLE/sceKernelMutex.h"
#include "Core/HLE/sceKernelSemaphore.h"
#include "Core/HLE/sceKernelMsgPipe.h"
#include "Core/HLE/sceKernelThread.h"
#include "Core/Debugger/WebSocket/HLEKernelObjectSubscriber.h"
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
DebuggerSubscriber *WebSocketHLEKernelObjectInit(DebuggerEventHandlerMap &map) {
map["hle.object.list"] = &WebSocketHLEObjectList;
map["hle.eventflag.list"] = &WebSocketHLEEventFlagList;
map["hle.eventflag.info"] = &WebSocketHLEEventFlagInfo;
map["hle.mutex.list"] = &WebSocketHLEMutexList;
map["hle.mutex.info"] = &WebSocketHLEMutexInfo;
map["hle.semaphore.list"] = &WebSocketHLESemaphoreList;
map["hle.semaphore.info"] = &WebSocketHLESemaphoreInfo;
map["hle.msgpipe.list"] = &WebSocketHLEMsgPipeList;
map["hle.msgpipe.info"] = &WebSocketHLEMsgPipeInfo;
map["hle.callback.list"] = &WebSocketHLECallbackList;
map["hle.callback.info"] = &WebSocketHLECallbackInfo;
return nullptr;
}
// Resolves uid to a T*, or fails the request and returns null (mismatched type counts as not
// found, same as the HLE syscalls themselves treat it - see KernelObjectPool::Get<T>().)
template <typename T>
static T *RequireKernelObject(DebuggerRequest &req, uint32_t uid) {
u32 error;
T *obj = kernelObjects.Get<T>((SceUID)uid, error);
if (!obj) {
req.Fail("No matching object found for that uid");
return nullptr;
}
return obj;
}
// Writes a plain {threadId} entry for each id in a wait list that's just SceUIDs (Mutex,
// Semaphore) - already inside an array context (ArrayScope) opened by the caller.
static void WriteThreadIdWaitList(JsonWriter &json, const std::vector<SceUID> &threadIDs) {
for (SceUID threadID : threadIDs) {
JsonWriter::DictScope d(json);
json.writeInt("threadId", threadID);
}
}
// List every live kernel object of every type (hle.object.list)
//
// A coarse overview across all kernel object kinds at once (threads, modules, semaphores, event
// flags, mutexes, msgpipes, callbacks, ...) - for the sync primitive types also covered by
// hle.eventflag.*/hle.mutex.*/etc., use those for full detail; this just gives uid/type/name/a
// one-line summary for everything, useful for figuring out what's alive before drilling in.
//
// Parameters:
// - type: optional string, only include objects whose type name equals this (as returned in
// each entry's own 'type' field - e.g. "EventFlag", "Mutex", "Semaphore", "MsgPipe",
// "CallBack", "Thread", "Module", ...; case sensitive, matches KernelObject::GetTypeName().)
//
// Response (same event name):
// - objects: array of objects, each with properties:
// - uid: unsigned integer kernel object handle.
// - type: string type name.
// - name: string name the object was created with.
// - quickInfo: string, a short type-specific one-line summary (whatever that type finds
// most useful - not machine-parseable, for human/log-style display only.)
void WebSocketHLEObjectList(DebuggerRequest &req) {
std::string typeFilter;
if (!req.ParamString("type", &typeFilter, DebuggerParamType::OPTIONAL))
return;
// Route the actual kernel object reads to the CPU thread instead of poking at them directly
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope objects(json, "objects");
kernelObjects.IterateAll([&](int uid, KernelObject *obj) -> bool {
if (!typeFilter.empty() && typeFilter != obj->GetTypeName())
return true;
char quickInfo[256];
obj->GetQuickInfo(quickInfo, sizeof(quickInfo));
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("type", obj->GetTypeName());
json.writeString("name", obj->GetName());
json.writeString("quickInfo", quickInfo);
return true;
});
});
}
// List all event flags (hle.eventflag.list)
//
// No parameters.
//
// Response (same event name):
// - eventFlags: array of objects, each with properties:
// - uid: unsigned integer kernel object handle - pass to hle.eventflag.info for detail.
// - name: string name.
// - currentPattern: unsigned integer, the flag's current bit pattern.
// - numWaitThreads: integer, how many threads are currently waiting on it.
void WebSocketHLEEventFlagList(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope arr(json, "eventFlags");
kernelObjects.Iterate<EventFlag>([&](int uid, EventFlag *e) -> bool {
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("name", e->nef.name);
json.writeUint("currentPattern", e->nef.currentPattern);
json.writeInt("numWaitThreads", (int)e->waitingThreads.size());
return true;
});
});
}
// Get full detail for one event flag (hle.eventflag.info)
//
// Parameters:
// - uid: unsigned integer kernel object handle (as returned by hle.eventflag.list.)
//
// Response (same event name):
// - name: string name.
// - attr: unsigned integer creation attributes.
// - initPattern: unsigned integer, the bit pattern the flag was created with.
// - currentPattern: unsigned integer, the flag's current bit pattern.
// - waitingThreads: array of objects, each with properties:
// - threadId: integer thread uid.
// - bits: unsigned integer, the bit pattern this thread is waiting to match.
// - matchAny: boolean, true if any one bit matches (OR), false if all bits must (AND.)
// - clear: boolean, true if matched bits get cleared from the pattern on match.
// - clearAll: boolean, true if the whole pattern gets cleared to 0 on match.
void WebSocketHLEEventFlagInfo(DebuggerRequest &req) {
uint32_t uid;
if (!req.ParamU32("uid", &uid))
return;
Core_RunOnCPUThread([&] {
EventFlag *e = RequireKernelObject<EventFlag>(req, uid);
if (!e)
return;
JsonWriter &json = req.Respond();
json.writeString("name", e->nef.name);
json.writeUint("attr", e->nef.attr);
json.writeUint("initPattern", e->nef.initPattern);
json.writeUint("currentPattern", e->nef.currentPattern);
{
JsonWriter::ArrayScope waiters(json, "waitingThreads");
for (const EventFlagTh &t : e->waitingThreads) {
JsonWriter::DictScope d(json);
json.writeInt("threadId", t.threadID);
json.writeUint("bits", t.bits);
// PSP_EVENT_WAIT* bits, see sceKernelWaitEventFlag()'s 'wait' parameter -
// 0x01=OR, 0x10=clear-all-on-match, 0x20=clear-matched-on-match.
json.writeBool("matchAny", (t.wait & 0x01) != 0);
json.writeBool("clear", (t.wait & 0x20) != 0);
json.writeBool("clearAll", (t.wait & 0x10) != 0);
}
}
});
}
// List all mutexes (hle.mutex.list)
//
// No parameters.
//
// Response (same event name):
// - mutexes: array of objects, each with properties:
// - uid: unsigned integer kernel object handle - pass to hle.mutex.info for detail.
// - name: string name.
// - lockThread: integer thread uid currently holding the lock, or -1 if unlocked.
// - lockLevel: integer recursion depth (only >1 possible if created with the recursive attr.)
// - numWaitThreads: integer, how many threads are currently waiting to lock it.
void WebSocketHLEMutexList(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope arr(json, "mutexes");
kernelObjects.Iterate<PSPMutex>([&](int uid, PSPMutex *m) -> bool {
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("name", m->nm.name);
json.writeInt("lockThread", m->nm.lockThread);
json.writeInt("lockLevel", m->nm.lockLevel);
json.writeInt("numWaitThreads", (int)m->waitingThreads.size());
return true;
});
});
}
// Get full detail for one mutex (hle.mutex.info)
//
// Parameters:
// - uid: unsigned integer kernel object handle (as returned by hle.mutex.list.)
//
// Response (same event name):
// - name: string name.
// - attr: unsigned integer creation attributes.
// - initialCount: integer lock count the mutex was created with.
// - lockThread: integer thread uid currently holding the lock, or -1 if unlocked.
// - lockLevel: integer recursion depth.
// - waitingThreads: array of objects, each with properties:
// - threadId: integer thread uid waiting to lock this mutex.
void WebSocketHLEMutexInfo(DebuggerRequest &req) {
uint32_t uid;
if (!req.ParamU32("uid", &uid))
return;
Core_RunOnCPUThread([&] {
PSPMutex *m = RequireKernelObject<PSPMutex>(req, uid);
if (!m)
return;
JsonWriter &json = req.Respond();
json.writeString("name", m->nm.name);
json.writeUint("attr", m->nm.attr);
json.writeInt("initialCount", m->nm.initialCount);
json.writeInt("lockThread", m->nm.lockThread);
json.writeInt("lockLevel", m->nm.lockLevel);
{
JsonWriter::ArrayScope waiters(json, "waitingThreads");
WriteThreadIdWaitList(json, m->waitingThreads);
}
});
}
// List all semaphores (hle.semaphore.list)
//
// No parameters.
//
// Response (same event name):
// - semaphores: array of objects, each with properties:
// - uid: unsigned integer kernel object handle - pass to hle.semaphore.info for detail.
// - name: string name.
// - currentCount: integer current count.
// - numWaitThreads: integer, how many threads are currently waiting on it.
void WebSocketHLESemaphoreList(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope arr(json, "semaphores");
kernelObjects.Iterate<PSPSemaphore>([&](int uid, PSPSemaphore *sema) -> bool {
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("name", sema->ns.name);
json.writeInt("currentCount", sema->ns.currentCount);
json.writeInt("numWaitThreads", (int)sema->waitingThreads.size());
return true;
});
});
}
// Get full detail for one semaphore (hle.semaphore.info)
//
// Parameters:
// - uid: unsigned integer kernel object handle (as returned by hle.semaphore.list.)
//
// Response (same event name):
// - name: string name.
// - attr: unsigned integer creation attributes.
// - initCount: integer count the semaphore was created with.
// - currentCount: integer current count.
// - maxCount: integer maximum count.
// - waitingThreads: array of objects, each with properties:
// - threadId: integer thread uid waiting on this semaphore.
void WebSocketHLESemaphoreInfo(DebuggerRequest &req) {
uint32_t uid;
if (!req.ParamU32("uid", &uid))
return;
Core_RunOnCPUThread([&] {
PSPSemaphore *sema = RequireKernelObject<PSPSemaphore>(req, uid);
if (!sema)
return;
JsonWriter &json = req.Respond();
json.writeString("name", sema->ns.name);
json.writeUint("attr", sema->ns.attr);
json.writeInt("initCount", sema->ns.initCount);
json.writeInt("currentCount", sema->ns.currentCount);
json.writeInt("maxCount", sema->ns.maxCount);
{
JsonWriter::ArrayScope waiters(json, "waitingThreads");
WriteThreadIdWaitList(json, sema->waitingThreads);
}
});
}
// List all message pipes (hle.msgpipe.list)
//
// No parameters.
//
// Response (same event name):
// - msgPipes: array of objects, each with properties:
// - uid: unsigned integer kernel object handle - pass to hle.msgpipe.info for detail.
// - name: string name.
// - freeSize: integer free bytes currently in the pipe's buffer.
// - numSendWaitThreads: integer threads currently blocked trying to send.
// - numReceiveWaitThreads: integer threads currently blocked trying to receive.
void WebSocketHLEMsgPipeList(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope arr(json, "msgPipes");
kernelObjects.Iterate<MsgPipe>([&](int uid, MsgPipe *m) -> bool {
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("name", m->nmp.name);
json.writeInt("freeSize", m->nmp.freeSize);
json.writeInt("numSendWaitThreads", (int)m->sendWaitingThreads.size());
json.writeInt("numReceiveWaitThreads", (int)m->receiveWaitingThreads.size());
return true;
});
});
}
static void WriteMsgPipeWaitList(JsonWriter &json, const std::vector<MsgPipeWaitingThread> &threads) {
for (const MsgPipeWaitingThread &t : threads) {
JsonWriter::DictScope d(json);
json.writeInt("threadId", t.threadID);
json.writeUint("bufSize", t.bufSize);
json.writeUint("freeSize", t.freeSize);
json.writeInt("waitMode", t.waitMode);
}
}
// Get full detail for one message pipe (hle.msgpipe.info)
//
// Parameters:
// - uid: unsigned integer kernel object handle (as returned by hle.msgpipe.list.)
//
// Response (same event name):
// - name: string name.
// - attr: unsigned integer creation attributes.
// - bufSize: integer total buffer size in bytes.
// - freeSize: integer free bytes currently in the buffer.
// - sendWaitingThreads / receiveWaitingThreads: array of objects, each with properties:
// - threadId: integer thread uid.
// - bufSize: unsigned integer size of this thread's own send/receive buffer.
// - freeSize: unsigned integer bytes not yet transferred for this thread.
// - waitMode: integer, 0 = wait for the full transfer (SCE_KERNEL_MPW_FULL), 1 = accept a
// partial transfer (SCE_KERNEL_MPW_ASAP.)
void WebSocketHLEMsgPipeInfo(DebuggerRequest &req) {
uint32_t uid;
if (!req.ParamU32("uid", &uid))
return;
Core_RunOnCPUThread([&] {
MsgPipe *m = RequireKernelObject<MsgPipe>(req, uid);
if (!m)
return;
JsonWriter &json = req.Respond();
json.writeString("name", m->nmp.name);
json.writeUint("attr", m->nmp.attr);
json.writeInt("bufSize", m->nmp.bufSize);
json.writeInt("freeSize", m->nmp.freeSize);
{
JsonWriter::ArrayScope waiters(json, "sendWaitingThreads");
WriteMsgPipeWaitList(json, m->sendWaitingThreads);
}
{
JsonWriter::ArrayScope waiters(json, "receiveWaitingThreads");
WriteMsgPipeWaitList(json, m->receiveWaitingThreads);
}
});
}
// List all callbacks (hle.callback.list)
//
// No parameters.
//
// Response (same event name):
// - callbacks: array of objects, each with properties:
// - uid: unsigned integer kernel object handle - pass to hle.callback.info for detail.
// - name: string name.
// - threadId: integer uid of the thread this callback belongs to.
// - notifyCount: integer, incremented each time the callback is queued to run.
void WebSocketHLECallbackList(DebuggerRequest &req) {
Core_RunOnCPUThread([&] {
JsonWriter &json = req.Respond();
JsonWriter::ArrayScope arr(json, "callbacks");
kernelObjects.Iterate<PSPCallback>([&](int uid, PSPCallback *c) -> bool {
JsonWriter::DictScope d(json);
json.writeUint("uid", uid);
json.writeString("name", c->nc.name);
json.writeInt("threadId", c->nc.threadId);
json.writeInt("notifyCount", c->nc.notifyCount);
return true;
});
});
}
// Get full detail for one callback (hle.callback.info)
//
// Parameters:
// - uid: unsigned integer kernel object handle (as returned by hle.callback.list.)
//
// Response (same event name):
// - name: string name.
// - threadId: integer uid of the thread this callback belongs to.
// - entrypoint: unsigned integer address of the callback function.
// - commonArgument: unsigned integer, the 'common' argument passed to every invocation.
// - notifyCount: integer, incremented each time the callback is queued to run.
// - notifyArg: integer, the argument from the most recent notification.
void WebSocketHLECallbackInfo(DebuggerRequest &req) {
uint32_t uid;
if (!req.ParamU32("uid", &uid))
return;
Core_RunOnCPUThread([&] {
PSPCallback *c = RequireKernelObject<PSPCallback>(req, uid);
if (!c)
return;
JsonWriter &json = req.Respond();
json.writeString("name", c->nc.name);
json.writeInt("threadId", c->nc.threadId);
json.writeUint("entrypoint", c->nc.entrypoint);
json.writeUint("commonArgument", c->nc.commonArgument);
json.writeInt("notifyCount", c->nc.notifyCount);
json.writeInt("notifyArg", c->nc.notifyArg);
});
}
@@ -0,0 +1,35 @@
// Copyright (c) 2026- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
DebuggerSubscriber *WebSocketHLEKernelObjectInit(DebuggerEventHandlerMap &map);
void WebSocketHLEObjectList(DebuggerRequest &req);
void WebSocketHLEEventFlagList(DebuggerRequest &req);
void WebSocketHLEEventFlagInfo(DebuggerRequest &req);
void WebSocketHLEMutexList(DebuggerRequest &req);
void WebSocketHLEMutexInfo(DebuggerRequest &req);
void WebSocketHLESemaphoreList(DebuggerRequest &req);
void WebSocketHLESemaphoreInfo(DebuggerRequest &req);
void WebSocketHLEMsgPipeList(DebuggerRequest &req);
void WebSocketHLEMsgPipeInfo(DebuggerRequest &req);
void WebSocketHLECallbackList(DebuggerRequest &req);
void WebSocketHLECallbackInfo(DebuggerRequest &req);
+2
View File
@@ -111,6 +111,7 @@
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUDisasmSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUStatsSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLEKernelObjectSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputBroadcaster.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputSubscriber.h" />
@@ -387,6 +388,7 @@
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUDisasmSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUStatsSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLEKernelObjectSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputBroadcaster.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputSubscriber.cpp" />
+2
View File
@@ -24,6 +24,7 @@
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUDisasmSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUStatsSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLEKernelObjectSubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\HLESubscriber.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputBroadcaster.cpp" />
<ClCompile Include="..\..\Core\Debugger\WebSocket\InputSubscriber.cpp" />
@@ -443,6 +444,7 @@
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUDisasmSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPURecordSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUStatsSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLEKernelObjectSubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\HLESubscriber.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputBroadcaster.h" />
<ClInclude Include="..\..\Core\Debugger\WebSocket\InputSubscriber.h" />
+1
View File
@@ -667,6 +667,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/Debugger/WebSocket/GPUDisasmSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/GPURecordSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/HLEKernelObjectSubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \
$(SRC)/Core/Debugger/WebSocket/InputBroadcaster.cpp \
$(SRC)/Core/Debugger/WebSocket/InputSubscriber.cpp \
+1
View File
@@ -117,6 +117,7 @@ file - this is just an index.
| GE display list disassembly | `gpu.displaylist.disasm` - like `memory.disasm` but for GE command words (`CLEARMODE`, `PRIM`, etc.) instead of CPU instructions; also supports `compact=true` | `GPUDisasmSubscriber.cpp` |
| HLE | `hle.thread.list/wake/stop`, `hle.func.list/add/remove/removeRange/rename/scan`, `hle.module.list`, `hle.module.saveSymbols/loadSymbols` (save/load one module's symbols to/from its standard `PSP/SYSTEM/SYMBOLS/<moduleName>_<crc>.ppsym` file, shared across any game that loads the same module - see `SymbolMap::GetModuleSymbolsPath`), `hle.backtrace` | `HLESubscriber.cpp` |
| Data symbols | `hle.data.list/add/remove/rename` - label discovered data (structs, tables, buffers) with a name/type, same idea as `hle.func.*` but for `ST_DATA` symbols | `HLESubscriber.cpp` |
| Kernel objects | `hle.object.list` (every live kernel object of every type at once, with an optional `type` filter - uid/type/name/one-line summary only); `hle.eventflag.list/info`, `hle.mutex.list/info`, `hle.semaphore.list/info`, `hle.msgpipe.list/info`, `hle.callback.list/info` (per-type full detail, including waiting-thread lists) - all read-only, never mutate kernel state | `HLEKernelObjectSubscriber.cpp` |
| GPU stats | `gpu.stats.get`, `gpu.stats.feed` | `GPUStatsSubscriber.cpp` |
| GPU recording | `gpu.record.dump` | `GPURecordSubscriber.cpp` |
| GPU buffers | `gpu.buffer.screenshot`, `gpu.buffer.renderColor/renderDepth/renderStencil`, `gpu.buffer.texture`, `gpu.buffer.clut` | `GPUBufferSubscriber.cpp` |