summaryrefslogtreecommitdiff
path: root/src/core/debugger/debugger.cpp
diff options
context:
space:
mode:
authorGravatar Liam2022-05-30 19:35:01 -0400
committerGravatar Liam2022-06-01 00:01:25 -0400
commitfb4b3c127f7c390358d7f4cadd2f58de116fec48 (patch)
treee2588a859d364a32be0cd9e0401a345c6492d1a7 /src/core/debugger/debugger.cpp
parentMerge pull request #8368 from german77/seventimes (diff)
downloadyuzu-fb4b3c127f7c390358d7f4cadd2f58de116fec48.tar.gz
yuzu-fb4b3c127f7c390358d7f4cadd2f58de116fec48.tar.xz
yuzu-fb4b3c127f7c390358d7f4cadd2f58de116fec48.zip
core/debugger: Implement new GDB stub debugger
Diffstat (limited to 'src/core/debugger/debugger.cpp')
-rw-r--r--src/core/debugger/debugger.cpp259
1 files changed, 259 insertions, 0 deletions
diff --git a/src/core/debugger/debugger.cpp b/src/core/debugger/debugger.cpp
new file mode 100644
index 000000000..7a2012d3c
--- /dev/null
+++ b/src/core/debugger/debugger.cpp
@@ -0,0 +1,259 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <mutex>
5#include <thread>
6
7#include <boost/asio.hpp>
8#include <boost/process/async_pipe.hpp>
9
10#include "common/logging/log.h"
11#include "common/thread.h"
12#include "core/core.h"
13#include "core/debugger/debugger.h"
14#include "core/debugger/debugger_interface.h"
15#include "core/debugger/gdbstub.h"
16#include "core/hle/kernel/global_scheduler_context.h"
17
18template <typename Readable, typename Buffer, typename Callback>
19static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
20 static_assert(std::is_trivial_v<Buffer>);
21 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
22 r.async_read_some(boost_buffer, [&](const boost::system::error_code& error, size_t bytes_read) {
23 if (!error.failed()) {
24 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
25 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
26 c(received_data);
27 }
28
29 AsyncReceiveInto(r, buffer, c);
30 });
31}
32
33template <typename Readable, typename Buffer>
34static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
35 static_assert(std::is_trivial_v<Buffer>);
36 auto boost_buffer{boost::asio::buffer(&buffer, sizeof(Buffer))};
37 size_t bytes_read = r.read_some(boost_buffer);
38 const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
39 std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
40 return received_data;
41}
42
43namespace Core {
44
45class DebuggerImpl : public DebuggerBackend {
46public:
47 explicit DebuggerImpl(Core::System& system_, u16 port)
48 : system{system_}, signal_pipe{io_context}, client_socket{io_context} {
49 frontend = std::make_unique<GDBStub>(*this, system);
50 InitializeServer(port);
51 }
52
53 ~DebuggerImpl() {
54 ShutdownServer();
55 }
56
57 bool NotifyThreadStopped(Kernel::KThread* thread) {
58 std::scoped_lock lk{connection_lock};
59
60 if (stopped) {
61 // Do not notify the debugger about another event.
62 // It should be ignored.
63 return false;
64 }
65 stopped = true;
66
67 signal_pipe.write_some(boost::asio::buffer(&thread, sizeof(thread)));
68 return true;
69 }
70
71 std::span<const u8> ReadFromClient() override {
72 return ReceiveInto(client_socket, client_data);
73 }
74
75 void WriteToClient(std::span<const u8> data) override {
76 client_socket.write_some(boost::asio::buffer(data.data(), data.size_bytes()));
77 }
78
79 void SetActiveThread(Kernel::KThread* thread) override {
80 active_thread = thread;
81 }
82
83 Kernel::KThread* GetActiveThread() override {
84 return active_thread;
85 }
86
87 bool IsStepping() const {
88 return stepping;
89 }
90
91private:
92 void InitializeServer(u16 port) {
93 using boost::asio::ip::tcp;
94
95 LOG_INFO(Debug_GDBStub, "Starting server on port {}...", port);
96
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.
103 connection_thread = std::jthread([&](std::stop_token stop_token) {
104 try {
105 ThreadLoop(stop_token);
106 } catch (const std::exception& ex) {
107 LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
108 }
109
110 client_socket.shutdown(client_socket.shutdown_both);
111 client_socket.close();
112 });
113 }
114
115 void ShutdownServer() {
116 connection_thread.request_stop();
117 io_context.stop();
118 connection_thread.join();
119 }
120
121 void ThreadLoop(std::stop_token stop_token) {
122 Common::SetCurrentThreadName("yuzu:Debugger");
123
124 // Set up the client signals for new data.
125 AsyncReceiveInto(signal_pipe, active_thread, [&](auto d) { PipeData(d); });
126 AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
127
128 // Stop the emulated CPU.
129 AllCoreStop();
130
131 // Set the active thread.
132 active_thread = ThreadList()[0];
133 active_thread->Resume(Kernel::SuspendType::Debug);
134
135 // Set up the frontend.
136 frontend->Connected();
137
138 // Main event loop.
139 while (!stop_token.stop_requested() && io_context.run()) {
140 }
141 }
142
143 void PipeData(std::span<const u8> data) {
144 AllCoreStop();
145 active_thread->Resume(Kernel::SuspendType::Debug);
146 frontend->Stopped(active_thread);
147 }
148
149 void ClientData(std::span<const u8> data) {
150 const auto actions{frontend->ClientData(data)};
151 for (const auto action : actions) {
152 switch (action) {
153 case DebuggerAction::Interrupt: {
154 {
155 std::scoped_lock lk{connection_lock};
156 stopped = true;
157 }
158 AllCoreStop();
159 active_thread = ThreadList()[0];
160 active_thread->Resume(Kernel::SuspendType::Debug);
161 frontend->Stopped(active_thread);
162 break;
163 }
164 case DebuggerAction::Continue:
165 stepping = false;
166 ResumeInactiveThreads();
167 AllCoreResume();
168 break;
169 case DebuggerAction::StepThread:
170 stepping = true;
171 SuspendInactiveThreads();
172 AllCoreResume();
173 break;
174 case DebuggerAction::ShutdownEmulation: {
175 // Suspend all threads and release any locks held
176 active_thread->RequestSuspend(Kernel::SuspendType::Debug);
177 SuspendInactiveThreads();
178 AllCoreResume();
179
180 // Spawn another thread that will exit after shutdown,
181 // to avoid a deadlock
182 Core::System* system_ref{&system};
183 std::thread t([system_ref] { system_ref->Exit(); });
184 t.detach();
185 break;
186 }
187 }
188 }
189 }
190
191 void AllCoreStop() {
192 if (!suspend) {
193 suspend = system.StallCPU();
194 }
195 }
196
197 void AllCoreResume() {
198 stopped = false;
199 system.UnstallCPU();
200 suspend.reset();
201 }
202
203 void SuspendInactiveThreads() {
204 for (auto* thread : ThreadList()) {
205 if (thread != active_thread) {
206 thread->RequestSuspend(Kernel::SuspendType::Debug);
207 }
208 }
209 }
210
211 void ResumeInactiveThreads() {
212 for (auto* thread : ThreadList()) {
213 if (thread != active_thread) {
214 thread->Resume(Kernel::SuspendType::Debug);
215 }
216 }
217 }
218
219 const std::vector<Kernel::KThread*>& ThreadList() {
220 return system.GlobalSchedulerContext().GetThreadList();
221 }
222
223private:
224 System& system;
225 std::unique_ptr<DebuggerFrontend> frontend;
226
227 std::jthread connection_thread;
228 std::mutex connection_lock;
229 boost::asio::io_context io_context;
230 boost::process::async_pipe signal_pipe;
231 boost::asio::ip::tcp::socket client_socket;
232 std::optional<std::unique_lock<std::mutex>> suspend;
233
234 Kernel::KThread* active_thread;
235 bool stopped;
236 bool stepping;
237
238 std::array<u8, 4096> client_data;
239};
240
241Debugger::Debugger(Core::System& system, u16 port) {
242 try {
243 impl = std::make_unique<DebuggerImpl>(system, port);
244 } catch (const std::exception& ex) {
245 LOG_CRITICAL(Debug_GDBStub, "Failed to initialize debugger: {}", ex.what());
246 }
247}
248
249Debugger::~Debugger() = default;
250
251bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
252 return impl && impl->NotifyThreadStopped(thread);
253}
254
255bool Debugger::IsStepping() const {
256 return impl && impl->IsStepping();
257}
258
259} // namespace Core