Path: blob/master/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
3187 views
// Copyright (c) 2018- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/StringUtils.h"18#include "Core/Core.h"19#include "Core/System.h"20#include "Core/CoreTiming.h"21#include "Core/Debugger/Breakpoints.h"22#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"23#include "Core/Debugger/WebSocket/WebSocketUtils.h"24#include "Core/HLE/sceKernelThread.h"25#include "Core/MIPS/MIPS.h"26#include "Core/MIPS/MIPSDebugInterface.h"27#include "Core/Reporting.h"2829DebuggerSubscriber *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) {30// No need to bind or alloc state, these are all global.31map["cpu.stepping"] = &WebSocketCPUStepping;32map["cpu.resume"] = &WebSocketCPUResume;33map["cpu.status"] = &WebSocketCPUStatus;34map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs;35map["cpu.getReg"] = &WebSocketCPUGetReg;36map["cpu.setReg"] = &WebSocketCPUSetReg;37map["cpu.evaluate"] = &WebSocketCPUEvaluate;3839return nullptr;40}4142static std::string RegValueAsFloat(uint32_t u) {43union {44uint32_t u;45float f;46} bits = { u };47return StringFromFormat("%f", bits.f);48}4950static DebugInterface *CPUFromRequest(DebuggerRequest &req) {51if (!req.HasParam("thread"))52return currentDebugMIPS;5354u32 uid;55if (!req.ParamU32("thread", &uid))56return nullptr;5758DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);59if (!cpuDebug)60req.Fail("Thread could not be found");61return cpuDebug;62}6364// Begin stepping and pause the CPU (cpu.stepping)65//66// No parameters.67//68// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent.69void WebSocketCPUStepping(DebuggerRequest &req) {70if (!currentDebugMIPS->isAlive()) {71return req.Fail("CPU not started");72}73if (!Core_IsStepping() && Core_IsActive()) {74Core_Break(BreakReason::DebugStep, 0);75}76}7778// Stop stepping and resume the CPU (cpu.resume)79//80// No parameters.81//82// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent.83void WebSocketCPUResume(DebuggerRequest &req) {84if (!currentDebugMIPS->isAlive()) {85return req.Fail("CPU not started");86}87if (!Core_IsStepping() || coreState == CORE_POWERDOWN) {88return req.Fail("CPU not stepping");89}9091g_breakpoints.SetSkipFirst(currentMIPS->pc);92if (currentMIPS->inDelaySlot) {93Core_RequestCPUStep(CPUStepType::Into, 1);94}95Core_Resume();96}9798// Request the current CPU status (cpu.status)99//100// No parameters.101//102// Response (same event name):103// - stepping: boolean, CPU currently stepping.104// - paused: boolean, CPU paused or not started yet.105// - pc: number value of PC register (inaccurate unless stepping.)106// - ticks: number of CPU cycles into emulation.107void WebSocketCPUStatus(DebuggerRequest &req) {108JsonWriter &json = req.Respond();109110const bool pspInited = PSP_GetBootState() == BootState::Complete;111112json.writeBool("stepping", pspInited && Core_IsStepping() && coreState != CORE_POWERDOWN);113json.writeBool("paused", GetUIState() != UISTATE_INGAME);114// Avoid NULL deference.115json.writeUint("pc", pspInited ? currentMIPS->pc : 0);116// A double ought to be good enough for a 156 day debug session.117json.writeFloat("ticks", pspInited ? CoreTiming::GetTicks() : 0);118}119120// Retrieve all regs and their values (cpu.getAllRegs)121//122// Parameters:123// - thread: optional number indicating the thread id to get regs for.124//125// Response (same event name):126// - categories: array of objects:127// - id: "category" property to use for other events.128// - name: a category name, such as "GPR".129// - registerNames: array of string names of the registers (size varies per category.)130// - uintValues: array of unsigned integer values for the registers.131// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf".132void WebSocketCPUGetAllRegs(DebuggerRequest &req) {133auto cpuDebug = CPUFromRequest(req);134if (!cpuDebug)135return;136137JsonWriter &json = req.Respond();138139json.pushArray("categories");140for (int c = 0; c < MIPSDebugInterface::GetNumCategories(); ++c) {141json.pushDict();142json.writeInt("id", c);143json.writeString("name", MIPSDebugInterface::GetCategoryName(c));144145int total = MIPSDebugInterface::GetNumRegsInCategory(c);146147json.pushArray("registerNames");148for (int r = 0; r < total; ++r)149json.writeString(MIPSDebugInterface::GetRegName(c, r));150if (c == 0) {151json.writeString("pc");152json.writeString("hi");153json.writeString("lo");154}155json.pop();156157json.pushArray("uintValues");158// Writing as floating point to avoid negatives. Actually double, so safe.159for (int r = 0; r < total; ++r)160json.writeUint(cpuDebug->GetRegValue(c, r));161if (c == 0) {162json.writeUint(cpuDebug->GetPC());163json.writeUint(cpuDebug->GetHi());164json.writeUint(cpuDebug->GetLo());165}166json.pop();167168json.pushArray("floatValues");169// Note: String so it can have Infinity and NaN.170for (int r = 0; r < total; ++r)171json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r)));172if (c == 0) {173json.writeString(RegValueAsFloat(cpuDebug->GetPC()));174json.writeString(RegValueAsFloat(cpuDebug->GetHi()));175json.writeString(RegValueAsFloat(cpuDebug->GetLo()));176}177json.pop();178179json.pop();180}181json.pop();182}183184enum class DebuggerRegType {185INVALID,186NORMAL,187PC,188HI,189LO,190};191192static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {193if (name == "pc") {194*cat = 0;195*reg = 32;196return DebuggerRegType::PC;197}198if (name == "hi") {199*cat = 0;200*reg = 33;201return DebuggerRegType::HI;202}203if (name == "lo") {204*cat = 0;205*reg = 34;206return DebuggerRegType::LO;207}208209for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {210int total = currentDebugMIPS->GetNumRegsInCategory(c);211for (int r = 0; r < total; ++r) {212if (name == currentDebugMIPS->GetRegName(c, r)) {213*cat = c;214*reg = r;215return DebuggerRegType::NORMAL;216}217}218}219220req.Fail("Invalid 'name' parameter");221return DebuggerRegType::INVALID;222}223224static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {225const char *name = req.data.getStringOr("name", nullptr);226if (name)227return ValidateRegName(req, name, cat, reg);228229*cat = req.data.getInt("category", -1);230*reg = req.data.getInt("register", -1);231232if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {233req.Fail("Invalid 'category' parameter");234return DebuggerRegType::INVALID;235}236237// TODO: We fake it for GPR... not sure yet if this is a good thing.238if (*cat == 0) {239// Intentionally retains the reg value.240if (*reg == 32)241return DebuggerRegType::PC;242if (*reg == 33)243return DebuggerRegType::HI;244if (*reg == 34)245return DebuggerRegType::LO;246}247248if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {249req.Fail("Invalid 'register' parameter");250return DebuggerRegType::INVALID;251}252253return DebuggerRegType::NORMAL;254}255256// Retrieve the value of a single register (cpu.getReg)257//258// Parameters (by name):259// - thread: optional number indicating the thread id to get from.260// - name: string name of register to lookup.261//262// Parameters (by category id and index, ignored if name specified):263// - thread: optional number indicating the thread id to get from.264// - category: id of category for the register.265// - register: index into array of registers.266//267// Response (same event name):268// - category: id of category for the register.269// - register: index into array of registers.270// - uintValue: value in register.271// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".272void WebSocketCPUGetReg(DebuggerRequest &req) {273auto cpuDebug = CPUFromRequest(req);274if (!cpuDebug)275return;276277int cat, reg;278uint32_t val;279switch (ValidateCatReg(req, &cat, ®)) {280case DebuggerRegType::NORMAL:281val = cpuDebug->GetRegValue(cat, reg);282break;283284case DebuggerRegType::PC:285val = cpuDebug->GetPC();286break;287case DebuggerRegType::HI:288val = cpuDebug->GetHi();289break;290case DebuggerRegType::LO:291val = cpuDebug->GetLo();292break;293294case DebuggerRegType::INVALID:295// Error response already sent.296return;297}298299JsonWriter &json = req.Respond();300json.writeInt("category", cat);301json.writeInt("register", reg);302json.writeUint("uintValue", val);303json.writeString("floatValue", RegValueAsFloat(val));304}305306// Update the value of a single register (cpu.setReg)307//308// Parameters (by name):309// - thread: optional number indicating the thread id to update.310// - name: string name of register to lookup.311// - value: number (uint values only) or string to set to. Values may include312// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".313//314// Parameters (by category id and index, ignored if name specified):315// - thread: optional number indicating the thread id to update.316// - category: id of category for the register.317// - register: index into array of registers.318// - value: number (uint values only) or string to set to. Values may include319// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".320//321// Response (same event name):322// - category: id of category for the register.323// - register: index into array of registers.324// - uintValue: new value in register.325// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".326//327// NOTE: Cannot be called unless the CPU is currently stepping.328void WebSocketCPUSetReg(DebuggerRequest &req) {329if (!currentDebugMIPS->isAlive()) {330return req.Fail("CPU not started");331}332if (!Core_IsStepping()) {333return req.Fail("CPU currently running (cpu.stepping first)");334}335336auto cpuDebug = CPUFromRequest(req);337if (!cpuDebug)338return;339340uint32_t val;341if (!req.ParamU32("value", &val, true)) {342// Already sent error.343return;344}345346int cat, reg;347switch (ValidateCatReg(req, &cat, ®)) {348case DebuggerRegType::NORMAL:349if (cat == 0 && reg == 0 && val != 0) {350return req.Fail("Cannot change reg zero");351}352cpuDebug->SetRegValue(cat, reg, val);353// In case part of it was ignored (e.g. flags reg.)354val = cpuDebug->GetRegValue(cat, reg);355break;356357case DebuggerRegType::PC:358cpuDebug->SetPC(val);359break;360case DebuggerRegType::HI:361cpuDebug->SetHi(val);362break;363case DebuggerRegType::LO:364cpuDebug->SetLo(val);365break;366367case DebuggerRegType::INVALID:368// Error response already sent.369return;370}371372Reporting::NotifyDebugger();373374JsonWriter &json = req.Respond();375// Repeat it back just to avoid confusion on how it parsed.376json.writeInt("category", cat);377json.writeInt("register", reg);378json.writeUint("uintValue", val);379json.writeString("floatValue", RegValueAsFloat(val));380}381382// Evaluate an expression (cpu.evaluate)383//384// Parameters:385// - thread: optional number indicating the thread id to update.386// - expression: string containing labels, operators, regs, etc.387//388// Response (same event name):389// - uintValue: value in register.390// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".391void WebSocketCPUEvaluate(DebuggerRequest &req) {392if (!currentDebugMIPS->isAlive()) {393return req.Fail("CPU not started");394}395396auto cpuDebug = CPUFromRequest(req);397if (!cpuDebug)398return;399400std::string exp;401if (!req.ParamString("expression", &exp)) {402// Already sent error.403return;404}405406u32 val;407PostfixExpression postfix;408if (!initExpression(cpuDebug, exp.c_str(), postfix)) {409return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));410}411if (!parseExpression(cpuDebug, postfix, val)) {412return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));413}414415JsonWriter &json = req.Respond();416json.writeUint("uintValue", val);417json.writeString("floatValue", RegValueAsFloat(val));418}419420421