CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp
Views: 1401
// 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/CoreTiming.h"20#include "Core/Debugger/Breakpoints.h"21#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h"22#include "Core/Debugger/WebSocket/WebSocketUtils.h"23#include "Core/HLE/sceKernelThread.h"24#include "Core/MIPS/MIPS.h"25#include "Core/MIPS/MIPSDebugInterface.h"26#include "Core/Reporting.h"2728DebuggerSubscriber *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) {29// No need to bind or alloc state, these are all global.30map["cpu.stepping"] = &WebSocketCPUStepping;31map["cpu.resume"] = &WebSocketCPUResume;32map["cpu.status"] = &WebSocketCPUStatus;33map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs;34map["cpu.getReg"] = &WebSocketCPUGetReg;35map["cpu.setReg"] = &WebSocketCPUSetReg;36map["cpu.evaluate"] = &WebSocketCPUEvaluate;3738return nullptr;39}4041static std::string RegValueAsFloat(uint32_t u) {42union {43uint32_t u;44float f;45} bits = { u };46return StringFromFormat("%f", bits.f);47}4849static DebugInterface *CPUFromRequest(DebuggerRequest &req) {50if (!req.HasParam("thread"))51return currentDebugMIPS;5253u32 uid;54if (!req.ParamU32("thread", &uid))55return nullptr;5657DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid);58if (!cpuDebug)59req.Fail("Thread could not be found");60return cpuDebug;61}6263// Begin stepping and pause the CPU (cpu.stepping)64//65// No parameters.66//67// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent.68void WebSocketCPUStepping(DebuggerRequest &req) {69if (!currentDebugMIPS->isAlive()) {70return req.Fail("CPU not started");71}72if (!Core_IsStepping() && Core_IsActive()) {73Core_EnableStepping(true, "cpu.stepping", 0);74}75}7677// Stop stepping and resume the CPU (cpu.resume)78//79// No parameters.80//81// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent.82void WebSocketCPUResume(DebuggerRequest &req) {83if (!currentDebugMIPS->isAlive()) {84return req.Fail("CPU not started");85}86if (!Core_IsStepping() || coreState == CORE_POWERDOWN) {87return req.Fail("CPU not stepping");88}8990CBreakPoints::SetSkipFirst(currentMIPS->pc);91if (currentMIPS->inDelaySlot) {92Core_DoSingleStep();93}94Core_EnableStepping(false);95}9697// Request the current CPU status (cpu.status)98//99// No parameters.100//101// Response (same event name):102// - stepping: boolean, CPU currently stepping.103// - paused: boolean, CPU paused or not started yet.104// - pc: number value of PC register (inaccurate unless stepping.)105// - ticks: number of CPU cycles into emulation.106void WebSocketCPUStatus(DebuggerRequest &req) {107JsonWriter &json = req.Respond();108json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN);109json.writeBool("paused", GetUIState() != UISTATE_INGAME);110// Avoid NULL deference.111json.writeUint("pc", PSP_IsInited() ? currentMIPS->pc : 0);112// A double ought to be good enough for a 156 day debug session.113json.writeFloat("ticks", PSP_IsInited() ? CoreTiming::GetTicks() : 0);114}115116// Retrieve all regs and their values (cpu.getAllRegs)117//118// Parameters:119// - thread: optional number indicating the thread id to get regs for.120//121// Response (same event name):122// - categories: array of objects:123// - id: "category" property to use for other events.124// - name: a category name, such as "GPR".125// - registerNames: array of string names of the registers (size varies per category.)126// - uintValues: array of unsigned integer values for the registers.127// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf".128void WebSocketCPUGetAllRegs(DebuggerRequest &req) {129auto cpuDebug = CPUFromRequest(req);130if (!cpuDebug)131return;132133JsonWriter &json = req.Respond();134135json.pushArray("categories");136for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) {137json.pushDict();138json.writeInt("id", c);139json.writeString("name", cpuDebug->GetCategoryName(c));140141int total = cpuDebug->GetNumRegsInCategory(c);142143json.pushArray("registerNames");144for (int r = 0; r < total; ++r)145json.writeString(cpuDebug->GetRegName(c, r));146if (c == 0) {147json.writeString("pc");148json.writeString("hi");149json.writeString("lo");150}151json.pop();152153json.pushArray("uintValues");154// Writing as floating point to avoid negatives. Actually double, so safe.155for (int r = 0; r < total; ++r)156json.writeUint(cpuDebug->GetRegValue(c, r));157if (c == 0) {158json.writeUint(cpuDebug->GetPC());159json.writeUint(cpuDebug->GetHi());160json.writeUint(cpuDebug->GetLo());161}162json.pop();163164json.pushArray("floatValues");165// Note: String so it can have Infinity and NaN.166for (int r = 0; r < total; ++r)167json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r)));168if (c == 0) {169json.writeString(RegValueAsFloat(cpuDebug->GetPC()));170json.writeString(RegValueAsFloat(cpuDebug->GetHi()));171json.writeString(RegValueAsFloat(cpuDebug->GetLo()));172}173json.pop();174175json.pop();176}177json.pop();178}179180enum class DebuggerRegType {181INVALID,182NORMAL,183PC,184HI,185LO,186};187188static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) {189if (name == "pc") {190*cat = 0;191*reg = 32;192return DebuggerRegType::PC;193}194if (name == "hi") {195*cat = 0;196*reg = 33;197return DebuggerRegType::HI;198}199if (name == "lo") {200*cat = 0;201*reg = 34;202return DebuggerRegType::LO;203}204205for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) {206int total = currentDebugMIPS->GetNumRegsInCategory(c);207for (int r = 0; r < total; ++r) {208if (name == currentDebugMIPS->GetRegName(c, r)) {209*cat = c;210*reg = r;211return DebuggerRegType::NORMAL;212}213}214}215216req.Fail("Invalid 'name' parameter");217return DebuggerRegType::INVALID;218}219220static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) {221const char *name = req.data.getStringOr("name", nullptr);222if (name)223return ValidateRegName(req, name, cat, reg);224225*cat = req.data.getInt("category", -1);226*reg = req.data.getInt("register", -1);227228if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) {229req.Fail("Invalid 'category' parameter");230return DebuggerRegType::INVALID;231}232233// TODO: We fake it for GPR... not sure yet if this is a good thing.234if (*cat == 0) {235// Intentionally retains the reg value.236if (*reg == 32)237return DebuggerRegType::PC;238if (*reg == 33)239return DebuggerRegType::HI;240if (*reg == 34)241return DebuggerRegType::LO;242}243244if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) {245req.Fail("Invalid 'register' parameter");246return DebuggerRegType::INVALID;247}248249return DebuggerRegType::NORMAL;250}251252// Retrieve the value of a single register (cpu.getReg)253//254// Parameters (by name):255// - thread: optional number indicating the thread id to get from.256// - name: string name of register to lookup.257//258// Parameters (by category id and index, ignored if name specified):259// - thread: optional number indicating the thread id to get from.260// - category: id of category for the register.261// - register: index into array of registers.262//263// Response (same event name):264// - category: id of category for the register.265// - register: index into array of registers.266// - uintValue: value in register.267// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".268void WebSocketCPUGetReg(DebuggerRequest &req) {269auto cpuDebug = CPUFromRequest(req);270if (!cpuDebug)271return;272273int cat, reg;274uint32_t val;275switch (ValidateCatReg(req, &cat, ®)) {276case DebuggerRegType::NORMAL:277val = cpuDebug->GetRegValue(cat, reg);278break;279280case DebuggerRegType::PC:281val = cpuDebug->GetPC();282break;283case DebuggerRegType::HI:284val = cpuDebug->GetHi();285break;286case DebuggerRegType::LO:287val = cpuDebug->GetLo();288break;289290case DebuggerRegType::INVALID:291// Error response already sent.292return;293}294295JsonWriter &json = req.Respond();296json.writeInt("category", cat);297json.writeInt("register", reg);298json.writeUint("uintValue", val);299json.writeString("floatValue", RegValueAsFloat(val));300}301302// Update the value of a single register (cpu.setReg)303//304// Parameters (by name):305// - thread: optional number indicating the thread id to update.306// - name: string name of register to lookup.307// - value: number (uint values only) or string to set to. Values may include308// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".309//310// Parameters (by category id and index, ignored if name specified):311// - thread: optional number indicating the thread id to update.312// - category: id of category for the register.313// - register: index into array of registers.314// - value: number (uint values only) or string to set to. Values may include315// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0".316//317// Response (same event name):318// - category: id of category for the register.319// - register: index into array of registers.320// - uintValue: new value in register.321// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".322//323// NOTE: Cannot be called unless the CPU is currently stepping.324void WebSocketCPUSetReg(DebuggerRequest &req) {325if (!currentDebugMIPS->isAlive()) {326return req.Fail("CPU not started");327}328if (!Core_IsStepping()) {329return req.Fail("CPU currently running (cpu.stepping first)");330}331332auto cpuDebug = CPUFromRequest(req);333if (!cpuDebug)334return;335336uint32_t val;337if (!req.ParamU32("value", &val, true)) {338// Already sent error.339return;340}341342int cat, reg;343switch (ValidateCatReg(req, &cat, ®)) {344case DebuggerRegType::NORMAL:345if (cat == 0 && reg == 0 && val != 0) {346return req.Fail("Cannot change reg zero");347}348cpuDebug->SetRegValue(cat, reg, val);349// In case part of it was ignored (e.g. flags reg.)350val = cpuDebug->GetRegValue(cat, reg);351break;352353case DebuggerRegType::PC:354cpuDebug->SetPC(val);355break;356case DebuggerRegType::HI:357cpuDebug->SetHi(val);358break;359case DebuggerRegType::LO:360cpuDebug->SetLo(val);361break;362363case DebuggerRegType::INVALID:364// Error response already sent.365return;366}367368Reporting::NotifyDebugger();369370JsonWriter &json = req.Respond();371// Repeat it back just to avoid confusion on how it parsed.372json.writeInt("category", cat);373json.writeInt("register", reg);374json.writeUint("uintValue", val);375json.writeString("floatValue", RegValueAsFloat(val));376}377378// Evaluate an expression (cpu.evaluate)379//380// Parameters:381// - thread: optional number indicating the thread id to update.382// - expression: string containing labels, operators, regs, etc.383//384// Response (same event name):385// - uintValue: value in register.386// - floatValue: string showing float representation. May be "nan", "inf", or "-inf".387void WebSocketCPUEvaluate(DebuggerRequest &req) {388if (!currentDebugMIPS->isAlive()) {389return req.Fail("CPU not started");390}391392auto cpuDebug = CPUFromRequest(req);393if (!cpuDebug)394return;395396std::string exp;397if (!req.ParamString("expression", &exp)) {398// Already sent error.399return;400}401402u32 val;403PostfixExpression postfix;404if (!cpuDebug->initExpression(exp.c_str(), postfix)) {405return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));406}407if (!cpuDebug->parseExpression(postfix, val)) {408return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError()));409}410411JsonWriter &json = req.Respond();412json.writeUint("uintValue", val);413json.writeString("floatValue", RegValueAsFloat(val));414}415416417