diff options
Diffstat (limited to 'src/core/debugger')
| -rw-r--r-- | src/core/debugger/debugger.cpp | 61 | ||||
| -rw-r--r-- | src/core/debugger/debugger.h | 5 | ||||
| -rw-r--r-- | src/core/debugger/debugger_interface.h | 9 | ||||
| -rw-r--r-- | src/core/debugger/gdbstub.cpp | 99 | ||||
| -rw-r--r-- | src/core/debugger/gdbstub.h | 2 |
5 files changed, 134 insertions, 42 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp index dd3e330e6..68ab33e46 100644 --- a/src/core/debugger/debugger.cpp +++ b/src/core/debugger/debugger.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <algorithm> | ||
| 4 | #include <mutex> | 5 | #include <mutex> |
| 5 | #include <thread> | 6 | #include <thread> |
| 6 | 7 | ||
| @@ -84,31 +85,31 @@ public: | |||
| 84 | return active_thread; | 85 | return active_thread; |
| 85 | } | 86 | } |
| 86 | 87 | ||
| 87 | bool IsStepping() const { | ||
| 88 | return stepping; | ||
| 89 | } | ||
| 90 | |||
| 91 | private: | 88 | private: |
| 92 | void InitializeServer(u16 port) { | 89 | void InitializeServer(u16 port) { |
| 93 | using boost::asio::ip::tcp; | 90 | using boost::asio::ip::tcp; |
| 94 | 91 | ||
| 95 | LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port); | 92 | LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port); |
| 96 | 93 | ||
| 97 | // Initialize the listening socket and accept a new client. | ||
| 98 | tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port}; | ||
| 99 | tcp::acceptor acceptor{io_context, endpoint}; | ||
| 100 | client_socket = acceptor.accept(); | ||
| 101 | |||
| 102 | // Run the connection thread. | 94 | // Run the connection thread. |
| 103 | connection_thread = std::jthread([&](std::stop_token stop_token) { | 95 | connection_thread = std::jthread([&, port](std::stop_token stop_token) { |
| 104 | try { | 96 | try { |
| 97 | // Initialize the listening socket and accept a new client. | ||
| 98 | tcp::endpoint endpoint{boost::asio::ip::address_v4::loopback(), port}; | ||
| 99 | tcp::acceptor acceptor{io_context, endpoint}; | ||
| 100 | |||
| 101 | acceptor.async_accept(client_socket, [](const auto&) {}); | ||
| 102 | io_context.run_one(); | ||
| 103 | io_context.restart(); | ||
| 104 | |||
| 105 | if (stop_token.stop_requested()) { | ||
| 106 | return; | ||
| 107 | } | ||
| 108 | |||
| 105 | ThreadLoop(stop_token); | 109 | ThreadLoop(stop_token); |
| 106 | } catch (const std::exception& ex) { | 110 | } catch (const std::exception& ex) { |
| 107 | LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); | 111 | LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what()); |
| 108 | } | 112 | } |
| 109 | |||
| 110 | client_socket.shutdown(client_socket.shutdown_both); | ||
| 111 | client_socket.close(); | ||
| 112 | }); | 113 | }); |
| 113 | } | 114 | } |
| 114 | 115 | ||
| @@ -129,8 +130,7 @@ private: | |||
| 129 | AllCoreStop(); | 130 | AllCoreStop(); |
| 130 | 131 | ||
| 131 | // Set the active thread. | 132 | // Set the active thread. |
| 132 | active_thread = ThreadList()[0]; | 133 | UpdateActiveThread(); |
| 133 | active_thread->Resume(Kernel::SuspendType::Debug); | ||
| 134 | 134 | ||
| 135 | // Set up the frontend. | 135 | // Set up the frontend. |
| 136 | frontend->Connected(); | 136 | frontend->Connected(); |
| @@ -142,7 +142,7 @@ private: | |||
| 142 | 142 | ||
| 143 | void PipeData(std::span<const u8> data) { | 143 | void PipeData(std::span<const u8> data) { |
| 144 | AllCoreStop(); | 144 | AllCoreStop(); |
| 145 | active_thread->Resume(Kernel::SuspendType::Debug); | 145 | UpdateActiveThread(); |
| 146 | frontend->Stopped(active_thread); | 146 | frontend->Stopped(active_thread); |
| 147 | } | 147 | } |
| 148 | 148 | ||
| @@ -156,18 +156,22 @@ private: | |||
| 156 | stopped = true; | 156 | stopped = true; |
| 157 | } | 157 | } |
| 158 | AllCoreStop(); | 158 | AllCoreStop(); |
| 159 | active_thread = ThreadList()[0]; | 159 | UpdateActiveThread(); |
| 160 | active_thread->Resume(Kernel::SuspendType::Debug); | ||
| 161 | frontend->Stopped(active_thread); | 160 | frontend->Stopped(active_thread); |
| 162 | break; | 161 | break; |
| 163 | } | 162 | } |
| 164 | case DebuggerAction::Continue: | 163 | case DebuggerAction::Continue: |
| 165 | stepping = false; | 164 | active_thread->SetStepState(Kernel::StepState::NotStepping); |
| 166 | ResumeInactiveThreads(); | 165 | ResumeInactiveThreads(); |
| 167 | AllCoreResume(); | 166 | AllCoreResume(); |
| 168 | break; | 167 | break; |
| 169 | case DebuggerAction::StepThread: | 168 | case DebuggerAction::StepThreadUnlocked: |
| 170 | stepping = true; | 169 | active_thread->SetStepState(Kernel::StepState::StepPending); |
| 170 | ResumeInactiveThreads(); | ||
| 171 | AllCoreResume(); | ||
| 172 | break; | ||
| 173 | case DebuggerAction::StepThreadLocked: | ||
| 174 | active_thread->SetStepState(Kernel::StepState::StepPending); | ||
| 171 | SuspendInactiveThreads(); | 175 | SuspendInactiveThreads(); |
| 172 | AllCoreResume(); | 176 | AllCoreResume(); |
| 173 | break; | 177 | break; |
| @@ -212,10 +216,20 @@ private: | |||
| 212 | for (auto* thread : ThreadList()) { | 216 | for (auto* thread : ThreadList()) { |
| 213 | if (thread != active_thread) { | 217 | if (thread != active_thread) { |
| 214 | thread->Resume(Kernel::SuspendType::Debug); | 218 | thread->Resume(Kernel::SuspendType::Debug); |
| 219 | thread->SetStepState(Kernel::StepState::NotStepping); | ||
| 215 | } | 220 | } |
| 216 | } | 221 | } |
| 217 | } | 222 | } |
| 218 | 223 | ||
| 224 | void UpdateActiveThread() { | ||
| 225 | const auto& threads{ThreadList()}; | ||
| 226 | if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) { | ||
| 227 | active_thread = threads[0]; | ||
| 228 | } | ||
| 229 | active_thread->Resume(Kernel::SuspendType::Debug); | ||
| 230 | active_thread->SetStepState(Kernel::StepState::NotStepping); | ||
| 231 | } | ||
| 232 | |||
| 219 | const std::vector<Kernel::KThread*>& ThreadList() { | 233 | const std::vector<Kernel::KThread*>& ThreadList() { |
| 220 | return system.GlobalSchedulerContext().GetThreadList(); | 234 | return system.GlobalSchedulerContext().GetThreadList(); |
| 221 | } | 235 | } |
| @@ -233,7 +247,6 @@ private: | |||
| 233 | 247 | ||
| 234 | Kernel::KThread* active_thread; | 248 | Kernel::KThread* active_thread; |
| 235 | bool stopped; | 249 | bool stopped; |
| 236 | bool stepping; | ||
| 237 | 250 | ||
| 238 | std::array<u8, 4096> client_data; | 251 | std::array<u8, 4096> client_data; |
| 239 | }; | 252 | }; |
| @@ -252,8 +265,4 @@ bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) { | |||
| 252 | return impl && impl->NotifyThreadStopped(thread); | 265 | return impl && impl->NotifyThreadStopped(thread); |
| 253 | } | 266 | } |
| 254 | 267 | ||
| 255 | bool Debugger::IsStepping() const { | ||
| 256 | return impl && impl->IsStepping(); | ||
| 257 | } | ||
| 258 | |||
| 259 | } // namespace Core | 268 | } // namespace Core |
diff --git a/src/core/debugger/debugger.h b/src/core/debugger/debugger.h index 7acd11815..ea36c6ab2 100644 --- a/src/core/debugger/debugger.h +++ b/src/core/debugger/debugger.h | |||
| @@ -35,11 +35,6 @@ public: | |||
| 35 | */ | 35 | */ |
| 36 | bool NotifyThreadStopped(Kernel::KThread* thread); | 36 | bool NotifyThreadStopped(Kernel::KThread* thread); |
| 37 | 37 | ||
| 38 | /** | ||
| 39 | * Returns whether a step is in progress. | ||
| 40 | */ | ||
| 41 | bool IsStepping() const; | ||
| 42 | |||
| 43 | private: | 38 | private: |
| 44 | std::unique_ptr<DebuggerImpl> impl; | 39 | std::unique_ptr<DebuggerImpl> impl; |
| 45 | }; | 40 | }; |
diff --git a/src/core/debugger/debugger_interface.h b/src/core/debugger/debugger_interface.h index e6d4c0190..35ba0bc61 100644 --- a/src/core/debugger/debugger_interface.h +++ b/src/core/debugger/debugger_interface.h | |||
| @@ -16,10 +16,11 @@ class KThread; | |||
| 16 | namespace Core { | 16 | namespace Core { |
| 17 | 17 | ||
| 18 | enum class DebuggerAction { | 18 | enum class DebuggerAction { |
| 19 | Interrupt, // Stop emulation as soon as possible. | 19 | Interrupt, ///< Stop emulation as soon as possible. |
| 20 | Continue, // Resume emulation. | 20 | Continue, ///< Resume emulation. |
| 21 | StepThread, // Step the currently-active thread. | 21 | StepThreadLocked, ///< Step the currently-active thread without resuming others. |
| 22 | ShutdownEmulation, // Shut down the emulator. | 22 | StepThreadUnlocked, ///< Step the currently-active thread and resume others. |
| 23 | ShutdownEmulation, ///< Shut down the emulator. | ||
| 23 | }; | 24 | }; |
| 24 | 25 | ||
| 25 | class DebuggerBackend { | 26 | class DebuggerBackend { |
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index ee7598165..0c36069a6 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -6,8 +6,7 @@ | |||
| 6 | #include <optional> | 6 | #include <optional> |
| 7 | #include <thread> | 7 | #include <thread> |
| 8 | 8 | ||
| 9 | #include <boost/asio.hpp> | 9 | #include <boost/algorithm/string.hpp> |
| 10 | #include <boost/process/async_pipe.hpp> | ||
| 11 | 10 | ||
| 12 | #include "common/hex_util.h" | 11 | #include "common/hex_util.h" |
| 13 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| @@ -114,6 +113,11 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 114 | return; | 113 | return; |
| 115 | } | 114 | } |
| 116 | 115 | ||
| 116 | if (packet.starts_with("vCont")) { | ||
| 117 | HandleVCont(packet.substr(5), actions); | ||
| 118 | return; | ||
| 119 | } | ||
| 120 | |||
| 117 | std::string_view command{packet.substr(1, packet.size())}; | 121 | std::string_view command{packet.substr(1, packet.size())}; |
| 118 | 122 | ||
| 119 | switch (packet[0]) { | 123 | switch (packet[0]) { |
| @@ -122,6 +126,8 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 122 | s64 thread_id{strtoll(command.data() + 1, nullptr, 16)}; | 126 | s64 thread_id{strtoll(command.data() + 1, nullptr, 16)}; |
| 123 | if (thread_id >= 1) { | 127 | if (thread_id >= 1) { |
| 124 | thread = GetThreadByID(thread_id); | 128 | thread = GetThreadByID(thread_id); |
| 129 | } else { | ||
| 130 | thread = backend.GetActiveThread(); | ||
| 125 | } | 131 | } |
| 126 | 132 | ||
| 127 | if (thread) { | 133 | if (thread) { |
| @@ -141,6 +147,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 141 | } | 147 | } |
| 142 | break; | 148 | break; |
| 143 | } | 149 | } |
| 150 | case 'Q': | ||
| 144 | case 'q': | 151 | case 'q': |
| 145 | HandleQuery(command); | 152 | HandleQuery(command); |
| 146 | break; | 153 | break; |
| @@ -204,7 +211,7 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 204 | break; | 211 | break; |
| 205 | } | 212 | } |
| 206 | case 's': | 213 | case 's': |
| 207 | actions.push_back(DebuggerAction::StepThread); | 214 | actions.push_back(DebuggerAction::StepThreadLocked); |
| 208 | break; | 215 | break; |
| 209 | case 'C': | 216 | case 'C': |
| 210 | case 'c': | 217 | case 'c': |
| @@ -248,12 +255,47 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 248 | } | 255 | } |
| 249 | } | 256 | } |
| 250 | 257 | ||
| 258 | static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { | ||
| 259 | switch (thread->GetWaitReasonForDebugging()) { | ||
| 260 | case Kernel::ThreadWaitReasonForDebugging::Sleep: | ||
| 261 | return "Sleep"; | ||
| 262 | case Kernel::ThreadWaitReasonForDebugging::IPC: | ||
| 263 | return "IPC"; | ||
| 264 | case Kernel::ThreadWaitReasonForDebugging::Synchronization: | ||
| 265 | return "Synchronization"; | ||
| 266 | case Kernel::ThreadWaitReasonForDebugging::ConditionVar: | ||
| 267 | return "ConditionVar"; | ||
| 268 | case Kernel::ThreadWaitReasonForDebugging::Arbitration: | ||
| 269 | return "Arbitration"; | ||
| 270 | case Kernel::ThreadWaitReasonForDebugging::Suspended: | ||
| 271 | return "Suspended"; | ||
| 272 | default: | ||
| 273 | return "Unknown"; | ||
| 274 | } | ||
| 275 | } | ||
| 276 | |||
| 277 | static std::string GetThreadState(const Kernel::KThread* thread) { | ||
| 278 | switch (thread->GetState()) { | ||
| 279 | case Kernel::ThreadState::Initialized: | ||
| 280 | return "Initialized"; | ||
| 281 | case Kernel::ThreadState::Waiting: | ||
| 282 | return fmt::format("Waiting ({})", GetThreadWaitReason(thread)); | ||
| 283 | case Kernel::ThreadState::Runnable: | ||
| 284 | return "Runnable"; | ||
| 285 | case Kernel::ThreadState::Terminated: | ||
| 286 | return "Terminated"; | ||
| 287 | default: | ||
| 288 | return "Unknown"; | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 251 | void GDBStub::HandleQuery(std::string_view command) { | 292 | void GDBStub::HandleQuery(std::string_view command) { |
| 252 | if (command.starts_with("TStatus")) { | 293 | if (command.starts_with("TStatus")) { |
| 253 | // no tracepoint support | 294 | // no tracepoint support |
| 254 | SendReply("T0"); | 295 | SendReply("T0"); |
| 255 | } else if (command.starts_with("Supported")) { | 296 | } else if (command.starts_with("Supported")) { |
| 256 | SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+"); | 297 | SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;" |
| 298 | "vContSupported+;QStartNoAckMode+"); | ||
| 257 | } else if (command.starts_with("Xfer:features:read:target.xml:")) { | 299 | } else if (command.starts_with("Xfer:features:read:target.xml:")) { |
| 258 | const auto offset{command.substr(30)}; | 300 | const auto offset{command.substr(30)}; |
| 259 | const auto amount{command.substr(command.find(',') + 1)}; | 301 | const auto amount{command.substr(command.find(',') + 1)}; |
| @@ -297,18 +339,57 @@ void GDBStub::HandleQuery(std::string_view command) { | |||
| 297 | 339 | ||
| 298 | const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | 340 | const auto& threads = system.GlobalSchedulerContext().GetThreadList(); |
| 299 | for (const auto& thread : threads) { | 341 | for (const auto& thread : threads) { |
| 300 | buffer += | 342 | buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)", |
| 301 | fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)", | 343 | thread->GetThreadID(), thread->GetActiveCore(), |
| 302 | thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID()); | 344 | thread->GetThreadID(), GetThreadState(thread)); |
| 303 | } | 345 | } |
| 304 | 346 | ||
| 305 | buffer += "</threads>"; | 347 | buffer += "</threads>"; |
| 306 | SendReply(buffer); | 348 | SendReply(buffer); |
| 349 | } else if (command.starts_with("Attached")) { | ||
| 350 | SendReply("0"); | ||
| 351 | } else if (command.starts_with("StartNoAckMode")) { | ||
| 352 | no_ack = true; | ||
| 353 | SendReply(GDB_STUB_REPLY_OK); | ||
| 307 | } else { | 354 | } else { |
| 308 | SendReply(GDB_STUB_REPLY_EMPTY); | 355 | SendReply(GDB_STUB_REPLY_EMPTY); |
| 309 | } | 356 | } |
| 310 | } | 357 | } |
| 311 | 358 | ||
| 359 | void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) { | ||
| 360 | if (command == "?") { | ||
| 361 | // Continuing and stepping are supported | ||
| 362 | // (signal is ignored, but required for GDB to use vCont) | ||
| 363 | SendReply("vCont;c;C;s;S"); | ||
| 364 | return; | ||
| 365 | } | ||
| 366 | |||
| 367 | Kernel::KThread* stepped_thread{nullptr}; | ||
| 368 | bool lock_execution{true}; | ||
| 369 | |||
| 370 | std::vector<std::string> entries; | ||
| 371 | boost::split(entries, command.substr(1), boost::is_any_of(";")); | ||
| 372 | for (const auto& thread_action : entries) { | ||
| 373 | std::vector<std::string> parts; | ||
| 374 | boost::split(parts, thread_action, boost::is_any_of(":")); | ||
| 375 | |||
| 376 | if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) { | ||
| 377 | lock_execution = false; | ||
| 378 | } | ||
| 379 | if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) { | ||
| 380 | stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16)); | ||
| 381 | } | ||
| 382 | } | ||
| 383 | |||
| 384 | if (stepped_thread) { | ||
| 385 | backend.SetActiveThread(stepped_thread); | ||
| 386 | actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked | ||
| 387 | : DebuggerAction::StepThreadUnlocked); | ||
| 388 | } else { | ||
| 389 | actions.push_back(DebuggerAction::Continue); | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 312 | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { | 393 | Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { |
| 313 | const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; | 394 | const auto& threads{system.GlobalSchedulerContext().GetThreadList()}; |
| 314 | for (auto* thread : threads) { | 395 | for (auto* thread : threads) { |
| @@ -374,6 +455,10 @@ void GDBStub::SendReply(std::string_view data) { | |||
| 374 | } | 455 | } |
| 375 | 456 | ||
| 376 | void GDBStub::SendStatus(char status) { | 457 | void GDBStub::SendStatus(char status) { |
| 458 | if (no_ack) { | ||
| 459 | return; | ||
| 460 | } | ||
| 461 | |||
| 377 | std::array<u8, 1> buf = {static_cast<u8>(status)}; | 462 | std::array<u8, 1> buf = {static_cast<u8>(status)}; |
| 378 | LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); | 463 | LOG_TRACE(Debug_GDBStub, "Writing status: {}", status); |
| 379 | backend.WriteToClient(buf); | 464 | backend.WriteToClient(buf); |
diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index e58d60719..aa1f7de6c 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h | |||
| @@ -28,6 +28,7 @@ public: | |||
| 28 | private: | 28 | private: |
| 29 | void ProcessData(std::vector<DebuggerAction>& actions); | 29 | void ProcessData(std::vector<DebuggerAction>& actions); |
| 30 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); | 30 | void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions); |
| 31 | void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions); | ||
| 31 | void HandleQuery(std::string_view command); | 32 | void HandleQuery(std::string_view command); |
| 32 | std::vector<char>::const_iterator CommandEnd() const; | 33 | std::vector<char>::const_iterator CommandEnd() const; |
| 33 | std::optional<std::string> DetachCommand(); | 34 | std::optional<std::string> DetachCommand(); |
| @@ -42,6 +43,7 @@ private: | |||
| 42 | std::unique_ptr<GDBStubArch> arch; | 43 | std::unique_ptr<GDBStubArch> arch; |
| 43 | std::vector<char> current_command; | 44 | std::vector<char> current_command; |
| 44 | std::map<VAddr, u32> replaced_instructions; | 45 | std::map<VAddr, u32> replaced_instructions; |
| 46 | bool no_ack{}; | ||
| 45 | }; | 47 | }; |
| 46 | 48 | ||
| 47 | } // namespace Core | 49 | } // namespace Core |