Path: blob/master/Core/Debugger/WebSocket/BreakpointSubscriber.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/Debugger/Breakpoints.h"19#include "Core/Debugger/DisassemblyManager.h"20#include "Core/Debugger/SymbolMap.h"21#include "Core/Debugger/WebSocket/BreakpointSubscriber.h"22#include "Core/Debugger/WebSocket/WebSocketUtils.h"23#include "Core/MIPS/MIPSDebugInterface.h"2425DebuggerSubscriber *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) {26// No need to bind or alloc state, these are all global.27map["cpu.breakpoint.add"] = &WebSocketCPUBreakpointAdd;28map["cpu.breakpoint.update"] = &WebSocketCPUBreakpointUpdate;29map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove;30map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList;3132map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd;33map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate;34map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove;35map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList;3637return nullptr;38}3940struct WebSocketCPUBreakpointParams {41uint32_t address = 0;42bool hasEnabled = false;43bool hasLog = false;44bool hasCondition = false;45bool hasLogFormat = false;4647bool enabled;48bool log;49std::string condition;50PostfixExpression compiledCondition;51std::string logFormat;5253bool Parse(DebuggerRequest &req) {54if (!currentDebugMIPS->isAlive()) {55req.Fail("CPU not started");56return false;57}5859if (!req.ParamU32("address", &address))60return false;6162hasEnabled = req.HasParam("enabled");63if (hasEnabled) {64if (!req.ParamBool("enabled", &enabled))65return false;66}67hasLog = req.HasParam("log");68if (hasLog) {69if (!req.ParamBool("log", &log))70return false;71}72hasCondition = req.HasParam("condition");73if (hasCondition) {74if (!req.ParamString("condition", &condition))75return false;76if (!initExpression(currentDebugMIPS, condition.c_str(), compiledCondition)) {77req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));78return false;79}80}81hasLogFormat = req.HasParam("logFormat");82if (hasLogFormat) {83if (!req.ParamString("logFormat", &logFormat))84return false;85}8687return true;88}8990void Apply() {91if (hasCondition && !condition.empty()) {92BreakPointCond cond;93cond.debug = currentDebugMIPS;94cond.expressionString = condition;95cond.expression = compiledCondition;96g_breakpoints.ChangeBreakPointAddCond(address, cond);97} else if (hasCondition && condition.empty()) {98g_breakpoints.ChangeBreakPointRemoveCond(address);99}100101if (hasLogFormat) {102g_breakpoints.ChangeBreakPointLogFormat(address, logFormat);103}104105// TODO: Fix this interface.106if (hasLog && !hasEnabled) {107g_breakpoints.IsAddressBreakPoint(address, &enabled);108hasEnabled = true;109}110if (hasLog && hasEnabled) {111BreakAction result = BREAK_ACTION_IGNORE;112if (log)113result |= BREAK_ACTION_LOG;114if (enabled)115result |= BREAK_ACTION_PAUSE;116g_breakpoints.ChangeBreakPoint(address, result);117} else if (hasEnabled) {118g_breakpoints.ChangeBreakPoint(address, enabled);119}120}121};122123// Add a new CPU instruction breakpoint (cpu.breakpoint.add)124//125// Parameters:126// - address: unsigned integer address of instruction to break at.127// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.128// - log: optional boolean, whether to log when this breakpoint trips.129// - condition: optional string expression to evaluate - breakpoint does not trip if false.130// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.131//132// Response (same event name) with no extra data.133//134// Note: will replace any breakpoint at the same address.135void WebSocketCPUBreakpointAdd(DebuggerRequest &req) {136WebSocketCPUBreakpointParams params;137if (!params.Parse(req))138return;139140g_breakpoints.AddBreakPoint(params.address);141params.Apply();142req.Respond();143}144145// Update a CPU instruction breakpoint (cpu.breakpoint.update)146//147// Parameters:148// - address: unsigned integer address of instruction to break at.149// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.150// - log: optional boolean, whether to log when this breakpoint trips.151// - condition: optional string expression to evaluate - breakpoint does not trip if false.152// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.153//154// Response (same event name) with no extra data.155void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) {156WebSocketCPUBreakpointParams params;157if (!params.Parse(req))158return;159bool enabled;160if (!g_breakpoints.IsAddressBreakPoint(params.address, &enabled))161return req.Fail("Breakpoint not found");162163params.Apply();164req.Respond();165}166167// Remove a CPU instruction breakpoint (cpu.breakpoint.remove)168//169// Parameters:170// - address: unsigned integer address of instruction to break at.171//172// Response (same event name) with no extra data.173void WebSocketCPUBreakpointRemove(DebuggerRequest &req) {174if (!currentDebugMIPS->isAlive()) {175return req.Fail("CPU not started");176}177178uint32_t address;179if (!req.ParamU32("address", &address))180return;181182g_breakpoints.RemoveBreakPoint(address);183req.Respond();184}185186// List all CPU instruction breakpoints (cpu.breakpoint.list)187//188// No parameters.189//190// Response (same event name):191// - breakpoints: array of objects, each with properties:192// - address: unsigned integer address of instruction to break at.193// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.194// - log: optional boolean, whether to log when this breakpoint trips.195// - condition: null, or string expression to evaluate - breakpoint does not trip if false.196// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.197// - symbol: null, or string label or symbol at breakpoint address.198// - code: string disassembly of breakpoint address.199void WebSocketCPUBreakpointList(DebuggerRequest &req) {200if (!currentDebugMIPS->isAlive()) {201return req.Fail("CPU not started");202}203204JsonWriter &json = req.Respond();205json.pushArray("breakpoints");206auto bps = g_breakpoints.GetBreakpoints();207for (const auto &bp : bps) {208if (bp.temporary)209continue;210211json.pushDict();212json.writeUint("address", bp.addr);213json.writeBool("enabled", bp.IsEnabled());214json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0);215if (bp.hasCond)216json.writeString("condition", bp.cond.expressionString);217else218json.writeNull("condition");219if (!bp.logFormat.empty())220json.writeString("logFormat", bp.logFormat);221else222json.writeNull("logFormat");223std::string symbol = g_symbolMap->GetLabelString(bp.addr);224if (symbol.empty())225json.writeNull("symbol");226else227json.writeString("symbol", symbol);228229DisassemblyLineInfo line;230g_disassemblyManager.getLine(g_disassemblyManager.getStartAddress(bp.addr), true, line, currentDebugMIPS);231json.writeString("code", line.name + " " + line.params);232233json.pop();234}235json.pop();236}237238struct WebSocketMemoryBreakpointParams {239uint32_t address = 0;240uint32_t end = 0;241bool hasEnabled = false;242bool hasLog = false;243bool hasCond = false;244bool hasCondition = false;245bool hasLogFormat = false;246247bool enabled = true;248bool log = true;249MemCheckCondition cond = MEMCHECK_READWRITE;250std::string condition;251PostfixExpression compiledCondition;252std::string logFormat;253254bool Parse(DebuggerRequest &req) {255if (!currentDebugMIPS->isAlive()) {256req.Fail("CPU not started");257return false;258}259260if (!req.ParamU32("address", &address))261return false;262uint32_t size;263if (!req.ParamU32("size", &size))264return false;265if (address + size < address) {266req.Fail("Size is too large");267return false;268}269end = size == 0 ? 0 : address + size;270271hasEnabled = req.HasParam("enabled");272if (hasEnabled) {273if (!req.ParamBool("enabled", &enabled))274return false;275}276hasLog = req.HasParam("log");277if (hasLog) {278if (!req.ParamBool("log", &log))279return false;280}281hasCond = req.HasParam("read") || req.HasParam("write") || req.HasParam("change");282if (hasCond) {283bool read = false, write = false, change = false;284if (!req.ParamBool("read", &read, DebuggerParamType::OPTIONAL) || !req.ParamBool("write", &write, DebuggerParamType::OPTIONAL) || !req.ParamBool("change", &change, DebuggerParamType::OPTIONAL))285return false;286int bits = (read ? MEMCHECK_READ : 0) | (write ? MEMCHECK_WRITE : 0) | (change ? MEMCHECK_WRITE_ONCHANGE : 0);287cond = MemCheckCondition(bits);288}289hasCondition = req.HasParam("condition");290if (hasCondition) {291if (!req.ParamString("condition", &condition))292return false;293if (!initExpression(currentDebugMIPS, condition.c_str(), compiledCondition)) {294req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError()));295return false;296}297}298hasLogFormat = req.HasParam("logFormat");299if (hasLogFormat) {300if (!req.ParamString("logFormat", &logFormat))301return false;302}303304return true;305}306307BreakAction Result(bool adding) {308int bits = MEMCHECK_READWRITE;309if (adding || (hasLog && hasEnabled)) {310bits = (enabled ? BREAK_ACTION_PAUSE : 0) | (log ? BREAK_ACTION_LOG : 0);311} else {312MemCheck prev;313if (g_breakpoints.GetMemCheck(address, end, &prev))314bits = prev.result;315316if (hasEnabled)317bits = (bits & ~BREAK_ACTION_PAUSE) | (enabled ? BREAK_ACTION_PAUSE : 0);318if (hasLog)319bits = (bits & ~BREAK_ACTION_LOG) | (log ? BREAK_ACTION_LOG : 0);320}321322return BreakAction(bits);323}324325void Apply() {326if (hasCondition && !condition.empty()) {327BreakPointCond cond;328cond.debug = currentDebugMIPS;329cond.expressionString = condition;330cond.expression = compiledCondition;331g_breakpoints.ChangeMemCheckAddCond(address, end, cond);332} else if (hasCondition && condition.empty()) {333g_breakpoints.ChangeMemCheckRemoveCond(address, end);334}335if (hasLogFormat) {336g_breakpoints.ChangeMemCheckLogFormat(address, end, logFormat);337}338}339};340341// Add a new memory breakpoint (memory.breakpoint.add)342//343// Parameters:344// - address: unsigned integer address for the start of the memory range.345// - size: unsigned integer specifying size of memory range.346// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.347// - log: optional boolean, whether to log when this breakpoint trips.348// - read: optional boolean, whether to trip on any read to this address.349// - write: optional boolean, whether to trip on any write to this address.350// - change: optional boolean, whether to trip on a write to this address which modifies data351// (or any write that may modify data.)352// - condition: optional string expression to evaluate - breakpoint does not trip if false.353// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.354//355// Response (same event name) with no extra data.356//357// Note: will replace any breakpoint that has the same start address and size.358void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) {359WebSocketMemoryBreakpointParams params;360if (!params.Parse(req))361return;362363g_breakpoints.AddMemCheck(params.address, params.end, params.cond, params.Result(true));364params.Apply();365req.Respond();366}367368// Update a memory breakpoint (memory.breakpoint.update)369//370// Parameters:371// - address: unsigned integer address for the start of the memory range.372// - size: unsigned integer specifying size of memory range.373// - enabled: optional boolean, whether to actually enter stepping when this breakpoint trips.374// - log: optional boolean, whether to log when this breakpoint trips.375// - read: optional boolean, whether to trip on any read to this address.376// - write: optional boolean, whether to trip on any write to this address.377// - change: optional boolean, whether to trip on a write to this address which modifies data378// (or any write that may modify data.)379// - condition: optional string expression to evaluate - breakpoint does not trip if false.380// - logFormat: optional string to log when breakpoint trips, may include {expression} parts.381//382// Response (same event name) with no extra data.383void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) {384WebSocketMemoryBreakpointParams params;385if (!params.Parse(req))386return;387388MemCheck mc;389if (!g_breakpoints.GetMemCheck(params.address, params.end, &mc))390return req.Fail("Breakpoint not found");391392g_breakpoints.ChangeMemCheck(params.address, params.end, params.cond, params.Result(true));393params.Apply();394req.Respond();395}396397// Remove a memory breakpoint (memory.breakpoint.remove)398//399// Parameters:400// - address: unsigned integer address for the start of the memory range.401// - size: unsigned integer specifying size of memory range.402//403// Response (same event name) with no extra data.404void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) {405if (!currentDebugMIPS->isAlive()) {406return req.Fail("CPU not started");407}408409uint32_t address;410if (!req.ParamU32("address", &address))411return;412uint32_t size;413if (!req.ParamU32("size", &size))414return;415416g_breakpoints.RemoveMemCheck(address, size == 0 ? 0 : address + size);417req.Respond();418}419420// List all memory breakpoints (memory.breakpoint.list)421//422// No parameters.423//424// Response (same event name):425// - breakpoints: array of objects, each with properties:426// - address: unsigned integer address for the start of the memory range.427// - size: unsigned integer specifying size of memory range.428// - enabled: boolean, whether to actually enter stepping when this breakpoint trips.429// - log: optional boolean, whether to log when this breakpoint trips.430// - read: optional boolean, whether to trip on any read to this address.431// - write: optional boolean, whether to trip on any write to this address.432// - change: optional boolean, whether to trip on a write to this address which modifies data433// (or any write that may modify data.)434// - condition: null, or string expression to evaluate - breakpoint does not trip if false.435// - logFormat: null, or string to log when breakpoint trips, may include {expression} parts.436// - symbol: null, or string label or symbol at breakpoint address.437void WebSocketMemoryBreakpointList(DebuggerRequest &req) {438if (!currentDebugMIPS->isAlive()) {439return req.Fail("CPU not started");440}441442JsonWriter &json = req.Respond();443json.pushArray("breakpoints");444auto mcs = g_breakpoints.GetMemChecks();445for (const auto &mc : mcs) {446json.pushDict();447json.writeUint("address", mc.start);448json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start);449json.writeBool("enabled", mc.IsEnabled());450json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0);451json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0);452json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0);453json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0);454json.writeUint("hits", mc.numHits);455if (mc.hasCondition)456json.writeString("condition", mc.condition.expressionString);457else458json.writeNull("condition");459if (!mc.logFormat.empty())460json.writeString("logFormat", mc.logFormat);461else462json.writeNull("logFormat");463std::string symbol = g_symbolMap->GetLabelString(mc.start);464if (symbol.empty())465json.writeNull("symbol");466else467json.writeString("symbol", symbol);468469json.pop();470}471json.pop();472}473474475