mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-16 07:50:28 +02:00
The core and the headless tools no longer use mutexes, condition variables, spinlocks, std::atomic, std::thread or volatile flags. Threads hand off through libretro-common's retro_atomic and rthreads plus a few small pieces on top of them in Common/Thread: Thread, MpscQueue, EventCounter, ParkingLot waits and ReaderGate. Every wait blocks until it's woken; nothing polls on a timer. State that only one thread touches just loses its lock. The PSP filesystem, sceIo and the savedata dialog now belong to the emulation thread: async reads and writes run when they're queued and complete later in emulated time, and the IO and savedata threads are gone. The friend finder only talks to the adhoc server and hands what it gets to the emulation thread. GPU init always runs on the thread that starts the game. The Vulkan render and compile queues, pipeline and render pass caches, delete list and SPIR-V cache, the GL render queue, the software renderer's jit caches, the file loader caches, the web debugger, UPnP, input, savestates, logging and the GE debugger all hand off through queues and atomics. The IR jit's spinning profiler thread goes. A CPU thread with nothing to run now sleeps until work is queued instead of spinning. Headless with --debugger waits for a client while stopped, and ends the run once every client that connected has left. Web server shutdown keeps servicing the CPU queue until the server has finished.
461 lines
15 KiB
C++
461 lines
15 KiB
C++
// Copyright (c) 2018- 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 "Common/StringUtils.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/System.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"
|
|
#include "Core/Debugger/WebSocket/WebSocketUtils.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/MIPS/MIPS.h"
|
|
#include "Core/MIPS/MIPSDebugInterface.h"
|
|
#include "Core/Reporting.h"
|
|
|
|
DebuggerSubscriber *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) {
|
|
// No need to bind or alloc state, these are all global.
|
|
map["cpu.stepping"] = &WebSocketCPUStepping;
|
|
map["cpu.resume"] = &WebSocketCPUResume;
|
|
map["cpu.status"] = &WebSocketCPUStatus;
|
|
map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs;
|
|
map["cpu.getReg"] = &WebSocketCPUGetReg;
|
|
map["cpu.setReg"] = &WebSocketCPUSetReg;
|
|
map["cpu.evaluate"] = &WebSocketCPUEvaluate;
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
static std::string RegValueAsFloat(uint32_t u) {
|
|
union {
|
|
uint32_t u;
|
|
float f;
|
|
} bits = { u };
|
|
return StringFromFormat("%f", bits.f);
|
|
}
|
|
|
|
static DebugInterface *CPUFromRequest(DebuggerRequest &req) {
|
|
if (!req.HasParam("thread"))
|
|
return currentDebugMIPS;
|
|
|
|
u32 uid;
|
|
if (!req.ParamU32("thread", &uid))
|
|
return nullptr;
|
|
|
|
DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);
|
|
if (!cpuDebug)
|
|
req.Fail("Thread could not be found");
|
|
return cpuDebug;
|
|
}
|
|
|
|
// Begin stepping and pause the CPU (cpu.stepping)
|
|
//
|
|
// No parameters.
|
|
//
|
|
// No immediate response (only a "deferred" event, if the client asked for those via
|
|
// client.config.set). Once CPU is stepping, a "cpu.stepping" event will be sent.
|
|
void WebSocketCPUStepping(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive()) {
|
|
return req.Fail("CPU not started");
|
|
}
|
|
if (!Core_IsStepping() && Core_IsActive()) {
|
|
// The CPU thread drains its queue at least once a frame while running, too.
|
|
Core_RunOnCPUThread([] {
|
|
if (!Core_IsStepping() && Core_IsActive()) {
|
|
Core_Break(BreakReason::DebugStep, 0);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Stop stepping and resume the CPU (cpu.resume)
|
|
//
|
|
// No parameters.
|
|
//
|
|
// No immediate response (only a "deferred" event, if the client asked for those via
|
|
// client.config.set). Once CPU is running again, a "cpu.resume" event will be sent.
|
|
void WebSocketCPUResume(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive()) {
|
|
return req.Fail("CPU not started");
|
|
}
|
|
if (!Core_IsStepping() || coreState == CORE_POWERDOWN) {
|
|
return req.Fail("CPU not stepping");
|
|
}
|
|
|
|
// Route the actual breakpoint/stepping manipulation to the CPU thread instead of poking at it directly
|
|
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
|
Core_RunOnCPUThread([&] {
|
|
g_breakpoints.SetSkipFirst(currentMIPS->pc);
|
|
if (currentMIPS->inDelaySlot) {
|
|
Core_RequestCPUStep(CPUStepType::Into);
|
|
}
|
|
Core_Resume();
|
|
});
|
|
}
|
|
|
|
// Request the current CPU status (cpu.status)
|
|
//
|
|
// No parameters.
|
|
//
|
|
// Response (same event name):
|
|
// - stepping: boolean, CPU currently stepping.
|
|
// - paused: boolean, CPU paused or not started yet.
|
|
// - pc: number value of PC register (inaccurate unless stepping.)
|
|
// - ticks: number of CPU cycles into emulation.
|
|
// - us: microseconds of emulated time. Not derivable from ticks on the client side: the PSP's
|
|
// clock frequency is changeable (scePowerSetCpuClockFrequency), and games do change it, so the
|
|
// ticks-per-second ratio isn't fixed over a run. This is what to use to answer "how far into
|
|
// the game am I", e.g. to line up input injection with a wall-clock repro.
|
|
// - clockHz: the CPU clock frequency right now, for reference.
|
|
// Deliberately not routed through Core_RunOnCPUThread(): this is meant to be a cheap, frequently-pollable
|
|
// status check, and the "pc" field is already documented as inaccurate unless stepping - queuing it would
|
|
// add up to a frame of latency to every poll for no real accuracy benefit. Matches how SteppingBroadcaster
|
|
// already reads this same state directly from the WebSocket thread.
|
|
void WebSocketCPUStatus(DebuggerRequest &req) {
|
|
JsonWriter &json = req.Respond();
|
|
|
|
const bool pspInited = PSP_GetBootState() == BootState::Complete;
|
|
|
|
json.writeBool("stepping", pspInited && Core_IsStepping() && coreState != CORE_POWERDOWN);
|
|
json.writeBool("paused", GetUIState() != UISTATE_INGAME);
|
|
// Avoid NULL deference.
|
|
json.writeUint("pc", pspInited ? currentMIPS->pc : 0);
|
|
// A double ought to be good enough for a 156 day debug session.
|
|
json.writeFloat("ticks", pspInited ? CoreTiming::GetTicks(currentMIPS) : 0);
|
|
json.writeFloat("us", pspInited ? (double)CoreTiming::PeekGlobalTimeUs() : 0);
|
|
json.writeUint("clockHz", pspInited ? CoreTiming::GetClockFrequencyHz() : 0);
|
|
}
|
|
|
|
// Retrieve all regs and their values (cpu.getAllRegs)
|
|
//
|
|
// Parameters:
|
|
// - thread: optional number indicating the thread id to get regs for.
|
|
//
|
|
// Response (same event name):
|
|
// - categories: array of objects:
|
|
// - id: "category" property to use for other events.
|
|
// - name: a category name, such as "GPR".
|
|
// - registerNames: array of string names of the registers (size varies per category.)
|
|
// - uintValues: array of unsigned integer values for the registers.
|
|
// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf".
|
|
void WebSocketCPUGetAllRegs(DebuggerRequest &req) {
|
|
// Route the actual register reads to the CPU thread instead of poking at them directly
|
|
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
|
Core_RunOnCPUThread([&] {
|
|
DebugInterface *cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
JsonWriter &json = req.Respond();
|
|
|
|
json.pushArray("categories");
|
|
for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) {
|
|
json.pushDict();
|
|
json.writeInt("id", c);
|
|
json.writeString("name", MIPSDebugInterface::GetCategoryName(c));
|
|
|
|
int total = MIPSDebugInterface::GetNumRegsInCategory(c);
|
|
|
|
json.pushArray("registerNames");
|
|
for (int r = 0; r < total; ++r)
|
|
json.writeString(MIPSDebugInterface::GetRegName(c, r));
|
|
if (c == 0) {
|
|
json.writeString("pc");
|
|
json.writeString("hi");
|
|
json.writeString("lo");
|
|
}
|
|
json.pop();
|
|
|
|
json.pushArray("uintValues");
|
|
// Writing as floating point to avoid negatives. Actually double, so safe.
|
|
for (int r = 0; r < total; ++r)
|
|
json.writeUint(cpuDebug->GetRegValue(c, r));
|
|
if (c == 0) {
|
|
json.writeUint(cpuDebug->GetPC());
|
|
json.writeUint(cpuDebug->GetHi());
|
|
json.writeUint(cpuDebug->GetLo());
|
|
}
|
|
json.pop();
|
|
|
|
json.pushArray("floatValues");
|
|
// Note: String so it can have Infinity and NaN.
|
|
for (int r = 0; r < total; ++r)
|
|
json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r)));
|
|
if (c == 0) {
|
|
json.writeString(RegValueAsFloat(cpuDebug->GetPC()));
|
|
json.writeString(RegValueAsFloat(cpuDebug->GetHi()));
|
|
json.writeString(RegValueAsFloat(cpuDebug->GetLo()));
|
|
}
|
|
json.pop();
|
|
|
|
json.pop();
|
|
}
|
|
json.pop();
|
|
});
|
|
}
|
|
|
|
enum class DebuggerRegType {
|
|
INVALID,
|
|
NORMAL,
|
|
PC,
|
|
HI,
|
|
LO,
|
|
};
|
|
|
|
static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {
|
|
if (name == "pc") {
|
|
*cat = 0;
|
|
*reg = 32;
|
|
return DebuggerRegType::PC;
|
|
}
|
|
if (name == "hi") {
|
|
*cat = 0;
|
|
*reg = 33;
|
|
return DebuggerRegType::HI;
|
|
}
|
|
if (name == "lo") {
|
|
*cat = 0;
|
|
*reg = 34;
|
|
return DebuggerRegType::LO;
|
|
}
|
|
|
|
for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {
|
|
int total = currentDebugMIPS->GetNumRegsInCategory(c);
|
|
for (int r = 0; r < total; ++r) {
|
|
if (name == currentDebugMIPS->GetRegName(c, r)) {
|
|
*cat = c;
|
|
*reg = r;
|
|
return DebuggerRegType::NORMAL;
|
|
}
|
|
}
|
|
}
|
|
|
|
req.Fail("Invalid 'name' parameter");
|
|
return DebuggerRegType::INVALID;
|
|
}
|
|
|
|
static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {
|
|
const char *name = req.data.getStringOr("name", nullptr);
|
|
if (name)
|
|
return ValidateRegName(req, name, cat, reg);
|
|
|
|
*cat = req.data.getInt("category", -1);
|
|
*reg = req.data.getInt("register", -1);
|
|
|
|
if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {
|
|
req.Fail("Invalid 'category' parameter");
|
|
return DebuggerRegType::INVALID;
|
|
}
|
|
|
|
// TODO: We fake it for GPR... not sure yet if this is a good thing.
|
|
if (*cat == 0) {
|
|
// Intentionally retains the reg value.
|
|
if (*reg == 32)
|
|
return DebuggerRegType::PC;
|
|
if (*reg == 33)
|
|
return DebuggerRegType::HI;
|
|
if (*reg == 34)
|
|
return DebuggerRegType::LO;
|
|
}
|
|
|
|
if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {
|
|
req.Fail("Invalid 'register' parameter");
|
|
return DebuggerRegType::INVALID;
|
|
}
|
|
|
|
return DebuggerRegType::NORMAL;
|
|
}
|
|
|
|
// Retrieve the value of a single register (cpu.getReg)
|
|
//
|
|
// Parameters (by name):
|
|
// - thread: optional number indicating the thread id to get from.
|
|
// - name: string name of register to lookup.
|
|
//
|
|
// Parameters (by category id and index, ignored if name specified):
|
|
// - thread: optional number indicating the thread id to get from.
|
|
// - category: id of category for the register.
|
|
// - register: index into array of registers.
|
|
//
|
|
// Response (same event name):
|
|
// - category: id of category for the register.
|
|
// - register: index into array of registers.
|
|
// - uintValue: value in register.
|
|
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
|
|
void WebSocketCPUGetReg(DebuggerRequest &req) {
|
|
// Route the actual register read to the CPU thread instead of poking at it directly
|
|
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
|
Core_RunOnCPUThread([&] {
|
|
DebugInterface *cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
int cat, reg;
|
|
uint32_t val = 0;
|
|
switch (ValidateCatReg(req, &cat, ®)) {
|
|
case DebuggerRegType::NORMAL:
|
|
val = cpuDebug->GetRegValue(cat, reg);
|
|
break;
|
|
|
|
case DebuggerRegType::PC:
|
|
val = cpuDebug->GetPC();
|
|
break;
|
|
case DebuggerRegType::HI:
|
|
val = cpuDebug->GetHi();
|
|
break;
|
|
case DebuggerRegType::LO:
|
|
val = cpuDebug->GetLo();
|
|
break;
|
|
|
|
case DebuggerRegType::INVALID:
|
|
// Error response already sent.
|
|
return;
|
|
}
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.writeInt("category", cat);
|
|
json.writeInt("register", reg);
|
|
json.writeUint("uintValue", val);
|
|
json.writeString("floatValue", RegValueAsFloat(val));
|
|
});
|
|
}
|
|
|
|
// Update the value of a single register (cpu.setReg)
|
|
//
|
|
// Parameters (by name):
|
|
// - thread: optional number indicating the thread id to update.
|
|
// - name: string name of register to lookup.
|
|
// - value: number (uint values only) or string to set to. Values may include
|
|
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
|
|
//
|
|
// Parameters (by category id and index, ignored if name specified):
|
|
// - thread: optional number indicating the thread id to update.
|
|
// - category: id of category for the register.
|
|
// - register: index into array of registers.
|
|
// - value: number (uint values only) or string to set to. Values may include
|
|
// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".
|
|
//
|
|
// Response (same event name):
|
|
// - category: id of category for the register.
|
|
// - register: index into array of registers.
|
|
// - uintValue: new value in register.
|
|
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
|
|
//
|
|
// NOTE: Cannot be called unless the CPU is currently stepping.
|
|
void WebSocketCPUSetReg(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive()) {
|
|
return req.Fail("CPU not started");
|
|
}
|
|
if (!Core_IsStepping()) {
|
|
return req.Fail("CPU currently running (cpu.stepping first)");
|
|
}
|
|
|
|
// Route the actual register write to the CPU thread instead of poking at it directly
|
|
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
|
Core_RunOnCPUThread([&] {
|
|
DebugInterface *cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
uint32_t val;
|
|
if (!req.ParamU32("value", &val, true)) {
|
|
// Already sent error.
|
|
return;
|
|
}
|
|
|
|
int cat, reg;
|
|
switch (ValidateCatReg(req, &cat, ®)) {
|
|
case DebuggerRegType::NORMAL:
|
|
if (cat == 0 && reg == 0 && val != 0) {
|
|
req.Fail("Cannot change reg zero");
|
|
return;
|
|
}
|
|
cpuDebug->SetRegValue(cat, reg, val);
|
|
// In case part of it was ignored (e.g. flags reg.)
|
|
val = cpuDebug->GetRegValue(cat, reg);
|
|
break;
|
|
|
|
case DebuggerRegType::PC:
|
|
cpuDebug->SetPC(val);
|
|
break;
|
|
case DebuggerRegType::HI:
|
|
cpuDebug->SetHi(val);
|
|
break;
|
|
case DebuggerRegType::LO:
|
|
cpuDebug->SetLo(val);
|
|
break;
|
|
|
|
case DebuggerRegType::INVALID:
|
|
// Error response already sent.
|
|
return;
|
|
}
|
|
|
|
Reporting::NotifyDebugger();
|
|
|
|
JsonWriter &json = req.Respond();
|
|
// Repeat it back just to avoid confusion on how it parsed.
|
|
json.writeInt("category", cat);
|
|
json.writeInt("register", reg);
|
|
json.writeUint("uintValue", val);
|
|
json.writeString("floatValue", RegValueAsFloat(val));
|
|
});
|
|
}
|
|
|
|
// Evaluate an expression (cpu.evaluate)
|
|
//
|
|
// Parameters:
|
|
// - thread: optional number indicating the thread id to update.
|
|
// - expression: string containing labels, operators, regs, etc.
|
|
//
|
|
// Response (same event name):
|
|
// - uintValue: the computed value.
|
|
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
|
|
void WebSocketCPUEvaluate(DebuggerRequest &req) {
|
|
if (!currentDebugMIPS->isAlive()) {
|
|
return req.Fail("CPU not started");
|
|
}
|
|
|
|
// Route the actual register/symbol reads to the CPU thread instead of poking at them directly
|
|
// from this WebSocket handler thread - see Core_RunOnCPUThread() in Core.h.
|
|
Core_RunOnCPUThread([&] {
|
|
DebugInterface *cpuDebug = CPUFromRequest(req);
|
|
if (!cpuDebug)
|
|
return;
|
|
|
|
std::string exp;
|
|
if (!req.ParamString("expression", &exp)) {
|
|
// Already sent error.
|
|
return;
|
|
}
|
|
|
|
u32 val;
|
|
PostfixExpression postfix;
|
|
if (!initExpression(cpuDebug, exp.c_str(), postfix)) {
|
|
req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
|
|
return;
|
|
}
|
|
if (!parseExpression(cpuDebug, postfix, val)) {
|
|
req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));
|
|
return;
|
|
}
|
|
|
|
JsonWriter &json = req.Respond();
|
|
json.writeUint("uintValue", val);
|
|
json.writeString("floatValue", RegValueAsFloat(val));
|
|
});
|
|
}
|