summaryrefslogtreecommitdiffstats
path: root/src/core/debugger
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/debugger/debugger.cpp19
-rw-r--r--src/core/debugger/debugger.h8
-rw-r--r--src/core/debugger/debugger_interface.h8
-rw-r--r--src/core/debugger/gdbstub.cpp139
-rw-r--r--src/core/debugger/gdbstub.h3
5 files changed, 151 insertions, 26 deletions
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<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});
}
}
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<DebuggerImpl> 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<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
std::vector<DebuggerAction> actions;
current_command.insert(current_command.end(), data.begin(), data.end());
@@ -278,41 +295,121 @@ 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))};
+ 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<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;
+ }
- 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<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
+}
+
+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 (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<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);