From 208ed712f42cfd277405a22663197dc1c5e84cfe Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 6 Jun 2022 12:56:01 -0400 Subject: core/debugger: memory breakpoint support --- src/core/debugger/debugger.cpp | 19 ++++- src/core/debugger/debugger.h | 8 +- src/core/debugger/debugger_interface.h | 8 +- src/core/debugger/gdbstub.cpp | 139 ++++++++++++++++++++++++++++----- src/core/debugger/gdbstub.h | 3 + 5 files changed, 151 insertions(+), 26 deletions(-) (limited to 'src/core/debugger') diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index ab3940922..ac64d2f9d 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp @@ -44,12 +44,14 @@ static std::span ReceiveInto(Readable& r, Buffer& buffer) { enum class SignalType { Stopped, + Watchpoint, ShuttingDown, }; struct SignalInfo { SignalType type; Kernel::KThread* thread; + const Kernel::DebugWatchpoint* watchpoint; }; namespace Core { @@ -157,13 +159,19 @@ private: void PipeData(std::span data) { switch (info.type) { case SignalType::Stopped: + case SignalType::Watchpoint: // Stop emulation. PauseEmulation(); // Notify the client. active_thread = info.thread; UpdateActiveThread(); - frontend->Stopped(active_thread); + + if (info.type == SignalType::Watchpoint) { + frontend->Watchpoint(active_thread, *info.watchpoint); + } else { + frontend->Stopped(active_thread); + } break; case SignalType::ShuttingDown: @@ -290,12 +298,17 @@ Debugger::Debugger(Core::System& system, u16 port) { Debugger::~Debugger() = default; bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { - return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread}); + return impl && impl->SignalDebugger(SignalInfo{SignalType::Stopped, thread, nullptr}); +} + +bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread, + const Kernel::DebugWatchpoint& watch) { + return impl && impl->SignalDebugger(SignalInfo{SignalType::Watchpoint, thread, &watch}); } void Debugger::NotifyShutdown() { if (impl) { - impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr}); + impl->SignalDebugger(SignalInfo{SignalType::ShuttingDown, nullptr, nullptr}); } } diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h index f9738ca3d..b2f503376 100644 --- a/src/core/debugger/debugger.h +++ b/src/core/debugger/debugger.h @@ -9,7 +9,8 @@ namespace Kernel { class KThread; -} +struct DebugWatchpoint; +} // namespace Kernel namespace Core { class System; @@ -40,6 +41,11 @@ public: */ void NotifyShutdown(); + /* + * Notify the debugger that the given thread has stopped due to hitting a watchpoint. + */ + bool NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch); + private: std::unique_ptr impl; }; diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h index c0bb4ecaf..5b31edc43 100644 --- a/src/core/debugger/debugger_interface.h +++ b/src/core/debugger/debugger_interface.h @@ -11,7 +11,8 @@ namespace Kernel { class KThread; -} +struct DebugWatchpoint; +} // namespace Kernel namespace Core { @@ -71,6 +72,11 @@ public: */ virtual void ShuttingDown() = 0; + /* + * Called when emulation has stopped on a watchpoint. + */ + virtual void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) = 0; + /** * Called when new data is asynchronously received on the client socket. * A list of actions to perform is returned. diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 52e76f659..f5e9a303d 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp @@ -112,6 +112,23 @@ void GDBStub::Stopped(Kernel::KThread* thread) { SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); } +void GDBStub::Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) { + const auto status{arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)}; + + switch (watch.type) { + case Kernel::DebugWatchpointType::Read: + SendReply(fmt::format("{}rwatch:{:x};", status, watch.start_address)); + break; + case Kernel::DebugWatchpointType::Write: + SendReply(fmt::format("{}watch:{:x};", status, watch.start_address)); + break; + case Kernel::DebugWatchpointType::ReadOrWrite: + default: + SendReply(fmt::format("{}awatch:{:x};", status, watch.start_address)); + break; + } +} + std::vector GDBStub::ClientData(std::span data) { std::vector actions; current_command.insert(current_command.end(), data.begin(), data.end()); @@ -278,41 +295,121 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector(strtoll(command.data() + addr_sep, nullptr, 16))}; + case 'Z': + HandleBreakpointInsert(command); + break; + case 'z': + HandleBreakpointRemove(command); + break; + default: + SendReply(GDB_STUB_REPLY_EMPTY); + break; + } +} - if (system.Memory().IsValidVirtualAddress(addr)) { - replaced_instructions[addr] = system.Memory().Read32(addr); - system.Memory().Write32(addr, arch->BreakpointInstruction()); - system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); +enum class BreakpointType { + Software = 0, + Hardware = 1, + WriteWatch = 2, + ReadWatch = 3, + AccessWatch = 4, +}; + +void GDBStub::HandleBreakpointInsert(std::string_view command) { + const auto type{static_cast(strtoll(command.data(), nullptr, 16))}; + const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - + command.begin() + 1}; + const size_t addr{static_cast(strtoll(command.data() + addr_sep, nullptr, 16))}; + const size_t size{static_cast(strtoll(command.data() + size_sep, nullptr, 16))}; + + if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { + SendReply(GDB_STUB_REPLY_ERR); + return; + } - SendReply(GDB_STUB_REPLY_OK); - } else { - SendReply(GDB_STUB_REPLY_ERR); - } + bool success{}; + + switch (type) { + case BreakpointType::Software: + replaced_instructions[addr] = system.Memory().Read32(addr); + system.Memory().Write32(addr, arch->BreakpointInstruction()); + system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); + success = true; + break; + case BreakpointType::WriteWatch: + success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, + Kernel::DebugWatchpointType::Write); break; + case BreakpointType::ReadWatch: + success = system.CurrentProcess()->InsertWatchpoint(system, addr, size, + Kernel::DebugWatchpointType::Read); + break; + case BreakpointType::AccessWatch: + success = system.CurrentProcess()->InsertWatchpoint( + system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); + break; + case BreakpointType::Hardware: + default: + SendReply(GDB_STUB_REPLY_EMPTY); + return; + } + + if (success) { + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); } - case 'z': { - const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; - const size_t addr{static_cast(strtoll(command.data() + addr_sep, nullptr, 16))}; +} + +void GDBStub::HandleBreakpointRemove(std::string_view command) { + const auto type{static_cast(strtoll(command.data(), nullptr, 16))}; + const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; + const auto size_sep{std::find(command.begin() + addr_sep, command.end(), ',') - + command.begin() + 1}; + const size_t addr{static_cast(strtoll(command.data() + addr_sep, nullptr, 16))}; + const size_t size{static_cast(strtoll(command.data() + size_sep, nullptr, 16))}; + + if (!system.Memory().IsValidVirtualAddressRange(addr, size)) { + SendReply(GDB_STUB_REPLY_ERR); + return; + } + + bool success{}; + switch (type) { + case BreakpointType::Software: { const auto orig_insn{replaced_instructions.find(addr)}; - if (system.Memory().IsValidVirtualAddress(addr) && - orig_insn != replaced_instructions.end()) { + if (orig_insn != replaced_instructions.end()) { system.Memory().Write32(addr, orig_insn->second); system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); replaced_instructions.erase(addr); - - SendReply(GDB_STUB_REPLY_OK); - } else { - SendReply(GDB_STUB_REPLY_ERR); + success = true; } break; } + case BreakpointType::WriteWatch: + success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, + Kernel::DebugWatchpointType::Write); + break; + case BreakpointType::ReadWatch: + success = system.CurrentProcess()->RemoveWatchpoint(system, addr, size, + Kernel::DebugWatchpointType::Read); + break; + case BreakpointType::AccessWatch: + success = system.CurrentProcess()->RemoveWatchpoint( + system, addr, size, Kernel::DebugWatchpointType::ReadOrWrite); + break; + case BreakpointType::Hardware: default: SendReply(GDB_STUB_REPLY_EMPTY); - break; + return; + } + + if (success) { + SendReply(GDB_STUB_REPLY_OK); + } else { + SendReply(GDB_STUB_REPLY_ERR); } } diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index ec934c77e..0b0f56e4b 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h @@ -24,6 +24,7 @@ public: void Connected() override; void Stopped(Kernel::KThread* thread) override; void ShuttingDown() override; + void Watchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) override; std::vector ClientData(std::span data) override; private: @@ -31,6 +32,8 @@ private: void ExecuteCommand(std::string_view packet, std::vector& actions); void HandleVCont(std::string_view command, std::vector& actions); void HandleQuery(std::string_view command); + void HandleBreakpointInsert(std::string_view command); + void HandleBreakpointRemove(std::string_view command); std::vector::const_iterator CommandEnd() const; std::optional DetachCommand(); Kernel::KThread* GetThreadByID(u64 thread_id); -- cgit v1.2.3