diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e0db289257e..5d1b359c53bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1402,6 +1402,28 @@ 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/Debugger/WebSocket/BreakpointSubscriber.cpp + Core/Debugger/WebSocket/BreakpointSubscriber.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 + 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 + 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 Core/Dialog/PSPDialog.h Core/Dialog/PSPGamedataInstallDialog.cpp 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/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/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/Core.vcxproj b/Core/Core.vcxproj index b068e321e9c5..d38b535a2399 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -184,6 +184,17 @@ + + + + + + + + + + + @@ -532,7 +543,19 @@ + + + + + + + + + + + + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index fe637033f806..221dd1c73861 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} + @@ -692,6 +695,39 @@ Core + + Debugger + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + @@ -1274,6 +1310,42 @@ Core + + Debugger + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + Debugger\WebSocket + + + HLE\Kernel + + + Debugger\WebSocket + diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index 0223a2f6fcbf..dd6184d2ce3a 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,73 +288,85 @@ void CBreakPoints::ClearTemporaryBreakPoints() update = true; } } - + + guard.unlock(); if (update) Update(); } void CBreakPoints::ChangeBreakPointAddCond(u32 addr, const BreakPointCond &cond) { - size_t bp = FindBreakpoint(addr, true, false); + std::unique_lock guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = true; breakPoints_[bp].cond = cond; + guard.unlock(); Update(addr); } } void CBreakPoints::ChangeBreakPointRemoveCond(u32 addr) { - size_t bp = FindBreakpoint(addr, true, false); + std::unique_lock guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT) { breakPoints_[bp].hasCond = false; + guard.unlock(); Update(addr); } } BreakPointCond *CBreakPoints::GetBreakPointCondition(u32 addr) { - size_t bp = FindBreakpoint(addr, true, false); + std::lock_guard guard(breakPointsMutex_); + size_t bp = FindBreakpoint(addr); if (bp != INVALID_BREAKPOINT && breakPoints_[bp].hasCond) return &breakPoints_[bp].cond; return NULL; } 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,49 +410,73 @@ 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(); } } +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_); + auto result = GetMemCheckLocked(address, size); + if (result) + *check = *result; + return result != nullptr; +} + +MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { std::vector::iterator iter; for (iter = memChecks_.begin(); iter != memChecks_.end(); ++iter) { @@ -425,9 +499,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 +522,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 +546,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 +586,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 +607,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..bd3dd670e487 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); @@ -128,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); @@ -144,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); @@ -172,6 +178,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_; diff --git a/Core/Debugger/DisassemblyManager.cpp b/Core/Debugger/DisassemblyManager.cpp index 77e8c917a50a..b04c40cc1651 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()) @@ -236,8 +237,10 @@ 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(); std::lock_guard guard(entriesLock_); auto it = findDisassemblyEntry(entries,address,false); if (it == entries.end()) @@ -248,7 +251,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; } @@ -270,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()) @@ -287,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)) { @@ -316,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)) { @@ -346,11 +352,11 @@ u32 DisassemblyManager::getNthNextAddress(u32 address, int n) } DisassemblyManager::~DisassemblyManager() { - clear(); } void DisassemblyManager::clear() { + auto memLock = Memory::Lock(); std::lock_guard guard(entriesLock_); for (auto it = entries.begin(); it != entries.end(); it++) { @@ -431,14 +437,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) @@ -485,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; @@ -524,7 +531,8 @@ void DisassemblyFunction::generateBranchLines() if (lane == -1) { - // error + // Let's just pile on. + lines[i].laneIndex = 15; continue; } @@ -718,16 +726,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; } @@ -746,13 +757,14 @@ void DisassemblyOpcode::getBranchLines(u32 start, u32 size, std::vectorGetLabelString(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; @@ -816,11 +830,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; @@ -865,7 +878,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; @@ -1063,7 +1076,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.cpp b/Core/Debugger/WebSocket.cpp new file mode 100644 index 000000000000..7bce20cc5e7a --- /dev/null +++ b/Core/Debugger/WebSocket.cpp @@ -0,0 +1,209 @@ +// 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 "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. +// +// 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. +// +// 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" +#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" +#include "Core/Debugger/WebSocket/HLESubscriber.h" +#include "Core/Debugger/WebSocket/SteppingSubscriber.h" + +typedef void *(*SubscriberInit)(DebuggerEventHandlerMap &map); +typedef void (*Subscribershutdown)(void *p); +struct SubscriberInfo { + SubscriberInit init; + Subscribershutdown shutdown; +}; + +static const std::vector subscribers({ + { &WebSocketBreakpointInit, nullptr }, + { &WebSocketCPUCoreInit, nullptr }, + { &WebSocketDisasmInit, &WebSocketDisasmShutdown }, + { &WebSocketGameInit, nullptr }, + { &WebSocketHLEInit, nullptr }, + { &WebSocketSteppingInit, &WebSocketSteppingShutdown }, +}); + +// 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; + +// 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: + case CoreLifecycle::MEMORY_REINITING: + if (debuggersConnected > 0) { + DEBUG_LOG(SYSTEM, "Waiting for debugger to complete on shutdown"); + } + lifecycleLock.lock(); + break; + + case CoreLifecycle::START_COMPLETE: + case CoreLifecycle::STOPPED: + case CoreLifecycle::MEMORY_REINITED: + 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) + return; + + setCurrentThreadName("Debugger"); + UpdateConnected(1); + SetupDebuggerLock(); + + LogBroadcaster logger; + GameBroadcaster game; + SteppingBroadcaster stepping; + + std::unordered_map eventHandlers; + std::vector subscriberData; + for (auto info : subscribers) { + std::lock_guard guard(lifecycleLock); + 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()) { + 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, root)); + return; + } + + DebuggerRequest req(event, ws, root); + auto eventFunc = eventHandlers.find(event); + if (eventFunc != eventHandlers.end()) { + std::lock_guard guard(lifecycleLock); + eventFunc->second(req); + if (!req.Finish()) { + // Poll more frequently for a second in case this triggers something. + highActivity = 1000; + } + } else { + req.Fail("Bad message: unknown event"); + } + }); + ws->SetBinaryHandler([&](const std::vector &d) { + ws->Send(DebuggerErrorEvent("Bad message", LogTypes::LERROR)); + }); + + 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); + game.Broadcast(ws); + stepping.Broadcast(ws); + + if (stopRequested) { + ws->Close(net::WebSocketClose::GOING_AWAY); + } + if (highActivity > 0) { + highActivity--; + } + } + + std::lock_guard guard(lifecycleLock); + for (size_t i = 0; i < subscribers.size(); ++i) { + if (subscribers[i].shutdown) { + subscribers[i].shutdown(subscriberData[i]); + } else { + assert(!subscriberData[i]); + } + } + + 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 new file mode 100644 index 000000000000..0151ca686a43 --- /dev/null +++ b/Core/Debugger/WebSocket.h @@ -0,0 +1,26 @@ +// 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); +// Note: blocks. +void StopAllDebuggers(); diff --git a/Core/Debugger/WebSocket/BreakpointSubscriber.cpp b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp new file mode 100644 index 000000000000..aa6681cb8258 --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.cpp @@ -0,0 +1,352 @@ +// 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/DisassemblyManager.h" +#include "Core/Debugger/SymbolMap.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; + + map["memory.breakpoint.add"] = &WebSocketMemoryBreakpointAdd; + map["memory.breakpoint.update"] = &WebSocketMemoryBreakpointUpdate; + map["memory.breakpoint.remove"] = &WebSocketMemoryBreakpointRemove; + map["memory.breakpoint.list"] = &WebSocketMemoryBreakpointList; + + 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"); + 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 new file mode 100644 index 000000000000..c9c2e1effcf5 --- /dev/null +++ b/Core/Debugger/WebSocket/BreakpointSubscriber.h @@ -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/. + +#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); + +void WebSocketMemoryBreakpointAdd(DebuggerRequest &req); +void WebSocketMemoryBreakpointUpdate(DebuggerRequest &req); +void WebSocketMemoryBreakpointRemove(DebuggerRequest &req); +void WebSocketMemoryBreakpointList(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp new file mode 100644 index 000000000000..0d2c42f66c2a --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.cpp @@ -0,0 +1,412 @@ +// 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/CoreTiming.h" +#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" + +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; + map["cpu.evaluate"] = &WebSocketCPUEvaluate; + + return nullptr; +} + +static std::string RegValueAsFloat(uint32_t u) { + union { + uint32_t u; + float f; + } bits = { 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. +// +// 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"); + } + + CBreakPoints::SetSkipFirst(currentMIPS->pc); + if (currentMIPS->inDelaySlot) { + Core_DoSingleStep(); + } + 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. +// - 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.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); +} + +// Retrieve all regs and their values (cpu.getAllRegs) +// +// Parameters: +// - thread: optional number indicating the thread id to get regs for. +// +// 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) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + JsonWriter &json = req.Respond(); + + json.pushArray("categories"); + for (int c = 0; c < cpuDebug->GetNumCategories(); ++c) { + json.pushDict(); + json.writeInt("id", c); + json.writeString("name", cpuDebug->GetCategoryName(c)); + + int total = cpuDebug->GetNumRegsInCategory(c); + + json.pushArray("registerNames"); + for (int r = 0; r < total; ++r) + json.writeString(cpuDebug->GetRegName(c, r)); + if (c == 0) { + json.writeString("pc"); + json.writeString("hi"); + json.writeString("lo"); + } + json.pop(); + + json.pushArray("uintValues"); + // Writing as floating point to avoid negatives. Actually double, so safe. + for (int r = 0; r < total; ++r) + json.writeUint(cpuDebug->GetRegValue(c, r)); + if (c == 0) { + 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(cpuDebug->GetRegValue(c, r))); + if (c == 0) { + json.writeString(RegValueAsFloat(cpuDebug->GetPC())); + json.writeString(RegValueAsFloat(cpuDebug->GetHi())); + json.writeString(RegValueAsFloat(cpuDebug->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; +} + +// 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. +// +// 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) { + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + int cat, reg; + uint32_t val; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + val = cpuDebug->GetRegValue(cat, reg); + break; + + case DebuggerRegType::PC: + val = cpuDebug->GetPC(); + break; + case DebuggerRegType::HI: + val = cpuDebug->GetHi(); + break; + case DebuggerRegType::LO: + val = cpuDebug->GetLo(); + break; + + case DebuggerRegType::INVALID: + // Error response already sent. + return; + } + + JsonWriter &json = req.Respond(); + json.writeInt("category", cat); + json.writeInt("register", reg); + json.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} + +// 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 +// "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.stepping first)"); + } + + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + uint32_t val; + if (!req.ParamU32("value", &val, true)) { + // Already sent error. + return; + } + + int cat, reg; + switch (ValidateCatReg(req, &cat, ®)) { + case DebuggerRegType::NORMAL: + if (cat == 0 && reg == 0 && val != 0) { + return req.Fail("Cannot change reg zero"); + } + cpuDebug->SetRegValue(cat, reg, val); + // In case part of it was ignored (e.g. flags reg.) + val = cpuDebug->GetRegValue(cat, reg); + break; + + case DebuggerRegType::PC: + cpuDebug->SetPC(val); + break; + case DebuggerRegType::HI: + cpuDebug->SetHi(val); + break; + case DebuggerRegType::LO: + cpuDebug->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.writeUint("uintValue", val); + json.writeString("floatValue", RegValueAsFloat(val)); +} + +// 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): +// - 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"); + } + + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + std::string exp; + if (!req.ParamString("expression", &exp)) { + // Already sent error. + return; + } + + u32 val; + PostfixExpression postfix; + if (!cpuDebug->initExpression(exp.c_str(), postfix)) { + return req.Fail(StringFromFormat("Could not parse expression syntax: %s", getExpressionError())); + } + if (!cpuDebug->parseExpression(postfix, val)) { + return req.Fail(StringFromFormat("Could not evaluate expression: %s", getExpressionError())); + } + + JsonWriter &json = req.Respond(); + json.writeUint("uintValue", 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..3667979e9649 --- /dev/null +++ b/Core/Debugger/WebSocket/CPUCoreSubscriber.h @@ -0,0 +1,30 @@ +// 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 *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); +void WebSocketCPUEvaluate(DebuggerRequest &req); diff --git a/Core/Debugger/WebSocket/DisasmSubscriber.cpp b/Core/Debugger/WebSocket/DisasmSubscriber.cpp new file mode 100644 index 000000000000..78ee607fdfac --- /dev/null +++ b/Core/Debugger/WebSocket/DisasmSubscriber.cpp @@ -0,0 +1,481 @@ +// 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 "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/HLE/sceKernelThread.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPSAsm.h" +#include "Core/MIPS/MIPSDebugInterface.h" + +struct WebSocketDisasmState { + WebSocketDisasmState() { + disasm_.setCpu(currentDebugMIPS); + } + ~WebSocketDisasmState() { + disasm_.clear(); + } + + 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_; +}; + +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; +} + +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(); + 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.writeUint("address", addr); + json.writeInt("addressSize", l.totalSize); + 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.writeUint(Memory::Read_Instruction(addr + off).encoding); + } + json.pop(); + } else { + json.writeNull("macroEncoding"); + } + 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.writeNull("symbol"); + 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)) { + json.pushDict("breakpoint"); + json.writeBool("enabled", enabled); + auto cond = CBreakPoints::GetBreakPointCondition(addr); + if (cond) + json.writeString("condition", cond->expressionString); + else + json.writeNull("condition"); + json.pop(); + } else { + json.writeNull("breakpoint"); + } + + // This is always the current execution's PC. + 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.writeUint("targetAddress", l.info.branchTarget); + json.writeNull("register"); + } else { + 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.writeNull("symbol"); + else + json.writeString("symbol", targetSymbol); + json.pop(); + } else { + json.writeNull("branch"); + } + + if (l.info.hasRelevantAddress) { + json.pushDict("relevantData"); + json.writeUint("address", l.info.relevantAddress); + if (Memory::IsValidRange(l.info.relevantAddress, 4)) + json.writeUint("uintValue", Memory::ReadUnchecked_U32(l.info.relevantAddress)); + else + json.writeNull("uintValue"); + json.pop(); + } else { + json.writeNull("relevantData"); + } + + if (l.info.isConditional) + json.writeBool("conditionMet", l.info.conditionMet); + else + json.writeNull("conditionMet"); + + if (l.info.isDataAccess) { + json.pushDict("dataAccess"); + 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.writeNull("uintValue"); + else if (l.info.dataSize == 1) + json.writeUint("uintValue", Memory::ReadUnchecked_U8(l.info.dataAddress)); + else if (l.info.dataSize == 2) + 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.writeUint("uintValue", data); + } + + if (!dataSymbol.empty()) + json.writeString("symbol", dataSymbol); + else + json.writeNull("symbol"); + if (!valueSymbol.empty()) + json.writeString("valueSymbol", valueSymbol); + else + json.writeNull("valueSymbol"); + json.pop(); + } else { + json.writeNull("dataAccess"); + } + + json.pop(); +} + +void WebSocketDisasmState::WriteBranchGuide(JsonWriter &json, const BranchLine &l) { + json.pushDict(); + 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) + json.writeString("direction", "down"); + else if (l.type == LINE_RIGHT) + json.writeString("direction", "right"); + json.writeInt("lane", l.laneIndex); + 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. +// +// 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): +// - 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. +// +// 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"); + 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; + + 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) { + count = std::min(count, MAX_RANGE); + // 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)) { + 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; + 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.writeUint("start", start); + json.writeUint("end", end); + json.pop(); + + json.pushArray("lines"); + DisassemblyLineInfo line; + uint32_t addr = start; + for (uint32_t i = 0; i < count; ++i) { + disasm_.getLine(addr, displaySymbols, line, cpuDebug); + 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(); + + json.pushArray("branchGuides"); + auto branchGuides = disasm_.getBranchLines(start, end - start); + for (auto bl : branchGuides) + WriteBranchGuide(json, bl); + json.pop(); +} + +// 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. +// - 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"); + auto cpuDebug = CPUFromRequest(req); + if (!cpuDebug) + return; + + 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, cpuDebug); + 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"); +} + +// 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"); + } + + 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); +} 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/GameBroadcaster.cpp b/Core/Debugger/WebSocket/GameBroadcaster.cpp new file mode 100644 index 000000000000..3045e70fc48a --- /dev/null +++ b/Core/Debugger/WebSocket/GameBroadcaster.cpp @@ -0,0 +1,94 @@ +// 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/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.writeNull("game"); + } + 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. Callbacks instead? + GlobalUIState state = GetUIState(); + if (prevState_ != state) { + if (state == UISTATE_PAUSEMENU) { + ws->Send(GameStatusEvent{"game.pause"}); + prevState_ = state; + } else if (state == UISTATE_INGAME && prevState_ == UISTATE_PAUSEMENU) { + 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; + } + } +} 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/GameSubscriber.cpp b/Core/Debugger/WebSocket/GameSubscriber.cpp new file mode 100644 index 000000000000..a17717e26215 --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.cpp @@ -0,0 +1,67 @@ +// 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; + map["version"] = &WebSocketVersion; + + 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.writeNull("game"); + } + 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 new file mode 100644 index 000000000000..77cec9e38b20 --- /dev/null +++ b/Core/Debugger/WebSocket/GameSubscriber.h @@ -0,0 +1,25 @@ +// 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); +void WebSocketVersion(DebuggerRequest &req); 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/Debugger/WebSocket/LogBroadcaster.cpp b/Core/Debugger/WebSocket/LogBroadcaster.cpp new file mode 100644 index 000000000000..07070be7b014 --- /dev/null +++ b/Core/Debugger/WebSocket/LogBroadcaster.cpp @@ -0,0 +1,118 @@ +// 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/LogBroadcaster.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.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 { + const LogMessage &l; + + operator std::string() { + JsonWriter j; + j.begin(); + j.writeString("event", "log"); + 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}); + } +} 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..bde51e1f2fca --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.cpp @@ -0,0 +1,61 @@ +// 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/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.writeUint("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_) { + ws->Send(CPUSteppingEvent()); + } else if (prevState_ == CORE_STEPPING && coreState != CORE_STEPPING && Core_IsActive()) { + ws->Send(R"({"event":"cpu.resume"})"); + } + lastCounter_ = steppingCounter; + prevState_ = coreState; + } else { + prevState_ = CORE_POWERDOWN; + } +} diff --git a/Core/Debugger/WebSocket/SteppingBroadcaster.h b/Core/Debugger/WebSocket/SteppingBroadcaster.h new file mode 100644 index 000000000000..4ca34f4552cb --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingBroadcaster.h @@ -0,0 +1,37 @@ +// 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_; + int lastCounter_ = 0; +}; diff --git a/Core/Debugger/WebSocket/SteppingSubscriber.cpp b/Core/Debugger/WebSocket/SteppingSubscriber.cpp new file mode 100644 index 000000000000..e1347f3a14bd --- /dev/null +++ b/Core/Debugger/WebSocket/SteppingSubscriber.cpp @@ -0,0 +1,298 @@ +// 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); + } + ~WebSocketSteppingState() { + disasm_.clear(); + } + + void Into(DebuggerRequest &req); + void Over(DebuggerRequest &req); + void Out(DebuggerRequest &req); + void RunUntil(DebuggerRequest &req); + void HLE(DebuggerRequest &req); + +protected: + uint32_t GetNextAddress(DebugInterface *cpuDebug); + int GetNextInstructionCount(DebugInterface *cpuDebug); + void PrepareResume(); + void AddThreadCondition(uint32_t breakpointAddress, uint32_t threadID); + + 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); +} + +static DebugInterface *CPUFromRequest(DebuggerRequest &req, uint32_t *threadID = nullptr) { + if (!req.HasParam("thread")) { + if (threadID) + *threadID = -1; + return currentDebugMIPS; + } + + uint32_t 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) +// +// 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()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) { + Core_EnableStepping(true); + return; + } + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + 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(cpuDebug); + for (int i = 0; i < c; ++i) { + Core_DoSingleStep(); + } + } else { + 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); + } + } +} + +// Step over the next instruction (cpu.stepOver) +// +// Note: this jumps over function calls, but also delay slots. +// +// 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()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; + + MipsOpcodeInfo info = GetOpcodeInfo(cpuDebug, cpuDebug->GetPC()); + uint32_t breakpointAddress = GetNextAddress(cpuDebug); + 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(). + cpuDebug = CPUFromRequest(req); + if (cpuDebug->GetPC() != breakpointAddress) { + CBreakPoints::AddBreakPoint(breakpointAddress, true); + if (cpuDebug != currentDebugMIPS) + AddThreadCondition(breakpointAddress, threadID); + Core_EnableStepping(false); + } +} + +// Step out of a function based on a stack walk (cpu.stepOut) +// +// 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()) + return req.Fail("CPU not started"); + if (!Core_IsStepping()) + return req.Fail("CPU currently running (cpu.stepping first)"); + + uint32_t threadID; + auto cpuDebug = CPUFromRequest(req, &threadID); + if (!cpuDebug) + return; + + auto threads = GetThreadsInfo(); + 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; + stackTop = th.initialStack; + break; + } + } + + 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) { + return req.Fail("Could not find function call to step out into"); + } + + 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); + } +} + +// Run until a certain address (cpu.runUntil) +// +// 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"); + } + + uint32_t 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); +} + +uint32_t WebSocketSteppingState::GetNextAddress(DebugInterface *cpuDebug) { + uint32_t current = disasm_.getStartAddress(cpuDebug->GetPC()); + return disasm_.getNthNextAddress(current, 1); +} + +int WebSocketSteppingState::GetNextInstructionCount(DebugInterface *cpuDebug) { + return (GetNextAddress(cpuDebug) - cpuDebug->GetPC()) / 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); + } +} + +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/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/Core/Debugger/WebSocket/WebSocketUtils.cpp b/Core/Debugger/WebSocket/WebSocketUtils.cpp new file mode 100644 index 000000000000..3a3536684c26 --- /dev/null +++ b/Core/Debugger/WebSocket/WebSocketUtils.cpp @@ -0,0 +1,267 @@ +// 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/StringUtils.h" +#include "Core/Debugger/WebSocket/WebSocketUtils.h" + +JsonWriter &DebuggerRequest::Respond() { + writer_.begin(); + writer_.writeString("event", name); + DebuggerJsonAddTicket(writer_, data); + + responseBegun_ = true; + return writer_; +} + +bool DebuggerRequest::Finish() { + if (responseBegun_ && !responseSent_) { + writer_.end(); + if (responsePartial_) + ws->AddFragment(true, writer_.str()); + else + ws->Send(writer_.str()); + responseBegun_ = false; + responseSent_ = true; + responsePartial_ = false; + } + + return responseSent_; +} + +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; + + // 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, 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; +} + +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; + + 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_NUMBER) { + double val = node->value.toNumber(); + bool isInteger = trunc(val) == val; + 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()) { + // 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; + } else if (allowLoose) { + *out = val >= 0 ? std::numeric_limits::max() : std::numeric_limits::min(); + return true; + } + + 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 (tag != JSON_STRING) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return true; + } + + if (U32FromString(node->value.toString(), out, allowFloatBits)) + return true; + + 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::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) { + if (required) + Fail(StringFromFormat("Missing '%s' parameter", name)); + return !required; + } + + 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; + return true; + } + + Fail(StringFromFormat("Could not parse '%s' parameter: should be true/1 or false/0", name)); + return false; + } + if (tag == JSON_TRUE) { + *out = true; + return true; + } + if (tag == JSON_FALSE) { + *out = false; + return true; + } + if (tag != JSON_STRING) { + if (type == DebuggerParamType::REQUIRED || tag != JSON_NULL) { + Fail(StringFromFormat("Invalid '%s' parameter type", name)); + return false; + } + return 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: 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 new file mode 100644 index 000000000000..e49647dcf8ab --- /dev/null +++ b/Core/Debugger/WebSocket/WebSocketUtils.h @@ -0,0 +1,100 @@ +// 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 +#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) { + // 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; + j.begin(); + j.writeString("event", "error"); + j.writeString("message", message); + j.writeInt("level", level); + if (!ticketRaw.empty()) { + j.writeRaw("ticket", ticketRaw); + } + j.end(); + return j.str(); + } +}; + +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) { + } + + 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 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); + + JsonWriter &Respond(); + void Flush(); + bool Finish(); + +private: + JsonWriter writer_; + bool responseBegun_ = false; + bool responseSent_ = false; + bool responsePartial_ = false; +}; + +typedef std::function DebuggerEventHandler; +typedef std::unordered_map DebuggerEventHandlerMap; 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..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" @@ -29,6 +30,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 +43,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 +496,7 @@ class Thread : public KernelObject return true; } - Thread() + Thread() : debug(currentMIPS, context) { currentStack.start = 0; } @@ -586,6 +589,7 @@ class Thread : public KernelObject SceUID currentCallbackId; ThreadContext context; + KernelThreadDebugInterface debug; std::vector callbacks; @@ -644,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; @@ -1151,8 +1157,9 @@ void __KernelIdle() __KernelReSchedule("idle"); } -void __KernelThreadingShutdown() -{ +void __KernelThreadingShutdown() { + std::lock_guard guard(threadqueueLock); + kernelMemory.Free(threadReturnHackAddr); threadqueue.clear(); threadReadyQueue.clear(); @@ -1587,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); @@ -1847,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); @@ -3505,19 +3514,18 @@ 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; - 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 +3534,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(); 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) 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: 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); 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/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(); } } 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/Core/WebServer.cpp b/Core/WebServer.cpp index 6b20646f2e9a..f5fdf68754ca 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)) { @@ -212,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/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/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 { 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); diff --git a/Windows/Debugger/CtrlDisAsmView.cpp b/Windows/Debugger/CtrlDisAsmView.cpp index fecc7de3976c..93d4ed61789e 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) @@ -200,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; @@ -468,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; @@ -1179,6 +1180,7 @@ void CtrlDisAsmView::calculatePixelPositions() void CtrlDisAsmView::search(bool continueSearch) { + auto memLock = Memory::Lock(); u32 searchAddress; if (continueSearch == false || searchQuery[0] == 0) @@ -1259,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); diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 428134fbcbeb..4501775884b2 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -299,7 +299,19 @@ 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/BreakpointSubscriber.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/HLESubscriber.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 \ $(SRC)/Core/Dialog/PSPMsgDialog.cpp \ diff --git a/ext/native/json/json_writer.cpp b/ext/native/json/json_writer.cpp index 3cc625e13d24..7d1c47a62031 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_ ? ", " : ","; } @@ -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,28 +161,28 @@ 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_ << "\": \""; + str_ << (pretty_ ? "\": \"" : "\":\""); writeEscapedString(value); str_ << "\""; 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 06afb66bc7a0..22c712c856d3 100644 --- a/ext/native/json/json_writer.h +++ b/ext/native/json/json_writer.h @@ -22,43 +22,35 @@ 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(); } + std::string flush() { + std::string result = str_.str(); + str_.str(""); + return result; + } + enum { NORMAL = 0, PRETTY = 1, @@ -70,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, 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; 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]{};