mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
Debugger: add gpu.displaylist.disasm - a GE display list decoder over the WebSocket API
Decoding a GE display list previously meant memory.read-ing the raw bytes
and hand-decoding each 32-bit command word against GPU/ge_constants.h's
GECommand enum - which is exactly what it took to find this session's
actual headline VSH boot finding (a display list that clears the screen
once, sets up per-icon render state 6 times, and never issues a single
further draw call - see docs/VSHBootInvestigation.md Attempt 22). That
manual process is real, repeatable, and error-prone by hand; PPSSPP
already has a proper GE disassembler (GPU/GeDisasm.cpp's
GeDisassembleOp(), and GPUCommon::DisassembleOpRange() built on top of
it) used by the ImGui/Windows GE debugger UI - it just wasn't reachable
from the WebSocket API.
New Core/Debugger/WebSocket/GPUDisasmSubscriber.cpp exposes
gpu->DisassembleOpRange() as gpu.displaylist.disasm, mirroring
memory.disasm's own parameter conventions (address+count or
address+end, capped at 10000 commands) and compact mode (one string per
command, "AAAAAAAA desc", instead of the full {address,cmd,op,desc}
object) added in the previous commit. GE command words live in normal
guest RAM like CPU code, so - unlike gpu.buffer.* - this doesn't require
the CPU/GPU to be paused first, matching memory.disasm's own live-read
behavior.
Added to all 6 build systems that compile the WebSocket debugger
(CMakeLists.txt, Core.vcxproj(.filters), UWP's CoreUWP.vcxproj(.filters),
android/jni/Android.mk - libretro doesn't build any Debugger/WebSocket
files at all, so nothing to add there).
Verified live via PPSSPPHeadless + wsdbg against a real demo ELF: both
compact and full-JSON modes correctly decode real GE command words (NOP/
NOP_FF) with no errors. UnitTest.exe all: 49/49 passed.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
This commit is contained in:
1 parent
a502b55fea
commit
2ee4f2fadb
9 files changed
+150
No files matched your search
@@ -307,6 +307,8 @@ add_library(Core STATIC
|
||||
Debugger/WebSocket/ClientConfigSubscriber.h
|
||||
Debugger/WebSocket/GPUBufferSubscriber.cpp
|
||||
Debugger/WebSocket/GPUBufferSubscriber.h
|
||||
Debugger/WebSocket/GPUDisasmSubscriber.cpp
|
||||
Debugger/WebSocket/GPUDisasmSubscriber.h
|
||||
Debugger/WebSocket/GPURecordSubscriber.cpp
|
||||
Debugger/WebSocket/GPURecordSubscriber.h
|
||||
Debugger/WebSocket/GPUStatsSubscriber.cpp
|
||||
|
||||
@@ -449,6 +449,7 @@
|
||||
<ClCompile Include="Debugger\WebSocket\GameBroadcaster.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\GameSubscriber.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\GPUDisasmSubscriber.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\GPURecordSubscriber.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\GPUStatsSubscriber.cpp" />
|
||||
<ClCompile Include="Debugger\WebSocket\HLESubscriber.cpp" />
|
||||
@@ -987,6 +988,7 @@
|
||||
<ClInclude Include="Debugger\WebSocket\GameSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\DisasmSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\GPUDisasmSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\GPURecordSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\GPUStatsSubscriber.h" />
|
||||
<ClInclude Include="Debugger\WebSocket\HLESubscriber.h" />
|
||||
|
||||
@@ -754,6 +754,9 @@
|
||||
<ClCompile Include="Debugger\WebSocket\GPUBufferSubscriber.cpp">
|
||||
<Filter>Debugger\WebSocket</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Debugger\WebSocket\GPUDisasmSubscriber.cpp">
|
||||
<Filter>Debugger\WebSocket</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="HLE\sceUsbAcc.cpp">
|
||||
<Filter>HLE\Libraries</Filter>
|
||||
</ClCompile>
|
||||
@@ -2025,6 +2028,9 @@
|
||||
<ClInclude Include="Debugger\WebSocket\GPUBufferSubscriber.h">
|
||||
<Filter>Debugger\WebSocket</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Debugger\WebSocket\GPUDisasmSubscriber.h">
|
||||
<Filter>Debugger\WebSocket</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ConfigValues.h">
|
||||
<Filter>Core</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "Core/Debugger/WebSocket/DisasmSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/GameSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/GPUBufferSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/GPUDisasmSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/GPURecordSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/GPUStatsSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/HLESubscriber.h"
|
||||
@@ -71,6 +72,7 @@ static const std::vector<SubscriberInit> subscribers({
|
||||
&WebSocketDisasmInit,
|
||||
&WebSocketGameInit,
|
||||
&WebSocketGPUBufferInit,
|
||||
&WebSocketGPUDisasmInit,
|
||||
&WebSocketGPURecordInit,
|
||||
&WebSocketGPUStatsInit,
|
||||
&WebSocketHLEInit,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
// 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/.
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Core/Debugger/WebSocket/GPUDisasmSubscriber.h"
|
||||
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
||||
#include "Core/MemMap.h"
|
||||
#include "GPU/GPU.h"
|
||||
#include "GPU/GPUCommon.h"
|
||||
#include "GPU/Common/GPUDebugInterface.h"
|
||||
|
||||
DebuggerSubscriber *WebSocketGPUDisasmInit(DebuggerEventHandlerMap &map) {
|
||||
// No need to bind or alloc state, this is all global (the "gpu" global and raw memory reads).
|
||||
map["gpu.displaylist.disasm"] = &WebSocketGPUDisplayListDisasm;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Disassemble a range of GE display list memory into named commands (gpu.displaylist.disasm)
|
||||
//
|
||||
// GE command words live in normal guest RAM (same as CPU code), so this decodes memory
|
||||
// directly rather than requiring the CPU/GPU to be paused first - unlike gpu.buffer.*, which
|
||||
// reads live host-side render target/texture data and does need CORE_STEPPING_CPU.
|
||||
//
|
||||
// Parameters (by count):
|
||||
// - address: number specifying the start address (a display list address, e.g. from
|
||||
// sceGe.cpp's "Starting DL execution at ..." log, or dlid via hle - not a CPU code address).
|
||||
// - count: number of GE command words to decode.
|
||||
// - compact: optional boolean, default false. See "lines" below.
|
||||
//
|
||||
// Parameters (by end address):
|
||||
// - address: number specifying the start address.
|
||||
// - end: number which must be after the start address (exclusive).
|
||||
// - compact: optional boolean, default false. See "lines" below.
|
||||
//
|
||||
// Response (same event name):
|
||||
// - lines: with compact=false (default), array of objects:
|
||||
// - address: address of this command word.
|
||||
// - cmd: unsigned integer, the command byte (op >> 24).
|
||||
// - op: unsigned integer, the raw 32-bit command word.
|
||||
// - desc: string description of the command (name and decoded parameters).
|
||||
// with compact=true, array of strings instead, one per command, formatted as
|
||||
// "AAAAAAAA desc" - meant for skimming a display list by eye instead of parsing full JSON,
|
||||
// same idea as memory.disasm's own compact mode.
|
||||
void WebSocketGPUDisplayListDisasm(DebuggerRequest &req) {
|
||||
if (!gpu) {
|
||||
return req.Fail("No GPU active (game not booted?)");
|
||||
}
|
||||
if (!Memory::IsActive()) {
|
||||
return req.Fail("Memory not active");
|
||||
}
|
||||
|
||||
// Mirrors memory.disasm's own limit - keeps a client typo (e.g. count=0xFFFFFFFF) from
|
||||
// blocking the debugger connection for an unreasonable amount of time.
|
||||
static const uint32_t MAX_RANGE = 10000;
|
||||
|
||||
uint32_t start;
|
||||
if (!req.ParamU32("address", &start))
|
||||
return;
|
||||
uint32_t end;
|
||||
uint32_t count = 0;
|
||||
if (req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL) && count != 0) {
|
||||
count = std::min(count, MAX_RANGE);
|
||||
end = start + count * 4;
|
||||
} else if (req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL)) {
|
||||
end = std::max(start, end);
|
||||
if (end - start > MAX_RANGE * 4)
|
||||
end = start + MAX_RANGE * 4;
|
||||
} else {
|
||||
return req.Fail("Must specify either 'count' or 'end'");
|
||||
}
|
||||
|
||||
bool compact = false;
|
||||
if (!req.ParamBool("compact", &compact, DebuggerParamType::OPTIONAL))
|
||||
return;
|
||||
|
||||
std::vector<GPUDebugOp> ops = gpu->DisassembleOpRange(start, end);
|
||||
|
||||
JsonWriter &json = req.Respond();
|
||||
json.pushArray("lines");
|
||||
for (const GPUDebugOp &op : ops) {
|
||||
if (compact) {
|
||||
json.writeString(StringFromFormat("%08x %s", op.pc, op.desc.c_str()));
|
||||
} else {
|
||||
json.pushDict();
|
||||
json.writeUint("address", op.pc);
|
||||
json.writeUint("cmd", op.cmd);
|
||||
json.writeUint("op", op.op);
|
||||
json.writeString("desc", op.desc);
|
||||
json.pop();
|
||||
}
|
||||
}
|
||||
json.pop();
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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 *WebSocketGPUDisasmInit(DebuggerEventHandlerMap &map);
|
||||
|
||||
void WebSocketGPUDisplayListDisasm(DebuggerRequest &req);
|
||||
@@ -108,6 +108,7 @@
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GameBroadcaster.h" />
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GameSubscriber.h" />
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.h" />
|
||||
<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\HLESubscriber.h" />
|
||||
@@ -382,6 +383,7 @@
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GameBroadcaster.cpp" />
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GameSubscriber.cpp" />
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.cpp" />
|
||||
<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\HLESubscriber.cpp" />
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GameBroadcaster.cpp" />
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GameSubscriber.cpp" />
|
||||
<ClCompile Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.cpp" />
|
||||
<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\HLESubscriber.cpp" />
|
||||
@@ -438,6 +439,7 @@
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GameBroadcaster.h" />
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GameSubscriber.h" />
|
||||
<ClInclude Include="..\..\Core\Debugger\WebSocket\GPUBufferSubscriber.h" />
|
||||
<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\HLESubscriber.h" />
|
||||
|
||||
@@ -664,6 +664,7 @@ EXEC_AND_LIB_FILES := \
|
||||
$(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/GPUBufferSubscriber.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/GPUDisasmSubscriber.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/GPURecordSubscriber.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/GPUStatsSubscriber.cpp \
|
||||
$(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \
|
||||
|
||||
Reference in new issue
Block a user