diff options
Diffstat (limited to '')
-rw-r--r-- | src/core/debugger/debugger.cpp | 154 | ||||
-rw-r--r-- | src/core/debugger/debugger.h | 13 | ||||
-rw-r--r-- | src/core/debugger/debugger_interface.h | 13 | ||||
-rw-r--r-- | src/core/debugger/gdbstub.cpp | 142 | ||||
-rw-r--r-- | src/core/debugger/gdbstub.h | 4 | ||||
-rw-r--r-- | src/core/debugger/gdbstub_arch.cpp | 8 |
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) { |