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 \