core/debugger: memory breakpoint support
This commit is contained in:
		| @@ -44,12 +44,14 @@ static std::span<const u8> 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<const u8> 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}); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -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<DebuggerImpl> impl; | ||||
| }; | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) { | ||||
|     std::vector<DebuggerAction> actions; | ||||
|     current_command.insert(current_command.end(), data.begin(), data.end()); | ||||
| @@ -278,44 +295,124 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | ||||
|     case 'c': | ||||
|         actions.push_back(DebuggerAction::Continue); | ||||
|         break; | ||||
|     case 'Z': { | ||||
|         const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | ||||
|         const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||||
|  | ||||
|         if (system.Memory().IsValidVirtualAddress(addr)) { | ||||
|             replaced_instructions[addr] = system.Memory().Read32(addr); | ||||
|             system.Memory().Write32(addr, arch->BreakpointInstruction()); | ||||
|             system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | ||||
|  | ||||
|             SendReply(GDB_STUB_REPLY_OK); | ||||
|         } else { | ||||
|             SendReply(GDB_STUB_REPLY_ERR); | ||||
|         } | ||||
|     case 'Z': | ||||
|         HandleBreakpointInsert(command); | ||||
|         break; | ||||
|     } | ||||
|     case 'z': { | ||||
|         const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1}; | ||||
|         const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||||
|  | ||||
|         const auto orig_insn{replaced_instructions.find(addr)}; | ||||
|         if (system.Memory().IsValidVirtualAddress(addr) && | ||||
|             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); | ||||
|         } | ||||
|     case 'z': | ||||
|         HandleBreakpointRemove(command); | ||||
|         break; | ||||
|     } | ||||
|     default: | ||||
|         SendReply(GDB_STUB_REPLY_EMPTY); | ||||
|         break; | ||||
|     } | ||||
| } | ||||
|  | ||||
| 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<BreakpointType>(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<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||||
|     const size_t size{static_cast<size_t>(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: | ||||
|         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); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void GDBStub::HandleBreakpointRemove(std::string_view command) { | ||||
|     const auto type{static_cast<BreakpointType>(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<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))}; | ||||
|     const size_t size{static_cast<size_t>(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 (orig_insn != replaced_instructions.end()) { | ||||
|             system.Memory().Write32(addr, orig_insn->second); | ||||
|             system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32)); | ||||
|             replaced_instructions.erase(addr); | ||||
|             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); | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     if (success) { | ||||
|         SendReply(GDB_STUB_REPLY_OK); | ||||
|     } else { | ||||
|         SendReply(GDB_STUB_REPLY_ERR); | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Structure offsets are from Atmosphere | ||||
| // See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp | ||||
|  | ||||
|   | ||||
| @@ -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<DebuggerAction> ClientData(std::span<const u8> data) override; | ||||
|  | ||||
| private: | ||||
| @@ -31,6 +32,8 @@ private: | ||||
|     void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); | ||||
|     void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); | ||||
|     void HandleQuery(std::string_view command); | ||||
|     void HandleBreakpointInsert(std::string_view command); | ||||
|     void HandleBreakpointRemove(std::string_view command); | ||||
|     std::vector<char>::const_iterator CommandEnd() const; | ||||
|     std::optional<std::string> DetachCommand(); | ||||
|     Kernel::KThread* GetThreadByID(u64 thread_id); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user