summaryrefslogtreecommitdiff
path: root/src/core/debugger/gdbstub.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/gdbstub.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/gdbstub.cpp')
-rw-r--r--src/core/debugger/gdbstub.cpp382
1 files changed, 382 insertions, 0 deletions
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp
new file mode 100644
index 000000000..718c45952
--- /dev/null
+++ b/src/core/debugger/gdbstub.cpp
@@ -0,0 +1,382 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <atomic>
5#include <numeric>
6#include <optional>
7#include <thread>
8
9#include <boost/asio.hpp>
10#include <boost/process/async_pipe.hpp>
11
12#include "common/hex_util.h"
13#include "common/logging/log.h"
14#include "common/scope_exit.h"
15#include "core/arm/arm_interface.h"
16#include "core/core.h"
17#include "core/debugger/gdbstub.h"
18#include "core/debugger/gdbstub_arch.h"
19#include "core/hle/kernel/k_page_table.h"
20#include "core/hle/kernel/k_process.h"
21#include "core/hle/kernel/k_thread.h"
22#include "core/loader/loader.h"
23#include "core/memory.h"
24
25namespace Core {
26
27constexpr char GDB_STUB_START = '$';
28constexpr char GDB_STUB_END = '#';
29constexpr char GDB_STUB_ACK = '+';
30constexpr char GDB_STUB_NACK = '-';
31constexpr char GDB_STUB_INT3 = 0x03;
32constexpr int GDB_STUB_SIGTRAP = 5;
33
34constexpr char GDB_STUB_REPLY_ERR[] = "E01";
35constexpr char GDB_STUB_REPLY_OK[] = "OK";
36constexpr char GDB_STUB_REPLY_EMPTY[] = "";
37
38GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
39 : DebuggerFrontend(backend_), system{system_} {
40 if (system.CurrentProcess()->Is64BitProcess()) {
41 arch = std::make_unique<GDBStubA64>();
42 } else {
43 arch = std::make_unique<GDBStubA32>();
44 }
45}
46
47GDBStub::~GDBStub() = default;
48
49void GDBStub::Connected() {}
50
51void GDBStub::Stopped(Kernel::KThread* thread) {
52 SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
53}
54
55std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
56 std::vector<DebuggerAction> actions;
57 current_command.insert(current_command.end(), data.begin(), data.end());
58
59 while (current_command.size() != 0) {
60 ProcessData(actions);
61 }
62
63 return actions;
64}
65
66void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
67 const char c{current_command[0]};
68
69 // Acknowledgement
70 if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
71 current_command.erase(current_command.begin());
72 return;
73 }
74
75 // Interrupt
76 if (c == GDB_STUB_INT3) {
77 LOG_INFO(Debug_GDBStub, "Received interrupt");
78 current_command.erase(current_command.begin());
79 actions.push_back(DebuggerAction::Interrupt);
80 SendStatus(GDB_STUB_ACK);
81 return;
82 }
83
84 // Otherwise, require the data to be the start of a command
85 if (c != GDB_STUB_START) {
86 LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
87 current_command.clear();
88 SendStatus(GDB_STUB_NACK);
89 return;
90 }
91
92 // Continue reading until command is complete
93 while (CommandEnd() == current_command.end()) {
94 const auto new_data{backend.ReadFromClient()};
95 current_command.insert(current_command.end(), new_data.begin(), new_data.end());
96 }
97
98 // Execute and respond to GDB
99 const auto command{DetachCommand()};
100
101 if (command) {
102 SendStatus(GDB_STUB_ACK);
103 ExecuteCommand(*command, actions);
104 } else {
105 SendStatus(GDB_STUB_NACK);
106 }
107}
108
109void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
110 LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
111
112 if (packet.length() == 0) {
113 SendReply(GDB_STUB_REPLY_ERR);
114 return;
115 }
116
117 std::string_view command{packet.substr(1, packet.size())};
118
119 switch (packet[0]) {
120 case 'H': {
121 Kernel::KThread* thread{nullptr};
122 s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
123 if (thread_id >= 1) {
124 thread = GetThreadByID(thread_id);
125 }
126
127 if (thread) {
128 SendReply(GDB_STUB_REPLY_OK);
129 backend.SetActiveThread(thread);
130 } else {
131 SendReply(GDB_STUB_REPLY_ERR);
132 }
133 break;
134 }
135 case 'T': {
136 s64 thread_id{strtoll(command.data(), nullptr, 16)};
137 if (GetThreadByID(thread_id)) {
138 SendReply(GDB_STUB_REPLY_OK);
139 } else {
140 SendReply(GDB_STUB_REPLY_ERR);
141 }
142 break;
143 }
144 case 'q':
145 HandleQuery(command);
146 break;
147 case '?':
148 SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
149 break;
150 case 'k':
151 LOG_INFO(Debug_GDBStub, "Shutting down emulation");
152 actions.push_back(DebuggerAction::ShutdownEmulation);
153 break;
154 case 'g':
155 SendReply(arch->ReadRegisters(backend.GetActiveThread()));
156 break;
157 case 'G':
158 arch->WriteRegisters(backend.GetActiveThread(), command);
159 SendReply(GDB_STUB_REPLY_OK);
160 break;
161 case 'p': {
162 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
163 SendReply(arch->RegRead(backend.GetActiveThread(), reg));
164 break;
165 }
166 case 'P': {
167 const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
168 const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
169 arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
170 break;
171 }
172 case 'm': {
173 const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
174 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
175 const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
176
177 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
178 std::vector<u8> mem(size);
179 system.Memory().ReadBlock(addr, mem.data(), size);
180
181 SendReply(Common::HexToString(mem));
182 } else {
183 SendReply(GDB_STUB_REPLY_ERR);
184 }
185 break;
186 }
187 case 'M': {
188 const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
189 const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
190
191 const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
192 const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
193
194 const auto mem_substr{std::string_view(command).substr(mem_sep)};
195 const auto mem{Common::HexStringToVector(mem_substr, false)};
196
197 if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
198 system.Memory().WriteBlock(addr, mem.data(), size);
199 system.InvalidateCpuInstructionCacheRange(addr, size);
200 SendReply(GDB_STUB_REPLY_OK);
201 } else {
202 SendReply(GDB_STUB_REPLY_ERR);
203 }
204 break;
205 }
206 case 's':
207 actions.push_back(DebuggerAction::StepThread);
208 break;
209 case 'C':
210 case 'c':
211 actions.push_back(DebuggerAction::Continue);
212 break;
213 case 'Z': {
214 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
215 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
216
217 if (system.Memory().IsValidVirtualAddress(addr)) {
218 replaced_instructions[addr] = system.Memory().Read32(addr);
219 system.Memory().Write32(addr, arch->BreakpointInstruction());
220 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
221
222 SendReply(GDB_STUB_REPLY_OK);
223 } else {
224 SendReply(GDB_STUB_REPLY_ERR);
225 }
226 break;
227 }
228 case 'z': {
229 const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
230 const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
231
232 const auto orig_insn{replaced_instructions.find(addr)};
233 if (system.Memory().IsValidVirtualAddress(addr) &&
234 orig_insn != replaced_instructions.end()) {
235 system.Memory().Write32(addr, orig_insn->second);
236 system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
237 replaced_instructions.erase(addr);
238
239 SendReply(GDB_STUB_REPLY_OK);
240 } else {
241 SendReply(GDB_STUB_REPLY_ERR);
242 }
243 break;
244 }
245 default:
246 SendReply(GDB_STUB_REPLY_EMPTY);
247 break;
248 }
249}
250
251void GDBStub::HandleQuery(std::string_view command) {
252 if (command.starts_with("TStatus")) {
253 // no tracepoint support
254 SendReply("T0");
255 } else if (command.starts_with("Supported")) {
256 SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+");
257 } else if (command.starts_with("Xfer:features:read:target.xml:")) {
258 const auto offset{command.substr(30)};
259 const auto amount{command.substr(command.find(',') + 1)};
260
261 const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
262 const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
263 const auto target_xml{arch->GetTargetXML()};
264
265 if (offset_val + amount_val > target_xml.size()) {
266 SendReply("l" + target_xml.substr(offset_val));
267 } else {
268 SendReply("m" + target_xml.substr(offset_val, amount_val));
269 }
270 } else if (command.starts_with("Offsets")) {
271 Loader::AppLoader::Modules modules;
272 system.GetAppLoader().ReadNSOModules(modules);
273
274 const auto main = std::find_if(modules.begin(), modules.end(),
275 [](const auto& key) { return key.second == "main"; });
276 if (main != modules.end()) {
277 SendReply(fmt::format("TextSeg={:x}", main->first));
278 } else {
279 SendReply(fmt::format("TextSeg={:x}",
280 system.CurrentProcess()->PageTable().GetCodeRegionStart()));
281 }
282 } else if (command.starts_with("fThreadInfo")) {
283 // beginning of list
284 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
285 std::vector<std::string> thread_ids;
286 for (const auto& thread : threads) {
287 thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
288 }
289 SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
290 } else if (command.starts_with("sThreadInfo")) {
291 // end of list
292 SendReply("l");
293 } else if (command.starts_with("Xfer:threads:read")) {
294 std::string buffer;
295 buffer += R"(l<?xml version="1.0"?>)";
296 buffer += "<threads>";
297
298 const auto& threads = system.GlobalSchedulerContext().GetThreadList();
299 for (const auto& thread : threads) {
300 buffer +=
301 fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}"/>)",
302 thread->GetThreadID(), thread->GetActiveCore(), thread->GetThreadID());
303 }
304
305 buffer += "</threads>";
306 SendReply(buffer);
307 } else {
308 SendReply(GDB_STUB_REPLY_EMPTY);
309 }
310}
311
312Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
313 const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
314 for (auto* thread : threads) {
315 if (thread->GetThreadID() == thread_id) {
316 return thread;
317 }
318 }
319
320 return nullptr;
321}
322
323std::vector<char>::const_iterator GDBStub::CommandEnd() const {
324 // Find the end marker
325 const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
326
327 // Require the checksum to be present
328 return std::min(end + 2, current_command.end());
329}
330
331std::optional<std::string> GDBStub::DetachCommand() {
332 // Slice the string part from the beginning to the end marker
333 const auto end{CommandEnd()};
334
335 // Extract possible command data
336 std::string data(current_command.data(), end - current_command.begin() + 1);
337
338 // Shift over the remaining contents
339 current_command.erase(current_command.begin(), end + 1);
340
341 // Validate received command
342 if (data[0] != GDB_STUB_START) {
343 LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
344 return std::nullopt;
345 }
346
347 u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
348 u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
349
350 // Verify checksum
351 if (calculated != received) {
352 LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
353 calculated, received);
354 return std::nullopt;
355 }
356
357 return data.substr(1, data.size() - 4);
358}
359
360u8 GDBStub::CalculateChecksum(std::string_view data) {
361 return static_cast<u8>(
362 std::accumulate(data.begin(), data.end(), u8{0}, [](u8 lhs, u8 rhs) { return lhs + rhs; }));
363}
364
365void GDBStub::SendReply(std::string_view data) {
366 const auto output{
367 fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))};
368 LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
369
370 // C++ string support is complete rubbish
371 const u8* output_begin = reinterpret_cast<const u8*>(output.data());
372 const u8* output_end = output_begin + output.size();
373 backend.WriteToClient(std::span<const u8>(output_begin, output_end));
374}
375
376void GDBStub::SendStatus(char status) {
377 std::array<u8, 1> buf = {static_cast<u8>(status)};
378 LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
379 backend.WriteToClient(buf);
380}
381
382} // namespace Core