summaryrefslogtreecommitdiffstats
path: root/src/core/debugger
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/debugger/debugger.cpp154
-rw-r--r--src/core/debugger/debugger.h13
-rw-r--r--src/core/debugger/debugger_interface.h13
-rw-r--r--src/core/debugger/gdbstub.cpp142
-rw-r--r--src/core/debugger/gdbstub.h4
-rw-r--r--src/core/debugger/gdbstub_arch.cpp8
6 files changed, 255 insertions, 79 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 8d64990ed..ac64d2f9d 100644
--- a/src/core/debugger/debugger.cpp
+++ b/src/core/debugger/debugger.cpp
@@ -42,6 +42,18 @@ static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
return received_data;
}
+enum class SignalType {
+ Stopped,
+ Watchpoint,
+ ShuttingDown,
+};
+
+struct SignalInfo {
+ SignalType type;
+ Kernel::KThread* thread;
+ const Kernel::DebugWatchpoint* watchpoint;
+};
+
namespace Core {
class DebuggerImpl : public DebuggerBackend {
@@ -56,17 +68,23 @@ public:
ShutdownServer();
}
- bool NotifyThreadStopped(Kernel::KThread* thread) {
- std::scoped_lock lk{connection_lock};
+ bool SignalDebugger(SignalInfo signal_info) {
+ {
+ std::scoped_lock lk{connection_lock};
+
+ if (stopped) {
+ // Do not notify the debugger about another event.
+ // It should be ignored.
+ return false;
+ }
- if (stopped) {
- // Do not notify the debugger about another event.
- // It should be ignored.
- return false;
+ // Set up the state.
+ stopped = true;
+ info = signal_info;
}
- stopped = true;
- boost::asio::write(signal_pipe, boost::asio::buffer(&thread, sizeof(thread)));
+ // Write a single byte into the pipe to wake up the debug interface.
+ boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
return true;
}
@@ -96,7 +114,7 @@ private:
connection_thread = std::jthread([&, port](std::stop_token stop_token) {
try {
// Initialize the listening socket and accept a new client.
- tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port};
+ tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
tcp::acceptor acceptor{io_context, endpoint};
acceptor.async_accept(client_socket, [](const auto&) {});
@@ -124,12 +142,9 @@ private:
Common::SetCurrentThreadName("yuzu:Debugger");
// Set up the client signals for new data.
- AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
+ AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
- // Stop the emulated CPU.
- AllCoreStop();
-
// Set the active thread.
UpdateActiveThread();
@@ -142,9 +157,33 @@ private:
}
void PipeData(std::span<const u8> data) {
- AllCoreStop();
- UpdateActiveThread();
- frontend->Stopped(active_thread);
+ switch (info.type) {
+ case SignalType::Stopped:
+ case SignalType::Watchpoint:
+ // Stop emulation.
+ PauseEmulation();
+
+ // Notify the client.
+ active_thread = info.thread;
+ UpdateActiveThread();
+
+ if (info.type == SignalType::Watchpoint) {
+ frontend->Watchpoint(active_thread, *info.watchpoint);
+ } else {
+ frontend->Stopped(active_thread);
+ }
+
+ break;
+ case SignalType::ShuttingDown:
+ frontend->ShuttingDown();
+
+ // Wait for emulation to shut down gracefully now.
+ signal_pipe.close();
+ client_socket.shutdown(boost::asio::socket_base::shutdown_both);
+ LOG_INFO(Debug_GDBStub, "Shut down server");
+
+ break;
+ }
}
void ClientData(std::span<const u8> data) {
@@ -156,32 +195,29 @@ private:
std::scoped_lock lk{connection_lock};
stopped = true;
}
- AllCoreStop();
+ PauseEmulation();
UpdateActiveThread();
frontend->Stopped(active_thread);
break;
}
case DebuggerAction::Continue:
- active_thread->SetStepState(Kernel::StepState::NotStepping);
- ResumeInactiveThreads();
- AllCoreResume();
+ MarkResumed([&] { ResumeEmulation(); });
break;
case DebuggerAction::StepThreadUnlocked:
- active_thread->SetStepState(Kernel::StepState::StepPending);
- ResumeInactiveThreads();
- AllCoreResume();
+ MarkResumed([&] {
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ active_thread->Resume(Kernel::SuspendType::Debug);
+ ResumeEmulation(active_thread);
+ });
break;
- case DebuggerAction::StepThreadLocked:
- active_thread->SetStepState(Kernel::StepState::StepPending);
- SuspendInactiveThreads();
- AllCoreResume();
+ case DebuggerAction::StepThreadLocked: {
+ MarkResumed([&] {
+ active_thread->SetStepState(Kernel::StepState::StepPending);
+ active_thread->Resume(Kernel::SuspendType::Debug);
+ });
break;
+ }
case DebuggerAction::ShutdownEmulation: {
- // Suspend all threads and release any locks held
- active_thread->RequestSuspend(Kernel::SuspendType::Debug);
- SuspendInactiveThreads();
- AllCoreResume();
-
// Spawn another thread that will exit after shutdown,
// to avoid a deadlock
Core::System* system_ref{&system};
@@ -193,33 +229,33 @@ private:
}
}
- void AllCoreStop() {
- if (!suspend) {
- suspend = system.StallCPU();
+ void PauseEmulation() {
+ // Put all threads to sleep on next scheduler round.
+ for (auto* thread : ThreadList()) {
+ thread->RequestSuspend(Kernel::SuspendType::Debug);
}
- }
- void AllCoreResume() {
- stopped = false;
- system.UnstallCPU();
- suspend.reset();
+ // Signal an interrupt so that scheduler will fire.
+ system.Kernel().InterruptAllPhysicalCores();
}
- void SuspendInactiveThreads() {
+ void ResumeEmulation(Kernel::KThread* except = nullptr) {
+ // Wake up all threads.
for (auto* thread : ThreadList()) {
- if (thread != active_thread) {
- thread->RequestSuspend(Kernel::SuspendType::Debug);
+ if (thread == except) {
+ continue;
}
+
+ thread->SetStepState(Kernel::StepState::NotStepping);
+ thread->Resume(Kernel::SuspendType::Debug);
}
}
- void ResumeInactiveThreads() {
- for (auto* thread : ThreadList()) {
- if (thread != active_thread) {
- thread->Resume(Kernel::SuspendType::Debug);
- thread->SetStepState(Kernel::StepState::NotStepping);
- }
- }
+ template <typename Callback>
+ void MarkResumed(Callback&& cb) {
+ std::scoped_lock lk{connection_lock};
+ stopped = false;
+ cb();
}
void UpdateActiveThread() {
@@ -227,8 +263,6 @@ private:
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
active_thread = threads[0];
}
- active_thread->Resume(Kernel::SuspendType::Debug);
- active_thread->SetStepState(Kernel::StepState::NotStepping);
}
const std::vector<Kernel::KThread*>& ThreadList() {
@@ -244,9 +278,10 @@ private:
boost::asio::io_context io_context;
boost::process::async_pipe signal_pipe;
boost::asio::ip::tcp::socket client_socket;
- std::optional<std::unique_lock<std::mutex>> suspend;
+ SignalInfo info;
Kernel::KThread* active_thread;
+ bool pipe_data;
bool stopped;
std::array<u8, 4096> client_data;
@@ -263,7 +298,18 @@ Debugger::Debugger(Core::System& system, u16 port) {
Debugger::~Debugger() = default;
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
- return impl && impl->NotifyThreadStopped(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, nullptr});
+ }
}
} // namespace Core
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h
index ea36c6ab2..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;
@@ -35,6 +36,16 @@ public:
*/
bool NotifyThreadStopped(Kernel::KThread* thread);
+ /**
+ * Notify the debugger that a shutdown is being performed now and disconnect.
+ */
+ 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 35ba0bc61..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 {
@@ -67,6 +68,16 @@ public:
virtual void Stopped(Kernel::KThread* thread) = 0;
/**
+ * Called when emulation is shutting down.
+ */
+ 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 f52d78829..884229c77 100644
--- a/src/core/debugger/gdbstub.cpp
+++ b/src/core/debugger/gdbstub.cpp
@@ -106,10 +106,29 @@ GDBStub::~GDBStub() = default;
void GDBStub::Connected() {}
+void GDBStub::ShuttingDown() {}
+
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());
@@ -233,6 +252,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction
const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
+ SendReply(GDB_STUB_REPLY_OK);
break;
}
case 'm': {
@@ -276,41 +296,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 1bb638187..0b0f56e4b 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -23,6 +23,8 @@ 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:
@@ -30,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);
diff --git a/src/core/debugger/gdbstub_arch.cpp b/src/core/debugger/gdbstub_arch.cpp
index 750c353b9..4bef09bd7 100644
--- a/src/core/debugger/gdbstub_arch.cpp
+++ b/src/core/debugger/gdbstub_arch.cpp
@@ -191,8 +191,10 @@ std::string GDBStubA64::RegRead(const Kernel::KThread* thread, size_t id) const
const auto& gprs{context.cpu_registers};
const auto& fprs{context.vector_registers};
- if (id <= SP_REGISTER) {
+ if (id < SP_REGISTER) {
return ValueToHex(gprs[id]);
+ } else if (id == SP_REGISTER) {
+ return ValueToHex(context.sp);
} else if (id == PC_REGISTER) {
return ValueToHex(context.pc);
} else if (id == PSTATE_REGISTER) {
@@ -215,8 +217,10 @@ void GDBStubA64::RegWrite(Kernel::KThread* thread, size_t id, std::string_view v
auto& context{thread->GetContext64()};
- if (id <= SP_REGISTER) {
+ if (id < SP_REGISTER) {
context.cpu_registers[id] = HexToValue<u64>(value);
+ } else if (id == SP_REGISTER) {
+ context.sp = HexToValue<u64>(value);
} else if (id == PC_REGISTER) {
context.pc = HexToValue<u64>(value);
} else if (id == PSTATE_REGISTER) {