From c2b9b5a9e81322dfb1bd0aab26e29174a123ce51 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 12 Apr 2018 22:11:11 -0700 Subject: [PATCH 01/45] Debugger: Serve a simple log listener WebSocket. Planning to add more functionality to it, and sharing the reporting port is possibly not ideal - although it would make discovery easier. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/Debugger/WebSocket.cpp | 127 ++++++++++++++++++++++++++++++++++++ Core/Debugger/WebSocket.h | 24 +++++++ Core/WebServer.cpp | 4 ++ Core/WebServer.h | 3 +- android/jni/Android.mk | 1 + 8 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 Core/Debugger/WebSocket.cpp create mode 100644 Core/Debugger/WebSocket.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e0db289257e..c4ef3860bfb7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1402,6 +1402,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/SymbolMap.h Core/Debugger/DisassemblyManager.cpp Core/Debugger/DisassemblyManager.h + Core/Debugger/WebSocket.cpp + Core/Debugger/WebSocket.h Core/Dialog/PSPDialog.cpp Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index b068e321e9c5..0d5a5c1940c3 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -184,6 +184,7 @@ + @@ -532,6 +533,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index fe637033f806..914b2e7ea1f6 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -692,6 +692,9 @@ Core + + Debugger + @@ -1274,6 +1277,9 @@ Core + + Debugger + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp new file mode 100644 index 000000000000..3d9cb95a6f1c --- /dev/null +++ b/Core/Debugger/WebSocket.cpp @@ -0,0 +1,127 @@ +// Copyright (c) 2017- 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 +#include +#include "json/json_writer.h" +#include "net/websocket_server.h" +#include "Core/Debugger/WebSocket.h" +#include "Common/LogManager.h" + +// TODO: Move this to its own file? +class DebuggerLogListener : public LogListener { +public: + void Log(const LogMessage &msg) override { + std::lock_guard guard(lock_); + messages_[nextMessage_] = msg; + nextMessage_++; + if (nextMessage_ >= BUFFER_SIZE) + nextMessage_ -= BUFFER_SIZE; + count_++; + } + + std::vector GetMessages() { + std::lock_guard guard(lock_); + int splitPoint; + int readCount; + if (read_ + BUFFER_SIZE < count_) { + // We'll start with our oldest then. + splitPoint = nextMessage_; + readCount = Count(); + } else { + splitPoint = read_; + readCount = count_ - read_; + } + + read_ = count_; + + std::vector results; + int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE); + for (int i = splitPoint; i < splitEnd; ++i) { + results.push_back(messages_[i]); + readCount--; + } + for (int i = 0; i < readCount; ++i) { + results.push_back(messages_[i]); + } + + return results; + } + + int Count() const { + return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE; + } + +private: + enum { BUFFER_SIZE = 128 }; + LogMessage messages_[BUFFER_SIZE]; + std::mutex lock_; + int nextMessage_ = 0; + int count_ = 0; + int read_ = 0; +}; + +struct DebuggerLogEvent { + std::string header; + std::string message; + int level; + const char *channel; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "log"); + j.writeString("header", header); + j.writeString("message", message); + j.writeInt("level", level); + j.writeString("channel", channel); + j.end(); + return j.str(); + } +}; + +void HandleDebuggerRequest(const http::Request &request) { + net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); + if (!ws) + return; + + DebuggerLogListener *logListener = new DebuggerLogListener(); + if (LogManager::GetInstance()) + LogManager::GetInstance()->AddListener(logListener); + + // TODO: Handle incoming messages. + ws->SetTextHandler([&](const std::string &t) { + ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + }); + ws->SetBinaryHandler([&](const std::vector &d) { + ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + }); + + while (ws->Process(0.1f)) { + auto messages = logListener->GetMessages(); + // TODO: Check for other conditions? + for (auto msg : messages) { + ws->Send(DebuggerLogEvent{msg.header, msg.msg, msg.level, msg.log}); + } + continue; + } + + if (LogManager::GetInstance()) + LogManager::GetInstance()->RemoveListener(logListener); + delete logListener; + delete ws; +} diff --git a/Core/Debugger/WebSocket.h b/Core/Debugger/WebSocket.h new file mode 100644 index 000000000000..73b4e5fe8ff0 --- /dev/null +++ b/Core/Debugger/WebSocket.h @@ -0,0 +1,24 @@ +// Copyright (c) 2017- 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 + +namespace http { +class Request; +} + +void HandleDebuggerRequest(const http::Request &request); diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index 6b20646f2e9a..d0f7c39fe73e 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -29,6 +29,7 @@ #include "Common/FileUtil.h" #include "Common/Log.h" #include "Core/Config.h" +#include "Core/Debugger/WebSocket.h" #include "Core/WebServer.h" enum class ServerStatus { @@ -188,6 +189,9 @@ static void ExecuteWebServer() { if (serverFlags & (int)WebServerFlags::DISCS) { RegisterDiscHandlers(http, &discPaths); } + if (serverFlags & (int)WebServerFlags::DEBUGGER) { + http->RegisterHandler("/debugger", &HandleDebuggerRequest); + } if (!http->Listen(g_Config.iRemoteISOPort)) { if (!http->Listen(0)) { diff --git a/Core/WebServer.h b/Core/WebServer.h index db0967cbf227..07c2efb4f043 100644 --- a/Core/WebServer.h +++ b/Core/WebServer.h @@ -17,8 +17,9 @@ enum class WebServerFlags { DISCS = 1, + DEBUGGER = 2, - ALL = 1, + ALL = 1 | 2, }; bool StartWebServer(WebServerFlags flags); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 428134fbcbeb..2d6fa3f9f6f4 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -300,6 +300,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/WebServer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ + $(SRC)/Core/Debugger/WebSocket.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ From b37d59e8fab30d5cb514e2eab591dd7d10b6cde2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 14 Apr 2018 08:54:04 -0700 Subject: [PATCH 02/45] Debugger: Organize WebSocket event handling. Just a starting point to organize it. Trying to keep it simple. --- CMakeLists.txt | 7 + Core/Core.vcxproj | 7 + Core/Core.vcxproj.filters | 24 ++++ Core/Debugger/WebSocket.cpp | 130 ++++++------------ Core/Debugger/WebSocket/Common.h | 42 ++++++ Core/Debugger/WebSocket/GameBroadcaster.cpp | 38 +++++ Core/Debugger/WebSocket/GameBroadcaster.h | 36 +++++ Core/Debugger/WebSocket/LogBroadcaster.cpp | 112 +++++++++++++++ Core/Debugger/WebSocket/LogBroadcaster.h | 35 +++++ .../WebSocket/SteppingBroadcaster.cpp | 32 +++++ Core/Debugger/WebSocket/SteppingBroadcaster.h | 36 +++++ android/jni/Android.mk | 3 + 12 files changed, 412 insertions(+), 90 deletions(-) create mode 100644 Core/Debugger/WebSocket/Common.h create mode 100644 Core/Debugger/WebSocket/GameBroadcaster.cpp create mode 100644 Core/Debugger/WebSocket/GameBroadcaster.h create mode 100644 Core/Debugger/WebSocket/LogBroadcaster.cpp create mode 100644 Core/Debugger/WebSocket/LogBroadcaster.h create mode 100644 Core/Debugger/WebSocket/SteppingBroadcaster.cpp create mode 100644 Core/Debugger/WebSocket/SteppingBroadcaster.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c4ef3860bfb7..2265f2b5defd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1404,6 +1404,13 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/DisassemblyManager.h Core/Debugger/WebSocket.cpp Core/Debugger/WebSocket.h + Core/Debugger/WebSocket/Common.h + Core/Debugger/WebSocket/GameBroadcaster.cpp + Core/Debugger/WebSocket/GameBroadcaster.h + Core/Debugger/WebSocket/LogBroadcaster.cpp + Core/Debugger/WebSocket/LogBroadcaster.h + Core/Debugger/WebSocket/SteppingBroadcaster.cpp + Core/Debugger/WebSocket/SteppingBroadcaster.h Core/Dialog/PSPDialog.cpp Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 0d5a5c1940c3..e713cd5057f9 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -185,6 +185,9 @@ + + + @@ -534,6 +537,10 @@ + + + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 914b2e7ea1f6..3449fa015828 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -70,6 +70,9 @@ {119ac973-e457-4025-9e1e-4fb34022ae23} + + {c21d9bb5-614d-451b-8c0b-3078b29122d8} + @@ -695,6 +698,15 @@ Debugger + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + @@ -1280,6 +1292,18 @@ Debugger + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 3d9cb95a6f1c..5030dfcc653e 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -15,113 +15,63 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include -#include -#include "json/json_writer.h" -#include "net/websocket_server.h" #include "Core/Debugger/WebSocket.h" -#include "Common/LogManager.h" +#include "Core/Debugger/WebSocket/Common.h" -// TODO: Move this to its own file? -class DebuggerLogListener : public LogListener { -public: - void Log(const LogMessage &msg) override { - std::lock_guard guard(lock_); - messages_[nextMessage_] = msg; - nextMessage_++; - if (nextMessage_ >= BUFFER_SIZE) - nextMessage_ -= BUFFER_SIZE; - count_++; - } - - std::vector GetMessages() { - std::lock_guard guard(lock_); - int splitPoint; - int readCount; - if (read_ + BUFFER_SIZE < count_) { - // We'll start with our oldest then. - splitPoint = nextMessage_; - readCount = Count(); - } else { - splitPoint = read_; - readCount = count_ - read_; - } - - read_ = count_; - - std::vector results; - int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE); - for (int i = splitPoint; i < splitEnd; ++i) { - results.push_back(messages_[i]); - readCount--; - } - for (int i = 0; i < readCount; ++i) { - results.push_back(messages_[i]); - } - - return results; - } - - int Count() const { - return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE; - } - -private: - enum { BUFFER_SIZE = 128 }; - LogMessage messages_[BUFFER_SIZE]; - std::mutex lock_; - int nextMessage_ = 0; - int count_ = 0; - int read_ = 0; -}; +#include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" -struct DebuggerLogEvent { - std::string header; - std::string message; - int level; - const char *channel; +// TODO: Just for now, testing... +static void WebSocketTestEvent(net::WebSocketServer *ws, const JsonGet &data) { + ws->Send(DebuggerErrorEvent("Test message", LogTypes::LNOTICE)); +} - operator std::string() { - JsonWriter j; - j.begin(); - j.writeString("event", "log"); - j.writeString("header", header); - j.writeString("message", message); - j.writeInt("level", level); - j.writeString("channel", channel); - j.end(); - return j.str(); - } -}; +typedef void (*DebuggerEventHandler)(net::WebSocketServer *ws, const JsonGet &data); +static const std::unordered_map debuggerEvents({ + {"test", &WebSocketTestEvent}, +}); void HandleDebuggerRequest(const http::Request &request) { net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); if (!ws) return; - DebuggerLogListener *logListener = new DebuggerLogListener(); - if (LogManager::GetInstance()) - LogManager::GetInstance()->AddListener(logListener); + LogBroadcaster logger; + GameBroadcaster game; + SteppingBroadcaster stepping; - // TODO: Handle incoming messages. ws->SetTextHandler([&](const std::string &t) { - ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + JsonReader reader(t.c_str(), t.size()); + if (!reader.ok()) { + ws->Send(DebuggerErrorEvent("Bad message: invalid JSON", LogTypes::LERROR)); + return; + } + + const JsonGet root = reader.root(); + const char *event = root ? root.getString("event", nullptr) : nullptr; + if (!event) { + ws->Send(DebuggerErrorEvent("Bad message: no event property", LogTypes::LERROR)); + return; + } + + auto eventFunc = debuggerEvents.find(event); + if (eventFunc != debuggerEvents.end()) { + eventFunc->second(ws, root); + } else { + ws->Send(DebuggerErrorEvent("Bad message: unknown event", LogTypes::LERROR)); + } }); ws->SetBinaryHandler([&](const std::vector &d) { - ws->Send(R"({"event":"error","message":"Bad message","level":2})"); + ws->Send(DebuggerErrorEvent("Bad message", LogTypes::LERROR)); }); - while (ws->Process(0.1f)) { - auto messages = logListener->GetMessages(); - // TODO: Check for other conditions? - for (auto msg : messages) { - ws->Send(DebuggerLogEvent{msg.header, msg.msg, msg.level, msg.log}); - } - continue; + while (ws->Process(1.0f / 60.0f)) { + // These send events that aren't just responses to requests. + logger.Broadcast(ws); + game.Broadcast(ws); + stepping.Broadcast(ws); } - if (LogManager::GetInstance()) - LogManager::GetInstance()->RemoveListener(logListener); - delete logListener; delete ws; } diff --git a/Core/Debugger/WebSocket/Common.h b/Core/Debugger/WebSocket/Common.h new file mode 100644 index 000000000000..ffde821f6e99 --- /dev/null +++ b/Core/Debugger/WebSocket/Common.h @@ -0,0 +1,42 @@ +// 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 +#include "json/json_reader.h" +#include "json/json_writer.h" +#include "net/websocket_server.h" +#include "Common/Log.h" + +struct DebuggerErrorEvent { + DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l) : message(m), level(l) { + } + + std::string message; + LogTypes::LOG_LEVELS level; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "error"); + j.writeString("message", message); + j.writeInt("level", level); + j.end(); + return j.str(); + } +}; diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp new file mode 100644 index 000000000000..86ee9df777e3 --- /dev/null +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -0,0 +1,38 @@ +// 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 "Core/Debugger/WebSocket/Common.h" +#include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/System.h" + +void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { + // TODO: This is ugly. Implement proper information instead. + // TODO: Should probably include info about which game, etc. + GlobalUIState state = GetUIState(); + if (prevState_ != state) { + if (state == UISTATE_PAUSEMENU) { + ws->Send(R"({"event":"game_pause"})"); + } else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) { + ws->Send(R"({"event":"game_resume"})"); + } else if (state == UISTATE_INGAME) { + ws->Send(R"({"event":"game_start"})"); + } else if (state == UISTATE_MENU) { + ws->Send(R"({"event":"game_quit"})"); + } + prevState_ = state; + } +} diff --git a/Core/Debugger/WebSocket/GameBroadcaster.h b/Core/Debugger/WebSocket/GameBroadcaster.h new file mode 100644 index 000000000000..63ef3eb346d9 --- /dev/null +++ b/Core/Debugger/WebSocket/GameBroadcaster.h @@ -0,0 +1,36 @@ +// 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/System.h" + +namespace net { +class WebSocketServer; +} + +struct GameBroadcaster { +public: + GameBroadcaster() { + prevState_ = GetUIState(); + } + + void Broadcast(net::WebSocketServer *ws); + +private: + GlobalUIState prevState_; +}; diff --git a/Core/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp new file mode 100644 index 000000000000..3f23751d7cb1 --- /dev/null +++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp @@ -0,0 +1,112 @@ +// 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 +#include +#include "Common/LogManager.h" +#include "Core/Debugger/WebSocket/Common.h" +#include "Core/Debugger/WebSocket/LogBroadcaster.h" + +class DebuggerLogListener : public LogListener { +public: + void Log(const LogMessage &msg) override { + std::lock_guard guard(lock_); + messages_[nextMessage_] = msg; + nextMessage_++; + if (nextMessage_ >= BUFFER_SIZE) + nextMessage_ -= BUFFER_SIZE; + count_++; + } + + std::vector GetMessages() { + std::lock_guard guard(lock_); + int splitPoint; + int readCount; + if (read_ + BUFFER_SIZE < count_) { + // We'll start with our oldest then. + splitPoint = nextMessage_; + readCount = Count(); + } else { + splitPoint = read_; + readCount = count_ - read_; + } + + read_ = count_; + + std::vector results; + int splitEnd = std::min(splitPoint + readCount, (int)BUFFER_SIZE); + for (int i = splitPoint; i < splitEnd; ++i) { + results.push_back(messages_[i]); + readCount--; + } + for (int i = 0; i < readCount; ++i) { + results.push_back(messages_[i]); + } + + return results; + } + + int Count() const { + return count_ < BUFFER_SIZE ? count_ : BUFFER_SIZE; + } + +private: + enum { BUFFER_SIZE = 1024 }; + LogMessage messages_[BUFFER_SIZE]; + std::mutex lock_; + int nextMessage_ = 0; + int count_ = 0; + int read_ = 0; +}; + +LogBroadcaster::LogBroadcaster() { + listener_ = new DebuggerLogListener(); + if (LogManager::GetInstance()) + LogManager::GetInstance()->AddListener(listener_); +} + +LogBroadcaster::~LogBroadcaster() { + if (LogManager::GetInstance()) + LogManager::GetInstance()->RemoveListener(listener_); + delete listener_; +} + +struct DebuggerLogEvent { + std::string header; + std::string message; + int level; + const char *channel; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "log"); + j.writeString("header", header); + j.writeString("message", message); + j.writeInt("level", level); + j.writeString("channel", channel); + j.end(); + return j.str(); + } +}; + +void LogBroadcaster::Broadcast(net::WebSocketServer *ws) { + auto messages = listener_->GetMessages(); + for (auto msg : messages) { + ws->Send(DebuggerLogEvent{msg.header, msg.msg, msg.level, msg.log}); + } +} diff --git a/Core/Debugger/WebSocket/LogBroadcaster.h b/Core/Debugger/WebSocket/LogBroadcaster.h new file mode 100644 index 000000000000..13f26d4a066f --- /dev/null +++ b/Core/Debugger/WebSocket/LogBroadcaster.h @@ -0,0 +1,35 @@ +// 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 + +namespace net { +class WebSocketServer; +} + +class DebuggerLogListener; + +struct LogBroadcaster { +public: + LogBroadcaster(); + ~LogBroadcaster(); + + void Broadcast(net::WebSocketServer *ws); + +private: + DebuggerLogListener *listener_; +}; diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp new file mode 100644 index 000000000000..60405b4c485d --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -0,0 +1,32 @@ +// 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 "Core/Core.h" +#include "Core/Debugger/WebSocket/Common.h" +#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +#include "Core/System.h" + +void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { + // TODO: This is somewhat primitive. It'd be nice to register a callback with Core instead? + if (coreState != prevState_) { + if (Core_IsStepping() && PSP_IsInited()) { + // TODO: Should send more data proactively. + ws->Send(R"({"event":"cpu_stepping"})"); + } + prevState_ = coreState; + } +} diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.h b/Core/Debugger/WebSocket/SteppingBroadcaster.h new file mode 100644 index 000000000000..df196d9ff8ae --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.h @@ -0,0 +1,36 @@ +// 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/Core.h" + +namespace net { +class WebSocketServer; +} + +struct SteppingBroadcaster { +public: + SteppingBroadcaster() { + prevState_ = coreState; + } + + void Broadcast(net::WebSocketServer *ws); + +private: + CoreState prevState_; +}; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 2d6fa3f9f6f4..d21c85484979 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -301,6 +301,9 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Debugger/WebSocket.cpp \ + $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ From 123723286d37ef8f925992404abcc39add12eda0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 14 Apr 2018 14:19:24 -0700 Subject: [PATCH 03/45] Debugger: Synchronize error events using tickets. --- Core/Debugger/WebSocket.cpp | 24 +++++++++++++++++++++--- Core/Debugger/WebSocket/Common.h | 13 ++++++++++++- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 5030dfcc653e..770772a1de67 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -22,9 +22,27 @@ #include "Core/Debugger/WebSocket/LogBroadcaster.h" #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +// This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP. +// Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface. +// +// Messages to and from PPSSPP follow the same basic format: +// { "event": "NAME", ... } +// +// And are primarily of these types: +// * Events from the debugger/client (you) to PPSSPP +// If there's a response, it will generally use the same name. It may not be immedate - it's an event. +// * Spontaneous events from PPSSPP +// Things like logs, breakpoint hits, etc. not directly requested. +// +// Otherwise you may see error events which indicate PPSSPP couldn't understand or failed internally: +// - "event": "error" +// - "message": A string describing what happened. +// - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE) +// - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value. + // TODO: Just for now, testing... static void WebSocketTestEvent(net::WebSocketServer *ws, const JsonGet &data) { - ws->Send(DebuggerErrorEvent("Test message", LogTypes::LNOTICE)); + ws->Send(DebuggerErrorEvent("Test message", LogTypes::LNOTICE, data)); } typedef void (*DebuggerEventHandler)(net::WebSocketServer *ws, const JsonGet &data); @@ -51,7 +69,7 @@ void HandleDebuggerRequest(const http::Request &request) { const JsonGet root = reader.root(); const char *event = root ? root.getString("event", nullptr) : nullptr; if (!event) { - ws->Send(DebuggerErrorEvent("Bad message: no event property", LogTypes::LERROR)); + ws->Send(DebuggerErrorEvent("Bad message: no event property", LogTypes::LERROR, root)); return; } @@ -59,7 +77,7 @@ void HandleDebuggerRequest(const http::Request &request) { if (eventFunc != debuggerEvents.end()) { eventFunc->second(ws, root); } else { - ws->Send(DebuggerErrorEvent("Bad message: unknown event", LogTypes::LERROR)); + ws->Send(DebuggerErrorEvent("Bad message: unknown event", LogTypes::LERROR, root)); } }); ws->SetBinaryHandler([&](const std::vector &d) { diff --git a/Core/Debugger/WebSocket/Common.h b/Core/Debugger/WebSocket/Common.h index ffde821f6e99..bdca2236561a 100644 --- a/Core/Debugger/WebSocket/Common.h +++ b/Core/Debugger/WebSocket/Common.h @@ -24,11 +24,19 @@ #include "Common/Log.h" struct DebuggerErrorEvent { - DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l) : message(m), level(l) { + DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l, const JsonGet data = JsonValue(JSON_NULL)) + : message(m), level(l) { + // Need to format right away, before it's out of scope. + if (data) { + const JsonNode *value = data.get("ticket"); + if (value) + ticketRaw = json_stringify(value); + } } std::string message; LogTypes::LOG_LEVELS level; + std::string ticketRaw; operator std::string() { JsonWriter j; @@ -36,6 +44,9 @@ struct DebuggerErrorEvent { j.writeString("event", "error"); j.writeString("message", message); j.writeInt("level", level); + if (!ticketRaw.empty()) { + j.writeRaw("ticket", ticketRaw); + } j.end(); return j.str(); } From 98cddad73a1befc9f962ba1d773345730c321c82 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 08:49:33 -0700 Subject: [PATCH 04/45] Debugger: Minor cleanup in MIPSDebugInterface. --- Core/MIPS/MIPSDebugInterface.h | 51 ++++++++++++---------------------- 1 file changed, 18 insertions(+), 33 deletions(-) diff --git a/Core/MIPS/MIPSDebugInterface.h b/Core/MIPS/MIPSDebugInterface.h index 31cbdde41483..088256e8029d 100644 --- a/Core/MIPS/MIPSDebugInterface.h +++ b/Core/MIPS/MIPSDebugInterface.h @@ -54,85 +54,70 @@ class MIPSDebugInterface : public DebugInterface u32 GetLR() override { return cpu->r[MIPS_REG_RA]; } void SetPC(u32 _pc) override { cpu->pc = _pc; } - const char *GetCategoryName(int cat) override - { - const char *names[3] = {("GPR"),("FPU"),("VFPU")}; + const char *GetCategoryName(int cat) override { + static const char *const names[3] = { "GPR", "FPU", "VFPU" }; return names[cat]; } int GetNumCategories() override { return 3; } - int GetNumRegsInCategory(int cat) override - { - int r[3] = {32,32,32}; + int GetNumRegsInCategory(int cat) override { + static int r[3] = { 32, 32, 128 }; return r[cat]; } const char *GetRegName(int cat, int index) override; - void PrintRegValue(int cat, int index, char *out) override - { - switch (cat) - { + void PrintRegValue(int cat, int index, char *out) override { + switch (cat) { case 0: sprintf(out, "%08X", cpu->r[index]); break; case 1: sprintf(out, "%f", cpu->f[index]); break; case 2: sprintf(out, "N/A"); break; } } - u32 GetHi() override - { + u32 GetHi() override { return cpu->hi; } - u32 GetLo() override - { + u32 GetLo() override { return cpu->lo; } - void SetHi(u32 val) override - { + void SetHi(u32 val) override { cpu->hi = val; } - void SetLo(u32 val) override - { + void SetLo(u32 val) override { cpu->lo = val; } - u32 GetRegValue(int cat, int index) override - { - u32 temp; - switch (cat) - { + u32 GetRegValue(int cat, int index) override { + switch (cat) { case 0: return cpu->r[index]; case 1: - memcpy(&temp, &cpu->f[index], 4); - return temp; + return cpu->fi[index]; case 2: - memcpy(&temp, &cpu->v[voffset[index]], 4); - return temp; + return cpu->vi[voffset[index]]; default: return 0; } } - void SetRegValue(int cat, int index, u32 value) override - { - switch (cat) - { + void SetRegValue(int cat, int index, u32 value) override { + switch (cat) { case 0: if (index != 0) cpu->r[index] = value; break; case 1: - memcpy(&cpu->f[index], &value, 4); + cpu->fi[index] = value; break; case 2: - memcpy(&cpu->v[voffset[index]], &value, 4); + cpu->vi[voffset[index]] = value; break; default: From 1f987bf1c177e7dd5e30d4dfde7da5be5e53e0d8 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 08:50:25 -0700 Subject: [PATCH 05/45] Debugger: Initial register get/set APIs. --- CMakeLists.txt | 5 +- Core/Core.vcxproj | 5 +- Core/Core.vcxproj.filters | 11 +- Core/Debugger/WebSocket.cpp | 28 ++- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 224 ++++++++++++++++++ Core/Debugger/WebSocket/CPUCoreSubscriber.h | 24 ++ Core/Debugger/WebSocket/GameBroadcaster.cpp | 2 +- Core/Debugger/WebSocket/LogBroadcaster.cpp | 2 +- .../WebSocket/SteppingBroadcaster.cpp | 2 +- Core/Debugger/WebSocket/WebSocketUtils.cpp | 106 +++++++++ .../WebSocket/{Common.h => WebSocketUtils.h} | 33 +++ android/jni/Android.mk | 2 + 12 files changed, 425 insertions(+), 19 deletions(-) create mode 100644 Core/Debugger/WebSocket/CPUCoreSubscriber.cpp create mode 100644 Core/Debugger/WebSocket/CPUCoreSubscriber.h create mode 100644 Core/Debugger/WebSocket/WebSocketUtils.cpp rename Core/Debugger/WebSocket/{Common.h => WebSocketUtils.h} (66%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2265f2b5defd..9ea61d912b6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1404,13 +1404,16 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/DisassemblyManager.h Core/Debugger/WebSocket.cpp Core/Debugger/WebSocket.h - Core/Debugger/WebSocket/Common.h + Core/Debugger/WebSocket/CPUCoreSubscriber.cpp + Core/Debugger/WebSocket/CPUCoreSubscriber.h Core/Debugger/WebSocket/GameBroadcaster.cpp Core/Debugger/WebSocket/GameBroadcaster.h Core/Debugger/WebSocket/LogBroadcaster.cpp Core/Debugger/WebSocket/LogBroadcaster.h Core/Debugger/WebSocket/SteppingBroadcaster.cpp Core/Debugger/WebSocket/SteppingBroadcaster.h + Core/Debugger/WebSocket/WebSocketUtils.cpp + Core/Debugger/WebSocket/WebSocketUtils.h Core/Dialog/PSPDialog.cpp Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index e713cd5057f9..c102fd2cb72c 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -185,9 +185,11 @@ + + @@ -537,7 +539,8 @@ - + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 3449fa015828..3a8a72a82eb0 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -707,6 +707,12 @@ Debugger\WebSocket + + Debugger\WebSocket + + + Debugger\WebSocket + @@ -1301,7 +1307,10 @@ Debugger\WebSocket - + + Debugger\WebSocket + + Debugger\WebSocket diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 770772a1de67..2e3e54084354 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -16,11 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Core/Debugger/WebSocket.h" -#include "Core/Debugger/WebSocket/Common.h" - -#include "Core/Debugger/WebSocket/GameBroadcaster.h" -#include "Core/Debugger/WebSocket/LogBroadcaster.h" -#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" // This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP. // Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface. @@ -40,14 +36,17 @@ // - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE) // - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value. -// TODO: Just for now, testing... -static void WebSocketTestEvent(net::WebSocketServer *ws, const JsonGet &data) { - ws->Send(DebuggerErrorEvent("Test message", LogTypes::LNOTICE, data)); -} +#include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/SteppingBroadcaster.h" -typedef void (*DebuggerEventHandler)(net::WebSocketServer *ws, const JsonGet &data); +#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" + +typedef void (*DebuggerEventHandler)(DebuggerRequest &req); static const std::unordered_map debuggerEvents({ - {"test", &WebSocketTestEvent}, + {"cpu.getAllRegs", &WebSocketCPUGetAllRegs}, + {"cpu.getReg", &WebSocketCPUGetReg}, + {"cpu.setReg", &WebSocketCPUSetReg}, }); void HandleDebuggerRequest(const http::Request &request) { @@ -73,11 +72,14 @@ void HandleDebuggerRequest(const http::Request &request) { return; } + DebuggerRequest req(event, ws, root); + auto eventFunc = debuggerEvents.find(event); if (eventFunc != debuggerEvents.end()) { - eventFunc->second(ws, root); + eventFunc->second(req); + req.Finish(); } else { - ws->Send(DebuggerErrorEvent("Bad message: unknown event", LogTypes::LERROR, root)); + req.Fail("Bad message: unknown event"); } }); ws->SetBinaryHandler([&](const std::vector &d) { diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp new file mode 100644 index 000000000000..c3ef6e6ff562 --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -0,0 +1,224 @@ +// 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/Core.h" +#include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +static std::string RegValueAsFloat(uint32_t u) { + union { + uint32_t u; + float f; + } bits = { u }; + return StringFromFormat("%f", bits.f); +} + +void WebSocketCPUGetAllRegs(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + + json.pushArray("categories"); + for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) { + json.pushDict(); + json.writeInt("id", c); + json.writeString("name", currentDebugMIPS->GetCategoryName(c)); + + int total = currentDebugMIPS->GetNumRegsInCategory(c); + + json.pushArray("names"); + for (int r = 0; r < total; ++r) + json.writeString(currentDebugMIPS->GetRegName(c, r)); + if (c == 0) { + json.writeString("pc"); + json.writeString("hi"); + json.writeString("lo"); + } + json.pop(); + + json.pushArray("intValues"); + // Writing as floating point to avoid negatives. Actually double, so safe. + for (int r = 0; r < total; ++r) + json.writeFloat(currentDebugMIPS->GetRegValue(c, r)); + if (c == 0) { + json.writeFloat(currentDebugMIPS->GetPC()); + json.writeFloat(currentDebugMIPS->GetHi()); + json.writeFloat(currentDebugMIPS->GetLo()); + } + json.pop(); + + json.pushArray("floatValues"); + // Note: String so it can have Infinity and NaN. + for (int r = 0; r < total; ++r) + json.writeString(RegValueAsFloat(currentDebugMIPS->GetRegValue(c, r))); + if (c == 0) { + json.writeString(RegValueAsFloat(currentDebugMIPS->GetPC())); + json.writeString(RegValueAsFloat(currentDebugMIPS->GetHi())); + json.writeString(RegValueAsFloat(currentDebugMIPS->GetLo())); + } + json.pop(); + + json.pop(); + } + json.pop(); +} + +enum class DebuggerRegType { + INVALID, + NORMAL, + PC, + HI, + LO, +}; + +static DebuggerRegType ValidateRegName(DebuggerRequest &req, const std::string &name, int *cat, int *reg) { + if (name == "pc") { + *cat = 0; + *reg = 32; + return DebuggerRegType::PC; + } + if (name == "hi") { + *cat = 0; + *reg = 33; + return DebuggerRegType::HI; + } + if (name == "lo") { + *cat = 0; + *reg = 34; + return DebuggerRegType::LO; + } + + for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) { + int total = currentDebugMIPS->GetNumRegsInCategory(c); + for (int r = 0; r < total; ++r) { + if (name == currentDebugMIPS->GetRegName(c, r)) { + *cat = c; + *reg = r; + return DebuggerRegType::NORMAL; + } + } + } + + req.Fail("Invalid 'name' parameter"); + return DebuggerRegType::INVALID; +} + +static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) { + const char *name = req.data.getString("name", nullptr); + if (name) + return ValidateRegName(req, name, cat, reg); + + *cat = req.data.getInt("category", -1); + *reg = req.data.getInt("register", -1); + + if (*cat < 0 || *cat >= currentDebugMIPS->GetNumCategories()) { + req.Fail("Invalid 'category' parameter"); + return DebuggerRegType::INVALID; + } + + // TODO: We fake it for GPR... not sure yet if this is a good thing. + if (*cat == 0) { + // Intentionally retains the reg value. + if (*reg == 32) + return DebuggerRegType::PC; + if (*reg == 33) + return DebuggerRegType::HI; + if (*reg == 34) + return DebuggerRegType::LO; + } + + if (*reg < 0 || *reg >= currentDebugMIPS->GetNumRegsInCategory(*cat)) { + req.Fail("Invalid 'register' parameter"); + return DebuggerRegType::INVALID; + } + + return DebuggerRegType::NORMAL; +} + +void WebSocketCPUGetReg(DebuggerRequest &req) { + int cat, reg; + uint32_t val; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + val = currentDebugMIPS->GetRegValue(cat, reg); + break; + + case DebuggerRegType::PC: + val = currentDebugMIPS->GetPC(); + break; + case DebuggerRegType::HI: + val = currentDebugMIPS->GetHi(); + break; + case DebuggerRegType::LO: + val = currentDebugMIPS->GetLo(); + break; + + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } + + JsonWriter &json = req.Respond(); + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeFloat("intValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} + +void WebSocketCPUSetReg(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping()) { + return req.Fail("CPU currently running (cpu.interrupt first)"); + } + + uint32_t val; + if (!req.ParamU32OrFloatBits("value", &val)) { + // Already sent error. + return; + } + + int cat, reg; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + currentDebugMIPS->SetRegValue(cat, reg, val); + break; + + case DebuggerRegType::PC: + currentDebugMIPS->SetPC(val); + break; + case DebuggerRegType::HI: + currentDebugMIPS->SetHi(val); + break; + case DebuggerRegType::LO: + currentDebugMIPS->SetLo(val); + break; + + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } + + JsonWriter &json = req.Respond(); + // Repeat it back just to avoid confusion on how it parsed. + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeFloat("intValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h new file mode 100644 index 000000000000..e24b16398f1a --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -0,0 +1,24 @@ +// 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 + +struct DebuggerRequest; + +void WebSocketCPUGetAllRegs(DebuggerRequest &req); +void WebSocketCPUGetReg(DebuggerRequest &req); +void WebSocketCPUSetReg(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp index 86ee9df777e3..e1a9d2ae4e83 100644 --- a/Core/Debugger/WebSocket/GameBroadcaster.cpp +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -15,8 +15,8 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include "Core/Debugger/WebSocket/Common.h" #include "Core/Debugger/WebSocket/GameBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/System.h" void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { diff --git a/Core/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp index 3f23751d7cb1..2474294e0a7a 100644 --- a/Core/Debugger/WebSocket/LogBroadcaster.cpp +++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp @@ -18,8 +18,8 @@ #include #include #include "Common/LogManager.h" -#include "Core/Debugger/WebSocket/Common.h" #include "Core/Debugger/WebSocket/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" class DebuggerLogListener : public LogListener { public: diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index 60405b4c485d..ca44bc06b44d 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -16,8 +16,8 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Core/Core.h" -#include "Core/Debugger/WebSocket/Common.h" #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/System.h" void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp new file mode 100644 index 000000000000..3293e5da222f --- /dev/null +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -0,0 +1,106 @@ +// 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/WebSocket/WebSocketUtils.h" + +JsonWriter &DebuggerRequest::Respond() { + writer_.begin(); + writer_.writeString("event", name); + DebuggerJsonAddTicket(writer_, data); + + responseBegun_ = true; + return writer_; +} + +void DebuggerRequest::Finish() { + if (responseBegun_ && !responseSent_) { + writer_.end(); + ws->Send(writer_.str()); + responseBegun_ = false; + responseSent_ = true; + } +} + +static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { + if (TryParse(str, out)) + return true; + + // Now let's try signed (the above parses only positive.) + if (str[0] == '-' && TryParse(&str[1], out)) { + *out = static_cast(-static_cast(*out)); + return true; + } + + // We have to try float last because we use float bits. + union { + uint32_t u; + float f; + } bits; + if (allowFloat && TryParse(str, &bits.f)) { + *out = bits.u; + return true; + } + + return false; +} + +bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) { + const JsonNode *node = data.get(name); + if (!node) { + Fail(StringFromFormat("Missing '%s' parameter", name)); + return false; + } + + // TODO: For now, only supporting strings. Switch to gason? + // Otherwise we get overflow (signed integer parsing.) + if (node->value.getTag() != JSON_STRING) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + + if (U32FromString(node->value.toString(), out, false)) + return true; + + Fail(StringFromFormat("Could not parse '%s' parameter", name)); + return false; +} + +bool DebuggerRequest::ParamU32OrFloatBits(const char *name, uint32_t *out) { + const JsonNode *node = data.get(name); + if (!node) { + Fail(StringFromFormat("Missing '%s' parameter", name)); + return false; + } + + // TODO: For now, only supporting strings and floats. Switch to gason? + // Otherwise we get overflow (signed integer parsing.) + if (node->value.getTag() == JSON_NUMBER) { + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name)); + return false; + } + if (node->value.getTag() != JSON_STRING) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + + if (U32FromString(node->value.toString(), out, true)) + return true; + + Fail(StringFromFormat("Could not parse '%s' parameter", name)); + return false; +} diff --git a/Core/Debugger/WebSocket/Common.h b/Core/Debugger/WebSocket/WebSocketUtils.h similarity index 66% rename from Core/Debugger/WebSocket/Common.h rename to Core/Debugger/WebSocket/WebSocketUtils.h index bdca2236561a..19894939bb45 100644 --- a/Core/Debugger/WebSocket/Common.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -17,12 +17,19 @@ #pragma once +#include #include #include "json/json_reader.h" #include "json/json_writer.h" #include "net/websocket_server.h" #include "Common/Log.h" +static inline void DebuggerJsonAddTicket(JsonWriter &writer, const JsonGet &data) { + const JsonNode *value = data.get("ticket"); + if (value) + writer.writeRaw("ticket", json_stringify(value)); +} + struct DebuggerErrorEvent { DebuggerErrorEvent(const std::string m, LogTypes::LOG_LEVELS l, const JsonGet data = JsonValue(JSON_NULL)) : message(m), level(l) { @@ -51,3 +58,29 @@ struct DebuggerErrorEvent { return j.str(); } }; + +struct DebuggerRequest { + DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d) + : name(n), ws(w), data(d) { + } + + const char *name; + net::WebSocketServer *ws; + const JsonGet data; + + void Fail(const std::string &message) { + ws->Send(DebuggerErrorEvent(message, LogTypes::LERROR, data)); + responseSent_ = true; + } + + bool ParamU32(const char *name, uint32_t *out); + bool ParamU32OrFloatBits(const char *name, uint32_t *out); + + JsonWriter &Respond(); + void Finish(); + +private: + JsonWriter writer_; + bool responseBegun_ = false; + bool responseSent_ = false; +}; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index d21c85484979..b5b8dda1d227 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -301,9 +301,11 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/Breakpoints.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Debugger/WebSocket.cpp \ + $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/WebSocketUtils.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ From be3b50dd74f18c55745994471fe4e487058fdf27 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 11:24:10 -0700 Subject: [PATCH 06/45] Debugger: Improve JSON number parsing. With gason, this becomes doable without overflow issues. --- Core/Debugger/WebSocket.cpp | 1 - Core/Debugger/WebSocket/WebSocketUtils.cpp | 49 +++++++++++++++++++--- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 2e3e54084354..3615bc4c27cd 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -73,7 +73,6 @@ void HandleDebuggerRequest(const http::Request &request) { } DebuggerRequest req(event, ws, root); - auto eventFunc = debuggerEvents.find(event); if (eventFunc != debuggerEvents.end()) { eventFunc->second(req); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index 3293e5da222f..b6981821485a 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -15,6 +15,8 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include #include "Common/StringUtils.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" @@ -66,8 +68,26 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) { return false; } - // TODO: For now, only supporting strings. Switch to gason? - // Otherwise we get overflow (signed integer parsing.) + if (node->value.getTag() == JSON_NUMBER) { + double val = node->value.toNumber(); + bool isInteger = trunc(val) == val; + if (!isInteger) { + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); + return false; + } + + if (val < 0 && val >= std::numeric_limits::min()) { + // Convert to unsigned representation. + *out = (uint32_t)(int32_t)val; + return true; + } else if (val >= 0 && val <= std::numeric_limits::max()) { + *out = (uint32_t)val; + return true; + } + + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name)); + return false; + } if (node->value.getTag() != JSON_STRING) { Fail(StringFromFormat("Invalid '%s' parameter type", name)); return false; @@ -76,7 +96,7 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) { if (U32FromString(node->value.toString(), out, false)) return true; - Fail(StringFromFormat("Could not parse '%s' parameter", name)); + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); return false; } @@ -87,9 +107,26 @@ bool DebuggerRequest::ParamU32OrFloatBits(const char *name, uint32_t *out) { return false; } - // TODO: For now, only supporting strings and floats. Switch to gason? - // Otherwise we get overflow (signed integer parsing.) if (node->value.getTag() == JSON_NUMBER) { + double val = node->value.toNumber(); + bool isInteger = trunc(val) == val; + + // JSON doesn't give a great way to differentiate ints and floats. + // Let's play it safe and require a string. + if (!isInteger) { + Fail(StringFromFormat("Could not parse '%s' parameter: use a string for non integer values", name)); + return false; + } + + if (val < 0 && val >= std::numeric_limits::min()) { + // Convert to unsigned representation. + *out = (uint32_t)(int32_t)val; + return true; + } else if (val >= 0 && val <= std::numeric_limits::max()) { + *out = (uint32_t)val; + return true; + } + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name)); return false; } @@ -101,6 +138,6 @@ bool DebuggerRequest::ParamU32OrFloatBits(const char *name, uint32_t *out) { if (U32FromString(node->value.toString(), out, true)) return true; - Fail(StringFromFormat("Could not parse '%s' parameter", name)); + Fail(StringFromFormat("Could not parse '%s' parameter: number expected", name)); return false; } From 3c458310edd6c6df47dfb85713ebc4556d53e2a0 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 11:34:57 -0700 Subject: [PATCH 07/45] Debugger: Allow setting regs to inf/nan. --- Core/Debugger/WebSocket/WebSocketUtils.cpp | 29 ++++++++++++++++------ 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index b6981821485a..8d75122018ae 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -48,14 +48,27 @@ static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { return true; } - // We have to try float last because we use float bits. - union { - uint32_t u; - float f; - } bits; - if (allowFloat && TryParse(str, &bits.f)) { - *out = bits.u; - return true; + // We have to try float last because we use float bits, so 1.0 != 1. + if (allowFloat) { + union { + uint32_t u; + float f; + } bits; + if (TryParse(str, &bits.f)) { + *out = bits.u; + return true; + } + + if (!strcasecmp(str, "nan")) { + *out = 0x7FC00000; + return true; + } else if (!strcasecmp(str, "infinity") || !strcasecmp(str, "inf")) { + *out = 0x7F800000; + return true; + } else if (!strcasecmp(str, "-infinity") || !strcasecmp(str, "-inf")) { + *out = 0xFF800000; + return true; + } } return false; From 0a21063525965d43a556c1da18995d97c7fb66ed Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 11:51:19 -0700 Subject: [PATCH 08/45] Debugger: Cleanup, add initial API docs. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 55 +++++++++++++++++-- Core/Debugger/WebSocket/GameBroadcaster.cpp | 8 +-- .../WebSocket/SteppingBroadcaster.cpp | 2 +- 3 files changed, 55 insertions(+), 10 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index c3ef6e6ff562..f2ef93b75e79 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -30,6 +30,17 @@ static std::string RegValueAsFloat(uint32_t u) { return StringFromFormat("%f", bits.f); } +// Retrieve all regs and their values (cpu.getAllRegs) +// +// No parameters. +// +// Response (same event name): +// - categories: array of objects: +// - id: "category" property to use for other events. +// - name: a category name, such as "GPR". +// - registerNames: array of string names of the registers (size varies per category.) +// - uintValues: array of unsigned integer values for the registers. +// - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetAllRegs(DebuggerRequest &req) { JsonWriter &json = req.Respond(); @@ -41,7 +52,7 @@ void WebSocketCPUGetAllRegs(DebuggerRequest &req) { int total = currentDebugMIPS->GetNumRegsInCategory(c); - json.pushArray("names"); + json.pushArray("registerNames"); for (int r = 0; r < total; ++r) json.writeString(currentDebugMIPS->GetRegName(c, r)); if (c == 0) { @@ -51,7 +62,7 @@ void WebSocketCPUGetAllRegs(DebuggerRequest &req) { } json.pop(); - json.pushArray("intValues"); + json.pushArray("uintValues"); // Writing as floating point to avoid negatives. Actually double, so safe. for (int r = 0; r < total; ++r) json.writeFloat(currentDebugMIPS->GetRegValue(c, r)); @@ -150,6 +161,20 @@ static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) return DebuggerRegType::NORMAL; } +// Retrieve the value of a single register (cpu.getReg) +// +// Parameters (by name): +// - name: string name of register to lookup. +// +// Parameters (by category id and index, ignored if name specified): +// - category: id of category for the register. +// - register: index into array of registers. +// +// Response (same event name): +// - category: id of category for the register. +// - register: index into array of registers. +// - uintValue: value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetReg(DebuggerRequest &req) { int cat, reg; uint32_t val; @@ -176,16 +201,36 @@ void WebSocketCPUGetReg(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.writeInt("category", cat); json.writeInt("register", reg); - json.writeFloat("intValue", val); + json.writeFloat("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } +// Retrieve the value of a single register (cpu.getReg) +// +// Parameters (by name): +// - name: string name of register to lookup. +// - value: number (uint values only) or string to set to. Values may include +// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0". +// +// Parameters (by category id and index, ignored if name specified): +// - category: id of category for the register. +// - register: index into array of registers. +// - value: number (uint values only) or string to set to. Values may include +// "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0". +// +// Response (same event name): +// - category: id of category for the register. +// - register: index into array of registers. +// - uintValue: new value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". +// +// NOTE: Cannot be called unless the CPU is currently stepping. void WebSocketCPUSetReg(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive()) { return req.Fail("CPU not started"); } if (!Core_IsStepping()) { - return req.Fail("CPU currently running (cpu.interrupt first)"); + return req.Fail("CPU currently running (cpu.stepping first)"); } uint32_t val; @@ -219,6 +264,6 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { // Repeat it back just to avoid confusion on how it parsed. json.writeInt("category", cat); json.writeInt("register", reg); - json.writeFloat("intValue", val); + json.writeFloat("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp index e1a9d2ae4e83..37a9e2c919cb 100644 --- a/Core/Debugger/WebSocket/GameBroadcaster.cpp +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -25,13 +25,13 @@ void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { GlobalUIState state = GetUIState(); if (prevState_ != state) { if (state == UISTATE_PAUSEMENU) { - ws->Send(R"({"event":"game_pause"})"); + ws->Send(R"({"event":"game.pause"})"); } else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) { - ws->Send(R"({"event":"game_resume"})"); + ws->Send(R"({"event":"game.resume"})"); } else if (state == UISTATE_INGAME) { - ws->Send(R"({"event":"game_start"})"); + ws->Send(R"({"event":"game.start"})"); } else if (state == UISTATE_MENU) { - ws->Send(R"({"event":"game_quit"})"); + ws->Send(R"({"event":"game.quit"})"); } prevState_ = state; } diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index ca44bc06b44d..7be39bd7984b 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -25,7 +25,7 @@ void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { if (coreState != prevState_) { if (Core_IsStepping() && PSP_IsInited()) { // TODO: Should send more data proactively. - ws->Send(R"({"event":"cpu_stepping"})"); + ws->Send(R"({"event":"cpu.stepping"})"); } prevState_ = coreState; } From a4044fd6a01e34abc6ef45b6d49c638a129fd445 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 15:52:00 -0700 Subject: [PATCH 09/45] Debugger: Reorganize state handling. Looking like this will be needed for disasm caches, etc. --- Core/Debugger/WebSocket.cpp | 32 +++++++++++++++---- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 9 ++++++ Core/Debugger/WebSocket/CPUCoreSubscriber.h | 4 ++- Core/Debugger/WebSocket/WebSocketUtils.h | 3 ++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 3615bc4c27cd..9d703f19f169 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -42,11 +42,15 @@ #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" -typedef void (*DebuggerEventHandler)(DebuggerRequest &req); -static const std::unordered_map debuggerEvents({ - {"cpu.getAllRegs", &WebSocketCPUGetAllRegs}, - {"cpu.getReg", &WebSocketCPUGetReg}, - {"cpu.setReg", &WebSocketCPUSetReg}, +typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); +typedef void (*Subscribershutdown)(void *p); +struct SubscriberInfo { + SubscriberInit init; + Subscribershutdown shutdown; +}; + +static const std::vector subscribers({ + { &WebSocketCPUCoreInit, nullptr }, }); void HandleDebuggerRequest(const http::Request &request) { @@ -58,6 +62,12 @@ void HandleDebuggerRequest(const http::Request &request) { GameBroadcaster game; SteppingBroadcaster stepping; + std::unordered_map eventHandlers; + std::vector subscriberData; + for (auto info : subscribers) { + subscriberData.push_back(info.init(eventHandlers)); + } + ws->SetTextHandler([&](const std::string &t) { JsonReader reader(t.c_str(), t.size()); if (!reader.ok()) { @@ -73,8 +83,8 @@ void HandleDebuggerRequest(const http::Request &request) { } DebuggerRequest req(event, ws, root); - auto eventFunc = debuggerEvents.find(event); - if (eventFunc != debuggerEvents.end()) { + auto eventFunc = eventHandlers.find(event); + if (eventFunc != eventHandlers.end()) { eventFunc->second(req); req.Finish(); } else { @@ -92,5 +102,13 @@ void HandleDebuggerRequest(const http::Request &request) { stepping.Broadcast(ws); } + for (size_t i = 0; i < subscribers.size(); ++i) { + if (subscribers[i].shutdown) { + subscribers[i].shutdown(subscriberData[i]); + } else { + assert(!subscriberData[i]); + } + } + delete ws; } diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index f2ef93b75e79..4e5fab8a5943 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -22,6 +22,15 @@ #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSDebugInterface.h" +void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) { + // No need to bind or alloc state, these are all global. + map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs; + map["cpu.getReg"] = &WebSocketCPUGetReg; + map["cpu.setReg"] = &WebSocketCPUSetReg; + + return nullptr; +} + static std::string RegValueAsFloat(uint32_t u) { union { uint32_t u; diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h index e24b16398f1a..78c317317458 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.h +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -17,7 +17,9 @@ #pragma once -struct DebuggerRequest; +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map); void WebSocketCPUGetAllRegs(DebuggerRequest &req); void WebSocketCPUGetReg(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 19894939bb45..6e4e62d36c86 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -84,3 +84,6 @@ struct DebuggerRequest { bool responseBegun_ = false; bool responseSent_ = false; }; + +typedef std::function DebuggerEventHandler; +typedef std::unordered_map DebuggerEventHandlerMap; From a3419946222624b0addc5b3296643460645d4d6b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 15:53:36 -0700 Subject: [PATCH 10/45] Debugger: Add cpu.stepping and cpu.resume. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 32 +++++++++++++++++++ Core/Debugger/WebSocket/CPUCoreSubscriber.h | 2 ++ .../WebSocket/SteppingBroadcaster.cpp | 9 ++++-- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 4e5fab8a5943..8f7fd07e7868 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -24,6 +24,8 @@ void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) { // No need to bind or alloc state, these are all global. + map["cpu.stepping"] = &WebSocketCPUStepping; + map["cpu.resume"] = &WebSocketCPUResume; map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs; map["cpu.getReg"] = &WebSocketCPUGetReg; map["cpu.setReg"] = &WebSocketCPUSetReg; @@ -39,6 +41,36 @@ static std::string RegValueAsFloat(uint32_t u) { return StringFromFormat("%f", bits.f); } +// Begin stepping and pause the CPU (cpu.stepping) +// +// No parameters. +// +// No immediate response. Once CPU is stepping, a "cpu.stepping" event will be sent. +void WebSocketCPUStepping(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping() && Core_IsActive()) { + Core_EnableStepping(true); + } +} + +// Stop stepping and resume the CPU (cpu.resume) +// +// No parameters. +// +// No immediate response. Once CPU is stepping, a "cpu.resume" event will be sent. +void WebSocketCPUResume(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + if (!Core_IsStepping() || coreState == CORE_POWERDOWN) { + return req.Fail("CPU not stepping"); + } + + Core_EnableStepping(false); +} + // Retrieve all regs and their values (cpu.getAllRegs) // // No parameters. diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h index 78c317317458..4998d9af0b9a 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.h +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -21,6 +21,8 @@ void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map); +void WebSocketCPUStepping(DebuggerRequest &req); +void WebSocketCPUResume(DebuggerRequest &req); void WebSocketCPUGetAllRegs(DebuggerRequest &req); void WebSocketCPUGetReg(DebuggerRequest &req); void WebSocketCPUSetReg(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index 7be39bd7984b..89e91bc63dab 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -22,11 +22,14 @@ void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { // TODO: This is somewhat primitive. It'd be nice to register a callback with Core instead? - if (coreState != prevState_) { - if (Core_IsStepping() && PSP_IsInited()) { + if (coreState != prevState_ && PSP_IsInited()) { + // We ignore CORE_POWERDOWN. + if (coreState == CORE_STEPPING) { // TODO: Should send more data proactively. ws->Send(R"({"event":"cpu.stepping"})"); + } else if (prevState_ == CORE_STEPPING && Core_IsActive()) { + ws->Send(R"({"event":"cpu.resume"})"); } - prevState_ = coreState; } + prevState_ = coreState; } From c44d8dbe2d0d318b8a99548bfb3f1dffdd61322e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 15 Apr 2018 16:31:00 -0700 Subject: [PATCH 11/45] Debugger: Name the WebSocket debugger thread. --- Core/Debugger/WebSocket.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 9d703f19f169..6343a8331d75 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "thread/threadutil.h" #include "Core/Debugger/WebSocket.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" @@ -58,6 +59,8 @@ void HandleDebuggerRequest(const http::Request &request) { if (!ws) return; + setCurrentThreadName("Debugger"); + LogBroadcaster logger; GameBroadcaster game; SteppingBroadcaster stepping; From d67a1d4a3aac40f31a40b4d5aa6599ca7367040b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 19 Apr 2018 20:59:20 -0700 Subject: [PATCH 12/45] Debugger: Allow pinging current status. Helps especially when reconnecting. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 19 +++++++++++++++++++ Core/Debugger/WebSocket/CPUCoreSubscriber.h | 1 + 2 files changed, 20 insertions(+) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 8f7fd07e7868..2453675a337f 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -26,6 +26,7 @@ void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) { // No need to bind or alloc state, these are all global. map["cpu.stepping"] = &WebSocketCPUStepping; map["cpu.resume"] = &WebSocketCPUResume; + map["cpu.status"] = &WebSocketCPUStatus; map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs; map["cpu.getReg"] = &WebSocketCPUGetReg; map["cpu.setReg"] = &WebSocketCPUSetReg; @@ -71,6 +72,19 @@ void WebSocketCPUResume(DebuggerRequest &req) { Core_EnableStepping(false); } +// Request the current CPU status (cpu.status) +// +// No parameters. +// +// Response (same event name): +// - stepping: boolean, CPU currently stepping. +// - paused: boolean, CPU paused or not started yet. +void WebSocketCPUStatus(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN); + json.writeBool("paused", GetUIState() != UISTATE_INGAME); +} + // Retrieve all regs and their values (cpu.getAllRegs) // // No parameters. @@ -283,7 +297,12 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { int cat, reg; switch (ValidateCatReg(req, &cat, ®)) { case DebuggerRegType::NORMAL: + if (cat == 0 && reg == 0 && val != 0) { + return req.Fail("Cannot change reg zero"); + } currentDebugMIPS->SetRegValue(cat, reg, val); + // In case part of it was ignored (e.g. flags reg.) + val = currentDebugMIPS->GetRegValue(cat, reg); break; case DebuggerRegType::PC: diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h index 4998d9af0b9a..effff73a7623 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.h +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -23,6 +23,7 @@ void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map); void WebSocketCPUStepping(DebuggerRequest &req); void WebSocketCPUResume(DebuggerRequest &req); +void WebSocketCPUStatus(DebuggerRequest &req); void WebSocketCPUGetAllRegs(DebuggerRequest &req); void WebSocketCPUGetReg(DebuggerRequest &req); void WebSocketCPUSetReg(DebuggerRequest &req); From f02bd4daff968d44c9c348cec93cd3ef64d4754b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 19 Apr 2018 21:14:01 -0700 Subject: [PATCH 13/45] Debugger: Use a counter when entering stepping. This allows us to poll coreState and know if a new stepping needs to be sent. This is useful to allow e.g. regs to show changes per step. --- Core/Debugger/WebSocket/SteppingBroadcaster.cpp | 15 +++++++++------ Core/Debugger/WebSocket/SteppingBroadcaster.h | 1 + 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index 89e91bc63dab..35b1a8c12489 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -21,15 +21,18 @@ #include "Core/System.h" void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { - // TODO: This is somewhat primitive. It'd be nice to register a callback with Core instead? - if (coreState != prevState_ && PSP_IsInited()) { - // We ignore CORE_POWERDOWN. - if (coreState == CORE_STEPPING) { + if (PSP_IsInited()) { + int steppingCounter = Core_GetSteppingCounter(); + // We ignore CORE_POWERDOWN as a stepping state. + if (coreState == CORE_STEPPING && steppingCounter != lastCounter_) { // TODO: Should send more data proactively. ws->Send(R"({"event":"cpu.stepping"})"); - } else if (prevState_ == CORE_STEPPING && Core_IsActive()) { + } else if (prevState_ == CORE_STEPPING && coreState != CORE_STEPPING && Core_IsActive()) { ws->Send(R"({"event":"cpu.resume"})"); } + lastCounter_ = steppingCounter; + prevState_ = coreState; + } else { + prevState_ = CORE_POWERDOWN; } - prevState_ = coreState; } diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.h b/Core/Debugger/WebSocket/SteppingBroadcaster.h index df196d9ff8ae..4ca34f4552cb 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.h +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.h @@ -33,4 +33,5 @@ struct SteppingBroadcaster { private: CoreState prevState_; + int lastCounter_ = 0; }; From f81fa27abe138b418af5a362a16650151e76e489 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 13:54:44 -0700 Subject: [PATCH 14/45] Debugger: Add config for remote debugger on start. --- Core/Config.cpp | 1 + Core/Config.h | 1 + UI/NativeApp.cpp | 8 +++++--- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 51b232e0eb52..b458914d10b3 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -380,6 +380,7 @@ static ConfigSetting generalSettings[] = { ConfigSetting("RemoteISOManualConfig", &g_Config.bRemoteISOManual, false), ConfigSetting("RemoteShareOnStartup", &g_Config.bRemoteShareOnStartup, false), ConfigSetting("RemoteISOSubdir", &g_Config.sRemoteISOSubdir, "/"), + ConfigSetting("RemoteDebuggerOnStartup", &g_Config.bRemoteDebuggerOnStartup, false), #ifdef __ANDROID__ ConfigSetting("ScreenRotation", &g_Config.iScreenRotation, 1), diff --git a/Core/Config.h b/Core/Config.h index 05a482420368..e423013842ba 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -141,6 +141,7 @@ struct Config { bool bRemoteISOManual; bool bRemoteShareOnStartup; std::string sRemoteISOSubdir; + bool bRemoteDebuggerOnStartup; bool bMemStickInserted; int iScreenRotation; // The rotation angle of the PPSSPP UI. Only supported on Android and possibly other mobile platforms. diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 8ae0bedb41dc..5e8b8deaf0aa 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -587,10 +587,12 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch screenManager->switchScreen(new LogoScreen()); } - if (g_Config.bRemoteShareOnStartup) { - // TODO: Separate config options. + if (g_Config.bRemoteShareOnStartup && g_Config.bRemoteDebuggerOnStartup) StartWebServer(WebServerFlags::ALL); - } + else if (g_Config.bRemoteShareOnStartup) + StartWebServer(WebServerFlags::DISCS); + else if (g_Config.bRemoteDebuggerOnStartup) + StartWebServer(WebServerFlags::DEBUGGER); std::string sysName = System_GetProperty(SYSPROP_NAME); isOuya = KeyMap::IsOuya(sysName); From a2d82cb6547ad30b778ae940e10d0d5a8d5c812f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 14:17:33 -0700 Subject: [PATCH 15/45] Debugger: Add checkbox to enable remote debugger. It's not a setting though, so maybe a button would be better. Or, it could be the startup setting... --- UI/GameSettingsScreen.cpp | 24 ++++++++++++++++++++++++ UI/GameSettingsScreen.h | 6 +++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index cced4b40f3fa..0ad1a44cf5ed 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -52,6 +52,7 @@ #include "Core/Host.h" #include "Core/System.h" #include "Core/Reporting.h" +#include "Core/WebServer.h" #include "GPU/Common/PostShader.h" #include "android/jni/TestRunner.h" #include "GPU/GPUInterface.h" @@ -1270,6 +1271,12 @@ void DeveloperToolsScreen::CreateViews() { createTextureIni->SetEnabled(false); } #endif + + allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); + canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); + CheckBox *allowDebugger = new CheckBox(&allowDebugger_, dev->T("Allow remote debugger")); + list->Add(allowDebugger)->OnClick.Handle(this, &DeveloperToolsScreen::OnRemoteDebugger); + allowDebugger->SetEnabledPtr(&canAllowDebugger_); } void DeveloperToolsScreen::onFinish(DialogResult result) { @@ -1371,6 +1378,23 @@ UI::EventReturn DeveloperToolsScreen::OnJitAffectingSetting(UI::EventParams &e) return UI::EVENT_DONE; } +UI::EventReturn DeveloperToolsScreen::OnRemoteDebugger(UI::EventParams &e) { + if (allowDebugger_) { + StartWebServer(WebServerFlags::DEBUGGER); + } else { + StopWebServer(WebServerFlags::DEBUGGER); + } + // Persist the setting. Maybe should separate? + g_Config.bRemoteDebuggerOnStartup = allowDebugger_; + return UI::EVENT_CONTINUE; +} + +void DeveloperToolsScreen::update() { + UIDialogScreenWithBackground::update(); + allowDebugger_ = !WebServerStopped(WebServerFlags::DEBUGGER); + canAllowDebugger_ = !WebServerStopping(WebServerFlags::DEBUGGER); +} + void ProAdhocServerScreen::CreateViews() { using namespace UI; I18NCategory *sy = GetI18NCategory("System"); diff --git a/UI/GameSettingsScreen.h b/UI/GameSettingsScreen.h index 3acad0dcfe1f..c68fb8939a37 100644 --- a/UI/GameSettingsScreen.h +++ b/UI/GameSettingsScreen.h @@ -140,13 +140,13 @@ class SettingInfoMessage : public UI::LinearLayout { class DeveloperToolsScreen : public UIDialogScreenWithBackground { public: DeveloperToolsScreen() {} + void update() override; void onFinish(DialogResult result) override; protected: void CreateViews() override; private: - UI::EventReturn OnBack(UI::EventParams &e); UI::EventReturn OnRunCPUTests(UI::EventParams &e); UI::EventReturn OnLoggingChanged(UI::EventParams &e); UI::EventReturn OnLoadLanguageIni(UI::EventParams &e); @@ -154,6 +154,10 @@ class DeveloperToolsScreen : public UIDialogScreenWithBackground { UI::EventReturn OnOpenTexturesIniFile(UI::EventParams &e); UI::EventReturn OnLogConfig(UI::EventParams &e); UI::EventReturn OnJitAffectingSetting(UI::EventParams &e); + UI::EventReturn OnRemoteDebugger(UI::EventParams &e); + + bool allowDebugger_ = false; + bool canAllowDebugger_ = true; }; class ProAdhocServerScreen : public UIDialogScreenWithBackground { From 6bec3db3fb623c193583cb1c1002399085f42dfa Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 15:33:34 -0700 Subject: [PATCH 16/45] Debugger: Disconnect on shutdown/disable. Although, it could be made safe to keep them up when restarting with debugging still enabled. --- Core/Debugger/WebSocket.cpp | 31 +++++++++++++++++++++++++++++++ Core/Debugger/WebSocket.h | 2 ++ Core/WebServer.cpp | 2 ++ ext/native/net/http_server.cpp | 2 +- ext/native/net/http_server.h | 1 + 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 6343a8331d75..d42fdbd3a262 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -15,6 +15,8 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include +#include #include "thread/threadutil.h" #include "Core/Debugger/WebSocket.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" @@ -54,12 +56,25 @@ static const std::vector subscribers({ { &WebSocketCPUCoreInit, nullptr }, }); +// To handle webserver restart, keep track of how many running. +static volatile int debuggersConnected = 0; +static volatile bool stopRequested = false; +static std::mutex stopLock; +static std::condition_variable stopCond; + +static void UpdateConnected(int delta) { + std::lock_guard guard(stopLock); + debuggersConnected += delta; + stopCond.notify_all(); +} + void HandleDebuggerRequest(const http::Request &request) { net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); if (!ws) return; setCurrentThreadName("Debugger"); + UpdateConnected(1); LogBroadcaster logger; GameBroadcaster game; @@ -103,6 +118,10 @@ void HandleDebuggerRequest(const http::Request &request) { logger.Broadcast(ws); game.Broadcast(ws); stepping.Broadcast(ws); + + if (stopRequested) { + ws->Close(net::WebSocketClose::GOING_AWAY); + } } for (size_t i = 0; i < subscribers.size(); ++i) { @@ -114,4 +133,16 @@ void HandleDebuggerRequest(const http::Request &request) { } delete ws; + UpdateConnected(-1); +} + +void StopAllDebuggers() { + std::unique_lock guard(stopLock); + while (debuggersConnected != 0) { + stopRequested = true; + stopCond.wait(guard); + } + + // Reset it back for next time. + stopRequested = false; } diff --git a/Core/Debugger/WebSocket.h b/Core/Debugger/WebSocket.h index 73b4e5fe8ff0..0151ca686a43 100644 --- a/Core/Debugger/WebSocket.h +++ b/Core/Debugger/WebSocket.h @@ -22,3 +22,5 @@ class Request; } void HandleDebuggerRequest(const http::Request &request); +// Note: blocks. +void StopAllDebuggers(); diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index d0f7c39fe73e..f5fdf68754ca 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -216,6 +216,8 @@ static void ExecuteWebServer() { } http->Stop(); + StopAllDebuggers(); + delete http; // Move to STARTING to lock flags/STOPPING. if (UpdateStatus(ServerStatus::STARTING, ServerStatus::RESTARTING)) { diff --git a/ext/native/net/http_server.cpp b/ext/native/net/http_server.cpp index ace77907c45f..c77dd3716940 100644 --- a/ext/native/net/http_server.cpp +++ b/ext/native/net/http_server.cpp @@ -279,7 +279,7 @@ void Server::HandleConnection(int conn_fd) { WLOG("Bad request, ignoring."); return; } - HandleRequestDefault(request); + HandleRequest(request); // TODO: Way to mark the content body as read, read it here if never read. // This allows the handler to stream if need be. diff --git a/ext/native/net/http_server.h b/ext/native/net/http_server.h index 5220bda7622a..6fc97d907b3e 100644 --- a/ext/native/net/http_server.h +++ b/ext/native/net/http_server.h @@ -63,6 +63,7 @@ class Request { class Server { public: Server(threading::Executor *executor); + virtual ~Server() {} typedef std::function UrlHandlerFunc; typedef std::map UrlHandlerMap; From 77131e737bbf923b14e51a565331d471afcd5fdf Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 17:31:29 -0700 Subject: [PATCH 17/45] Log: Use a separate field for the timestamp. Better than parsing it since we already have the header separate. Simpler too. --- Common/ConsoleListener.cpp | 2 +- Common/LogManager.cpp | 18 ++++++---------- Common/LogManager.h | 3 ++- Core/Debugger/WebSocket/LogBroadcaster.cpp | 24 ++++++++++++++-------- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/Common/ConsoleListener.cpp b/Common/ConsoleListener.cpp index 34491ec49f2f..b5285e1f25a3 100644 --- a/Common/ConsoleListener.cpp +++ b/Common/ConsoleListener.cpp @@ -600,7 +600,7 @@ void ConsoleListener::PixelSpace(int Left, int Top, int Width, int Height, bool void ConsoleListener::Log(const LogMessage &msg) { char Text[2048]; - snprintf(Text, sizeof(Text), "%s %s", msg.header, msg.msg.c_str()); + snprintf(Text, sizeof(Text), "%s %s %s", msg.timestamp, msg.header, msg.msg.c_str()); Text[sizeof(Text) - 2] = '\n'; Text[sizeof(Text) - 1] = '\0'; diff --git a/Common/LogManager.cpp b/Common/LogManager.cpp index c138f3453347..65545b1fc082 100644 --- a/Common/LogManager.cpp +++ b/Common/LogManager.cpp @@ -225,22 +225,17 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const if (fileshort != file) file = fileshort + 1; } - - char formattedTime[13]; std::lock_guard lk(log_lock_); - Common::Timer::GetTimeFormatted(formattedTime); + Common::Timer::GetTimeFormatted(message.timestamp); - size_t prefixLen; if (hleCurrentThreadName) { - prefixLen = snprintf(message.header, sizeof(message.header), "%s %-12.12s %c[%s]: %s:%d", - formattedTime, + snprintf(message.header, sizeof(message.header), "%-12.12s %c[%s]: %s:%d", hleCurrentThreadName, level_to_char[(int)level], log.m_shortName, file, line); } else { - prefixLen = snprintf(message.header, sizeof(message.header), "%s %s:%d %c[%s]:", - formattedTime, + snprintf(message.header, sizeof(message.header), "%s:%d %c[%s]:", file, line, level_to_char[(int)level], log.m_shortName); } @@ -250,15 +245,14 @@ void LogManager::Log(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const va_copy(args_copy, args); size_t neededBytes = vsnprintf(msgBuf, sizeof(msgBuf), format, args); + message.msg.resize(neededBytes + 1); if (neededBytes > sizeof(msgBuf)) { // Needed more space? Re-run vsnprintf. - message.msg.resize(neededBytes + 1); vsnprintf(&message.msg[0], neededBytes + 1, format, args_copy); } else { - message.msg.resize(neededBytes + 1); memcpy(&message.msg[0], msgBuf, neededBytes); } - message.msg[message.msg.size() - 1] = '\n'; + message.msg[neededBytes] = '\n'; va_end(args_copy); std::lock_guard listeners_lock(listeners_lock_); @@ -313,7 +307,7 @@ void FileLogListener::Log(const LogMessage &message) { return; std::lock_guard lk(m_log_lock); - m_logfile << message.header << " " << message.msg << std::flush; + m_logfile << message.timestamp << " " << message.header << " " << message.msg << std::flush; } void OutputDebugStringLogListener::Log(const LogMessage &message) { diff --git a/Common/LogManager.h b/Common/LogManager.h index 3a778ba11c0e..1e8e39547d45 100644 --- a/Common/LogManager.h +++ b/Common/LogManager.h @@ -34,7 +34,8 @@ extern const char *hleCurrentThreadName; // Struct that listeners can output how they want. For example, on Android we don't want to add // timestamp or write the level as a string, those already exist. struct LogMessage { - char header[64]; // timestamp and the other stuff in front... + char timestamp[16]; + char header[64]; // Filename/thread/etc. in front. LogTypes::LOG_LEVELS level; const char *log; std::string msg; // The actual log message. diff --git a/Core/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp index 2474294e0a7a..07070be7b014 100644 --- a/Core/Debugger/WebSocket/LogBroadcaster.cpp +++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp @@ -86,27 +86,33 @@ LogBroadcaster::~LogBroadcaster() { } struct DebuggerLogEvent { - std::string header; - std::string message; - int level; - const char *channel; + const LogMessage &l; operator std::string() { JsonWriter j; j.begin(); j.writeString("event", "log"); - j.writeString("header", header); - j.writeString("message", message); - j.writeInt("level", level); - j.writeString("channel", channel); + j.writeString("timestamp", l.timestamp); + j.writeString("header", l.header); + j.writeString("message", l.msg); + j.writeInt("level", l.level); + j.writeString("channel", l.log); j.end(); return j.str(); } }; +// Log message (log) +// +// Sent unexpectedly with these properties: +// - timestamp: string timestamp of event. +// - header: string header information about the event (including file/line.) +// - message: actual log message as a string. +// - level: number severity level (1 = highest.) +// - channel: string describing log channel / grouping. void LogBroadcaster::Broadcast(net::WebSocketServer *ws) { auto messages = listener_->GetMessages(); for (auto msg : messages) { - ws->Send(DebuggerLogEvent{msg.header, msg.msg, msg.level, msg.log}); + ws->Send(DebuggerLogEvent{msg}); } } From 3dac5f21032694a338536139b4c1eadd50ae8a67 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 21 Apr 2018 18:26:36 -0700 Subject: [PATCH 18/45] Debugger: Add current game info. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/Debugger/WebSocket.cpp | 2 + Core/Debugger/WebSocket/GameBroadcaster.cpp | 74 ++++++++++++++++++--- Core/Debugger/WebSocket/GameSubscriber.cpp | 51 ++++++++++++++ Core/Debugger/WebSocket/GameSubscriber.h | 24 +++++++ Core/System.cpp | 4 ++ Core/System.h | 1 + android/jni/Android.mk | 1 + 10 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 Core/Debugger/WebSocket/GameSubscriber.cpp create mode 100644 Core/Debugger/WebSocket/GameSubscriber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ea61d912b6f..9d8d1eb29535 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1408,6 +1408,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket/CPUCoreSubscriber.h Core/Debugger/WebSocket/GameBroadcaster.cpp Core/Debugger/WebSocket/GameBroadcaster.h + Core/Debugger/WebSocket/GameSubscriber.cpp + Core/Debugger/WebSocket/GameSubscriber.h Core/Debugger/WebSocket/LogBroadcaster.cpp Core/Debugger/WebSocket/LogBroadcaster.h Core/Debugger/WebSocket/SteppingBroadcaster.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index c102fd2cb72c..4e8826e6e14e 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -187,6 +187,7 @@ + @@ -539,6 +540,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 3a8a72a82eb0..e0d584013120 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -713,6 +713,9 @@ Debugger\WebSocket + + Debugger\WebSocket + @@ -1313,6 +1316,9 @@ Debugger\WebSocket + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index d42fdbd3a262..2f5aafa403f8 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -44,6 +44,7 @@ #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" +#include "Core/Debugger/WebSocket/GameSubscriber.h" typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); typedef void (*Subscribershutdown)(void *p); @@ -54,6 +55,7 @@ struct SubscriberInfo { static const std::vector subscribers({ { &WebSocketCPUCoreInit, nullptr }, + { &WebSocketGameInit, nullptr }, }); // To handle webserver restart, keep track of how many running. diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp index 37a9e2c919cb..4c53522a98ed 100644 --- a/Core/Debugger/WebSocket/GameBroadcaster.cpp +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -17,22 +17,78 @@ #include "Core/Debugger/WebSocket/GameBroadcaster.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/ELF/ParamSFO.h" #include "Core/System.h" +struct GameStatusEvent { + const char *ev; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", ev); + if (PSP_IsInited()) { + j.pushDict("game"); + j.writeString("id", g_paramSFO.GetDiscID()); + j.writeString("version", g_paramSFO.GetValueString("DISC_VERSION")); + j.writeString("title", g_paramSFO.GetValueString("TITLE")); + j.pop(); + } else { + j.writeRaw("game", "null"); + } + j.end(); + return j.str(); + } +}; + +// Game started (game.start) +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. + +// Game quit / ended (game.quit) +// +// Sent unexpectedly with these properties: +// - game: null + +// Game paused (game.pause) +// +// Note: this is not the same as stepping. This means the user went to the pause menu. +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. + +// Game resumed (game.pause) +// +// Note: this is not the same as stepping. This means the user resumed from the pause menu. +// +// Sent unexpectedly with these properties: +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. void GameBroadcaster::Broadcast(net::WebSocketServer *ws) { - // TODO: This is ugly. Implement proper information instead. - // TODO: Should probably include info about which game, etc. + // TODO: This is ugly. Callbacks instead? GlobalUIState state = GetUIState(); if (prevState_ != state) { if (state == UISTATE_PAUSEMENU) { - ws->Send(R"({"event":"game.pause"})"); + ws->Send(GameStatusEvent{"game.pause"}); + prevState_ = state; } else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) { - ws->Send(R"({"event":"game.resume"})"); - } else if (state == UISTATE_INGAME) { - ws->Send(R"({"event":"game.start"})"); - } else if (state == UISTATE_MENU) { - ws->Send(R"({"event":"game.quit"})"); + ws->Send(GameStatusEvent{"game.resume"}); + prevState_ = state; + } else if (state == UISTATE_INGAME && PSP_IsInited()) { + ws->Send(GameStatusEvent{"game.start"}); + prevState_ = state; + } else if (state == UISTATE_MENU && !PSP_IsInited() && !PSP_IsQuitting()) { + ws->Send(GameStatusEvent{"game.quit"}); + prevState_ = state; } - prevState_ = state; } } diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp new file mode 100644 index 000000000000..2e91505ac2e8 --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -0,0 +1,51 @@ +// 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 "Core/Debugger/WebSocket/GameSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/ELF/ParamSFO.h" +#include "Core/System.h" + +void *WebSocketGameInit(DebuggerEventHandlerMap &map) { + map["game.status"] = &WebSocketGameStatus; + + return nullptr; +} + +// Check game status (game.status) +// +// No parameters. +// +// Response (same event name): +// - game: null or an object with properties: +// - id: string disc ID (such as ULUS12345.) +// - version: string disc version. +// - title: string game title. +// - paused: boolean, true when gameplay is paused (not the same as stepping.) +void WebSocketGameStatus(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + if (PSP_IsInited()) { + json.pushDict("game"); + json.writeString("id", g_paramSFO.GetDiscID()); + json.writeString("version", g_paramSFO.GetValueString("DISC_VERSION")); + json.writeString("title", g_paramSFO.GetValueString("TITLE")); + json.pop(); + } else { + json.writeRaw("game", "null"); + } + json.writeBool("paused", GetUIState() == UISTATE_PAUSEMENU); +} diff --git a/Core/Debugger/WebSocket/GameSubscriber.h b/Core/Debugger/WebSocket/GameSubscriber.h new file mode 100644 index 000000000000..4236b54885c0 --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.h @@ -0,0 +1,24 @@ +// 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 *WebSocketGameInit(DebuggerEventHandlerMap &map); + +void WebSocketGameStatus(DebuggerRequest &req); diff --git a/Core/System.cpp b/Core/System.cpp index 6355c66dd387..b59a36587212 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -385,6 +385,10 @@ bool PSP_IsInited() { return pspIsInited && !pspIsQuitting; } +bool PSP_IsQuitting() { + return pspIsQuitting; +} + void PSP_Shutdown() { // Do nothing if we never inited. if (!pspIsInited && !pspIsIniting && !pspIsQuitting) { diff --git a/Core/System.h b/Core/System.h index 32182c3bc281..f25974970e1b 100644 --- a/Core/System.h +++ b/Core/System.h @@ -66,6 +66,7 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string); bool PSP_InitUpdate(std::string *error_string); bool PSP_IsIniting(); bool PSP_IsInited(); +bool PSP_IsQuitting(); void PSP_Shutdown(); void PSP_BeginHostFrame(); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index b5b8dda1d227..9e32c054a7b0 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -303,6 +303,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/WebSocket.cpp \ $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/WebSocketUtils.cpp \ From 05c560b52d3082a8d8ce6c45c9e4408d8e816c19 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 22 Apr 2018 00:04:28 -0700 Subject: [PATCH 19/45] Debugger: Include PC and ticks in stepping events. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 7 +++++ .../WebSocket/SteppingBroadcaster.cpp | 27 +++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 2453675a337f..3d42a46df358 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -17,6 +17,7 @@ #include "Common/StringUtils.h" #include "Core/Core.h" +#include "Core/CoreTiming.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/MIPS/MIPS.h" @@ -79,10 +80,16 @@ void WebSocketCPUResume(DebuggerRequest &req) { // Response (same event name): // - stepping: boolean, CPU currently stepping. // - paused: boolean, CPU paused or not started yet. +// - pc: number value of PC register (inaccurate unless stepping.) +// - ticks: number of CPU cycles into emulation. void WebSocketCPUStatus(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN); json.writeBool("paused", GetUIState() != UISTATE_INGAME); + // Avoid NULL deference. + json.writeFloat("pc", PSP_IsInited() ? currentMIPS->pc : 0); + // A double ought to be good enough for a 156 day debug session. + json.writeFloat("ticks", PSP_IsInited() ? CoreTiming::GetTicks() : 0); } // Retrieve all regs and their values (cpu.getAllRegs) diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index 35b1a8c12489..9b9cbf3200ae 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -16,17 +16,40 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "Core/Core.h" +#include "Core/CoreTiming.h" #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MIPS/MIPS.h" #include "Core/System.h" +struct CPUSteppingEvent { + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "cpu.stepping"); + j.writeFloat("pc", currentMIPS->pc); + // A double ought to be good enough for a 156 day debug session. + j.writeFloat("ticks", CoreTiming::GetTicks()); + j.end(); + return j.str(); + } +}; + +// CPU has begun stepping (cpu.stepping) +// +// Sent unexpectedly with these properties: +// - pc: number value of PC register (inaccurate unless stepping.) +// - ticks: number of CPU cycles into emulation. + +// CPU has resumed from stepping (cpu.resume) +// +// Sent unexpectedly with no other properties. void SteppingBroadcaster::Broadcast(net::WebSocketServer *ws) { if (PSP_IsInited()) { int steppingCounter = Core_GetSteppingCounter(); // We ignore CORE_POWERDOWN as a stepping state. if (coreState == CORE_STEPPING && steppingCounter != lastCounter_) { - // TODO: Should send more data proactively. - ws->Send(R"({"event":"cpu.stepping"})"); + ws->Send(CPUSteppingEvent()); } else if (prevState_ == CORE_STEPPING && coreState != CORE_STEPPING && Core_IsActive()) { ws->Send(R"({"event":"cpu.resume"})"); } From b756d921399e728d9f4669c8becdf3ca7d536525 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 22 Apr 2018 00:28:41 -0700 Subject: [PATCH 20/45] Debugger: Add version event for future proofing. Game isn't precisely right, but it's close enough. --- Core/Debugger/WebSocket.cpp | 4 ++++ Core/Debugger/WebSocket/GameSubscriber.cpp | 16 ++++++++++++++++ Core/Debugger/WebSocket/GameSubscriber.h | 1 + 3 files changed, 21 insertions(+) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 2f5aafa403f8..e99253b81353 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -38,6 +38,10 @@ // - "message": A string describing what happened. // - "level": Integer severity level. (1 = NOTICE, 2 = ERROR, 3 = WARN, 4 = INFO, 5 = DEBUG, 6 = VERBOSE) // - "ticket": Optional, present if in response to an event with a "ticket" field, simply repeats that value. +// +// At start, please send a "version" event. See WebSocket/GameSubscriber.cpp for more details. +// +// For other events, look inside Core/Debugger/WebSocket/ for details on each event. #include "Core/Debugger/WebSocket/GameBroadcaster.h" #include "Core/Debugger/WebSocket/LogBroadcaster.h" diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index 2e91505ac2e8..e2241ac04e1c 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -22,6 +22,7 @@ void *WebSocketGameInit(DebuggerEventHandlerMap &map) { map["game.status"] = &WebSocketGameStatus; + map["version"] = &WebSocketVersion; return nullptr; } @@ -49,3 +50,18 @@ void WebSocketGameStatus(DebuggerRequest &req) { } json.writeBool("paused", GetUIState() == UISTATE_PAUSEMENU); } + +// Notify debugger version info (version) +// +// Parameters: +// - name: string indicating name of app or tool. +// - version: string version. +// +// Response (same event name): +// - name: string, "PPSSPP" unless some special build. +// - version: string, typically starts with "v" and may have git build info. +void WebSocketVersion(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeString("name", "PPSSPP"); + json.writeString("version", PPSSPP_GIT_VERSION); +} diff --git a/Core/Debugger/WebSocket/GameSubscriber.h b/Core/Debugger/WebSocket/GameSubscriber.h index 4236b54885c0..77cec9e38b20 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.h +++ b/Core/Debugger/WebSocket/GameSubscriber.h @@ -22,3 +22,4 @@ void *WebSocketGameInit(DebuggerEventHandlerMap &map); void WebSocketGameStatus(DebuggerRequest &req); +void WebSocketVersion(DebuggerRequest &req); From 4da97b3f9e999bd12d531b4f3dcba50c0d099e3b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 22 Apr 2018 08:33:22 -0700 Subject: [PATCH 21/45] Debugger: Lock during startup/shutdown. Otherwise things can get freed while we're trying to inspect them. --- Core/Debugger/WebSocket.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index e99253b81353..5b323bce4bce 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -68,12 +68,43 @@ static volatile bool stopRequested = false; static std::mutex stopLock; static std::condition_variable stopCond; +// Prevent threading surprises and obscure crashes by locking startup/shutdown. +static bool lifecycleLockSetup = false; +static std::mutex lifecycleLock; + static void UpdateConnected(int delta) { std::lock_guard guard(stopLock); debuggersConnected += delta; stopCond.notify_all(); } +static void WebSocketNotifyLifecycle(CoreLifecycle stage) { + switch (stage) { + case CoreLifecycle::STARTING: + case CoreLifecycle::STOPPING: + if (debuggersConnected > 0) { + DEBUG_LOG(SYSTEM, "Waiting for debugger to complete on shutdown"); + } + lifecycleLock.lock(); + break; + + case CoreLifecycle::START_COMPLETE: + case CoreLifecycle::STOPPED: + lifecycleLock.unlock(); + if (debuggersConnected > 0) { + DEBUG_LOG(SYSTEM, "Debugger ready for shutdown"); + } + break; + } +} + +static void SetupDebuggerLock() { + if (!lifecycleLockSetup) { + Core_ListenLifecycle(&WebSocketNotifyLifecycle); + lifecycleLockSetup = true; + } +} + void HandleDebuggerRequest(const http::Request &request) { net::WebSocketServer *ws = net::WebSocketServer::CreateAsUpgrade(request, "debugger.ppsspp.org"); if (!ws) @@ -81,6 +112,7 @@ void HandleDebuggerRequest(const http::Request &request) { setCurrentThreadName("Debugger"); UpdateConnected(1); + SetupDebuggerLock(); LogBroadcaster logger; GameBroadcaster game; @@ -89,6 +121,7 @@ void HandleDebuggerRequest(const http::Request &request) { std::unordered_map eventHandlers; std::vector subscriberData; for (auto info : subscribers) { + std::lock_guard guard(lifecycleLock); subscriberData.push_back(info.init(eventHandlers)); } @@ -109,6 +142,7 @@ void HandleDebuggerRequest(const http::Request &request) { DebuggerRequest req(event, ws, root); auto eventFunc = eventHandlers.find(event); if (eventFunc != eventHandlers.end()) { + std::lock_guard guard(lifecycleLock); eventFunc->second(req); req.Finish(); } else { @@ -120,6 +154,7 @@ void HandleDebuggerRequest(const http::Request &request) { }); while (ws->Process(1.0f / 60.0f)) { + std::lock_guard guard(lifecycleLock); // These send events that aren't just responses to requests. logger.Broadcast(ws); game.Broadcast(ws); @@ -130,6 +165,7 @@ void HandleDebuggerRequest(const http::Request &request) { } } + std::lock_guard guard(lifecycleLock); for (size_t i = 0; i < subscribers.size(); ++i) { if (subscribers[i].shutdown) { subscribers[i].shutdown(subscriberData[i]); From 944948a5f4ce172a78fe1995c99d89a9e79cabe4 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 22 Apr 2018 20:35:24 -0700 Subject: [PATCH 22/45] Debugger: Initial disasm API. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/Debugger/WebSocket.cpp | 2 + Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 2 +- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 244 ++++++++++++++++++ Core/Debugger/WebSocket/DisasmSubscriber.h | 23 ++ Core/Debugger/WebSocket/WebSocketUtils.cpp | 108 +++++--- Core/Debugger/WebSocket/WebSocketUtils.h | 11 +- android/jni/Android.mk | 2 + 10 files changed, 366 insertions(+), 36 deletions(-) create mode 100644 Core/Debugger/WebSocket/DisasmSubscriber.cpp create mode 100644 Core/Debugger/WebSocket/DisasmSubscriber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d8d1eb29535..b61d39d89b9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1406,6 +1406,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket.h Core/Debugger/WebSocket/CPUCoreSubscriber.cpp Core/Debugger/WebSocket/CPUCoreSubscriber.h + Core/Debugger/WebSocket/DisasmSubscriber.cpp + Core/Debugger/WebSocket/DisasmSubscriber.h Core/Debugger/WebSocket/GameBroadcaster.cpp Core/Debugger/WebSocket/GameBroadcaster.h Core/Debugger/WebSocket/GameSubscriber.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 4e8826e6e14e..d21c3913240f 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -189,6 +189,7 @@ + @@ -541,6 +542,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index e0d584013120..d9d65387df75 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -716,6 +716,9 @@ Debugger\WebSocket + + Debugger\WebSocket + @@ -1319,6 +1322,9 @@ Debugger\WebSocket + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 5b323bce4bce..390dd4fa002f 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -48,6 +48,7 @@ #include "Core/Debugger/WebSocket/SteppingBroadcaster.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" +#include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/GameSubscriber.h" typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); @@ -59,6 +60,7 @@ struct SubscriberInfo { static const std::vector subscribers({ { &WebSocketCPUCoreInit, nullptr }, + { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, { &WebSocketGameInit, nullptr }, }); diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 3d42a46df358..2d6ee2f9e827 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -296,7 +296,7 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { } uint32_t val; - if (!req.ParamU32OrFloatBits("value", &val)) { + if (!req.ParamU32("value", &val, true)) { // Already sent error. return; } diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp new file mode 100644 index 000000000000..37b4bc655cd4 --- /dev/null +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -0,0 +1,244 @@ +// 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 "base/stringutil.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/WebSocket/DisasmSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +struct WebSocketDisasmState { + WebSocketDisasmState() { + disasm_.setCpu(currentDebugMIPS); + } + + void Base(DebuggerRequest &req); + void Disasm(DebuggerRequest &req); + +protected: + void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l); + void WriteBranchGuide(JsonWriter &json, const BranchLine &l); + + DisassemblyManager disasm_; +}; + +void *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { + auto p = new WebSocketDisasmState(); + map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1); + map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1); + + return p; +} + +void WebSocketDisasmShutdown(void *p) { + delete static_cast(p); +} + +void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) { + u32 addr = l.info.opcodeAddress; + json.pushDict(); + if (l.type == DISTYPE_OPCODE) + json.writeString("type", "opcode"); + else if (l.type == DISTYPE_MACRO) + json.writeString("type", "macro"); + else if (l.type == DISTYPE_DATA) + json.writeString("type", "data"); + else if (l.type == DISTYPE_OTHER) + json.writeString("type", "other"); + + json.writeFloat("address", addr); + json.writeInt("addressSize", l.totalSize); + json.writeFloat("encoding", l.info.encodedOpcode.encoding); + int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF; + json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16)); + json.writeString("name", l.name); + json.writeString("params", l.params); + + const std::string addressSymbol = g_symbolMap->GetLabelString(addr); + if (addressSymbol.empty()) + json.writeRaw("symbol", "null"); + else + json.writeString("symbol", addressSymbol); + + bool enabled; + // TODO: Account for bp inside macro? + if (CBreakPoints::IsAddressBreakPoint(addr, &enabled)) { + json.pushDict("breakpoint"); + json.writeBool("enabled", enabled); + auto cond = CBreakPoints::GetBreakPointCondition(addr); + if (cond) + json.writeString("expression", cond->expressionString); + else + json.writeRaw("expression", "null"); + json.pop(); + } else { + json.writeRaw("breakpoint", "null"); + } + + json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr); + if (l.info.isBranch) { + json.pushDict("branch"); + if (!l.info.isBranchToRegister) { + json.writeFloat("targetAddress", l.info.branchTarget); + json.writeRaw("register", "null"); + } else { + json.writeRaw("targetAddress", "null"); + json.writeInt("register", l.info.branchRegisterNum); + } + json.writeBool("isLinked", l.info.isLinkedBranch); + json.writeBool("isLikely", l.info.isLikelyBranch); + json.pop(); + } else { + json.writeRaw("branch", "null"); + } + + if (l.info.hasRelevantAddress) { + json.pushDict("relevantData"); + json.writeFloat("address", l.info.relevantAddress); + if (Memory::IsValidRange(l.info.relevantAddress, 4)) + json.writeFloat("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress)); + else + json.writeRaw("uintValue", "null"); + json.pop(); + } else { + json.writeRaw("relevantData", "null"); + } + + if (l.info.isConditional) + json.writeBool("conditionMet", l.info.conditionMet); + else + json.writeRaw("conditionMet", "null"); + + if (l.info.isDataAccess) { + json.pushDict("dataAccess"); + json.writeFloat("address", l.info.dataAddress); + json.writeInt("size", l.info.dataSize); + + std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress); + std::string valueSymbol; + if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize)) + json.writeRaw("uintValue", "null"); + else if (l.info.dataSize == 1) + json.writeFloat("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress)); + else if (l.info.dataSize == 2) + json.writeFloat("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress)); + else if (l.info.dataSize >= 4) { + u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress); + valueSymbol = g_symbolMap->GetLabelString(data); + json.writeFloat("uintValue", data); + } + + if (!dataSymbol.empty()) + json.writeString("symbol", dataSymbol); + else + json.writeRaw("symbol", "null"); + if (!valueSymbol.empty()) + json.writeString("valueSymbol", valueSymbol); + else + json.writeRaw("valueSymbol", "null"); + json.pop(); + } else { + json.writeRaw("dataAccess", "null"); + } + + json.pop(); +} + +void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) { + json.pushDict(); + json.writeFloat("top", l.first); + json.writeFloat("bottom", l.second); + if (l.type == LINE_UP) + json.writeString("direction", "up"); + else if (l.type == LINE_DOWN) + json.writeString("direction", "down"); + else if (l.type == LINE_RIGHT) + json.writeString("direction", "right"); + json.writeInt("lane", l.laneIndex); + json.pop(); +} + +void WebSocketDisasmState::Base(DebuggerRequest &req) { + JsonWriter &json = req.Respond(); + json.writeString("addressHex", StringFromFormat("%016llx", Memory::base)); +} + +void WebSocketDisasmState::Disasm(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + return req.Fail("CPU not started"); + } + + uint32_t start, end; + if (!req.ParamU32("address", &start)) + return; + uint32_t count = 0; + if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL)) + return; + if (count != 0) { + // Let's assume everything is two instructions. + disasm_.analyze(start - 4, count * 8 + 8); + start = disasm_.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + end = disasm_.getNthNextAddress(start, count); + } else if (req.ParamU32("end", &end)) { + // Let's assume everything is two instructions. + disasm_.analyze(start - 4, end - start + 8); + start = disasm_.getStartAddress(start); + if (start == -1) + req.ParamU32("address", &start); + + // Correct end and calculate count based on it. + // This accounts for macros as one line, although two instructions. + u32 stop = end; + count = 0; + for (end = start; end < stop; end = disasm_.getNthNextAddress(end, 1)) { + count++; + } + } else { + // Error message already sent. + return; + } + + bool displaySymbols = true; + if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) + return; + + JsonWriter &json = req.Respond(); + json.pushDict("range"); + json.writeFloat("start", start); + json.writeFloat("end", end); + json.pop(); + + json.pushArray("lines"); + DisassemblyLineInfo line; + u32 addr = start; + for (u32 i = 0; i < count; ++i) { + disasm_.getLine(addr, displaySymbols, line); + WriteDisasmLine(json, line); + addr += line.totalSize; + } + json.pop(); + + json.pushArray("branchGuides"); + auto branchGuides = disasm_.getBranchLines(start, end - start); + for (auto bl : branchGuides) + WriteBranchGuide(json, bl); + json.pop(); +} diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.h b/Core/Debugger/WebSocket/DisasmSubscriber.h new file mode 100644 index 000000000000..e6c5b0e71c3a --- /dev/null +++ b/Core/Debugger/WebSocket/DisasmSubscriber.h @@ -0,0 +1,23 @@ +// 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 *WebSocketDisasmInit(DebuggerEventHandlerMap &map); +void WebSocketDisasmShutdown(void *p); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index 8d75122018ae..140ff8da826f 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -74,19 +74,35 @@ static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { return false; } -bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) { +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; + const JsonNode *node = data.get(name); if (!node) { - Fail(StringFromFormat("Missing '%s' parameter", name)); - return false; + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; } if (node->value.getTag() == JSON_NUMBER) { double val = node->value.toNumber(); bool isInteger = trunc(val) == val; - if (!isInteger) { - Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); + if (!isInteger && !allowLoose) { + // JSON doesn't give a great way to differentiate ints and floats. + // Let's play it safe and require a string. + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: use a string for non integer values", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); return false; + } else if (!isInteger && allowFloatBits) { + union { + float f; + uint32_t u; + } bits = { (float)val }; + *out = bits.u; + return true; } if (val < 0 && val >= std::numeric_limits::min()) { @@ -96,61 +112,87 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out) { } else if (val >= 0 && val <= std::numeric_limits::max()) { *out = (uint32_t)val; return true; + } else if (allowLoose) { + *out = val >= 0 ? std::numeric_limits::max() : std::numeric_limits::min(); + return true; } - Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name)); + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name)); return false; } if (node->value.getTag() != JSON_STRING) { - Fail(StringFromFormat("Invalid '%s' parameter type", name)); - return false; + if (required || node->value.getTag() != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; } - if (U32FromString(node->value.toString(), out, false)) + if (U32FromString(node->value.toString(), out, allowFloatBits)) return true; - Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); + if (allowFloatBits) + Fail(StringFromFormat("Could not parse '%s' parameter: number expected", name)); + else + Fail(StringFromFormat("Could not parse '%s' parameter: integer required", name)); return false; } -bool DebuggerRequest::ParamU32OrFloatBits(const char *name, uint32_t *out) { +bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType type) { + bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; + bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; + const JsonNode *node = data.get(name); if (!node) { - Fail(StringFromFormat("Missing '%s' parameter", name)); - return false; + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; } if (node->value.getTag() == JSON_NUMBER) { double val = node->value.toNumber(); - bool isInteger = trunc(val) == val; - - // JSON doesn't give a great way to differentiate ints and floats. - // Let's play it safe and require a string. - if (!isInteger) { - Fail(StringFromFormat("Could not parse '%s' parameter: use a string for non integer values", name)); - return false; - } - - if (val < 0 && val >= std::numeric_limits::min()) { - // Convert to unsigned representation. - *out = (uint32_t)(int32_t)val; - return true; - } else if (val >= 0 && val <= std::numeric_limits::max()) { - *out = (uint32_t)val; + if (val == 1.0 || val == 0.0 || allowLoose) { + *out = val != 0.0; return true; } - Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range (use string for float)", name)); + Fail(StringFromFormat("Could not parse '%s' parameter: should be true/1 or false/0", name)); return false; } + if (node->value.getTag() == JSON_TRUE) { + *out = true; + return true; + } + if (node->value.getTag() == JSON_FALSE) { + *out = false; + return true; + } if (node->value.getTag() != JSON_STRING) { - Fail(StringFromFormat("Invalid '%s' parameter type", name)); - return false; + if (type == DebuggerParamType::REQUIRED || node->value.getTag() != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; } - if (U32FromString(node->value.toString(), out, true)) + const std::string s = node->value.toString(); + if (s == "1" || s == "true") { + *out = true; + return true; + } + if (s == "0" || s == "false" || (s == "" && allowLoose)) { + *out = false; return true; + } + + if (allowLoose) { + *out = true; + return true; + } - Fail(StringFromFormat("Could not parse '%s' parameter: number expected", name)); + Fail(StringFromFormat("Could not parse '%s' parameter: boolean required", name)); return false; } diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 6e4e62d36c86..132789b26dfe 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -59,6 +59,13 @@ struct DebuggerErrorEvent { } }; +enum class DebuggerParamType { + REQUIRED, + OPTIONAL, + REQUIRED_LOOSE, + OPTIONAL_LOOSE, +}; + struct DebuggerRequest { DebuggerRequest(const char *n, net::WebSocketServer *w, const JsonGet &d) : name(n), ws(w), data(d) { @@ -73,8 +80,8 @@ struct DebuggerRequest { responseSent_ = true; } - bool ParamU32(const char *name, uint32_t *out); - bool ParamU32OrFloatBits(const char *name, uint32_t *out); + 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); JsonWriter &Respond(); void Finish(); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 9e32c054a7b0..de838dc4538b 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -299,9 +299,11 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/TextureReplacer.cpp \ $(SRC)/Core/WebServer.cpp \ $(SRC)/Core/Debugger/Breakpoints.cpp \ + $(SRC)/Core/Debugger/DisassemblyManager.cpp \ $(SRC)/Core/Debugger/SymbolMap.cpp \ $(SRC)/Core/Debugger/WebSocket.cpp \ $(SRC)/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ From 1c107308cd99d474ff3b0f0f545d92f755a47e1f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 27 Apr 2018 07:55:46 -0700 Subject: [PATCH 23/45] Debugger: Add additional encoding/symbol info. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 37b4bc655cd4..6e984e56160c 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -64,7 +64,16 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.writeFloat("address", addr); json.writeInt("addressSize", l.totalSize); - json.writeFloat("encoding", l.info.encodedOpcode.encoding); + json.writeFloat("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0); + if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) { + json.pushArray("macroEncoding"); + for (u32 off = 0; off < l.totalSize; off += 4) { + json.writeFloat(Memory::Read_Instruction(addr + off).encoding); + } + json.pop(); + } else { + json.writeRaw("macroEncoding", "null"); + } int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF; json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16)); json.writeString("name", l.name); @@ -94,7 +103,9 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr); if (l.info.isBranch) { json.pushDict("branch"); + std::string targetSymbol; if (!l.info.isBranchToRegister) { + targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget); json.writeFloat("targetAddress", l.info.branchTarget); json.writeRaw("register", "null"); } else { @@ -103,6 +114,10 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi } json.writeBool("isLinked", l.info.isLinkedBranch); json.writeBool("isLikely", l.info.isLikelyBranch); + if (targetSymbol.empty()) + json.writeRaw("symbol", "null"); + else + json.writeString("symbol", targetSymbol); json.pop(); } else { json.writeRaw("branch", "null"); From 8a0e02e223919c792565471222000ee93f00a748 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 28 Apr 2018 14:10:06 -0700 Subject: [PATCH 24/45] Debugger: Prevent crazy disasm range. Also add some documentation. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 44 +++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 6e984e56160c..25e15bec2f72 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -15,6 +15,7 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include "base/stringutil.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/DisassemblyManager.h" @@ -189,16 +190,56 @@ void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine & json.pop(); } +// Request the current PSP memory base address (memory.base) +// +// WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space. +// +// No parameters. +// +// Response (same event name): +// - addressHex: string indicating base address in hexadecimal (may be 64 bit.) void WebSocketDisasmState::Base(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.writeString("addressHex", StringFromFormat("%016llx", Memory::base)); } +// Disassemble a range of memory as CPU instructions (memory.disasm) +// +// Parameters (by count): +// - address: number specifying the start address. +// - count: number of lines to return (may be clamped to an internal limit.) +// - displaySymbols: boolean true to show symbol names in instruction params. +// +// Parameters (by end address): +// - address: number specifying the start address. +// - end: number which must be after the start address (may be clamped to an internal limit.) +// - displaySymbols: boolean true to show symbol names in instruction params. +// +// Response (same event name): +// - range: object with result "start" and "end" properties, the addresses actually used. +// (disassembly may have snapped to a nearby instruction.) +// - branchGuides: array of objects: +// - top: the earlier address as a number. +// - bottom: the later address as a number. +// - direction: "up", "down", or "right" depending on the flow of the branch. +// - lane: number index to avoid overlapping guides. +// - lines: array of objects: +// - type: "opcode", "macro", "data", or "other". +// - address: address of first actual instruction. +// - addressSize: bytes used by this line (might be more than 4.) +// - encoding: uint value of actual instruction (may differ from memory read when using jit.) +// - macroEncoding: null, or an array of encodings if this line represents multiple instructions. +// - name: string name of the instruction. +// - params: formatted parameters for the instruction. +// - (other info about the disassembled line.) void WebSocketDisasmState::Disasm(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } + // In case of client errors, we limit the range to something that won't make us crash. + static const uint32_t MAX_RANGE = 10000; + uint32_t start, end; if (!req.ParamU32("address", &start)) return; @@ -206,6 +247,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { if (!req.ParamU32("count", &count, false, DebuggerParamType::OPTIONAL)) return; if (count != 0) { + count = std::min(count, MAX_RANGE); // Let's assume everything is two instructions. disasm_.analyze(start - 4, count * 8 + 8); start = disasm_.getStartAddress(start); @@ -213,12 +255,12 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { req.ParamU32("address", &start); end = disasm_.getNthNextAddress(start, count); } else if (req.ParamU32("end", &end)) { + end = std::min(std::max(start, end), start + MAX_RANGE * 4); // Let's assume everything is two instructions. disasm_.analyze(start - 4, end - start + 8); start = disasm_.getStartAddress(start); if (start == -1) req.ParamU32("address", &start); - // Correct end and calculate count based on it. // This accounts for macros as one line, although two instructions. u32 stop = end; From f66738eac705d39378b1a2de60bd6c648433fd4b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 29 Apr 2018 19:30:14 -0700 Subject: [PATCH 25/45] Debugger: Add cpu.evaluate method. Probably useful for a bunch of things, really. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 34 ++++++++++ Core/Debugger/WebSocket/CPUCoreSubscriber.h | 1 + Core/Debugger/WebSocket/WebSocketUtils.cpp | 63 ++++++++++++++++--- Core/Debugger/WebSocket/WebSocketUtils.h | 1 + 4 files changed, 91 insertions(+), 8 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 2d6ee2f9e827..1b795899a74b 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -31,6 +31,7 @@ void *WebSocketCPUCoreInit(DebuggerEventHandlerMap &map) { map["cpu.getAllRegs"] = &WebSocketCPUGetAllRegs; map["cpu.getReg"] = &WebSocketCPUGetReg; map["cpu.setReg"] = &WebSocketCPUSetReg; + map["cpu.evaluate"] = &WebSocketCPUEvaluate; return nullptr; } @@ -334,3 +335,36 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { json.writeFloat("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } + +// Evaluate an expression (cpu.evaluate) +// +// Parameters: +// - expression: string containing labels, operators, regs, etc. +// +// Response (same event name): +// - uintValue: value in register. +// - floatValue: string showing float representation. May be "nan", "inf", or "-inf". +void WebSocketCPUEvaluate(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + std::string exp; + if (!req.ParamString("expression", &exp)) { + // Already sent error. + return; + } + + u32 val; + PostfixExpression postfix; + if (!currentDebugMIPS->initExpression(exp.c_str(), postfix)) { + return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + } + if (!currentDebugMIPS->parseExpression(postfix, val)) { + return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); + } + + JsonWriter &json = req.Respond(); + json.writeFloat("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.h b/Core/Debugger/WebSocket/CPUCoreSubscriber.h index effff73a7623..3667979e9649 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.h +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -27,3 +27,4 @@ void WebSocketCPUStatus(DebuggerRequest &req); void WebSocketCPUGetAllRegs(DebuggerRequest &req); void WebSocketCPUGetReg(DebuggerRequest &req); void WebSocketCPUSetReg(DebuggerRequest &req); +void WebSocketCPUEvaluate(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index 140ff8da826f..d21b93e9d378 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -85,7 +85,8 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatB return !required; } - if (node->value.getTag() == JSON_NUMBER) { + auto tag = node->value.getTag(); + if (tag == JSON_NUMBER) { double val = node->value.toNumber(); bool isInteger = trunc(val) == val; if (!isInteger && !allowLoose) { @@ -123,8 +124,8 @@ bool DebuggerRequest::ParamU32(const char *name, uint32_t *out, bool allowFloatB Fail(StringFromFormat("Could not parse '%s' parameter: outside 32 bit range", name)); return false; } - if (node->value.getTag() != JSON_STRING) { - if (required || node->value.getTag() != JSON_NULL) { + if (tag != JSON_STRING) { + if (required || tag != JSON_NULL) { Fail(StringFromFormat("Invalid '%s' parameter type", name)); return false; } @@ -152,7 +153,8 @@ bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType t return !required; } - if (node->value.getTag() == JSON_NUMBER) { + auto tag = node->value.getTag(); + if (tag == JSON_NUMBER) { double val = node->value.toNumber(); if (val == 1.0 || val == 0.0 || allowLoose) { *out = val != 0.0; @@ -162,16 +164,16 @@ bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType t Fail(StringFromFormat("Could not parse '%s' parameter: should be true/1 or false/0", name)); return false; } - if (node->value.getTag() == JSON_TRUE) { + if (tag == JSON_TRUE) { *out = true; return true; } - if (node->value.getTag() == JSON_FALSE) { + if (tag == JSON_FALSE) { *out = false; return true; } - if (node->value.getTag() != JSON_STRING) { - if (type == DebuggerParamType::REQUIRED || node->value.getTag() != JSON_NULL) { + if (tag != JSON_STRING) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { Fail(StringFromFormat("Invalid '%s' parameter type", name)); return false; } @@ -196,3 +198,48 @@ bool DebuggerRequest::ParamBool(const char *name, bool *out, DebuggerParamType t Fail(StringFromFormat("Could not parse '%s' parameter: boolean required", name)); return false; } + +bool DebuggerRequest::ParamString(const char *name, std::string *out, DebuggerParamType type) { + bool allowLoose = type == DebuggerParamType::REQUIRED_LOOSE || type == DebuggerParamType::OPTIONAL_LOOSE; + bool required = type == DebuggerParamType::REQUIRED || type == DebuggerParamType::REQUIRED_LOOSE; + + const JsonNode *node = data.get(name); + if (!node) { + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; + } + + auto tag = node->value.getTag(); + if (tag == JSON_STRING) { + *out = node->value.toString(); + return true; + } else if (!allowLoose) { + if (required || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; + } + + // For loose, let's allow a few things. + if (tag == JSON_TRUE) { + *out = "true"; + return true; + } else if (tag == JSON_FALSE) { + *out = "false"; + return true; + } else if (tag == JSON_NULL) { + if (required) { + *out = ""; + } + return true; + } else if (tag == JSON_NUMBER) { + // Will have a decimal place, though. + *out = StringFromFormat("%f", node->value.toNumber()); + return true; + } + + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; +} diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 132789b26dfe..1391e6614c37 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -82,6 +82,7 @@ struct DebuggerRequest { 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); JsonWriter &Respond(); void Finish(); From e746a2d10691c5b57c695c359344d03367468452 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 29 Apr 2018 22:02:04 -0700 Subject: [PATCH 26/45] Debugger: Add stepping to WebSocket API. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/Debugger/WebSocket.cpp | 2 + Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 3 + .../Debugger/WebSocket/SteppingSubscriber.cpp | 224 ++++++++++++++++++ Core/Debugger/WebSocket/SteppingSubscriber.h | 23 ++ android/jni/Android.mk | 1 + 8 files changed, 263 insertions(+) create mode 100644 Core/Debugger/WebSocket/SteppingSubscriber.cpp create mode 100644 Core/Debugger/WebSocket/SteppingSubscriber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b61d39d89b9d..608d05742521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1416,6 +1416,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket/LogBroadcaster.h Core/Debugger/WebSocket/SteppingBroadcaster.cpp Core/Debugger/WebSocket/SteppingBroadcaster.h + Core/Debugger/WebSocket/SteppingSubscriber.cpp + Core/Debugger/WebSocket/SteppingSubscriber.h Core/Debugger/WebSocket/WebSocketUtils.cpp Core/Debugger/WebSocket/WebSocketUtils.h Core/Dialog/PSPDialog.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index d21c3913240f..c06e5f1e7863 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -191,6 +191,7 @@ + @@ -543,6 +544,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index d9d65387df75..796be8ac6d6f 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -719,6 +719,9 @@ Debugger\WebSocket + + Debugger\WebSocket + @@ -1325,6 +1328,9 @@ Debugger\WebSocket + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 390dd4fa002f..7dbb1b42b792 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -50,6 +50,7 @@ #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/GameSubscriber.h" +#include "Core/Debugger/WebSocket/SteppingSubscriber.h" typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); typedef void (*Subscribershutdown)(void *p); @@ -62,6 +63,7 @@ static const std::vector subscribers({ { &WebSocketCPUCoreInit, nullptr }, { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, { &WebSocketGameInit, nullptr }, + { &WebSocketSteppingInit, &WebSocketSteppingShutdown }, }); // To handle webserver restart, keep track of how many running. diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 1b795899a74b..4bba5df54e5c 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -71,6 +71,9 @@ void WebSocketCPUResume(DebuggerRequest &req) { return req.Fail("CPU not stepping"); } + if (currentMIPS->inDelaySlot) { + Core_DoSingleStep(); + } Core_EnableStepping(false); } diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp new file mode 100644 index 000000000000..3b3842353a92 --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -0,0 +1,224 @@ +// 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 "base/stringutil.h" +#include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/WebSocket/SteppingSubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/Core.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/sceKernelThread.h" +#include "Core/MIPS/MIPSDebugInterface.h" +#include "Core/MIPS/MIPSStackWalk.h" + +using namespace MIPSAnalyst; + +struct WebSocketSteppingState { + WebSocketSteppingState() { + disasm_.setCpu(currentDebugMIPS); + } + + void Into(DebuggerRequest &req); + void Over(DebuggerRequest &req); + void Out(DebuggerRequest &req); + void RunUntil(DebuggerRequest &req); + void HLE(DebuggerRequest &req); + +protected: + u32 GetNextAddress(); + int GetNextInstructionCount(); + void PrepareResume(); + + DisassemblyManager disasm_; +}; + +void *WebSocketSteppingInit(DebuggerEventHandlerMap &map) { + auto p = new WebSocketSteppingState(); + map["cpu.stepInto"] = std::bind(&WebSocketSteppingState::Into, p, std::placeholders::_1); + map["cpu.stepOver"] = std::bind(&WebSocketSteppingState::Over, p, std::placeholders::_1); + map["cpu.stepOut"] = std::bind(&WebSocketSteppingState::Out, p, std::placeholders::_1); + map["cpu.runUntil"] = std::bind(&WebSocketSteppingState::RunUntil, p, std::placeholders::_1); + map["cpu.nextHLE"] = std::bind(&WebSocketSteppingState::HLE, p, std::placeholders::_1); + + return p; +} + +void WebSocketSteppingShutdown(void *p) { + delete static_cast(p); +} + +// Single step into the next instruction (cpu.stepInto) +// +// No parameters. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::Into(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + if (!Core_IsStepping()) { + Core_EnableStepping(true); + return; + } + + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + CBreakPoints::SetSkipFirst(currentMIPS->pc); + + int c = GetNextInstructionCount(); + for (int i = 0; i < c; ++i) { + Core_DoSingleStep(); + } +} + +// Step over the next instruction (cpu.stepOver) +// +// Note: this jumps over function calls, but also delay slots. +// +// No parameters. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::Over(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + MipsOpcodeInfo info = GetOpcodeInfo(currentDebugMIPS, currentMIPS->pc); + u32 breakpointAddress = GetNextAddress(); + if (info.isBranch) { + if (info.isConditional && !info.isLinkedBranch) { + if (info.conditionMet) { + breakpointAddress = info.branchTarget; + } else { + // Skip over the delay slot. + breakpointAddress += 4; + } + } else { + if (info.isLinkedBranch) { + // jal or jalr - a function call. Skip the delay slot. + breakpointAddress += 4; + } else { + // j - for absolute branches, set the breakpoint at the branch target. + breakpointAddress = info.branchTarget; + } + } + } + + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + if (currentMIPS->pc != breakpointAddress) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + Core_EnableStepping(false); + } +} + +// Step out of a function based on a stack walk (cpu.stepOut) +// +// No parameters. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::Out(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + auto threads = GetThreadsInfo(); + u32 entry = currentMIPS->pc; + u32 stackTop = 0; + for (const DebugThreadInfo &th : threads) { + if (th.isCurrent) { + entry = th.entrypoint; + stackTop = th.initialStack; + break; + } + } + + auto frames = MIPSStackWalk::Walk(currentMIPS->pc, currentMIPS->r[MIPS_REG_RA], currentMIPS->r[MIPS_REG_SP], entry, stackTop); + if (frames.size() < 2) { + // TODO: Respond in some way? + return; + } + + u32 breakpointAddress = frames[1].pc; + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + if (currentMIPS->pc != breakpointAddress) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + Core_EnableStepping(false); + } +} + +// Run until a certain address (cpu.stepOut) +// +// Parameters: +// - address: number parameter for destination. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::RunUntil(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + u32 address = 0; + if (!req.ParamU32("address", &address)) { + // Error already sent. + return; + } + + bool wasAtAddress = currentMIPS->pc == address; + PrepareResume(); + // We may have arrived already if PauseResume() stepped out of a delay slot. + if (currentMIPS->pc != address || wasAtAddress) { + CBreakPoints::AddBreakPoint(address, true); + Core_EnableStepping(false); + } +} + +// Jump after the next HLE call (cpu.nextHLE) +// +// No parameters. +// +// No immediate response. A cpu.stepping event will be sent once complete. +void WebSocketSteppingState::HLE(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + PrepareResume(); + hleDebugBreak(); + Core_EnableStepping(false); +} + +u32 WebSocketSteppingState::GetNextAddress() { + u32 current = disasm_.getStartAddress(currentMIPS->pc); + return disasm_.getNthNextAddress(current, 1); +} + +int WebSocketSteppingState::GetNextInstructionCount() { + return (GetNextAddress() - currentMIPS->pc) / 4; +} + +void WebSocketSteppingState::PrepareResume() { + if (currentMIPS->inDelaySlot) { + Core_DoSingleStep(); + } else { + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + CBreakPoints::SetSkipFirst(currentMIPS->pc); + } +} + diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.h b/Core/Debugger/WebSocket/SteppingSubscriber.h new file mode 100644 index 000000000000..319c608caa40 --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingSubscriber.h @@ -0,0 +1,23 @@ +// 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 *WebSocketSteppingInit(DebuggerEventHandlerMap &map); +void WebSocketSteppingShutdown(void *p); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index de838dc4538b..35ee9c930e01 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -308,6 +308,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ + $(SRC)/Core/Debugger/WebSocket/SteppingSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/WebSocketUtils.cpp \ $(SRC)/Core/Dialog/PSPDialog.cpp \ $(SRC)/Core/Dialog/PSPGamedataInstallDialog.cpp \ From ccea863f00dbcf44ca6c4191a0af39694a0e48f2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 30 Apr 2018 19:21:45 -0700 Subject: [PATCH 27/45] Debugger: Use fragments for long chunks. Gets it on the wire faster. More importantly, this allows us to usually avoid rampant memory allocation even for large responses. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 4 ++++ Core/Debugger/WebSocket/WebSocketUtils.cpp | 11 ++++++++++- Core/Debugger/WebSocket/WebSocketUtils.h | 2 ++ ext/native/json/json_writer.cpp | 4 ++-- ext/native/json/json_writer.h | 6 ++++++ ext/native/net/websocket_server.cpp | 11 +++++++++++ ext/native/net/websocket_server.h | 1 + 7 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 25e15bec2f72..f3507aa34a24 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -290,6 +290,10 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { disasm_.getLine(addr, displaySymbols, line); WriteDisasmLine(json, line); addr += line.totalSize; + + // These are pretty long, so let's grease the wheels a bit. + if (i % 50 == 0) + req.Flush(); } json.pop(); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index d21b93e9d378..fe6e343b0a16 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -32,12 +32,21 @@ JsonWriter &DebuggerRequest::Respond() { void DebuggerRequest::Finish() { if (responseBegun_ && !responseSent_) { writer_.end(); - ws->Send(writer_.str()); + if (responsePartial_) + ws->AddFragment(true, writer_.str()); + else + ws->Send(writer_.str()); responseBegun_ = false; responseSent_ = true; + responsePartial_ = false; } } +void DebuggerRequest::Flush() { + ws->AddFragment(false, writer_.flush()); + responsePartial_ = true; +} + static bool U32FromString(const char *str, uint32_t *out, bool allowFloat) { if (TryParse(str, out)) return true; diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 1391e6614c37..6bad56e5b9f9 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -85,12 +85,14 @@ struct DebuggerRequest { bool ParamString(const char *name, std::string *out, DebuggerParamType type = DebuggerParamType::REQUIRED); JsonWriter &Respond(); + void Flush(); void Finish(); private: JsonWriter writer_; bool responseBegun_ = false; bool responseSent_ = false; + bool responsePartial_ = false; }; typedef std::function DebuggerEventHandler; diff --git a/ext/native/json/json_writer.cpp b/ext/native/json/json_writer.cpp index 3cc625e13d24..15defaac8425 100644 --- a/ext/native/json/json_writer.cpp +++ b/ext/native/json/json_writer.cpp @@ -72,7 +72,7 @@ const char *JsonWriter::comma() const { const char *JsonWriter::arrayComma() const { if (stack_.back().first) { - return "\n"; + return pretty_ ? "\n" : ""; } else { return pretty_ ? ", " : ","; } @@ -159,7 +159,7 @@ void JsonWriter::writeString(const char *value) { void JsonWriter::writeString(const char *name, const char *value) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": \""; + str_ << (pretty_ ? "\": \"" : "\":\""); writeEscapedString(value); str_ << "\""; stack_.back().first = false; diff --git a/ext/native/json/json_writer.h b/ext/native/json/json_writer.h index 06afb66bc7a0..ee63708fb4c4 100644 --- a/ext/native/json/json_writer.h +++ b/ext/native/json/json_writer.h @@ -59,6 +59,12 @@ class JsonWriter { return str_.str(); } + std::string flush() { + std::string result = str_.str(); + str_.str(""); + return result; + } + enum { NORMAL = 0, PRETTY = 1, diff --git a/ext/native/net/websocket_server.cpp b/ext/native/net/websocket_server.cpp index 0302ddbb225f..40cadfb733c8 100644 --- a/ext/native/net/websocket_server.cpp +++ b/ext/native/net/websocket_server.cpp @@ -46,6 +46,8 @@ enum class Opcode { CONTROL_MAX = 10, }; +static const size_t OUT_PRESSURE = 65536; + static inline std::string TrimString(const std::string &s) { auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { // isspace() expects 0 - 255, so convert any sign-extended value. @@ -508,6 +510,14 @@ void WebSocketServer::SendBytes(const void *p, size_t sz) { size_t pos = outBuf_.size(); outBuf_.resize(pos + sz); memcpy(&outBuf_[pos], data, sz); + + if (pos + sz > lastPressure_ + OUT_PRESSURE) { + size_t pushed = out_->PushAtMost((const char *)&outBuf_[0], outBuf_.size()); + if (pushed != 0) { + outBuf_.erase(outBuf_.begin(), outBuf_.begin() + pushed); + } + lastPressure_ = outBuf_.size(); + } } } @@ -529,6 +539,7 @@ void WebSocketServer::SendFlush() { // Hopefully this is usually the entire buffer. outBuf_.erase(outBuf_.begin(), outBuf_.begin() + totalPushed); } + lastPressure_ = outBuf_.size(); } }; diff --git a/ext/native/net/websocket_server.h b/ext/native/net/websocket_server.h index 98426f53cf4e..2c4c59739e1b 100644 --- a/ext/native/net/websocket_server.h +++ b/ext/native/net/websocket_server.h @@ -86,6 +86,7 @@ class WebSocketServer { OutputSink *out_ = nullptr; WebSocketClose closeReason_ = WebSocketClose::NO_STATUS; std::vector outBuf_; + size_t lastPressure_ = 0; std::vector pendingBuf_; uint8_t pendingMask_[4]{}; From fc8ad3b47b77be43ab569845b93861d8c878a11f Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 30 Apr 2018 20:38:39 -0700 Subject: [PATCH 28/45] json: Optimize writing a bit. This improves the responsiveness of certain APIs. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 16 ++-- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 58 ++++++------ Core/Debugger/WebSocket/GameBroadcaster.cpp | 2 +- Core/Debugger/WebSocket/GameSubscriber.cpp | 2 +- .../WebSocket/SteppingBroadcaster.cpp | 2 +- ext/native/json/json_writer.cpp | 90 ++++++++++++++----- ext/native/json/json_writer.h | 42 +++------ 7 files changed, 121 insertions(+), 91 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 4bba5df54e5c..97092a710c2a 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -91,7 +91,7 @@ void WebSocketCPUStatus(DebuggerRequest &req) { json.writeBool("stepping", PSP_IsInited() && Core_IsStepping() && coreState != CORE_POWERDOWN); json.writeBool("paused", GetUIState() != UISTATE_INGAME); // Avoid NULL deference. - json.writeFloat("pc", PSP_IsInited() ? currentMIPS->pc : 0); + json.writeUint("pc", PSP_IsInited() ? currentMIPS->pc : 0); // A double ought to be good enough for a 156 day debug session. json.writeFloat("ticks", PSP_IsInited() ? CoreTiming::GetTicks() : 0); } @@ -131,11 +131,11 @@ void WebSocketCPUGetAllRegs(DebuggerRequest &req) { json.pushArray("uintValues"); // Writing as floating point to avoid negatives. Actually double, so safe. for (int r = 0; r < total; ++r) - json.writeFloat(currentDebugMIPS->GetRegValue(c, r)); + json.writeUint(currentDebugMIPS->GetRegValue(c, r)); if (c == 0) { - json.writeFloat(currentDebugMIPS->GetPC()); - json.writeFloat(currentDebugMIPS->GetHi()); - json.writeFloat(currentDebugMIPS->GetLo()); + json.writeUint(currentDebugMIPS->GetPC()); + json.writeUint(currentDebugMIPS->GetHi()); + json.writeUint(currentDebugMIPS->GetLo()); } json.pop(); @@ -267,7 +267,7 @@ void WebSocketCPUGetReg(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.writeInt("category", cat); json.writeInt("register", reg); - json.writeFloat("uintValue", val); + json.writeUint("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } @@ -335,7 +335,7 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { // Repeat it back just to avoid confusion on how it parsed. json.writeInt("category", cat); json.writeInt("register", reg); - json.writeFloat("uintValue", val); + json.writeUint("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } @@ -368,6 +368,6 @@ void WebSocketCPUEvaluate(DebuggerRequest &req) { } JsonWriter &json = req.Respond(); - json.writeFloat("uintValue", val); + json.writeUint("uintValue", val); json.writeString("floatValue", RegValueAsFloat(val)); } diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index f3507aa34a24..676d0a21f506 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -63,17 +63,17 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi else if (l.type == DISTYPE_OTHER) json.writeString("type", "other"); - json.writeFloat("address", addr); + json.writeUint("address", addr); json.writeInt("addressSize", l.totalSize); - json.writeFloat("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0); + json.writeUint("encoding", Memory::IsValidAddress(addr) ? Memory::Read_Instruction(addr).encoding : 0); if (l.totalSize >= 8 && Memory::IsValidRange(addr, l.totalSize)) { json.pushArray("macroEncoding"); for (u32 off = 0; off < l.totalSize; off += 4) { - json.writeFloat(Memory::Read_Instruction(addr + off).encoding); + json.writeUint(Memory::Read_Instruction(addr + off).encoding); } json.pop(); } else { - json.writeRaw("macroEncoding", "null"); + json.writeNull("macroEncoding"); } int c = currentDebugMIPS->getColor(addr) & 0x00FFFFFF; json.writeString("backgroundColor", StringFromFormat("#%02x%02x%02x", c & 0xFF, (c >> 8) & 0xFF, c >> 16)); @@ -82,7 +82,7 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi const std::string addressSymbol = g_symbolMap->GetLabelString(addr); if (addressSymbol.empty()) - json.writeRaw("symbol", "null"); + json.writeNull("symbol"); else json.writeString("symbol", addressSymbol); @@ -95,10 +95,10 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi if (cond) json.writeString("expression", cond->expressionString); else - json.writeRaw("expression", "null"); + json.writeNull("expression"); json.pop(); } else { - json.writeRaw("breakpoint", "null"); + json.writeNull("breakpoint"); } json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr); @@ -107,70 +107,70 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi std::string targetSymbol; if (!l.info.isBranchToRegister) { targetSymbol = g_symbolMap->GetLabelString(l.info.branchTarget); - json.writeFloat("targetAddress", l.info.branchTarget); - json.writeRaw("register", "null"); + json.writeUint("targetAddress", l.info.branchTarget); + json.writeNull("register"); } else { - json.writeRaw("targetAddress", "null"); + json.writeNull("targetAddress"); json.writeInt("register", l.info.branchRegisterNum); } json.writeBool("isLinked", l.info.isLinkedBranch); json.writeBool("isLikely", l.info.isLikelyBranch); if (targetSymbol.empty()) - json.writeRaw("symbol", "null"); + json.writeNull("symbol"); else json.writeString("symbol", targetSymbol); json.pop(); } else { - json.writeRaw("branch", "null"); + json.writeNull("branch"); } if (l.info.hasRelevantAddress) { json.pushDict("relevantData"); - json.writeFloat("address", l.info.relevantAddress); + json.writeUint("address", l.info.relevantAddress); if (Memory::IsValidRange(l.info.relevantAddress, 4)) - json.writeFloat("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress)); + json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress)); else - json.writeRaw("uintValue", "null"); + json.writeNull("uintValue"); json.pop(); } else { - json.writeRaw("relevantData", "null"); + json.writeNull("relevantData"); } if (l.info.isConditional) json.writeBool("conditionMet", l.info.conditionMet); else - json.writeRaw("conditionMet", "null"); + json.writeNull("conditionMet"); if (l.info.isDataAccess) { json.pushDict("dataAccess"); - json.writeFloat("address", l.info.dataAddress); + json.writeUint("address", l.info.dataAddress); json.writeInt("size", l.info.dataSize); std::string dataSymbol = g_symbolMap->GetLabelString(l.info.dataAddress); std::string valueSymbol; if (!Memory::IsValidRange(l.info.dataAddress, l.info.dataSize)) - json.writeRaw("uintValue", "null"); + json.writeNull("uintValue"); else if (l.info.dataSize == 1) - json.writeFloat("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress)); + json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress)); else if (l.info.dataSize == 2) - json.writeFloat("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress)); + json.writeUint("uintValue", Memory::ReadUnchecked_U16(l.info.dataAddress)); else if (l.info.dataSize >= 4) { u32 data = Memory::ReadUnchecked_U32(l.info.dataAddress); valueSymbol = g_symbolMap->GetLabelString(data); - json.writeFloat("uintValue", data); + json.writeUint("uintValue", data); } if (!dataSymbol.empty()) json.writeString("symbol", dataSymbol); else - json.writeRaw("symbol", "null"); + json.writeNull("symbol"); if (!valueSymbol.empty()) json.writeString("valueSymbol", valueSymbol); else - json.writeRaw("valueSymbol", "null"); + json.writeNull("valueSymbol"); json.pop(); } else { - json.writeRaw("dataAccess", "null"); + json.writeNull("dataAccess"); } json.pop(); @@ -178,8 +178,8 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) { json.pushDict(); - json.writeFloat("top", l.first); - json.writeFloat("bottom", l.second); + json.writeUint("top", l.first); + json.writeUint("bottom", l.second); if (l.type == LINE_UP) json.writeString("direction", "up"); else if (l.type == LINE_DOWN) @@ -279,8 +279,8 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { JsonWriter &json = req.Respond(); json.pushDict("range"); - json.writeFloat("start", start); - json.writeFloat("end", end); + json.writeUint("start", start); + json.writeUint("end", end); json.pop(); json.pushArray("lines"); diff --git a/Core/Debugger/WebSocket/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp index 4c53522a98ed..3045e70fc48a 100644 --- a/Core/Debugger/WebSocket/GameBroadcaster.cpp +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -34,7 +34,7 @@ struct GameStatusEvent { j.writeString("title", g_paramSFO.GetValueString("TITLE")); j.pop(); } else { - j.writeRaw("game", "null"); + j.writeNull("game"); } j.end(); return j.str(); diff --git a/Core/Debugger/WebSocket/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp index e2241ac04e1c..a17717e26215 100644 --- a/Core/Debugger/WebSocket/GameSubscriber.cpp +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -46,7 +46,7 @@ void WebSocketGameStatus(DebuggerRequest &req) { json.writeString("title", g_paramSFO.GetValueString("TITLE")); json.pop(); } else { - json.writeRaw("game", "null"); + json.writeNull("game"); } json.writeBool("paused", GetUIState() == UISTATE_PAUSEMENU); } diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp index 9b9cbf3200ae..bde51e1f2fca 100644 --- a/Core/Debugger/WebSocket/SteppingBroadcaster.cpp +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -27,7 +27,7 @@ struct CPUSteppingEvent { JsonWriter j; j.begin(); j.writeString("event", "cpu.stepping"); - j.writeFloat("pc", currentMIPS->pc); + j.writeUint("pc", currentMIPS->pc); // A double ought to be good enough for a 156 day debug session. j.writeFloat("ticks", CoreTiming::GetTicks()); j.end(); diff --git a/ext/native/json/json_writer.cpp b/ext/native/json/json_writer.cpp index 15defaac8425..7d1c47a62031 100644 --- a/ext/native/json/json_writer.cpp +++ b/ext/native/json/json_writer.cpp @@ -84,10 +84,10 @@ void JsonWriter::pushDict() { stack_.push_back(StackEntry(DICT)); } -void JsonWriter::pushDict(const char *name) { +void JsonWriter::pushDict(const std::string &name) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": {"; + str_ << (pretty_ ? "\": {" : "\":{"); stack_.back().first = false; stack_.push_back(StackEntry(DICT)); } @@ -98,10 +98,10 @@ void JsonWriter::pushArray() { stack_.push_back(StackEntry(ARRAY)); } -void JsonWriter::pushArray(const char *name) { +void JsonWriter::pushArray(const std::string &name) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": ["; + str_ << (pretty_ ? "\": [" : "\":["); stack_.push_back(StackEntry(ARRAY)); } @@ -110,10 +110,10 @@ void JsonWriter::writeBool(bool value) { stack_.back().first = false; } -void JsonWriter::writeBool(const char *name, bool value) { +void JsonWriter::writeBool(const std::string &name, bool value) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": " << (value ? "true" : "false"); + str_ << (pretty_ ? "\": " : "\":") << (value ? "true" : "false"); stack_.back().first = false; } @@ -122,10 +122,22 @@ void JsonWriter::writeInt(int value) { stack_.back().first = false; } -void JsonWriter::writeInt(const char *name, int value) { +void JsonWriter::writeInt(const std::string &name, int value) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": " << value; + str_ << (pretty_ ? "\": " : "\":") << value; + stack_.back().first = false; +} + +void JsonWriter::writeUint(uint32_t value) { + str_ << arrayComma() << arrayIndent() << value; + stack_.back().first = false; +} + +void JsonWriter::writeUint(const std::string &name, uint32_t value) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << value; stack_.back().first = false; } @@ -138,10 +150,10 @@ void JsonWriter::writeFloat(double value) { stack_.back().first = false; } -void JsonWriter::writeFloat(const char *name, double value) { +void JsonWriter::writeFloat(const std::string &name, double value) { str_ << comma() << indent() << "\""; writeEscapedString(name); - str_ << "\": "; + str_ << (pretty_ ? "\": " : "\":"); if (std::isfinite(value)) str_ << value; else @@ -149,14 +161,14 @@ void JsonWriter::writeFloat(const char *name, double value) { stack_.back().first = false; } -void JsonWriter::writeString(const char *value) { +void JsonWriter::writeString(const std::string &value) { str_ << arrayComma() << arrayIndent() << "\""; writeEscapedString(value); str_ << "\""; stack_.back().first = false; } -void JsonWriter::writeString(const char *name, const char *value) { +void JsonWriter::writeString(const std::string &name, const std::string &value) { str_ << comma() << indent() << "\""; writeEscapedString(name); str_ << (pretty_ ? "\": \"" : "\":\""); @@ -165,12 +177,12 @@ void JsonWriter::writeString(const char *name, const char *value) { stack_.back().first = false; } -void JsonWriter::writeRaw(const char *value) { +void JsonWriter::writeRaw(const std::string &value) { str_ << arrayComma() << arrayIndent() << value; stack_.back().first = false; } -void JsonWriter::writeRaw(const char *name, const char *value) { +void JsonWriter::writeRaw(const std::string &name, const std::string &value) { str_ << comma() << indent() << "\""; writeEscapedString(name); str_ << (pretty_ ? "\": " : "\":"); @@ -178,6 +190,18 @@ void JsonWriter::writeRaw(const char *name, const char *value) { stack_.back().first = false; } +void JsonWriter::writeNull() { + str_ << arrayComma() << arrayIndent() << "null"; + stack_.back().first = false; +} + +void JsonWriter::writeNull(const std::string &name) { + str_ << comma() << indent() << "\""; + writeEscapedString(name); + str_ << (pretty_ ? "\": " : "\":") << "null"; + stack_.back().first = false; +} + void JsonWriter::pop() { BlockType type = stack_.back().type; stack_.pop_back(); @@ -197,33 +221,53 @@ void JsonWriter::pop() { stack_.back().first = false; } -void JsonWriter::writeEscapedString(const char *str) { +void JsonWriter::writeEscapedString(const std::string &str) { size_t pos = 0; - size_t len = strlen(str); + const size_t len = str.size(); auto update = [&](size_t current, size_t skip = 0) { size_t end = current; if (pos < end) - str_ << std::string(str + pos, end - pos); + str_ << str.substr(pos, end - pos); pos = end + skip; }; for (size_t i = 0; i < len; ++i) { - if (str[i] == '\\' || str[i] == '"' || str[i] == '/') { + switch (str[i]) { + case '\\': + case '"': + case '/': update(i); str_ << '\\'; - } else if (str[i] == '\r') { + break; + + case '\r': update(i, 1); str_ << "\\r"; - } else if (str[i] == '\n') { + break; + break; + + case '\n': update(i, 1); str_ << "\\n"; - } else if (str[i] == '\t') { + break; + break; + + case '\t': update(i, 1); str_ << "\\t"; - } else if (str[i] < 32) { + break; + + case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 11: + case 12: case 14: case 15: case 16: case 17: case 18: case 19: case 20: + case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: + case 29: case 30: case 31: update(i, 1); str_ << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)str[i] << std::dec << std::setw(0); + break; + + default: + break; } } @@ -273,7 +317,7 @@ std::string json_stringify(const JsonNode *node) { static void json_stringify_object(JsonWriter &writer, const JsonNode *node) { switch (node->value.getTag()) { case JSON_NULL: - writer.writeRaw(node->key, "null"); + writer.writeNull(node->key); break; case JSON_STRING: writer.writeString(node->key, node->value.toString()); diff --git a/ext/native/json/json_writer.h b/ext/native/json/json_writer.h index ee63708fb4c4..22c712c856d3 100644 --- a/ext/native/json/json_writer.h +++ b/ext/native/json/json_writer.h @@ -22,38 +22,24 @@ class JsonWriter { void beginRaw(); void end(); void pushDict(); - void pushDict(const char *name); + void pushDict(const std::string &name); void pushArray(); - void pushArray(const char *name); + void pushArray(const std::string &name); void pop(); void writeBool(bool value); - void writeBool(const char *name, bool value); + void writeBool(const std::string &name, bool value); void writeInt(int value); - void writeInt(const char *name, int value); + void writeInt(const std::string &name, int value); + void writeUint(uint32_t value); + void writeUint(const std::string &name, uint32_t value); void writeFloat(double value); - void writeFloat(const char *name, double value); - void writeString(const char *value); - void writeString(const char *name, const char *value); - void writeString(const std::string &value) { - writeString(value.c_str()); - } - void writeString(const char *name, const std::string &value) { - writeString(name, value.c_str()); - } - void writeString(const std::string &name, const std::string &value) { - writeString(name.c_str(), value.c_str()); - } - void writeRaw(const char *value); - void writeRaw(const char *name, const char *value); - void writeRaw(const std::string &value) { - writeRaw(value.c_str()); - } - void writeRaw(const char *name, const std::string &value) { - writeRaw(name, value.c_str()); - } - void writeRaw(const std::string &name, const std::string &value) { - writeRaw(name.c_str(), value.c_str()); - } + void writeFloat(const std::string &name, double value); + void writeString(const std::string &value); + void writeString(const std::string &name, const std::string &value); + void writeRaw(const std::string &value); + void writeRaw(const std::string &name, const std::string &value); + void writeNull(); + void writeNull(const std::string &name); std::string str() const { return str_.str(); @@ -76,7 +62,7 @@ class JsonWriter { const char *arrayComma() const; const char *indent() const; const char *arrayIndent() const; - void writeEscapedString(const char *s); + void writeEscapedString(const std::string &s); enum BlockType { ARRAY, From 2f3b6c19d006f4d2f58c456466f07c88dc56de42 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Mon, 30 Apr 2018 20:39:18 -0700 Subject: [PATCH 29/45] Debugger: Poll frequently after stepping. This will make us listen for events slightly less often, so we don't want to sustain it. --- Core/Debugger/WebSocket.cpp | 12 ++++++++++-- Core/Debugger/WebSocket/WebSocketUtils.cpp | 4 +++- Core/Debugger/WebSocket/WebSocketUtils.h | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 7dbb1b42b792..dc7d44c59b46 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -129,6 +129,8 @@ void HandleDebuggerRequest(const http::Request &request) { subscriberData.push_back(info.init(eventHandlers)); } + // There's a tradeoff between responsiveness to incoming events, and polling for changes. + int highActivity = 0; ws->SetTextHandler([&](const std::string &t) { JsonReader reader(t.c_str(), t.size()); if (!reader.ok()) { @@ -148,7 +150,10 @@ void HandleDebuggerRequest(const http::Request &request) { if (eventFunc != eventHandlers.end()) { std::lock_guard guard(lifecycleLock); eventFunc->second(req); - req.Finish(); + if (!req.Finish()) { + // Poll more frequently for a second in case this triggers something. + highActivity = 1000; + } } else { req.Fail("Bad message: unknown event"); } @@ -157,7 +162,7 @@ void HandleDebuggerRequest(const http::Request &request) { ws->Send(DebuggerErrorEvent("Bad message", LogTypes::LERROR)); }); - while (ws->Process(1.0f / 60.0f)) { + while (ws->Process(highActivity ? 1.0f / 1000.0f : 1.0f / 60.0f)) { std::lock_guard guard(lifecycleLock); // These send events that aren't just responses to requests. logger.Broadcast(ws); @@ -167,6 +172,9 @@ void HandleDebuggerRequest(const http::Request &request) { if (stopRequested) { ws->Close(net::WebSocketClose::GOING_AWAY); } + if (highActivity > 0) { + highActivity--; + } } std::lock_guard guard(lifecycleLock); diff --git a/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp index fe6e343b0a16..54761aaa55a0 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.cpp +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -29,7 +29,7 @@ JsonWriter &DebuggerRequest::Respond() { return writer_; } -void DebuggerRequest::Finish() { +bool DebuggerRequest::Finish() { if (responseBegun_ && !responseSent_) { writer_.end(); if (responsePartial_) @@ -40,6 +40,8 @@ void DebuggerRequest::Finish() { responseSent_ = true; responsePartial_ = false; } + + return responseSent_; } void DebuggerRequest::Flush() { diff --git a/Core/Debugger/WebSocket/WebSocketUtils.h b/Core/Debugger/WebSocket/WebSocketUtils.h index 6bad56e5b9f9..807d8e3f1070 100644 --- a/Core/Debugger/WebSocket/WebSocketUtils.h +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -86,7 +86,7 @@ struct DebuggerRequest { JsonWriter &Respond(); void Flush(); - void Finish(); + bool Finish(); private: JsonWriter writer_; From 65feb5f09ce2e86603bc2e796c81d5b4f613d1b2 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 1 May 2018 20:17:12 -0700 Subject: [PATCH 30/45] Debugger: Include assembler API. --- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 2 +- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 97092a710c2a..d08e2b20609c 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -271,7 +271,7 @@ void WebSocketCPUGetReg(DebuggerRequest &req) { json.writeString("floatValue", RegValueAsFloat(val)); } -// Retrieve the value of a single register (cpu.getReg) +// Update the value of a single register (cpu.setReg) // // Parameters (by name): // - name: string name of register to lookup. diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 676d0a21f506..133f845bcda6 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -17,11 +17,13 @@ #include #include "base/stringutil.h" +#include "util/text/utf8.h" #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/DisassemblyManager.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/MemMap.h" +#include "Core/MIPS/MIPSAsm.h" #include "Core/MIPS/MIPSDebugInterface.h" struct WebSocketDisasmState { @@ -31,6 +33,7 @@ struct WebSocketDisasmState { void Base(DebuggerRequest &req); void Disasm(DebuggerRequest &req); + void Assemble(DebuggerRequest &req); protected: void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l); @@ -43,6 +46,7 @@ void *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { auto p = new WebSocketDisasmState(); map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1); map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1); + map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1); return p; } @@ -285,8 +289,8 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { json.pushArray("lines"); DisassemblyLineInfo line; - u32 addr = start; - for (u32 i = 0; i < count; ++i) { + uint32_t addr = start; + for (uint32_t i = 0; i < count; ++i) { disasm_.getLine(addr, displaySymbols, line); WriteDisasmLine(json, line); addr += line.totalSize; @@ -303,3 +307,22 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { WriteBranchGuide(json, bl); json.pop(); } + +void WebSocketDisasmState::Assemble(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + std::string code; + if (!req.ParamString("code", &code)) + return; + + if (!MIPSAsm::MipsAssembleOpcode(code.c_str(), currentDebugMIPS, address)) + return req.Fail(StringFromFormat("Could not assemble: %s", ConvertWStringToUTF8(MIPSAsm::GetAssembleError()).c_str())); + + JsonWriter &json = req.Respond(); + json.writeUint("encoding", Memory::Read_Instruction(address).encoding); +} From 5b132c904ad044f01917efbc39681919495f930a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 1 May 2018 22:26:21 -0700 Subject: [PATCH 31/45] Debugger: Fix resume not skipping breakpoints. --- Core/Debugger/DisassemblyManager.cpp | 3 ++- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 77e8c917a50a..660ce2aa89ef 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -524,7 +524,8 @@ void DisassemblyFunction::generateBranchLines() if (lane == -1) { - // error + // Let's just pile on. + lines[i].laneIndex = 15; continue; } diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index d08e2b20609c..7b1ffb68ac2e 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -18,6 +18,7 @@ #include "Common/StringUtils.h" #include "Core/Core.h" #include "Core/CoreTiming.h" +#include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/MIPS/MIPS.h" @@ -71,6 +72,7 @@ void WebSocketCPUResume(DebuggerRequest &req) { return req.Fail("CPU not stepping"); } + CBreakPoints::SetSkipFirst(currentMIPS->pc); if (currentMIPS->inDelaySlot) { Core_DoSingleStep(); } From 6cf24b34fc1edaaf0511455eec8ceae1a52fd0d3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 1 May 2018 22:59:50 -0700 Subject: [PATCH 32/45] Debugger: Add thread safety to breakpoints. --- Core/Debugger/Breakpoints.cpp | 162 ++++++++++++++++++++++++++-------- Core/Debugger/Breakpoints.h | 10 ++- 2 files changed, 133 insertions(+), 39 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 0223a2f6fcbf..c5515f4f342d 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "Common/Log.h" #include "Core/Core.h" @@ -27,9 +28,11 @@ #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/CoreTiming.h" +static std::mutex breakPointsMutex_; std::vector CBreakPoints::breakPoints_; u32 CBreakPoints::breakSkipFirstAt_ = 0; u64 CBreakPoints::breakSkipFirstTicks_ = 0; +static std::mutex memCheckMutex_; std::vector CBreakPoints::memChecks_; std::vector CBreakPoints::cleanupMemChecks_; @@ -45,16 +48,21 @@ void MemCheck::Log(u32 addr, bool write, int size, u32 pc) { } } -BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) -{ +BreakAction MemCheck::Apply(u32 addr, bool write, int size, u32 pc) { int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ; - if (cond & mask) - { + if (cond & mask) { ++numHits; + return result; + } + + return BREAK_ACTION_IGNORE; +} +BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) { + int mask = write ? MEMCHECK_WRITE : MEMCHECK_READ; + if (cond & mask) { Log(addr, write, size, pc); - if (result & BREAK_ACTION_PAUSE) - { + if (result & BREAK_ACTION_PAUSE) { Core_EnableStepping(true); host->SetDebugMode(true); } @@ -65,38 +73,46 @@ BreakAction MemCheck::Action(u32 addr, bool write, int size, u32 pc) return BREAK_ACTION_IGNORE; } -void MemCheck::JitBefore(u32 addr, bool write, int size, u32 pc) -{ +void MemCheck::JitBeforeApply(u32 addr, bool write, int size, u32 pc) { int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE; - if (write && (cond & mask) == mask) - { + if (write && (cond & mask) == mask) { lastAddr = addr; lastPC = pc; lastSize = size; + } else { + lastAddr = 0; + Apply(addr, write, size, pc); + } +} +void MemCheck::JitBeforeAction(u32 addr, bool write, int size, u32 pc) { + if (lastAddr) { // We have to break to find out if it changed. Core_EnableStepping(true); - } - else - { - lastAddr = 0; + } else { Action(addr, write, size, pc); } } -void MemCheck::JitCleanup() -{ +bool MemCheck::JitApplyChanged() { if (lastAddr == 0 || lastPC == 0) - return; + return false; // Here's the tricky part: would this have changed memory? // Note that it did not actually get written. bool changed = MIPSAnalyst::OpWouldChangeMemory(lastPC, lastAddr, lastSize); if (changed) - { ++numHits; + return changed; +} + +void MemCheck::JitCleanup(bool changed) +{ + if (lastAddr == 0 || lastPC == 0) + return; + + if (changed) Log(lastAddr, true, lastSize, lastPC); - } // Resume if it should not have gone to stepping, or if it did not change. if ((!(result & BREAK_ACTION_PAUSE) || !changed) && coreState == CORE_STEPPING) @@ -108,6 +124,7 @@ void MemCheck::JitCleanup() host->SetDebugMode(true); } +// Note: must lock while calling this. size_t CBreakPoints::FindBreakpoint(u32 addr, bool matchTemp, bool temp) { size_t found = INVALID_BREAKPOINT; @@ -140,12 +157,14 @@ size_t CBreakPoints::FindMemCheck(u32 start, u32 end) bool CBreakPoints::IsAddressBreakPoint(u32 addr) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); return bp != INVALID_BREAKPOINT && breakPoints_[bp].result != BREAK_ACTION_IGNORE; } bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp == INVALID_BREAKPOINT) return false; if (enabled != nullptr) @@ -155,12 +174,14 @@ bool CBreakPoints::IsAddressBreakPoint(u32 addr, bool* enabled) bool CBreakPoints::IsTempBreakPoint(u32 addr) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, true); return bp != INVALID_BREAKPOINT; } bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size) { + std::lock_guard guard(breakPointsMutex_); const u32 end = addr + size; for (const auto &bp : breakPoints_) { @@ -173,6 +194,7 @@ bool CBreakPoints::RangeContainsBreakPoint(u32 addr, u32 size) void CBreakPoints::AddBreakPoint(u32 addr, bool temp) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, temp); if (bp == INVALID_BREAKPOINT) { @@ -182,18 +204,21 @@ void CBreakPoints::AddBreakPoint(u32 addr, bool temp) pt.addr = addr; breakPoints_.push_back(pt); + guard.unlock(); Update(addr); } else if (!breakPoints_[bp].IsEnabled()) { breakPoints_[bp].result |= BREAK_ACTION_PAUSE; breakPoints_[bp].hasCond = false; + guard.unlock(); Update(addr); } } void CBreakPoints::RemoveBreakPoint(u32 addr) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { @@ -204,12 +229,14 @@ void CBreakPoints::RemoveBreakPoint(u32 addr) if (bp != INVALID_BREAKPOINT) breakPoints_.erase(breakPoints_.begin() + bp); + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, bool status) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { @@ -217,31 +244,38 @@ void CBreakPoints::ChangeBreakPoint(u32 addr, bool status) breakPoints_[bp].result |= BREAK_ACTION_PAUSE; else breakPoints_[bp].result = BreakAction(breakPoints_[bp].result & ~BREAK_ACTION_PAUSE); + + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPoint(u32 addr, BreakAction result) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].result = result; + guard.unlock(); Update(addr); } } void CBreakPoints::ClearAllBreakPoints() { + std::unique_lock guard(breakPointsMutex_); if (!breakPoints_.empty()) { breakPoints_.clear(); + guard.unlock(); Update(); } } void CBreakPoints::ClearTemporaryBreakPoints() { + std::unique_lock guard(breakPointsMutex_); if (breakPoints_.empty()) return; @@ -254,34 +288,40 @@ void CBreakPoints::ClearTemporaryBreakPoints() update = true; } } - + + guard.unlock(); if (update) Update(); } void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = true; breakPoints_[bp].cond = cond; + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = false; + guard.unlock(); Update(addr); } } BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) { + std::lock_guard guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond) return &breakPoints_[bp].cond; @@ -289,38 +329,44 @@ BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) } void CBreakPoints::ChangeBreakPointLogFormat(u32 addr, const std::string &fmt) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, true, false); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].logFormat = fmt; + guard.unlock(); Update(addr); } } BreakAction CBreakPoints::ExecBreakPoint(u32 addr) { + std::unique_lock guard(breakPointsMutex_); size_t bp = FindBreakpoint(addr, false); if (bp != INVALID_BREAKPOINT) { - if (breakPoints_[bp].hasCond) { + BreakPoint info = breakPoints_[bp]; + guard.unlock(); + + if (info.hasCond) { // Evaluate the breakpoint and abort if necessary. auto cond = CBreakPoints::GetBreakPointCondition(currentMIPS->pc); if (cond && !cond->Evaluate()) return BREAK_ACTION_IGNORE; } - if (breakPoints_[bp].result & BREAK_ACTION_LOG) { - if (breakPoints_[bp].logFormat.empty()) { + if (info.result & BREAK_ACTION_LOG) { + if (info.logFormat.empty()) { NOTICE_LOG(JIT, "BKP PC=%08x (%s)", addr, g_symbolMap->GetDescription(addr).c_str()); } else { std::string formatted; - CBreakPoints::EvaluateLogFormat(currentDebugMIPS, breakPoints_[bp].logFormat, formatted); + CBreakPoints::EvaluateLogFormat(currentDebugMIPS, info.logFormat, formatted); NOTICE_LOG(JIT, "BKP PC=%08x: %s", addr, formatted.c_str()); } } - if (breakPoints_[bp].result & BREAK_ACTION_PAUSE) { + if (info.result & BREAK_ACTION_PAUSE) { Core_EnableStepping(true); host->SetDebugMode(true); } - return breakPoints_[bp].result; + return info.result; } return BREAK_ACTION_IGNORE; @@ -328,6 +374,7 @@ BreakAction CBreakPoints::ExecBreakPoint(u32 addr) { void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); @@ -341,18 +388,21 @@ void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, Break check.result = result; memChecks_.push_back(check); + guard.unlock(); Update(); } else { memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond); memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result); + guard.unlock(); Update(); } } void CBreakPoints::RemoveMemCheck(u32 start, u32 end) { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); @@ -360,37 +410,44 @@ void CBreakPoints::RemoveMemCheck(u32 start, u32 end) if (mc != INVALID_MEMCHECK) { memChecks_.erase(memChecks_.begin() + mc); + guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheck(u32 start, u32 end, MemCheckCondition cond, BreakAction result) { + std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].cond = cond; memChecks_[mc].result = result; + guard.unlock(); Update(); } } void CBreakPoints::ClearAllMemChecks() { + std::unique_lock guard(memCheckMutex_); // This will ruin any pending memchecks. cleanupMemChecks_.clear(); if (!memChecks_.empty()) { memChecks_.clear(); + guard.unlock(); Update(); } } void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt) { + std::unique_lock guard(memCheckMutex_); size_t mc = FindMemCheck(start, end); if (mc != INVALID_MEMCHECK) { memChecks_[mc].logFormat = fmt; + guard.unlock(); Update(); } } @@ -401,8 +458,12 @@ static inline u32 NotCached(u32 val) return val & ~0x40000000; } -MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) -{ +MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) { + std::lock_guard guard(memCheckMutex_); + return GetMemCheckLocked(address, size); +} + +MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { std::vector::iterator iter; for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter) { @@ -425,9 +486,14 @@ MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc) { - auto check = GetMemCheck(address, size); - if (check) - return check->Action(address, write, size, pc); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); + if (check) { + check->Apply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + return copy.Action(address, write, size, pc); + } return BREAK_ACTION_IGNORE; } @@ -443,15 +509,23 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc) } bool write = MIPSAnalyst::IsOpMemoryWrite(pc); - auto check = GetMemCheck(address, size); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); if (check) { int mask = MEMCHECK_WRITE | MEMCHECK_WRITE_ONCHANGE; + bool apply = false; if (write && (check->cond & mask) == mask) { if (MIPSAnalyst::OpWouldChangeMemory(pc, address, size)) { - return check->Action(address, write, size, pc); + apply = true; } } else { - return check->Action(address, write, size, pc); + apply = true; + } + if (apply) { + check->Apply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + return copy.Action(address, write, size, pc); } } return BREAK_ACTION_IGNORE; @@ -459,18 +533,28 @@ BreakAction CBreakPoints::ExecOpMemCheck(u32 address, u32 pc) void CBreakPoints::ExecMemCheckJitBefore(u32 address, bool write, int size, u32 pc) { - auto check = GetMemCheck(address, size); + std::unique_lock guard(memCheckMutex_); + auto check = GetMemCheckLocked(address, size); if (check) { - check->JitBefore(address, write, size, pc); + check->JitBeforeApply(address, write, size, pc); + auto copy = *check; + guard.unlock(); + copy.JitBeforeAction(address, write, size, pc); + guard.lock(); cleanupMemChecks_.push_back(check); } } void CBreakPoints::ExecMemCheckJitCleanup() { + std::unique_lock guard(memCheckMutex_); for (auto it = cleanupMemChecks_.begin(), end = cleanupMemChecks_.end(); it != end; ++it) { auto check = *it; - check->JitCleanup(); + bool changed = check->JitApplyChanged(); + auto copy = *check; + guard.unlock(); + copy.JitCleanup(changed); + guard.lock(); } cleanupMemChecks_.clear(); } @@ -489,6 +573,7 @@ u32 CBreakPoints::CheckSkipFirst() } const std::vector CBreakPoints::GetMemCheckRanges(bool write) { + std::lock_guard guard(memCheckMutex_); std::vector ranges = memChecks_; for (const auto &check : memChecks_) { if (!(check.cond & MEMCHECK_READ) && !write) @@ -509,16 +594,19 @@ const std::vector CBreakPoints::GetMemCheckRanges(bool write) { const std::vector CBreakPoints::GetMemChecks() { + std::lock_guard guard(memCheckMutex_); return memChecks_; } const std::vector CBreakPoints::GetBreakpoints() { + std::lock_guard guard(breakPointsMutex_); return breakPoints_; } bool CBreakPoints::HasMemChecks() { + std::lock_guard guard(memCheckMutex_); return !memChecks_.empty(); } diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index 8cd7acdc4d5b..5c5804290427 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -93,9 +93,14 @@ struct MemCheck { u32 lastAddr = 0; int lastSize = 0; + // Called on the stored memcheck (affects numHits, etc.) + BreakAction Apply(u32 addr, bool write, int size, u32 pc); + // Called on a copy. BreakAction Action(u32 addr, bool write, int size, u32 pc); - void JitBefore(u32 addr, bool write, int size, u32 pc); - void JitCleanup(); + void JitBeforeApply(u32 addr, bool write, int size, u32 pc); + void JitBeforeAction(u32 addr, bool write, int size, u32 pc); + bool JitApplyChanged(); + void JitCleanup(bool changed); void Log(u32 addr, bool write, int size, u32 pc); @@ -172,6 +177,7 @@ class CBreakPoints static size_t FindBreakpoint(u32 addr, bool matchTemp = false, bool temp = false); // Finds exactly, not using a range check. static size_t FindMemCheck(u32 start, u32 end); + static MemCheck *GetMemCheckLocked(u32 address, int size); static std::vector breakPoints_; static u32 breakSkipFirstAt_; From 22940f039325adcb5289916f827e7f5e10e68a3d Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 2 May 2018 20:31:39 -0700 Subject: [PATCH 33/45] Debugger: Avoid asserts in disassembly. --- Core/MIPS/MIPSDisVFPU.cpp | 62 ++++++++++----------- Core/MIPS/MIPSVFPUUtils.cpp | 108 +++++++++++++++++++++++++----------- Core/MIPS/MIPSVFPUUtils.h | 6 ++ 3 files changed, 113 insertions(+), 63 deletions(-) diff --git a/Core/MIPS/MIPSDisVFPU.cpp b/Core/MIPS/MIPSDisVFPU.cpp index 3a0b16dfd7c5..772bf691a034 100644 --- a/Core/MIPS/MIPSDisVFPU.cpp +++ b/Core/MIPS/MIPSDisVFPU.cpp @@ -258,7 +258,7 @@ namespace MIPSDis { const char *name = MIPSGetName(op); int vd = _VD; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s",name,VSuff(op),MN(vd, sz)); } void Dis_MatrixSet2(MIPSOpcode op, char *out) @@ -266,7 +266,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz)); } void Dis_MatrixSet3(MIPSOpcode op, char *out) @@ -275,7 +275,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(vs,sz),MN(vt,sz)); } @@ -285,7 +285,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); // TODO: Xpose? sprintf(out, "%s%s\t%s, %s, %s",name,VSuff(op),MN(vd, sz),MN(Xpose(vs),sz),MN(vt,sz)); } @@ -296,7 +296,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - MatrixSize sz = GetMtxSize(op); + MatrixSize sz = GetMtxSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), MN(vd, sz), MN(vs, sz), VN(vt, V_Single)); } @@ -306,7 +306,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz), VN(vt, sz)); } @@ -316,8 +316,8 @@ namespace MIPSDis int vs = _VS; int vt = _VT; int ins = (op>>23) & 7; - VectorSize sz = GetVecSize(op); - MatrixSize msz = GetMtxSize(op); + VectorSize sz = GetVecSizeSafe(op); + MatrixSize msz = GetMtxSizeSafe(op); int n = GetNumVectorElements(sz); if (n == ins) @@ -346,7 +346,7 @@ namespace MIPSDis int vt = _VT; int vs = _VS; int vd = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); if (sz != V_Triple) { sprintf(out, "vcrs\tERROR"); @@ -362,7 +362,7 @@ namespace MIPSDis int vt = _VT; int vs = _VS; int cond = op&15; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); const char *condNames[16] = {"FL","EQ","LT","LE","TR","NE","GE","GT","EZ","EN","EI","ES","NZ","NN","NI","NS"}; sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), condNames[cond], VN(vs, sz), VN(vt,sz)); } @@ -370,7 +370,7 @@ namespace MIPSDis void Dis_Vcmov(MIPSOpcode op, char *out) { const char *name = MIPSGetName(op); - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; int tf = (op >> 19)&3; @@ -391,7 +391,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s", name, VSuff(op), VN(vd, V_Single), VN(vs,sz)); } @@ -401,7 +401,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, V_Single)); } @@ -409,7 +409,7 @@ namespace MIPSDis { const char *name = MIPSGetName(op); int vd = _VD; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s",name,VSuff(op),VN(vd, sz)); } void Dis_VectorSet2(MIPSOpcode op, char *out) @@ -417,7 +417,7 @@ namespace MIPSDis const char *name = MIPSGetName(op); int vd = _VD; int vs = _VS; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s",name,VSuff(op),VN(vd, sz),VN(vs, sz)); } void Dis_VectorSet3(MIPSOpcode op, char *out) @@ -426,7 +426,7 @@ namespace MIPSDis int vd = _VD; int vs = _VS; int vt = _VT; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); sprintf(out, "%s%s\t%s, %s, %s", name, VSuff(op), VN(vd, sz), VN(vs,sz), VN(vt, sz)); } @@ -445,7 +445,7 @@ namespace MIPSDis } c[(imm>>2) & 3] = 'S'; c[imm&3] = 'C'; - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int numElems = GetNumVectorElements(sz); int pos = 0; temp[pos++] = '['; @@ -465,7 +465,7 @@ namespace MIPSDis void Dis_CrossQuat(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); const char *name; switch (sz) { @@ -490,7 +490,7 @@ namespace MIPSDis void Dis_Vbfy(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; const char *name = MIPSGetName(op); @@ -499,7 +499,7 @@ namespace MIPSDis void Dis_Vf2i(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; int imm = (op>>16)&0x1f; @@ -509,7 +509,7 @@ namespace MIPSDis void Dis_Vs2i(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; const char *name = MIPSGetName(op); @@ -518,8 +518,8 @@ namespace MIPSDis void Dis_Vi2x(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); if (((op>>16)&3)==0) dsz = V_Single; @@ -531,7 +531,7 @@ namespace MIPSDis void Dis_Vwbn(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; int vs = _VS; @@ -542,8 +542,8 @@ namespace MIPSDis void Dis_Vf2h(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); if (((op>>16)&3)==0) dsz = V_Single; @@ -555,8 +555,8 @@ namespace MIPSDis void Dis_Vh2f(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetDoubleVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetDoubleVectorSizeSafe(sz); int vd = _VD; int vs = _VS; @@ -566,8 +566,8 @@ namespace MIPSDis void Dis_ColorConv(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); - VectorSize dsz = GetHalfVectorSize(sz); + VectorSize sz = GetVecSizeSafe(op); + VectorSize dsz = GetHalfVectorSizeSafe(sz); int vd = _VD; int vs = _VS; @@ -584,7 +584,7 @@ namespace MIPSDis void Dis_VrndX(MIPSOpcode op, char *out) { - VectorSize sz = GetVecSize(op); + VectorSize sz = GetVecSizeSafe(op); int vd = _VD; const char *name = MIPSGetName(op); diff --git a/Core/MIPS/MIPSVFPUUtils.cpp b/Core/MIPS/MIPSVFPUUtils.cpp index 5c1b35fffb7e..e493074665a4 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -303,89 +303,133 @@ int GetNumVectorElements(VectorSize sz) { } } -VectorSize GetHalfVectorSize(VectorSize sz) { +VectorSize GetHalfVectorSizeSafe(VectorSize sz) { switch (sz) { case V_Pair: return V_Single; case V_Quad: return V_Pair; - default: _assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__); return V_Invalid; + default: return V_Invalid; } } -VectorSize GetDoubleVectorSize(VectorSize sz) -{ - switch (sz) - { +VectorSize GetHalfVectorSize(VectorSize sz) { + VectorSize res = GetHalfVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetDoubleVectorSizeSafe(VectorSize sz) { + switch (sz) { case V_Single: return V_Pair; case V_Pair: return V_Quad; - default: _assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__); return V_Invalid; + default: return V_Invalid; } } -VectorSize GetVecSize(MIPSOpcode op) -{ - int a = (op>>7)&1; - int b = (op>>15)&1; - a += (b<<1); - switch (a) - { - case 0: return V_Single; - case 1: return V_Pair; - case 2: return V_Triple; - case 3: return V_Quad; - default: _assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__); return V_Invalid; +VectorSize GetDoubleVectorSize(VectorSize sz) { + VectorSize res = GetDoubleVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetVecSizeSafe(MIPSOpcode op) { + int a = (op >> 7) & 1; + int b = (op >> 15) & 1; + a += (b << 1); + switch (a) { + case 0: return V_Single; + case 1: return V_Pair; + case 2: return V_Triple; + case 3: return V_Quad; + default: return V_Invalid; } } -VectorSize GetVectorSize(MatrixSize sz) { +VectorSize GetVecSize(MIPSOpcode op) { + VectorSize res = GetVecSizeSafe(op); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +VectorSize GetVectorSizeSafe(MatrixSize sz) { switch (sz) { case M_2x2: return V_Pair; case M_3x3: return V_Triple; case M_4x4: return V_Quad; - default: _assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__); return V_Invalid; + default: return V_Invalid; } } -MatrixSize GetMatrixSize(VectorSize sz) { +VectorSize GetVectorSize(MatrixSize sz) { + VectorSize res = GetVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +MatrixSize GetMatrixSizeSafe(VectorSize sz) { switch (sz) { case V_Single: return M_Invalid; case V_Pair: return M_2x2; case V_Triple: return M_3x3; case V_Quad: return M_4x4; - default: _assert_msg_(JIT, 0, "%s: Bad vector size", __FUNCTION__); return M_Invalid; + default: return M_Invalid; } } -MatrixSize GetMtxSize(MIPSOpcode op) { - int a = (op>>7)&1; - int b = (op>>15)&1; - a += (b<<1); +MatrixSize GetMatrixSize(VectorSize sz) { + MatrixSize res = GetMatrixSizeSafe(sz); + _assert_msg_(JIT, res != M_Invalid, "%s: Bad vector size", __FUNCTION__); + return res; +} + +MatrixSize GetMtxSizeSafe(MIPSOpcode op) { + int a = (op >> 7) & 1; + int b = (op >> 15) & 1; + a += (b << 1); switch (a) { case 0: return M_4x4; // This happens in disassembly of junk case 1: return M_2x2; case 2: return M_3x3; case 3: return M_4x4; - default: _assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__); return M_Invalid; + default: return M_Invalid; } } -VectorSize MatrixVectorSize(MatrixSize sz) { +MatrixSize GetMtxSize(MIPSOpcode op) { + MatrixSize res = GetMtxSizeSafe(op); + _assert_msg_(JIT, res != M_Invalid, "%s: Bad matrix size", __FUNCTION__); + return res; +} + +VectorSize MatrixVectorSizeSafe(MatrixSize sz) { switch (sz) { case M_2x2: return V_Pair; case M_3x3: return V_Triple; case M_4x4: return V_Quad; - default: _assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__); return V_Invalid; + default: return V_Invalid; } } -int GetMatrixSide(MatrixSize sz) { +VectorSize MatrixVectorSize(MatrixSize sz) { + VectorSize res = MatrixVectorSizeSafe(sz); + _assert_msg_(JIT, res != V_Invalid, "%s: Bad matrix size", __FUNCTION__); + return res; +} + +int GetMatrixSideSafe(MatrixSize sz) { switch (sz) { case M_2x2: return 2; case M_3x3: return 3; case M_4x4: return 4; - default: _assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__); return 0; + default: return 0; } } +int GetMatrixSide(MatrixSize sz) { + int res = MatrixVectorSizeSafe(sz); + _assert_msg_(JIT, res != 0, "%s: Bad matrix size", __FUNCTION__); + return res; +} + // TODO: Optimize MatrixOverlapType GetMatrixOverlap(int mtx1, int mtx2, MatrixSize msize) { int n = GetMatrixSide(msize); diff --git a/Core/MIPS/MIPSVFPUUtils.h b/Core/MIPS/MIPSVFPUUtils.h index 2e2cb620407a..8e25821b1d9a 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -158,12 +158,18 @@ inline int GetMtx(int matrixReg) { return (matrixReg >> 2) & 7; } +VectorSize GetVecSizeSafe(MIPSOpcode op); VectorSize GetVecSize(MIPSOpcode op); +MatrixSize GetMtxSizeSafe(MIPSOpcode op); MatrixSize GetMtxSize(MIPSOpcode op); +VectorSize GetHalfVectorSizeSafe(VectorSize sz); VectorSize GetHalfVectorSize(VectorSize sz); +VectorSize GetDoubleVectorSizeSafe(VectorSize sz); VectorSize GetDoubleVectorSize(VectorSize sz); +VectorSize MatrixVectorSizeSafe(MatrixSize sz); VectorSize MatrixVectorSize(MatrixSize sz); int GetNumVectorElements(VectorSize sz); +int GetMatrixSideSafe(MatrixSize sz); int GetMatrixSide(MatrixSize sz); const char *GetVectorNotation(int reg, VectorSize size); const char *GetMatrixNotation(int reg, MatrixSize size); From 18dcea4cdc9fe1402f98cc47a3a85b606d3afbe6 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 3 May 2018 06:19:48 -0700 Subject: [PATCH 34/45] Debugger: Disasm search API. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 89 +++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 133f845bcda6..6f928c4a7e04 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include "base/stringutil.h" #include "util/text/utf8.h" #include "Core/Debugger/Breakpoints.h" @@ -33,11 +34,13 @@ struct WebSocketDisasmState { void Base(DebuggerRequest &req); void Disasm(DebuggerRequest &req); + void SearchDisasm(DebuggerRequest &req); void Assemble(DebuggerRequest &req); protected: void WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l); void WriteBranchGuide(JsonWriter &json, const BranchLine &l); + uint32_t RoundAddressUp(uint32_t addr); DisassemblyManager disasm_; }; @@ -46,6 +49,7 @@ void *WebSocketDisasmInit(DebuggerEventHandlerMap &map) { auto p = new WebSocketDisasmState(); map["memory.base"] = std::bind(&WebSocketDisasmState::Base, p, std::placeholders::_1); map["memory.disasm"] = std::bind(&WebSocketDisasmState::Disasm, p, std::placeholders::_1); + map["memory.searchDisasm"] = std::bind(&WebSocketDisasmState::SearchDisasm, p, std::placeholders::_1); map["memory.assemble"] = std::bind(&WebSocketDisasmState::Assemble, p, std::placeholders::_1); return p; @@ -97,9 +101,9 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.writeBool("enabled", enabled); auto cond = CBreakPoints::GetBreakPointCondition(addr); if (cond) - json.writeString("expression", cond->expressionString); + json.writeString("condition", cond->expressionString); else - json.writeNull("expression"); + json.writeNull("condition"); json.pop(); } else { json.writeNull("breakpoint"); @@ -194,6 +198,18 @@ void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine & json.pop(); } +uint32_t WebSocketDisasmState::RoundAddressUp(uint32_t addr) { + if (addr < PSP_GetScratchpadMemoryBase()) + return PSP_GetScratchpadMemoryBase(); + else if (addr >= PSP_GetScratchpadMemoryEnd() && addr < PSP_GetVidMemBase()) + return PSP_GetVidMemBase(); + else if (addr >= PSP_GetVidMemEnd() && addr < PSP_GetKernelMemoryBase()) + return PSP_GetKernelMemoryBase(); + else if (addr >= PSP_GetUserMemoryEnd()) + return PSP_GetScratchpadMemoryBase(); + return addr; +} + // Request the current PSP memory base address (memory.base) // // WARNING: Avoid this unless you have a good reason. Uses PPSSPP's address space. @@ -308,6 +324,75 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { json.pop(); } +void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + return req.Fail("CPU not started"); + } + + uint32_t start; + if (!req.ParamU32("address", &start)) + return; + uint32_t end = start; + if (!req.ParamU32("end", &end, false, DebuggerParamType::OPTIONAL)) + return; + std::string match; + if (!req.ParamString("match", &match)) + return; + bool displaySymbols = true; + if (!req.ParamBool("displaySymbols", &displaySymbols, DebuggerParamType::OPTIONAL)) + return; + + bool loopSearch = end <= start; + start = RoundAddressUp(start); + if ((end <= start) != loopSearch) { + // We must've passed end by rounding up. + JsonWriter &json = req.Respond(); + json.writeNull("address"); + return; + } + + // We do this after the check in case both were in unused memory. + end = RoundAddressUp(end); + + std::transform(match.begin(), match.end(), match.begin(), ::tolower); + + DisassemblyLineInfo line; + bool found = false; + uint32_t addr = start; + do { + disasm_.getLine(addr, displaySymbols, line); + const std::string addressSymbol = g_symbolMap->GetLabelString(addr); + + std::string mergeForSearch; + // Address+space (9) + symbol + colon+space (2) + name + space(1) + params = 12 fixed size worst case. + mergeForSearch.resize(12 + addressSymbol.size() + line.name.size() + line.params.size()); + + sprintf(&mergeForSearch[0], "%08x ", addr); + auto inserter = mergeForSearch.begin() + 9; + if (!addressSymbol.empty()) { + inserter = std::transform(addressSymbol.begin(), addressSymbol.end(), inserter, ::tolower); + *inserter++ = ':'; + *inserter++ = ' '; + } + inserter = std::transform(line.name.begin(), line.name.end(), inserter, ::tolower); + *inserter++ = ' '; + inserter = std::transform(line.params.begin(), line.params.end(), inserter, ::tolower); + + if (mergeForSearch.find(match) != mergeForSearch.npos) { + found = true; + break; + } + + addr = RoundAddressUp(addr + line.totalSize); + } while (addr != end); + + JsonWriter &json = req.Respond(); + if (found) + json.writeUint("address", addr); + else + json.writeNull("address"); +} + void WebSocketDisasmState::Assemble(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); From 29d93c56c7ddf417d02065f20e3ce6d344c46feb Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 3 May 2018 06:20:20 -0700 Subject: [PATCH 35/45] Debugger: Initial breakpoint APIs. --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 + Core/Debugger/WebSocket.cpp | 2 + .../WebSocket/BreakpointSubscriber.cpp | 179 ++++++++++++++++++ .../Debugger/WebSocket/BreakpointSubscriber.h | 27 +++ Core/Debugger/WebSocket/WebSocketUtils.cpp | 13 +- Core/Debugger/WebSocket/WebSocketUtils.h | 1 + android/jni/Android.mk | 1 + 9 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 Core/Debugger/WebSocket/BreakpointSubscriber.cpp create mode 100644 Core/Debugger/WebSocket/BreakpointSubscriber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 608d05742521..a1cc6b95f530 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1404,6 +1404,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 796be8ac6d6f..c5fe328767f9 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -722,6 +722,9 @@ Debugger\WebSocket + + Debugger\WebSocket + @@ -1331,6 +1334,9 @@ Debugger\WebSocket + + Debugger\WebSocket + 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 \ From b2cc4a0965eea4836587b5642fca1e0d7fb99413 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 4 May 2018 23:04:17 -0700 Subject: [PATCH 36/45] Debugger: Add memory breakpoint management. --- Core/Debugger/Breakpoints.cpp | 17 +- Core/Debugger/Breakpoints.h | 3 +- Core/Debugger/DisassemblyManager.cpp | 1 - .../WebSocket/BreakpointSubscriber.cpp | 177 +++++++++++++++++- .../Debugger/WebSocket/BreakpointSubscriber.h | 5 + Core/Debugger/WebSocket/DisasmSubscriber.cpp | 21 +++ .../Debugger/WebSocket/SteppingSubscriber.cpp | 3 + Core/MIPS/x86/JitSafeMem.cpp | 12 +- Windows/Debugger/CtrlDisAsmView.cpp | 1 + 9 files changed, 227 insertions(+), 13 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index c5515f4f342d..d1b293c578bb 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -452,15 +452,28 @@ void CBreakPoints::ChangeMemCheckLogFormat(u32 start, u32 end, const std::string } } +bool CBreakPoints::GetMemCheck(u32 start, u32 end, MemCheck *check) { + std::lock_guard guard(memCheckMutex_); + size_t mc = FindMemCheck(start, end); + if (mc != INVALID_MEMCHECK) { + *check = memChecks_[mc]; + return true; + } + return false; +} + static inline u32 NotCached(u32 val) { // Remove the cached part of the address. return val & ~0x40000000; } -MemCheck *CBreakPoints::GetMemCheck(u32 address, int size) { +bool CBreakPoints::GetMemCheckInRange(u32 address, int size, MemCheck *check) { std::lock_guard guard(memCheckMutex_); - return GetMemCheckLocked(address, size); + auto result = GetMemCheckLocked(address, size); + if (result) + *check = *result; + return result != nullptr; } MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index 5c5804290427..45f91d3cc8fd 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -149,7 +149,8 @@ class CBreakPoints static void ChangeMemCheckLogFormat(u32 start, u32 end, const std::string &fmt); - static MemCheck *GetMemCheck(u32 address, int size); + static bool GetMemCheck(u32 start, u32 end, MemCheck *check); + static bool GetMemCheckInRange(u32 address, int size, MemCheck *check); static BreakAction ExecMemCheck(u32 address, bool write, int size, u32 pc); static BreakAction ExecOpMemCheck(u32 address, u32 pc); diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 660ce2aa89ef..c5ab4b2e91d4 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -346,7 +346,6 @@ u32 DisassemblyManager::getNthNextAddress(u32 address, int n) } DisassemblyManager::~DisassemblyManager() { - clear(); } void DisassemblyManager::clear() diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp index d93bd340514c..aa6681cb8258 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -17,6 +17,8 @@ #include "Common/StringUtils.h" #include "Core/Debugger/Breakpoints.h" +#include "Core/Debugger/DisassemblyManager.h" +#include "Core/Debugger/SymbolMap.h" #include "Core/Debugger/WebSocket/BreakpointSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" #include "Core/MIPS/MIPSDebugInterface.h" @@ -28,6 +30,11 @@ void *WebSocketBreakpointInit(DebuggerEventHandlerMap &map) { map["cpu.breakpoint.remove"] = &WebSocketCPUBreakpointRemove; map["cpu.breakpoint.list"] = &WebSocketCPUBreakpointList; + map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd; + map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate; + map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove; + map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList; + return nullptr; } @@ -57,12 +64,12 @@ struct WebSocketCPUBreakpointParams { 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)) @@ -173,6 +180,172 @@ void WebSocketCPUBreakpointList(DebuggerRequest &req) { json.writeString("logFormat", bp.logFormat); else json.writeNull("logFormat"); + std::string symbol = g_symbolMap->GetLabelString(bp.addr); + if (symbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", symbol); + + DisassemblyManager manager; + DisassemblyLineInfo line; + manager.getLine(manager.getStartAddress(bp.addr), true, line); + json.writeString("code", line.name + " " + line.params); + + json.pop(); + } + json.pop(); +} + +struct WebSocketMemoryBreakpointParams { + uint32_t address = 0; + uint32_t end = 0; + bool hasEnabled = false; + bool hasLog = false; + bool hasCond = false; + bool hasLogFormat = false; + + bool enabled = true; + bool log = true; + MemCheckCondition cond = MEMCHECK_READWRITE; + std::string logFormat; + + bool Parse(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + req.Fail("CPU not started"); + return false; + } + + if (!req.ParamU32("address", &address)) + return false; + uint32_t size; + if (!req.ParamU32("size", &size)) + return false; + if (address + size < address) { + req.Fail("Size is too large"); + return false; + } + end = size == 0 ? 0 : address + size; + + 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; + } + hasCond = req.HasParam("read") || req.HasParam("write") || req.HasParam("change"); + if (hasCond) { + bool read, write, change; + if (!req.ParamBool("read", &read) || !req.ParamBool("write", &write) || !req.ParamBool("change", &change)) + return false; + int bits = (read ? MEMCHECK_READ : 0) | (write ? MEMCHECK_WRITE : 0) | (change ? MEMCHECK_WRITE_ONCHANGE : 0); + cond = MemCheckCondition(bits); + } + hasLogFormat = req.HasParam("logFormat"); + if (hasLogFormat) { + if (!req.ParamString("logFormat", &logFormat)) + return false; + } + + return true; + } + + BreakAction Result(bool adding) { + int bits = MEMCHECK_READWRITE; + if (adding || (hasLog && hasEnabled)) { + bits = (enabled ? BREAK_ACTION_PAUSE : 0) | (log ? BREAK_ACTION_LOG : 0); + } else { + MemCheck prev; + if (CBreakPoints::GetMemCheck(address, end, &prev)) + bits = prev.result; + + if (hasEnabled) + bits = (bits & ~BREAK_ACTION_PAUSE) | (enabled ? BREAK_ACTION_PAUSE : 0); + if (hasLog) + bits = (bits & ~BREAK_ACTION_LOG) | (log ? BREAK_ACTION_LOG : 0); + } + + return BreakAction(bits); + } + + void Apply() { + if (hasLogFormat) { + CBreakPoints::ChangeMemCheckLogFormat(address, end, logFormat); + } + } +}; + +void WebSocketMemoryBreakpointAdd(DebuggerRequest &req) { + WebSocketMemoryBreakpointParams params; + if (!params.Parse(req)) + return; + + CBreakPoints::AddMemCheck(params.address, params.end, params.cond, params.Result(true)); + params.Apply(); + req.Respond(); +} + +void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req) { + WebSocketMemoryBreakpointParams params; + if (!params.Parse(req)) + return; + + MemCheck mc; + if (!CBreakPoints::GetMemCheck(params.address, params.end, &mc)) + return req.Fail("Breakpoint not found"); + + CBreakPoints::ChangeMemCheck(params.address, params.end, params.cond, params.Result(true)); + params.Apply(); + req.Respond(); +} + +void WebSocketMemoryBreakpointRemove(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + uint32_t address; + if (!req.ParamU32("address", &address)) + return; + uint32_t size; + if (!req.ParamU32("size", &size)) + return; + + CBreakPoints::RemoveMemCheck(address, size == 0 ? 0 : address + size); + req.Respond(); +} + +void WebSocketMemoryBreakpointList(DebuggerRequest &req) { + if (!currentDebugMIPS->isAlive()) { + return req.Fail("CPU not started"); + } + + JsonWriter &json = req.Respond(); + json.pushArray("breakpoints"); + auto mcs = CBreakPoints::GetMemChecks(); + for (const auto &mc : mcs) { + json.pushDict(); + json.writeUint("address", mc.start); + json.writeUint("size", mc.end == 0 ? 0 : mc.end - mc.start); + json.writeBool("enabled", mc.IsEnabled()); + json.writeBool("log", (mc.result & BREAK_ACTION_LOG) != 0); + json.writeBool("read", (mc.cond & MEMCHECK_READ) != 0); + json.writeBool("write", (mc.cond & MEMCHECK_WRITE) != 0); + json.writeBool("change", (mc.cond & MEMCHECK_WRITE_ONCHANGE) != 0); + json.writeUint("hits", mc.numHits); + if (!mc.logFormat.empty()) + json.writeString("logFormat", mc.logFormat); + else + json.writeNull("logFormat"); + std::string symbol = g_symbolMap->GetLabelString(mc.start); + if (symbol.empty()) + json.writeNull("symbol"); + else + json.writeString("symbol", symbol); + json.pop(); } json.pop(); diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.h b/Core/Debugger/WebSocket/BreakpointSubscriber.h index eccf23c3cba9..c9c2e1effcf5 100644 --- a/Core/Debugger/WebSocket/BreakpointSubscriber.h +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.h @@ -25,3 +25,8 @@ void WebSocketCPUBreakpointAdd(DebuggerRequest &req); void WebSocketCPUBreakpointUpdate(DebuggerRequest &req); void WebSocketCPUBreakpointRemove(DebuggerRequest &req); void WebSocketCPUBreakpointList(DebuggerRequest &req); + +void WebSocketMemoryBreakpointAdd(DebuggerRequest &req); +void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req); +void WebSocketMemoryBreakpointRemove(DebuggerRequest &req); +void WebSocketMemoryBreakpointList(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 6f928c4a7e04..95dddcebb820 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -31,6 +31,9 @@ struct WebSocketDisasmState { WebSocketDisasmState() { disasm_.setCpu(currentDebugMIPS); } + ~WebSocketDisasmState() { + disasm_.clear(); + } void Base(DebuggerRequest &req); void Disasm(DebuggerRequest &req); @@ -324,6 +327,16 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { json.pop(); } +// Search disassembly for some text (cpu.searchDisasm) +// +// Parameters: +// - address: starting address as a number. +// - end: optional end address as a number (otherwise uses start.) +// - match: string to search for. +// - displaySymbols: optional, specify false to hide symbols in the searched parameters. +// +// Response (same event name): +// - address: number address of match or null if none was found. void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); @@ -393,6 +406,14 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { json.writeNull("address"); } +// Assemble an instruction (cpu.assemble) +// +// Parameters: +// - address: number indicating the address to write to. +// - code: string containing the instruction to assemble. +// +// Response (same event name): +// - encoding: resulting encoding at this address. Always returns one value, even for macros. void WebSocketDisasmState::Assemble(DebuggerRequest &req) { if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 3b3842353a92..062064d9cd80 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -32,6 +32,9 @@ struct WebSocketSteppingState { WebSocketSteppingState() { disasm_.setCpu(currentDebugMIPS); } + ~WebSocketSteppingState() { + disasm_.clear(); + } void Into(DebuggerRequest &req); void Over(DebuggerRequest &req); diff --git a/Core/MIPS/x86/JitSafeMem.cpp b/Core/MIPS/x86/JitSafeMem.cpp index 104b6579a008..676f8b1ced3b 100644 --- a/Core/MIPS/x86/JitSafeMem.cpp +++ b/Core/MIPS/x86/JitSafeMem.cpp @@ -346,14 +346,12 @@ void JitSafeMem::Finish() jit_->SetJumpTarget(*it); } -void JitSafeMem::MemCheckImm(MemoryOpType type) -{ - MemCheck *check = CBreakPoints::GetMemCheck(iaddr_, size_); - if (check) - { - if (!(check->cond & MEMCHECK_READ) && type == MEM_READ) +void JitSafeMem::MemCheckImm(MemoryOpType type) { + MemCheck check; + if (CBreakPoints::GetMemCheckInRange(iaddr_, size_, &check)) { + if (!(check.cond & MEMCHECK_READ) && type == MEM_READ) return; - if (!(check->cond & MEMCHECK_WRITE) && type == MEM_WRITE) + if (!(check.cond & MEMCHECK_WRITE) && type == MEM_WRITE) return; jit_->MOV(32, MIPSSTATE_VAR(pc), Imm32(jit_->GetCompilerPC())); diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index fecc7de3976c..7b8ea0440684 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -183,6 +183,7 @@ CtrlDisAsmView::~CtrlDisAsmView() { DeleteObject(font); DeleteObject(boldfont); + manager.clear(); } COLORREF scaleColor(COLORREF color, float factor) From 8598fc99129c0bebcdf0b899108111cd989a18ae Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 6 May 2018 10:35:56 -0700 Subject: [PATCH 37/45] Debugger: Add some missing memory locks. We can restart memory when loading save states, so we need this even while we've got startup/shutdown locked. --- Core/Debugger/DisassemblyManager.cpp | 2 ++ Core/Debugger/WebSocket/DisasmSubscriber.cpp | 3 +++ Windows/Debugger/CtrlDisAsmView.cpp | 4 +++- Windows/Debugger/CtrlMemView.cpp | 5 +++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index c5ab4b2e91d4..54ffae28d4a9 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -238,6 +238,8 @@ std::vector DisassemblyManager::getBranchLines(u32 start, u32 size) void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest) { + // This is here really to avoid lock ordering issues. + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 95dddcebb820..84d0692ae2e4 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -256,6 +256,7 @@ void WebSocketDisasmState::Base(DebuggerRequest &req) { // - params: formatted parameters for the instruction. // - (other info about the disassembled line.) void WebSocketDisasmState::Disasm(DebuggerRequest &req) { + auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } @@ -338,6 +339,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { // Response (same event name): // - address: number address of match or null if none was found. void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { + auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } @@ -415,6 +417,7 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { // Response (same event name): // - encoding: resulting encoding at this address. Always returns one value, even for macros. void WebSocketDisasmState::Assemble(DebuggerRequest &req) { + auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index 7b8ea0440684..93d4ed61789e 100644 --- a/Windows/Debugger/CtrlDisAsmView.cpp +++ b/Windows/Debugger/CtrlDisAsmView.cpp @@ -201,7 +201,6 @@ COLORREF scaleColor(COLORREF color, float factor) bool CtrlDisAsmView::getDisasmAddressText(u32 address, char* dest, bool abbreviateLabels, bool showData) { - auto memLock = Memory::Lock(); if (!PSP_IsInited()) return false; @@ -469,6 +468,7 @@ void CtrlDisAsmView::drawArguments(HDC hdc, const DisassemblyLineInfo &line, int void CtrlDisAsmView::onPaint(WPARAM wParam, LPARAM lParam) { + auto memLock = Memory::Lock(); if (!debugger->isAlive()) return; PAINTSTRUCT ps; @@ -1180,6 +1180,7 @@ void CtrlDisAsmView::calculatePixelPositions() void CtrlDisAsmView::search(bool continueSearch) { + auto memLock = Memory::Lock(); u32 searchAddress; if (continueSearch == false || searchQuery[0] == 0) @@ -1260,6 +1261,7 @@ void CtrlDisAsmView::search(bool continueSearch) std::string CtrlDisAsmView::disassembleRange(u32 start, u32 size) { + auto memLock = Memory::Lock(); std::string result; // gather all branch targets without labels diff --git a/Windows/Debugger/CtrlMemView.cpp b/Windows/Debugger/CtrlMemView.cpp index 4cede1270ea4..f1cc93e37289 100644 --- a/Windows/Debugger/CtrlMemView.cpp +++ b/Windows/Debugger/CtrlMemView.cpp @@ -174,6 +174,8 @@ CtrlMemView *CtrlMemView::getFrom(HWND hwnd) void CtrlMemView::onPaint(WPARAM wParam, LPARAM lParam) { + auto memLock = Memory::Lock(); + // draw to a bitmap for double buffering PAINTSTRUCT ps; HDC actualHdc = BeginPaint(wnd, &ps); @@ -445,6 +447,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_8: { + auto memLock = Memory::Lock(); char temp[24]; // it's admittedly not really useful like this @@ -462,6 +465,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_16: { + auto memLock = Memory::Lock(); char temp[24]; sprintf(temp,"%04X",Memory::IsValidAddress(curAddress) ? Memory::Read_U16(curAddress) : 0xFFFF); @@ -471,6 +475,7 @@ void CtrlMemView::onMouseUp(WPARAM wParam, LPARAM lParam, int button) case ID_MEMVIEW_COPYVALUE_32: { + auto memLock = Memory::Lock(); char temp[24]; sprintf(temp,"%08X",Memory::IsValidAddress(curAddress) ? Memory::Read_U32(curAddress) : 0xFFFFFFFF); From 3193772e78d7c58ab7fecb07c5a860337314bb0a Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 6 May 2018 10:40:02 -0700 Subject: [PATCH 38/45] Debugger: Use a lock for memory reallocs. Simpler this way, no need to remember to lock memory. --- Core/Core.h | 4 ++++ Core/Debugger/WebSocket.cpp | 3 +++ Core/Debugger/WebSocket/DisasmSubscriber.cpp | 3 --- Core/MemMap.cpp | 14 ++++++++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Core/Core.h b/Core/Core.h index 84fe707792b7..0b87f2b393e2 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -45,6 +45,10 @@ enum class CoreLifecycle { STOPPING, // Guaranteed call after STOPPING. STOPPED, + + // Sometimes called for save states. Guaranteed sequence, and never during STARTING or STOPPING. + MEMORY_REINITING, + MEMORY_REINITED, }; typedef void (* CoreLifecycleFunc)(CoreLifecycle stage); diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index 3ae93962a8ce..f477746c7898 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -20,6 +20,7 @@ #include "thread/threadutil.h" #include "Core/Debugger/WebSocket.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/MemMap.h" // This WebSocket (connected through the same port as disc sharing) allows API/debugger access to PPSSPP. // Currently, the only subprotocol "debugger.ppsspp.org" uses a simple JSON based interface. @@ -88,6 +89,7 @@ static void WebSocketNotifyLifecycle(CoreLifecycle stage) { switch (stage) { case CoreLifecycle::STARTING: case CoreLifecycle::STOPPING: + case CoreLifecycle::MEMORY_REINITING: if (debuggersConnected > 0) { DEBUG_LOG(SYSTEM, "Waiting for debugger to complete on shutdown"); } @@ -96,6 +98,7 @@ static void WebSocketNotifyLifecycle(CoreLifecycle stage) { case CoreLifecycle::START_COMPLETE: case CoreLifecycle::STOPPED: + case CoreLifecycle::MEMORY_REINITED: lifecycleLock.unlock(); if (debuggersConnected > 0) { DEBUG_LOG(SYSTEM, "Debugger ready for shutdown"); diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 84d0692ae2e4..95dddcebb820 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -256,7 +256,6 @@ void WebSocketDisasmState::Base(DebuggerRequest &req) { // - params: formatted parameters for the instruction. // - (other info about the disassembled line.) void WebSocketDisasmState::Disasm(DebuggerRequest &req) { - auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } @@ -339,7 +338,6 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { // Response (same event name): // - address: number address of match or null if none was found. void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { - auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } @@ -417,7 +415,6 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { // Response (same event name): // - encoding: resulting encoding at this address. Always returns one value, even for macros. void WebSocketDisasmState::Assemble(DebuggerRequest &req) { - auto memLock = Memory::Lock(); if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { return req.Fail("CPU not started"); } diff --git a/Core/MemMap.cpp b/Core/MemMap.cpp index 8282aac3dbbc..0fc1355ae0a9 100644 --- a/Core/MemMap.cpp +++ b/Core/MemMap.cpp @@ -291,6 +291,14 @@ void Init() { base, m_pPhysicalRAM, m_pUncachedRAM); } +void Reinit() { + _assert_msg_(SYSTEM, PSP_IsInited(), "Cannot reinit during startup/shutdown"); + Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITING); + Shutdown(); + Init(); + Core_NotifyLifecycle(CoreLifecycle::MEMORY_REINITED); +} + void DoState(PointerWrap &p) { auto s = p.Section("Memory", 1, 3); if (!s) @@ -308,8 +316,7 @@ void DoState(PointerWrap &p) { if (!g_RemasterMode) { g_MemorySize = g_PSPModel == PSP_MODEL_FAT ? RAM_NORMAL_SIZE : RAM_DOUBLE_SIZE; if (oldMemorySize < g_MemorySize) { - Shutdown(); - Init(); + Reinit(); } } } else { @@ -320,8 +327,7 @@ void DoState(PointerWrap &p) { p.DoMarker("PSPModel"); p.Do(g_MemorySize); if (oldMemorySize != g_MemorySize) { - Shutdown(); - Init(); + Reinit(); } } From 5670fc03ae00b817ff0f6a87bb683812df8f7917 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Wed, 9 May 2018 17:51:27 -0700 Subject: [PATCH 39/45] Debugger: Add debug interfaces for threads. This way we can switch context. --- Core/Core.vcxproj | 1 + Core/Core.vcxproj.filters | 3 + Core/Debugger/DisassemblyManager.cpp | 42 +++++---- Core/Debugger/DisassemblyManager.h | 14 +-- Core/Debugger/WebSocket/CPUCoreSubscriber.cpp | 85 +++++++++++++----- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 35 ++++++-- Core/HLE/KernelThreadDebugInterface.h | 90 +++++++++++++++++++ Core/HLE/sceKernelThread.cpp | 30 +++++-- Core/HLE/sceKernelThread.h | 2 + 9 files changed, 239 insertions(+), 63 deletions(-) create mode 100644 Core/HLE/KernelThreadDebugInterface.h diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index a9d703869fcb..1b95d40373c6 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -553,6 +553,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index c5fe328767f9..f4803dd56cb2 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -1337,6 +1337,9 @@ Debugger\WebSocket + + HLE\Kernel + diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 54ffae28d4a9..2c0a6edd54cf 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -236,7 +236,7 @@ std::vector DisassemblyManager::getBranchLines(u32 start, u32 size) return result; } -void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest) +void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug) { // This is here really to avoid lock ordering issues. auto memLock = Memory::Lock(); @@ -250,7 +250,7 @@ void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLin if (it != entries.end()) { DisassemblyEntry *entry = it->second; - if (entry->disassemble(address, dest, insertSymbols)) + if (entry->disassemble(address, dest, insertSymbols, cpuDebug)) return; } @@ -432,14 +432,14 @@ u32 DisassemblyFunction::getLineAddress(int line) return lineAddresses[line]; } -bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyFunction::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { std::lock_guard guard(lock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) return false; - return it->second->disassemble(address,dest,insertSymbols); + return it->second->disassemble(address, dest, insertSymbols, cpuDebug); } void DisassemblyFunction::getBranchLines(u32 start, u32 size, std::vector& dest) @@ -720,16 +720,19 @@ void DisassemblyFunction::clear() hash = 0; } -bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyOpcode::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { + if (!cpuDebug) + cpuDebug = DisassemblyManager::getCpu(); + char opcode[64],arguments[256]; - const char *dizz = DisassemblyManager::getCpu()->disasm(address,4); + const char *dizz = cpuDebug->disasm(address, 4); parseDisasm(dizz,opcode,arguments,insertSymbols); dest.type = DISTYPE_OPCODE; dest.name = opcode; dest.params = arguments; dest.totalSize = 4; - dest.info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),address); + dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address); return true; } @@ -789,11 +792,14 @@ void DisassemblyMacro::setMacroMemory(std::string _name, u32 _immediate, u8 _rt, numOpcodes = 2; } -bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { + if (!cpuDebug) + cpuDebug = DisassemblyManager::getCpu(); + char buffer[64]; dest.type = DISTYPE_MACRO; - dest.info = MIPSAnalyst::GetOpcodeInfo(DisassemblyManager::getCpu(),address); + dest.info = MIPSAnalyst::GetOpcodeInfo(cpuDebug, address); std::string addressSymbol; switch (type) @@ -802,11 +808,10 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool dest.name = name; addressSymbol = g_symbolMap->GetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) - { - sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str()); + if (!addressSymbol.empty() && insertSymbols) { + sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str()); } else { - sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate); + sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate); } dest.params = buffer; @@ -818,11 +823,10 @@ bool DisassemblyMacro::disassemble(u32 address, DisassemblyLineInfo& dest, bool dest.name = name; addressSymbol = g_symbolMap->GetLabelString(immediate); - if (!addressSymbol.empty() && insertSymbols) - { - sprintf(buffer,"%s,%s",DisassemblyManager::getCpu()->GetRegName(0,rt),addressSymbol.c_str()); + if (!addressSymbol.empty() && insertSymbols) { + sprintf(buffer, "%s,%s", cpuDebug->GetRegName(0, rt), addressSymbol.c_str()); } else { - sprintf(buffer,"%s,0x%08X",DisassemblyManager::getCpu()->GetRegName(0,rt),immediate); + sprintf(buffer, "%s,0x%08X", cpuDebug->GetRegName(0, rt), immediate); } dest.params = buffer; @@ -867,7 +871,7 @@ void DisassemblyData::recheck() } } -bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyData::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { dest.type = DISTYPE_DATA; @@ -1065,7 +1069,7 @@ DisassemblyComment::DisassemblyComment(u32 _address, u32 _size, std::string _nam } -bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) +bool DisassemblyComment::disassemble(u32 address, DisassemblyLineInfo &dest, bool insertSymbols, DebugInterface *cpuDebug) { dest.type = DISTYPE_OTHER; dest.name = name; diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index 7d84805b01e4..44ae397f6c69 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -63,7 +63,7 @@ class DisassemblyEntry virtual int getLineNum(u32 address, bool findStart) = 0; virtual u32 getLineAddress(int line) = 0; virtual u32 getTotalSize() = 0; - virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) = 0; + virtual bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) = 0; virtual void getBranchLines(u32 start, u32 size, std::vector& dest) { }; }; @@ -77,7 +77,7 @@ class DisassemblyFunction: public DisassemblyEntry int getLineNum(u32 address, bool findStart) override; u32 getLineAddress(int line) override; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; void getBranchLines(u32 start, u32 size, std::vector& dest) override; private: @@ -105,7 +105,7 @@ class DisassemblyOpcode: public DisassemblyEntry int getLineNum(u32 address, bool findStart) override { return (address - this->address) / 4; }; u32 getLineAddress(int line) override { return address + line * 4; }; u32 getTotalSize() override { return num * 4; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; void getBranchLines(u32 start, u32 size, std::vector& dest) override; private: @@ -128,7 +128,7 @@ class DisassemblyMacro: public DisassemblyEntry int getLineNum(u32 address, bool findStart) override { return 0; }; u32 getLineAddress(int line) override { return address; }; u32 getTotalSize() override { return numOpcodes * 4; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: enum MacroType { MACRO_LI, MACRO_MEMORYIMM }; @@ -153,7 +153,7 @@ class DisassemblyData: public DisassemblyEntry int getLineNum(u32 address, bool findStart) override; u32 getLineAddress(int line) override { return lineAddresses[line]; }; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: void createLines(); @@ -185,7 +185,7 @@ class DisassemblyComment: public DisassemblyEntry int getLineNum(u32 address, bool findStart) override { return 0; }; u32 getLineAddress(int line) override { return address; }; u32 getTotalSize() override { return size; }; - bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols) override; + bool disassemble(u32 address, DisassemblyLineInfo& dest, bool insertSymbols, DebugInterface *cpuDebug) override; private: u32 address; @@ -205,7 +205,7 @@ class DisassemblyManager void setCpu(DebugInterface* _cpu) { cpu = _cpu; }; void setMaxParamChars(int num) { maxParamChars = num; clear(); }; - void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo& dest); + void getLine(u32 address, bool insertSymbols, DisassemblyLineInfo &dest, DebugInterface *cpuDebug = nullptr); void analyze(u32 address, u32 size); std::vector getBranchLines(u32 start, u32 size); diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp index 7b1ffb68ac2e..0d2c42f66c2a 100644 --- a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -21,6 +21,7 @@ #include "Core/Debugger/Breakpoints.h" #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSDebugInterface.h" @@ -45,6 +46,20 @@ static std::string RegValueAsFloat(uint32_t u) { return StringFromFormat("%f", bits.f); } +static DebugInterface *CPUFromRequest(DebuggerRequest &req) { + if (!req.HasParam("thread")) + return currentDebugMIPS; + + u32 uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + return cpuDebug; +} + // Begin stepping and pause the CPU (cpu.stepping) // // No parameters. @@ -100,7 +115,8 @@ void WebSocketCPUStatus(DebuggerRequest &req) { // Retrieve all regs and their values (cpu.getAllRegs) // -// No parameters. +// Parameters: +// - thread: optional number indicating the thread id to get regs for. // // Response (same event name): // - categories: array of objects: @@ -110,19 +126,23 @@ void WebSocketCPUStatus(DebuggerRequest &req) { // - uintValues: array of unsigned integer values for the registers. // - floatValues: array of strings showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetAllRegs(DebuggerRequest &req) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + JsonWriter &json = req.Respond(); json.pushArray("categories"); - for (int c = 0; c < currentDebugMIPS->GetNumCategories(); ++c) { + for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) { json.pushDict(); json.writeInt("id", c); - json.writeString("name", currentDebugMIPS->GetCategoryName(c)); + json.writeString("name", cpuDebug->GetCategoryName(c)); - int total = currentDebugMIPS->GetNumRegsInCategory(c); + int total = cpuDebug->GetNumRegsInCategory(c); json.pushArray("registerNames"); for (int r = 0; r < total; ++r) - json.writeString(currentDebugMIPS->GetRegName(c, r)); + json.writeString(cpuDebug->GetRegName(c, r)); if (c == 0) { json.writeString("pc"); json.writeString("hi"); @@ -133,22 +153,22 @@ void WebSocketCPUGetAllRegs(DebuggerRequest &req) { json.pushArray("uintValues"); // Writing as floating point to avoid negatives. Actually double, so safe. for (int r = 0; r < total; ++r) - json.writeUint(currentDebugMIPS->GetRegValue(c, r)); + json.writeUint(cpuDebug->GetRegValue(c, r)); if (c == 0) { - json.writeUint(currentDebugMIPS->GetPC()); - json.writeUint(currentDebugMIPS->GetHi()); - json.writeUint(currentDebugMIPS->GetLo()); + json.writeUint(cpuDebug->GetPC()); + json.writeUint(cpuDebug->GetHi()); + json.writeUint(cpuDebug->GetLo()); } json.pop(); json.pushArray("floatValues"); // Note: String so it can have Infinity and NaN. for (int r = 0; r < total; ++r) - json.writeString(RegValueAsFloat(currentDebugMIPS->GetRegValue(c, r))); + json.writeString(RegValueAsFloat(cpuDebug->GetRegValue(c, r))); if (c == 0) { - json.writeString(RegValueAsFloat(currentDebugMIPS->GetPC())); - json.writeString(RegValueAsFloat(currentDebugMIPS->GetHi())); - json.writeString(RegValueAsFloat(currentDebugMIPS->GetLo())); + json.writeString(RegValueAsFloat(cpuDebug->GetPC())); + json.writeString(RegValueAsFloat(cpuDebug->GetHi())); + json.writeString(RegValueAsFloat(cpuDebug->GetLo())); } json.pop(); @@ -232,9 +252,11 @@ static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) // Retrieve the value of a single register (cpu.getReg) // // Parameters (by name): +// - thread: optional number indicating the thread id to get from. // - name: string name of register to lookup. // // Parameters (by category id and index, ignored if name specified): +// - thread: optional number indicating the thread id to get from. // - category: id of category for the register. // - register: index into array of registers. // @@ -244,21 +266,25 @@ static DebuggerRegType ValidateCatReg(DebuggerRequest &req, int *cat, int *reg) // - uintValue: value in register. // - floatValue: string showing float representation. May be "nan", "inf", or "-inf". void WebSocketCPUGetReg(DebuggerRequest &req) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + int cat, reg; uint32_t val; switch (ValidateCatReg(req, &cat, ®)) { case DebuggerRegType::NORMAL: - val = currentDebugMIPS->GetRegValue(cat, reg); + val = cpuDebug->GetRegValue(cat, reg); break; case DebuggerRegType::PC: - val = currentDebugMIPS->GetPC(); + val = cpuDebug->GetPC(); break; case DebuggerRegType::HI: - val = currentDebugMIPS->GetHi(); + val = cpuDebug->GetHi(); break; case DebuggerRegType::LO: - val = currentDebugMIPS->GetLo(); + val = cpuDebug->GetLo(); break; case DebuggerRegType::INVALID: @@ -276,11 +302,13 @@ void WebSocketCPUGetReg(DebuggerRequest &req) { // Update the value of a single register (cpu.setReg) // // Parameters (by name): +// - thread: optional number indicating the thread id to update. // - name: string name of register to lookup. // - value: number (uint values only) or string to set to. Values may include // "0x1234", "1.5", "nan", "-inf", etc. For a float, use a string with decimal e.g. "1.0". // // Parameters (by category id and index, ignored if name specified): +// - thread: optional number indicating the thread id to update. // - category: id of category for the register. // - register: index into array of registers. // - value: number (uint values only) or string to set to. Values may include @@ -301,6 +329,10 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { return req.Fail("CPU currently running (cpu.stepping first)"); } + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + uint32_t val; if (!req.ParamU32("value", &val, true)) { // Already sent error. @@ -313,19 +345,19 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { if (cat == 0 && reg == 0 && val != 0) { return req.Fail("Cannot change reg zero"); } - currentDebugMIPS->SetRegValue(cat, reg, val); + cpuDebug->SetRegValue(cat, reg, val); // In case part of it was ignored (e.g. flags reg.) - val = currentDebugMIPS->GetRegValue(cat, reg); + val = cpuDebug->GetRegValue(cat, reg); break; case DebuggerRegType::PC: - currentDebugMIPS->SetPC(val); + cpuDebug->SetPC(val); break; case DebuggerRegType::HI: - currentDebugMIPS->SetHi(val); + cpuDebug->SetHi(val); break; case DebuggerRegType::LO: - currentDebugMIPS->SetLo(val); + cpuDebug->SetLo(val); break; case DebuggerRegType::INVALID: @@ -344,6 +376,7 @@ void WebSocketCPUSetReg(DebuggerRequest &req) { // Evaluate an expression (cpu.evaluate) // // Parameters: +// - thread: optional number indicating the thread id to update. // - expression: string containing labels, operators, regs, etc. // // Response (same event name): @@ -354,6 +387,10 @@ void WebSocketCPUEvaluate(DebuggerRequest &req) { return req.Fail("CPU not started"); } + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + std::string exp; if (!req.ParamString("expression", &exp)) { // Already sent error. @@ -362,10 +399,10 @@ void WebSocketCPUEvaluate(DebuggerRequest &req) { u32 val; PostfixExpression postfix; - if (!currentDebugMIPS->initExpression(exp.c_str(), postfix)) { + if (!cpuDebug->initExpression(exp.c_str(), postfix)) { return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); } - if (!currentDebugMIPS->parseExpression(postfix, val)) { + if (!cpuDebug->parseExpression(postfix, val)) { return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); } diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 95dddcebb820..9971505a8448 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -23,6 +23,7 @@ #include "Core/Debugger/DisassemblyManager.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPSAsm.h" #include "Core/MIPS/MIPSDebugInterface.h" @@ -62,6 +63,20 @@ void WebSocketDisasmShutdown(void *p) { delete static_cast(p); } +static DebugInterface *CPUFromRequest(DebuggerRequest &req) { + if (!req.HasParam("thread")) + return currentDebugMIPS; + + u32 uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + return cpuDebug; +} + void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLineInfo &l) { u32 addr = l.info.opcodeAddress; json.pushDict(); @@ -112,6 +127,7 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi json.writeNull("breakpoint"); } + // This is always the current execution's PC. json.writeBool("isCurrentPC", currentDebugMIPS->GetPC() == addr); if (l.info.isBranch) { json.pushDict("branch"); @@ -229,11 +245,13 @@ void WebSocketDisasmState::Base(DebuggerRequest &req) { // Disassemble a range of memory as CPU instructions (memory.disasm) // // Parameters (by count): +// - thread: optional number indicating the thread id for branch info. // - address: number specifying the start address. // - count: number of lines to return (may be clamped to an internal limit.) // - displaySymbols: boolean true to show symbol names in instruction params. // // Parameters (by end address): +// - thread: optional number indicating the thread id for branch info. // - address: number specifying the start address. // - end: number which must be after the start address (may be clamped to an internal limit.) // - displaySymbols: boolean true to show symbol names in instruction params. @@ -256,9 +274,11 @@ void WebSocketDisasmState::Base(DebuggerRequest &req) { // - params: formatted parameters for the instruction. // - (other info about the disassembled line.) void WebSocketDisasmState::Disasm(DebuggerRequest &req) { - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) return req.Fail("CPU not started"); - } + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; // In case of client errors, we limit the range to something that won't make us crash. static const uint32_t MAX_RANGE = 10000; @@ -310,7 +330,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { DisassemblyLineInfo line; uint32_t addr = start; for (uint32_t i = 0; i < count; ++i) { - disasm_.getLine(addr, displaySymbols, line); + disasm_.getLine(addr, displaySymbols, line, cpuDebug); WriteDisasmLine(json, line); addr += line.totalSize; @@ -330,6 +350,7 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { // Search disassembly for some text (cpu.searchDisasm) // // Parameters: +// - thread: optional number indicating the thread id (may not affect search much.) // - address: starting address as a number. // - end: optional end address as a number (otherwise uses start.) // - match: string to search for. @@ -338,9 +359,11 @@ void WebSocketDisasmState::Disasm(DebuggerRequest &req) { // Response (same event name): // - address: number address of match or null if none was found. void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { - if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) { + if (!currentDebugMIPS->isAlive() || !Memory::IsActive()) return req.Fail("CPU not started"); - } + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; uint32_t start; if (!req.ParamU32("address", &start)) @@ -373,7 +396,7 @@ void WebSocketDisasmState::SearchDisasm(DebuggerRequest &req) { bool found = false; uint32_t addr = start; do { - disasm_.getLine(addr, displaySymbols, line); + disasm_.getLine(addr, displaySymbols, line, cpuDebug); const std::string addressSymbol = g_symbolMap->GetLabelString(addr); std::string mergeForSearch; diff --git a/Core/HLE/KernelThreadDebugInterface.h b/Core/HLE/KernelThreadDebugInterface.h new file mode 100644 index 000000000000..012a93fc3702 --- /dev/null +++ b/Core/HLE/KernelThreadDebugInterface.h @@ -0,0 +1,90 @@ +// 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 +#include "Core/HLE/sceKernelThread.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +class KernelThreadDebugInterface : public MIPSDebugInterface { +public: + KernelThreadDebugInterface(MIPSState *c, ThreadContext &t) : MIPSDebugInterface(c), ctx(t) { + } + + unsigned int getPC() override { return ctx.pc; } + void setPC(unsigned int address) override { ctx.pc = address; } + + u32 GetGPR32Value(int reg) override { return ctx.r[reg]; } + u32 GetPC() override { return ctx.pc; } + u32 GetLR() override { return ctx.r[MIPS_REG_RA]; } + void SetPC(u32 _pc) override { ctx.pc = _pc; } + + void PrintRegValue(int cat, int index, char *out) override { + switch (cat) { + case 0: sprintf(out, "%08X", ctx.r[index]); break; + case 1: sprintf(out, "%f", ctx.f[index]); break; + case 2: sprintf(out, "N/A"); break; + } + } + + u32 GetHi() override { + return ctx.hi; + } + + u32 GetLo() override { + return ctx.lo; + } + + void SetHi(u32 val) override { + ctx.hi = val; + } + + void SetLo(u32 val) override { + ctx.lo = val; + } + + u32 GetRegValue(int cat, int index) override { + switch (cat) { + case 0: return ctx.r[index]; + case 1: return ctx.fi[index]; + case 2: return ctx.vi[voffset[index]]; + default: return 0; + } + } + + void SetRegValue(int cat, int index, u32 value) override { + switch (cat) { + case 0: + if (index != 0) + ctx.r[index] = value; + break; + + case 1: + ctx.fi[index] = value; + break; + + case 2: + ctx.vi[voffset[index]] = value; + break; + + default: + break; + } + } + +protected: + ThreadContext &ctx; +}; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index fccf608db192..11d246576ecc 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -29,6 +29,7 @@ #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPS.h" +#include "Core/MIPS/MIPSDebugInterface.h" #include "Core/CoreTiming.h" #include "Core/MemMapHelpers.h" #include "Core/MIPS/JitCommon/JitCommon.h" @@ -41,6 +42,7 @@ #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelInterrupt.h" +#include "Core/HLE/KernelThreadDebugInterface.h" #include "Core/HLE/KernelWaitHelpers.h" #include "Core/HLE/ThreadQueueList.h" @@ -493,7 +495,7 @@ class Thread : public KernelObject return true; } - Thread() + Thread() : debug(currentMIPS, context) { currentStack.start = 0; } @@ -586,6 +588,7 @@ class Thread : public KernelObject SceUID currentCallbackId; ThreadContext context; + KernelThreadDebugInterface debug; std::vector callbacks; @@ -3510,14 +3513,13 @@ std::vector GetThreadsInfo() std::vector threadList; u32 error; - for (auto iter = threadqueue.begin(); iter != threadqueue.end(); ++iter) - { - Thread *t = kernelObjects.Get(*iter, error); + for (const auto uid : threadqueue) { + Thread *t = kernelObjects.Get(uid, error); if (!t) continue; DebugThreadInfo info; - info.id = *iter; + info.id = uid; strncpy(info.name,t->GetName(),KERNELOBJECT_MAX_NAME_LENGTH); info.name[KERNELOBJECT_MAX_NAME_LENGTH] = 0; info.status = t->nt.status; @@ -3526,17 +3528,31 @@ std::vector GetThreadsInfo() info.stackSize = (u32)t->nt.stackSize; info.priority = t->nt.currentPriority; info.waitType = (WaitType)(u32)t->nt.waitType; - if(*iter == currentThread) + info.isCurrent = uid == currentThread; + if (info.isCurrent) info.curPC = currentMIPS->pc; else info.curPC = t->context.pc; - info.isCurrent = (*iter == currentThread); threadList.push_back(info); } return threadList; } +DebugInterface *KernelDebugThread(SceUID threadID) { + if (threadID == currentThread) { + return currentDebugMIPS; + } + + u32 error; + Thread *t = kernelObjects.Get(threadID, error); + if (t) { + return &t->debug; + } + + return nullptr; +} + void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus) { u32 error; diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 53fa5e43678e..6216fc02ec3a 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -27,6 +27,7 @@ // http://code.google.com/p/jpcsp/source/browse/trunk/src/jpcsp/HLE/modules150/ThreadManForUser.java class Thread; +class DebugInterface; int sceKernelChangeThreadPriority(SceUID threadID, int priority); SceUID __KernelCreateThreadInternal(const char *threadName, SceUID moduleID, u32 entry, u32 prio, int stacksize, u32 attr); @@ -316,6 +317,7 @@ struct DebugThreadInfo }; std::vector GetThreadsInfo(); +DebugInterface *KernelDebugThread(SceUID threadID); void __KernelChangeThreadState(SceUID threadId, ThreadStatus newStatus); int LoadExecForUser_362A956B(); From 5bfba9b28440d0b5afcc26c3053369abb0e6c570 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 8 May 2018 17:23:14 -0700 Subject: [PATCH 40/45] Debugger: Add HLE API funcs. Lumping function symbols in here too, they're pretty related... --- CMakeLists.txt | 2 + Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/Debugger/WebSocket.cpp | 2 + Core/Debugger/WebSocket/HLESubscriber.cpp | 103 ++++++++++++++++++++++ Core/Debugger/WebSocket/HLESubscriber.h | 26 ++++++ Core/HLE/sceKernelThread.cpp | 26 +++--- android/jni/Android.mk | 1 + 8 files changed, 158 insertions(+), 10 deletions(-) create mode 100644 Core/Debugger/WebSocket/HLESubscriber.cpp create mode 100644 Core/Debugger/WebSocket/HLESubscriber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a1cc6b95f530..5d1b359c53bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1414,6 +1414,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Debugger/WebSocket/GameBroadcaster.h Core/Debugger/WebSocket/GameSubscriber.cpp Core/Debugger/WebSocket/GameSubscriber.h + Core/Debugger/WebSocket/HLESubscriber.cpp + Core/Debugger/WebSocket/HLESubscriber.h Core/Debugger/WebSocket/LogBroadcaster.cpp Core/Debugger/WebSocket/LogBroadcaster.h Core/Debugger/WebSocket/SteppingBroadcaster.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 1b95d40373c6..d38b535a2399 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -189,6 +189,7 @@ + @@ -546,6 +547,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index f4803dd56cb2..221dd1c73861 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -725,6 +725,9 @@ Debugger\WebSocket + + Debugger\WebSocket + @@ -1340,6 +1343,9 @@ HLE\Kernel + + Debugger\WebSocket + diff --git a/Core/Debugger/WebSocket.cpp b/Core/Debugger/WebSocket.cpp index f477746c7898..7bce20cc5e7a 100644 --- a/Core/Debugger/WebSocket.cpp +++ b/Core/Debugger/WebSocket.cpp @@ -52,6 +52,7 @@ #include "Core/Debugger/WebSocket/CPUCoreSubscriber.h" #include "Core/Debugger/WebSocket/DisasmSubscriber.h" #include "Core/Debugger/WebSocket/GameSubscriber.h" +#include "Core/Debugger/WebSocket/HLESubscriber.h" #include "Core/Debugger/WebSocket/SteppingSubscriber.h" typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); @@ -66,6 +67,7 @@ static const std::vector subscribers({ { &WebSocketCPUCoreInit, nullptr }, { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, { &WebSocketGameInit, nullptr }, + { &WebSocketHLEInit, nullptr }, { &WebSocketSteppingInit, &WebSocketSteppingShutdown }, }); diff --git a/Core/Debugger/WebSocket/HLESubscriber.cpp b/Core/Debugger/WebSocket/HLESubscriber.cpp new file mode 100644 index 000000000000..efb82c3b3613 --- /dev/null +++ b/Core/Debugger/WebSocket/HLESubscriber.cpp @@ -0,0 +1,103 @@ +// 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 "Core/Debugger/SymbolMap.h" +#include "Core/Debugger/WebSocket/HLESubscriber.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" +#include "Core/HLE/sceKernelThread.h" + +void *WebSocketHLEInit(DebuggerEventHandlerMap &map) { + map["hle.thread.list"] = &WebSocketHLEThreadList; + map["hle.func.list"] = &WebSocketHLEFuncList; + map["hle.module.list"] = &WebSocketHLEModuleList; + + return nullptr; +} + +void WebSocketHLEThreadList(DebuggerRequest &req) { + // Will just return none of the CPU isn't ready yet. + auto threads = GetThreadsInfo(); + + JsonWriter &json = req.Respond(); + json.pushArray("threads"); + for (auto th : threads) { + json.pushDict(); + json.writeUint("id", th.id); + json.writeString("name", th.name); + json.writeInt("status", th.status); + json.pushArray("statuses"); + if (th.status & THREADSTATUS_RUNNING) + json.writeString("running"); + if (th.status & THREADSTATUS_READY) + json.writeString("ready"); + if (th.status & THREADSTATUS_WAIT) + json.writeString("wait"); + if (th.status & THREADSTATUS_SUSPEND) + json.writeString("suspend"); + if (th.status & THREADSTATUS_DORMANT) + json.writeString("dormant"); + if (th.status & THREADSTATUS_DEAD) + json.writeString("dead"); + json.pop(); + json.writeUint("pc", th.curPC); + json.writeUint("entry", th.entrypoint); + json.writeUint("initialStackSize", th.initialStack); + json.writeUint("currentStackSize", th.stackSize); + json.writeInt("priority", th.priority); + json.writeInt("priority", (int)th.waitType); + json.writeBool("isCurrent", th.isCurrent); + json.pop(); + } + json.pop(); +} + +void WebSocketHLEFuncList(DebuggerRequest &req) { + if (!g_symbolMap) + return req.Fail("CPU not active"); + + auto functions = g_symbolMap->GetAllSymbols(ST_FUNCTION); + + JsonWriter &json = req.Respond(); + json.pushArray("functions"); + for (auto f : functions) { + json.pushDict(); + json.writeString("name", f.name); + json.writeUint("address", f.address); + json.writeUint("size", f.size); + json.pop(); + } + json.pop(); +} + +void WebSocketHLEModuleList(DebuggerRequest &req) { + if (!g_symbolMap) + return req.Fail("CPU not active"); + + auto modules = g_symbolMap->getAllModules(); + + JsonWriter &json = req.Respond(); + json.pushArray("functions"); + for (auto m : modules) { + json.pushDict(); + json.writeString("name", m.name); + json.writeUint("address", m.address); + json.writeUint("size", m.size); + json.writeBool("isActive", m.active); + json.pop(); + } + json.pop(); +} diff --git a/Core/Debugger/WebSocket/HLESubscriber.h b/Core/Debugger/WebSocket/HLESubscriber.h new file mode 100644 index 000000000000..61af4dc80d75 --- /dev/null +++ b/Core/Debugger/WebSocket/HLESubscriber.h @@ -0,0 +1,26 @@ +// 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 *WebSocketHLEInit(DebuggerEventHandlerMap &map); + +void WebSocketHLEThreadList(DebuggerRequest &req); +void WebSocketHLEFuncList(DebuggerRequest &req); +void WebSocketHLEModuleList(DebuggerRequest &req); diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 11d246576ecc..576c3f118267 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -15,10 +15,11 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. -#include +#include #include +#include +#include #include -#include #include "base/logging.h" @@ -647,6 +648,8 @@ static std::vector pendingDeleteThreads; // Lists all thread ids that aren't deleted/etc. std::vector threadqueue; +// Only for debugger, so not needed to read, just write. +std::mutex threadqueueLock; // Lists only ready thread ids. ThreadQueueList threadReadyQueue; @@ -1154,8 +1157,9 @@ void __KernelIdle() __KernelReSchedule("idle"); } -void __KernelThreadingShutdown() -{ +void __KernelThreadingShutdown() { + std::lock_guard guard(threadqueueLock); + kernelMemory.Free(threadReturnHackAddr); threadqueue.clear(); threadReadyQueue.clear(); @@ -1590,8 +1594,9 @@ void __KernelCancelThreadEndTimeout(SceUID threadID) CoreTiming::UnscheduleEvent(eventThreadEndTimeout, threadID); } -static void __KernelRemoveFromThreadQueue(SceUID threadID) -{ +static void __KernelRemoveFromThreadQueue(SceUID threadID) { + std::lock_guard guard(threadqueueLock); + int prio = __KernelGetThreadPrio(threadID); if (prio != 0) threadReadyQueue.remove(prio, threadID); @@ -1850,8 +1855,9 @@ void __KernelResetThread(Thread *t, int lowestPriority) ERROR_LOG_REPORT(SCEKERNEL, "Resetting thread with threads waiting on end?"); } -Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) -{ +Thread *__KernelCreateThread(SceUID &id, SceUID moduleId, const char *name, u32 entryPoint, u32 priority, int stacksize, u32 attr) { + std::lock_guard guard(threadqueueLock); + Thread *t = new Thread; id = kernelObjects.Create(t); @@ -3508,8 +3514,8 @@ void __KernelRegisterWaitTypeFuncs(WaitType type, WaitBeginCallbackFunc beginFun waitTypeFuncs[type].endFunc = endFunc; } -std::vector GetThreadsInfo() -{ +std::vector GetThreadsInfo() { + std::lock_guard guard(threadqueueLock); std::vector threadList; u32 error; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index b08ade3a289d..4501775884b2 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -307,6 +307,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/Debugger/WebSocket/DisasmSubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/GameBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/GameSubscriber.cpp \ + $(SRC)/Core/Debugger/WebSocket/HLESubscriber.cpp \ $(SRC)/Core/Debugger/WebSocket/LogBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingBroadcaster.cpp \ $(SRC)/Core/Debugger/WebSocket/SteppingSubscriber.cpp \ From b828497fe2a12caff429f0eedcb1799d6dbe9a08 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 11 May 2018 20:58:36 -0700 Subject: [PATCH 41/45] Debugger: Avoid some lock ordering issues. Ideally get rid of the memory lock... --- Core/Debugger/DisassemblyManager.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 2c0a6edd54cf..c520f86f5fbd 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -150,6 +150,7 @@ void DisassemblyManager::analyze(u32 address, u32 size = 1024) if (!PSP_IsInited()) return; + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries, address, false); if (it != entries.end()) @@ -272,6 +273,7 @@ void DisassemblyManager::getLine(u32 address, bool insertSymbols, DisassemblyLin u32 DisassemblyManager::getStartAddress(u32 address) { + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) @@ -289,6 +291,7 @@ u32 DisassemblyManager::getStartAddress(u32 address) u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n) { + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); while (Memory::IsValidAddress(address)) { @@ -318,6 +321,7 @@ u32 DisassemblyManager::getNthPreviousAddress(u32 address, int n) u32 DisassemblyManager::getNthNextAddress(u32 address, int n) { + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); while (Memory::IsValidAddress(address)) { @@ -352,6 +356,7 @@ DisassemblyManager::~DisassemblyManager() { void DisassemblyManager::clear() { + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); for (auto it = entries.begin(); it != entries.end(); it++) { From a863ce79adc7c74c69f893d2471c9387cb0b63ec Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 11 May 2018 21:16:03 -0700 Subject: [PATCH 42/45] Debugger: Allow stepping based on thread. --- .../Debugger/WebSocket/SteppingSubscriber.cpp | 113 +++++++++++++----- 1 file changed, 84 insertions(+), 29 deletions(-) diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 062064d9cd80..594726e2e207 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -43,8 +43,8 @@ struct WebSocketSteppingState { void HLE(DebuggerRequest &req); protected: - u32 GetNextAddress(); - int GetNextInstructionCount(); + u32 GetNextAddress(DebugInterface *cpuDebug); + int GetNextInstructionCount(DebugInterface *cpuDebug); void PrepareResume(); DisassemblyManager disasm_; @@ -65,27 +65,63 @@ void WebSocketSteppingShutdown(void *p) { delete static_cast(p); } +static DebugInterface *CPUFromRequest(DebuggerRequest &req, u32 *threadID = nullptr) { + if (!req.HasParam("thread")) { + if (threadID) + *threadID = -1; + return currentDebugMIPS; + } + + u32 uid; + if (!req.ParamU32("thread", &uid)) + return nullptr; + + DebugInterface *cpuDebug = KernelDebugThread((SceUID)uid); + if (!cpuDebug) + req.Fail("Thread could not be found"); + if (threadID) + *threadID = uid; + return cpuDebug; +} + // Single step into the next instruction (cpu.stepInto) // -// No parameters. +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. // // No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. void WebSocketSteppingState::Into(DebuggerRequest &req) { - if (!currentDebugMIPS->isAlive()) { + if (!currentDebugMIPS->isAlive()) return req.Fail("CPU not started"); - } - if (!Core_IsStepping()) { Core_EnableStepping(true); return; } - // If the current PC is on a breakpoint, the user doesn't want to do nothing. - CBreakPoints::SetSkipFirst(currentMIPS->pc); + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + if (cpuDebug == currentDebugMIPS) { + // If the current PC is on a breakpoint, the user doesn't want to do nothing. + CBreakPoints::SetSkipFirst(currentMIPS->pc); - int c = GetNextInstructionCount(); - for (int i = 0; i < c; ++i) { - Core_DoSingleStep(); + int c = GetNextInstructionCount(cpuDebug); + for (int i = 0; i < c; ++i) { + Core_DoSingleStep(); + } + } else { + u32 breakpointAddress = cpuDebug->GetPC(); + PrepareResume(); + // Could have advanced to the breakpoint already in PrepareResume(). + // Note: we need to get cpuDebug again anyway (in case we ran some HLE above.) + cpuDebug = CPUFromRequest(req); + if (cpuDebug != currentDebugMIPS) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + Core_EnableStepping(false); + } } } @@ -93,16 +129,23 @@ void WebSocketSteppingState::Into(DebuggerRequest &req) { // // Note: this jumps over function calls, but also delay slots. // -// No parameters. +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. // // No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. void WebSocketSteppingState::Over(DebuggerRequest &req) { - if (!currentDebugMIPS->isAlive()) { + if (!currentDebugMIPS->isAlive()) return req.Fail("CPU not started"); - } + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; - MipsOpcodeInfo info = GetOpcodeInfo(currentDebugMIPS, currentMIPS->pc); - u32 breakpointAddress = GetNextAddress(); + MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); + u32 breakpointAddress = GetNextAddress(cpuDebug); if (info.isBranch) { if (info.isConditional && !info.isLinkedBranch) { if (info.conditionMet) { @@ -124,7 +167,8 @@ void WebSocketSteppingState::Over(DebuggerRequest &req) { PrepareResume(); // Could have advanced to the breakpoint already in PrepareResume(). - if (currentMIPS->pc != breakpointAddress) { + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { CBreakPoints::AddBreakPoint(breakpointAddress, true); Core_EnableStepping(false); } @@ -132,26 +176,36 @@ void WebSocketSteppingState::Over(DebuggerRequest &req) { // Step out of a function based on a stack walk (cpu.stepOut) // -// No parameters. +// Parameters: +// - thread: optional number indicating the thread id to plan stepping on. // // No immediate response. A cpu.stepping event will be sent once complete. +// +// Note: any thread can wake the cpu when it hits the next instruction currently. void WebSocketSteppingState::Out(DebuggerRequest &req) { - if (!currentDebugMIPS->isAlive()) { + if (!currentDebugMIPS->isAlive()) return req.Fail("CPU not started"); - } + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + u32 threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; auto threads = GetThreadsInfo(); - u32 entry = currentMIPS->pc; + u32 entry = cpuDebug->GetPC(); u32 stackTop = 0; for (const DebugThreadInfo &th : threads) { - if (th.isCurrent) { + if ((threadID == -1 && th.isCurrent) || th.id == threadID) { entry = th.entrypoint; stackTop = th.initialStack; break; } } - auto frames = MIPSStackWalk::Walk(currentMIPS->pc, currentMIPS->r[MIPS_REG_RA], currentMIPS->r[MIPS_REG_SP], entry, stackTop); + u32 ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); + u32 sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); + auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop); if (frames.size() < 2) { // TODO: Respond in some way? return; @@ -160,13 +214,14 @@ void WebSocketSteppingState::Out(DebuggerRequest &req) { u32 breakpointAddress = frames[1].pc; PrepareResume(); // Could have advanced to the breakpoint already in PrepareResume(). - if (currentMIPS->pc != breakpointAddress) { + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { CBreakPoints::AddBreakPoint(breakpointAddress, true); Core_EnableStepping(false); } } -// Run until a certain address (cpu.stepOut) +// Run until a certain address (cpu.runUntil) // // Parameters: // - address: number parameter for destination. @@ -207,13 +262,13 @@ void WebSocketSteppingState::HLE(DebuggerRequest &req) { Core_EnableStepping(false); } -u32 WebSocketSteppingState::GetNextAddress() { - u32 current = disasm_.getStartAddress(currentMIPS->pc); +u32 WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { + u32 current = disasm_.getStartAddress(cpuDebug->GetPC()); return disasm_.getNthNextAddress(current, 1); } -int WebSocketSteppingState::GetNextInstructionCount() { - return (GetNextAddress() - currentMIPS->pc) / 4; +int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) { + return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 4; } void WebSocketSteppingState::PrepareResume() { From b11465632192a11b639f100bf489768b6e7a3de3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 12 May 2018 06:11:55 -0700 Subject: [PATCH 43/45] Debugger: Allow conditions on threadID/moduleID. And now step over/out/into can tie to the correct thread. --- Core/Debugger/Breakpoints.cpp | 6 +-- Core/Debugger/Breakpoints.h | 2 +- .../Debugger/WebSocket/SteppingSubscriber.cpp | 52 ++++++++++++------- Core/MIPS/MIPSDebugInterface.cpp | 19 ++++++- 4 files changed, 56 insertions(+), 23 deletions(-) diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index d1b293c578bb..dd6184d2ce3a 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -297,7 +297,7 @@ void CBreakPoints::ClearTemporaryBreakPoints() void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) { std::unique_lock guard(breakPointsMutex_); - size_t bp = FindBreakpoint(addr, true, false); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = true; @@ -310,7 +310,7 @@ void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) { std::unique_lock guard(breakPointsMutex_); - size_t bp = FindBreakpoint(addr, true, false); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = false; @@ -322,7 +322,7 @@ void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) { std::lock_guard guard(breakPointsMutex_); - size_t bp = FindBreakpoint(addr, true, false); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond) return &breakPoints_[bp].cond; return NULL; diff --git a/Core/Debugger/Breakpoints.h b/Core/Debugger/Breakpoints.h index 45f91d3cc8fd..bd3dd670e487 100644 --- a/Core/Debugger/Breakpoints.h +++ b/Core/Debugger/Breakpoints.h @@ -133,7 +133,7 @@ class CBreakPoints static void ClearAllBreakPoints(); static void ClearTemporaryBreakPoints(); - // Makes a copy. Temporary breakpoints can't have conditions. + // Makes a copy of the condition. static void ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond); static void ChangeBreakPointRemoveCond(u32 addr); static BreakPointCond *GetBreakPointCondition(u32 addr); diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp index 594726e2e207..e1347f3a14bd 100644 --- a/Core/Debugger/WebSocket/SteppingSubscriber.cpp +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -43,9 +43,10 @@ struct WebSocketSteppingState { void HLE(DebuggerRequest &req); protected: - u32 GetNextAddress(DebugInterface *cpuDebug); + uint32_t GetNextAddress(DebugInterface *cpuDebug); int GetNextInstructionCount(DebugInterface *cpuDebug); void PrepareResume(); + void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID); DisassemblyManager disasm_; }; @@ -65,14 +66,14 @@ void WebSocketSteppingShutdown(void *p) { delete static_cast(p); } -static DebugInterface *CPUFromRequest(DebuggerRequest &req, u32 *threadID = nullptr) { +static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) { if (!req.HasParam("thread")) { if (threadID) *threadID = -1; return currentDebugMIPS; } - u32 uid; + uint32_t uid; if (!req.ParamU32("thread", &uid)) return nullptr; @@ -100,7 +101,8 @@ void WebSocketSteppingState::Into(DebuggerRequest &req) { return; } - auto cpuDebug = CPUFromRequest(req); + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); if (!cpuDebug) return; @@ -113,13 +115,14 @@ void WebSocketSteppingState::Into(DebuggerRequest &req) { Core_DoSingleStep(); } } else { - u32 breakpointAddress = cpuDebug->GetPC(); + uint32_t breakpointAddress = cpuDebug->GetPC(); PrepareResume(); // Could have advanced to the breakpoint already in PrepareResume(). // Note: we need to get cpuDebug again anyway (in case we ran some HLE above.) cpuDebug = CPUFromRequest(req); if (cpuDebug != currentDebugMIPS) { CBreakPoints::AddBreakPoint(breakpointAddress, true); + AddThreadCondition(breakpointAddress, threadID); Core_EnableStepping(false); } } @@ -140,12 +143,14 @@ void WebSocketSteppingState::Over(DebuggerRequest &req) { return req.Fail("CPU not started"); if (!Core_IsStepping()) return req.Fail("CPU currently running (cpu.stepping first)"); - auto cpuDebug = CPUFromRequest(req); + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); if (!cpuDebug) return; MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); - u32 breakpointAddress = GetNextAddress(cpuDebug); + uint32_t breakpointAddress = GetNextAddress(cpuDebug); if (info.isBranch) { if (info.isConditional && !info.isLinkedBranch) { if (info.conditionMet) { @@ -170,6 +175,8 @@ void WebSocketSteppingState::Over(DebuggerRequest &req) { cpuDebug = CPUFromRequest(req); if (cpuDebug->GetPC() != breakpointAddress) { CBreakPoints::AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); Core_EnableStepping(false); } } @@ -187,14 +194,15 @@ void WebSocketSteppingState::Out(DebuggerRequest &req) { return req.Fail("CPU not started"); if (!Core_IsStepping()) return req.Fail("CPU currently running (cpu.stepping first)"); - u32 threadID; + + uint32_t threadID; auto cpuDebug = CPUFromRequest(req, &threadID); if (!cpuDebug) return; auto threads = GetThreadsInfo(); - u32 entry = cpuDebug->GetPC(); - u32 stackTop = 0; + uint32_t entry = cpuDebug->GetPC(); + uint32_t stackTop = 0; for (const DebugThreadInfo &th : threads) { if ((threadID == -1 && th.isCurrent) || th.id == threadID) { entry = th.entrypoint; @@ -203,20 +211,21 @@ void WebSocketSteppingState::Out(DebuggerRequest &req) { } } - u32 ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); - u32 sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); + uint32_t ra = cpuDebug->GetRegValue(0, MIPS_REG_RA); + uint32_t sp = cpuDebug->GetRegValue(0, MIPS_REG_SP); auto frames = MIPSStackWalk::Walk(cpuDebug->GetPC(), ra, sp, entry, stackTop); if (frames.size() < 2) { - // TODO: Respond in some way? - return; + return req.Fail("Could not find function call to step out into"); } - u32 breakpointAddress = frames[1].pc; + uint32_t breakpointAddress = frames[1].pc; PrepareResume(); // Could have advanced to the breakpoint already in PrepareResume(). cpuDebug = CPUFromRequest(req); if (cpuDebug->GetPC() != breakpointAddress) { CBreakPoints::AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); Core_EnableStepping(false); } } @@ -232,7 +241,7 @@ void WebSocketSteppingState::RunUntil(DebuggerRequest &req) { return req.Fail("CPU not started"); } - u32 address = 0; + uint32_t address = 0; if (!req.ParamU32("address", &address)) { // Error already sent. return; @@ -262,8 +271,8 @@ void WebSocketSteppingState::HLE(DebuggerRequest &req) { Core_EnableStepping(false); } -u32 WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { - u32 current = disasm_.getStartAddress(cpuDebug->GetPC()); +uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { + uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC()); return disasm_.getNthNextAddress(current, 1); } @@ -280,3 +289,10 @@ void WebSocketSteppingState::PrepareResume() { } } +void WebSocketSteppingState::AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID) { + BreakPointCond cond; + cond.debug = currentDebugMIPS; + cond.expressionString = StringFromFormat("threadid == 0x%08x", threadID); + if (currentDebugMIPS->initExpression(cond.expressionString.c_str(), cond.expression)) + CBreakPoints::ChangeBreakPointAddCond(breakpointAddress, cond); +} diff --git a/Core/MIPS/MIPSDebugInterface.cpp b/Core/MIPS/MIPSDebugInterface.cpp index 943e56493866..ae1a9a7a6cdb 100644 --- a/Core/MIPS/MIPSDebugInterface.cpp +++ b/Core/MIPS/MIPSDebugInterface.cpp @@ -23,6 +23,7 @@ #include "Core/Debugger/DebugInterface.h" #include "Core/MIPS/MIPSDebugInterface.h" +#include "Core/HLE/sceKernelThread.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPS.h" @@ -37,13 +38,16 @@ enum ReferenceIndexType { REF_INDEX_VFPU = 0x4000, REF_INDEX_VFPU_INT = 0x8000, REF_INDEX_IS_FLOAT = REF_INDEX_FPU | REF_INDEX_VFPU, + REF_INDEX_HLE = 0x10000, + REF_INDEX_THREAD = REF_INDEX_HLE | 0, + REF_INDEX_MODULE = REF_INDEX_HLE | 1, }; class MipsExpressionFunctions: public IExpressionFunctions { public: - MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { }; + MipsExpressionFunctions(DebugInterface* cpu): cpu(cpu) { } bool parseReference(char* str, uint32_t& referenceIndex) override { @@ -106,6 +110,15 @@ class MipsExpressionFunctions: public IExpressionFunctions return true; } + if (strcasecmp(str, "threadid") == 0) { + referenceIndex = REF_INDEX_THREAD; + return true; + } + if (strcasecmp(str, "moduleid") == 0) { + referenceIndex = REF_INDEX_MODULE; + return true; + } + return false; } @@ -124,6 +137,10 @@ class MipsExpressionFunctions: public IExpressionFunctions return cpu->GetHi(); if (referenceIndex == REF_INDEX_LO) return cpu->GetLo(); + if (referenceIndex == REF_INDEX_THREAD) + return __KernelGetCurThread(); + if (referenceIndex == REF_INDEX_MODULE) + return __KernelGetCurThreadModuleId(); if ((referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)) < 32) return cpu->GetRegValue(1, referenceIndex & ~(REF_INDEX_FPU | REF_INDEX_FPU_INT)); if ((referenceIndex & ~(REF_INDEX_VFPU | REF_INDEX_VFPU_INT)) < 128) From 25085fa39477c3b1ea2fb2648bf926fe8bb2e156 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 13 May 2018 10:45:12 -0700 Subject: [PATCH 44/45] Debugger: Add func name and data symbol to disasm. --- Core/Debugger/WebSocket/DisasmSubscriber.cpp | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp index 9971505a8448..78ee607fdfac 100644 --- a/Core/Debugger/WebSocket/DisasmSubscriber.cpp +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -112,6 +112,30 @@ void WebSocketDisasmState::WriteDisasmLine(JsonWriter &json, const DisassemblyLi else json.writeString("symbol", addressSymbol); + const u32 funcAddress = g_symbolMap->GetFunctionStart(addr); + const std::string funcName = g_symbolMap->GetLabelString(funcAddress); + if (funcName.empty()) + json.writeNull("function"); + else + json.writeString("function", funcName); + + if (l.type == DISTYPE_DATA) { + u32 dataStart = g_symbolMap->GetDataStart(addr); + if (dataStart == -1) + dataStart = addr; + const std::string dataLabel = g_symbolMap->GetLabelString(dataStart); + + json.pushDict("dataSymbol"); + json.writeUint("start", dataStart); + if (dataLabel.empty()) + json.writeNull("label"); + else + json.writeString("label", dataLabel); + json.pop(); + } else { + json.writeNull("dataSymbol"); + } + bool enabled; // TODO: Account for bp inside macro? if (CBreakPoints::IsAddressBreakPoint(addr, &enabled)) { From 085bcde86580b0fa965ca0ba288f153f15a19ef3 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sun, 13 May 2018 12:51:04 -0700 Subject: [PATCH 45/45] Debugger: Ignore invalid branches. These happen on bytes that are not actually code. --- Core/Debugger/DisassemblyManager.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index c520f86f5fbd..b04c40cc1651 100644 --- a/Core/Debugger/DisassemblyManager.cpp +++ b/Core/Debugger/DisassemblyManager.cpp @@ -491,11 +491,12 @@ void DisassemblyFunction::generateBranchLines() MIPSAnalyst::MipsOpcodeInfo opInfo = MIPSAnalyst::GetOpcodeInfo(cpu,funcPos); bool inFunction = (opInfo.branchTarget >= address && opInfo.branchTarget < end); - if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) - { + if (opInfo.isBranch && !opInfo.isBranchToRegister && !opInfo.isLinkedBranch && inFunction) { + if (!Memory::IsValidAddress(opInfo.branchTarget)) + continue; + BranchLine line; - if (opInfo.branchTarget < funcPos) - { + if (opInfo.branchTarget < funcPos) { line.first = opInfo.branchTarget; line.second = funcPos; line.type = LINE_UP; @@ -756,13 +757,14 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vector