mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
419 lines
12 KiB
C++
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, ®)) {
|
|
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, ®)) {
|
|
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));
|
|
}
|