diff --git a/CMakeLists.txt b/CMakeLists.txt index 5241cbdc45f5..f2ce244d4a9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1426,6 +1426,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/DisassemblyManager.h Core/Debugger/WebSocket.cpp Core/Debugger/WebSocket.h + Core/Debugger/WebSocket/BreakpointSubscriber.cpp + Core/Debugger/WebSocket/BreakpointSubscriber.h Core/Debugger/WebSocket/CPUCoreSubscriber.cpp Core/Debugger/WebSocket/CPUCoreSubscriber.h Core/Debugger/WebSocket/DisasmSubscriber.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index c06e5f1e7863..a9d703869fcb 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -185,6 +185,7 @@ + @@ -542,6 +543,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 85a13944f458..c75fa29dd88b 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -718,6 +718,7 @@ + @@ -1323,6 +1324,7 @@ + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index dc7d44c59b46..3ae93962a8ce 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -47,6 +47,7 @@ #include "Core/Debugger/WebSocket/LogBroadcaster.h" #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +#include "Core/Debugger/WebSocket/BreakpointSubscriber.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/GameSubscriber.h" @@ -60,6 +61,7 @@ struct SubscriberInfo { }; static const std::vector subscribers({ + { &WebSocketBreakpointInit, nullptr }, { &WebSocketCPUCoreInit, nullptr }, { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, { &WebSocketGameInit, nullptr }, diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp new file mode 100644 index 000000000000..d93bd340514c --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -0,0 +1,179 @@ +// 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/Debugger/Breakpoints.h" +#include "Core/Debugger/WebSocket/BreakpointSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["cpu.breakpoint.add"] = &WebSocketCPUBreakpointAdd; + map["cpu.breakpoint.update"] = &WebSocketCPUBreakpointUpdate; + map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove; + map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList; + + return nullptr; +} + +struct WebSocketCPUBreakpointParams { + uint32_t address = 0; + bool hasEnabled = false; + bool hasLog = false; + bool hasCondition = false; + bool hasLogFormat = false; + + bool enabled; + bool log; + std::string condition; + PostfixExpression compiledCondition; + std::string logFormat; + + bool Parse(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + req.Fail("CPU not started"); + return false; + } + + if (!req.ParamU32("address", &address)) + return false; + + hasEnabled = req.HasParam("enabled"); + if (hasEnabled) { + if (!req.ParamBool("enabled", &enabled)) + return false; + } + hasLog = req.HasParam("log"); + if (hasLog) { + if (!req.ParamBool("log", &log)) + return false; + } + hasCondition = req.HasParam("condition"); + if (hasCondition) { + if (!req.ParamString("condition", &condition)) + return false; + if (!currentDebugMIPS->initExpression(condition.c_str(), compiledCondition)) { + req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + return false; + } + } + hasLogFormat = req.HasParam("logFormat"); + if (hasLogFormat) { + if (!req.ParamString("logFormat", &logFormat)) + return false; + } + + return true; + } + + void Apply() { + if (hasCondition && !condition.empty()) { + BreakPointCond cond; + cond.debug = currentDebugMIPS; + cond.expressionString = condition; + cond.expression = compiledCondition; + CBreakPoints::ChangeBreakPointAddCond(address, cond); + } else if (hasCondition && condition.empty()) { + CBreakPoints::ChangeBreakPointRemoveCond(address); + } + + if (hasLogFormat) { + CBreakPoints::ChangeBreakPointLogFormat(address, logFormat); + } + + // TODO: Fix this interface. + if (hasLog && !hasEnabled) { + CBreakPoints::IsAddressBreakPoint(address, &enabled); + hasEnabled = true; + } + if (hasLog && hasEnabled) { + BreakAction result = BREAK_ACTION_IGNORE; + if (log) + result |= BREAK_ACTION_LOG; + if (enabled) + result |= BREAK_ACTION_PAUSE; + CBreakPoints::ChangeBreakPoint(address, result); + } else if (hasEnabled) { + CBreakPoints::ChangeBreakPoint(address, enabled); + } + } +}; + +void WebSocketCPUBreakpointAdd(DebuggerRequest &req) { + WebSocketCPUBreakpointParams params; + if (!params.Parse(req)) + return; + + CBreakPoints::AddBreakPoint(params.address); + params.Apply(); + req.Respond(); +} + +void WebSocketCPUBreakpointUpdate(DebuggerRequest &req) { + WebSocketCPUBreakpointParams params; + if (!params.Parse(req)) + return; + bool enabled; + if (!CBreakPoints::IsAddressBreakPoint(params.address, &enabled)) + return req.Fail("Breakpoint not found"); + + params.Apply(); + req.Respond(); +} + +void WebSocketCPUBreakpointRemove(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + + CBreakPoints::RemoveBreakPoint(address); + req.Respond(); +} + +void WebSocketCPUBreakpointList(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + JsonWriter &json = req.Respond(); + json.pushArray("breakpoints"); + auto bps = CBreakPoints::GetBreakpoints(); + for (const auto &bp : bps) { + if (bp.temporary) + continue; + + json.pushDict(); + json.writeUint("address", bp.addr); + json.writeBool("enabled", bp.IsEnabled()); + json.writeBool("log", (bp.result & BREAK_ACTION_LOG) != 0); + if (bp.hasCond) + json.writeString("condition", bp.cond.expressionString); + else + json.writeNull("condition"); + if (!bp.logFormat.empty()) + json.writeString("logFormat", bp.logFormat); + else + json.writeNull("logFormat"); + json.pop(); + } + json.pop(); +} diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.h b/Core/Debugger/WebSocket/BreakpointSubscriber.h new file mode 100644 index 000000000000..eccf23c3cba9 --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.h @@ -0,0 +1,27 @@ +// 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/. + +#pragma once + +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map); + +void WebSocketCPUBreakpointAdd(DebuggerRequest &req); +void WebSocketCPUBreakpointUpdate(DebuggerRequest &req); +void WebSocketCPUBreakpointRemove(DebuggerRequest &req); +void WebSocketCPUBreakpointList(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index 54761aaa55a0..3a3536684c26 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -85,6 +85,17 @@ static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { return false; } +bool DebuggerRequest::HasParam(const char *name, bool ignoreNull) { + const JsonNode *node = data.get(name); + if (!node) { + return false; + } + if (node->value.getTag() == JSON_NULL) { + return !ignoreNull; + } + return true; +} + bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatBits, DebuggerParamType type) { bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; @@ -136,7 +147,7 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatB return false; } if (tag != JSON_STRING) { - if (required || tag != JSON_NULL) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { Fail(StringFromFormat("Invalid '%s' parameter type", name)); return false; } diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 807d8e3f1070..e49647dcf8ab 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -80,6 +80,7 @@ struct DebuggerRequest { responseSent_ = true; } + bool HasParam(const char *name, bool ignoreNull = false); bool ParamU32(const char *name, uint32_t *out, bool allowFloatBits = false, DebuggerParamType type = DebuggerParamType::REQUIRED); bool ParamBool(const char *name, bool *out, DebuggerParamType type = DebuggerParamType::REQUIRED); bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 35ee9c930e01..b08ade3a289d 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -302,6 +302,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/DisassemblyManager.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Debugger/WebSocket.cpp \ + $(SRC)/Core/Debugger/WebSocket/BreakpointSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \