ppsspp/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
2025-03-30 14:02:29 +02:00

419 lines
12 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. 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()) {
Core_Break(BreakReason::DebugStep, 0);
}
}
// Stop stepping and resume the CPU (cpu.resume)
//
// No parameters.
//
// No immediate response. Once CPU is stepping, 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");
}
g_breakpoints.SetSkipFirst(currentMIPS->pc);
if (currentMIPS->inDelaySlot) {
Core_RequestCPUStep(CPUStepType::Into, 1);
}
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.
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() : 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) {
auto 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) {
auto cpuDebug = CPUFromRequest(req);
if (!cpuDebug)
return;
int cat, reg;
uint32_t val;
switch (ValidateCatReg(req, &cat, &reg)) {
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)");
}
auto 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, &reg)) {
case DebuggerRegType::NORMAL:
if (cat == 0 && reg == 0 && val != 0) {
return req.Fail("Cannot change reg zero");
}
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: value in register.
// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".
void WebSocketCPUEvaluate(DebuggerRequest &req) {
if (!currentDebugMIPS->isAlive()) {
return req.Fail("CPU not started");
}
auto 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)) {
return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));
}
if (!parseExpression(cpuDebug, postfix, val)) {
return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));
}
JsonWriter &json = req.Respond();
json.writeUint("uintValue", val);
json.writeString("floatValue", RegValueAsFloat(val));
}