summaryrefslogtreecommitdiff
path: root/src/core/debugger
diff options
context:
space:
mode:
authorGravatar Liam2022-05-31 14:37:37 -0400
committerGravatar Liam2022-06-01 02:15:15 -0400
commit989d4a7a41f449af0ea09e34bee331a3a3ac8170 (patch)
treedf24bd9d7e6942b939e3ea42d08c0d65006e539f /src/core/debugger
parentcore/debugger: Implement new GDB stub debugger (diff)
downloadyuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.gz
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.tar.xz
yuzu-989d4a7a41f449af0ea09e34bee331a3a3ac8170.zip
core/debugger: Improved stepping mechanism and misc fixes
Diffstat (limited to 'src/core/debugger')
-rw-r--r--src/core/debugger/debugger.cpp61
-rw-r--r--src/core/debugger/debugger.h5
-rw-r--r--src/core/debugger/debugger_interface.h9
-rw-r--r--src/core/debugger/gdbstub.cpp99
-rw-r--r--src/core/debugger/gdbstub.h2
5 files changed, 134 insertions, 42 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
index 7a2012d3c..a73f2279d 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
91private: 88private:
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
255bool 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
43private: 38private:
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 0b357fcb5..6ae3dc091 100644
--- a/src/core/debugger/debugger_interface.h
+++ b/src/core/debugger/debugger_interface.h
@@ -16,10 +16,11 @@ class KThread;
16namespace Core { 16namespace Core {
17 17
18enum class DebuggerAction { 18enum 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
25class DebuggerBackend { 26class DebuggerBackend {
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
index 718c45952..44ebbe3e0 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
258static 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
277static 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
251void GDBStub::HandleQuery(std::string_view command) { 292void 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
359void 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
312Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) { 393Kernel::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
376void GDBStub::SendStatus(char status) { 457void 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 b93a3a511..6f8ac2263 100644
--- a/src/core/debugger/gdbstub.h
+++ b/src/core/debugger/gdbstub.h
@@ -28,6 +28,7 @@ public:
28private: 28private:
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