gdbstub.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <atomic>
  4. #include <numeric>
  5. #include <optional>
  6. #include <thread>
  7. #include <boost/algorithm/string.hpp>
  8. #include "common/hex_util.h"
  9. #include "common/logging/log.h"
  10. #include "common/scope_exit.h"
  11. #include "core/arm/arm_interface.h"
  12. #include "core/core.h"
  13. #include "core/debugger/gdbstub.h"
  14. #include "core/debugger/gdbstub_arch.h"
  15. #include "core/hle/kernel/k_page_table.h"
  16. #include "core/hle/kernel/k_process.h"
  17. #include "core/hle/kernel/k_thread.h"
  18. #include "core/loader/loader.h"
  19. #include "core/memory.h"
  20. namespace Core {
  21. constexpr char GDB_STUB_START = '$';
  22. constexpr char GDB_STUB_END = '#';
  23. constexpr char GDB_STUB_ACK = '+';
  24. constexpr char GDB_STUB_NACK = '-';
  25. constexpr char GDB_STUB_INT3 = 0x03;
  26. constexpr int GDB_STUB_SIGTRAP = 5;
  27. constexpr char GDB_STUB_REPLY_ERR[] = "E01";
  28. constexpr char GDB_STUB_REPLY_OK[] = "OK";
  29. constexpr char GDB_STUB_REPLY_EMPTY[] = "";
  30. static u8 CalculateChecksum(std::string_view data) {
  31. return std::accumulate(data.begin(), data.end(), u8{0},
  32. [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); });
  33. }
  34. static std::string EscapeGDB(std::string_view data) {
  35. std::string escaped;
  36. escaped.reserve(data.size());
  37. for (char c : data) {
  38. switch (c) {
  39. case '#':
  40. escaped += "}\x03";
  41. break;
  42. case '$':
  43. escaped += "}\x04";
  44. break;
  45. case '*':
  46. escaped += "}\x0a";
  47. break;
  48. case '}':
  49. escaped += "}\x5d";
  50. break;
  51. default:
  52. escaped += c;
  53. break;
  54. }
  55. }
  56. return escaped;
  57. }
  58. static std::string EscapeXML(std::string_view data) {
  59. std::string escaped;
  60. escaped.reserve(data.size());
  61. for (char c : data) {
  62. switch (c) {
  63. case '&':
  64. escaped += "&amp;";
  65. break;
  66. case '"':
  67. escaped += "&quot;";
  68. break;
  69. case '<':
  70. escaped += "&lt;";
  71. break;
  72. case '>':
  73. escaped += "&gt;";
  74. break;
  75. default:
  76. escaped += c;
  77. break;
  78. }
  79. }
  80. return escaped;
  81. }
  82. GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_)
  83. : DebuggerFrontend(backend_), system{system_} {
  84. if (system.CurrentProcess()->Is64BitProcess()) {
  85. arch = std::make_unique<GDBStubA64>();
  86. } else {
  87. arch = std::make_unique<GDBStubA32>();
  88. }
  89. }
  90. GDBStub::~GDBStub() = default;
  91. void GDBStub::Connected() {}
  92. void GDBStub::ShuttingDown() {}
  93. void GDBStub::Stopped(Kernel::KThread* thread) {
  94. SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP));
  95. }
  96. std::vector<DebuggerAction> GDBStub::ClientData(std::span<const u8> data) {
  97. std::vector<DebuggerAction> actions;
  98. current_command.insert(current_command.end(), data.begin(), data.end());
  99. while (current_command.size() != 0) {
  100. ProcessData(actions);
  101. }
  102. return actions;
  103. }
  104. void GDBStub::ProcessData(std::vector<DebuggerAction>& actions) {
  105. const char c{current_command[0]};
  106. // Acknowledgement
  107. if (c == GDB_STUB_ACK || c == GDB_STUB_NACK) {
  108. current_command.erase(current_command.begin());
  109. return;
  110. }
  111. // Interrupt
  112. if (c == GDB_STUB_INT3) {
  113. LOG_INFO(Debug_GDBStub, "Received interrupt");
  114. current_command.erase(current_command.begin());
  115. actions.push_back(DebuggerAction::Interrupt);
  116. SendStatus(GDB_STUB_ACK);
  117. return;
  118. }
  119. // Otherwise, require the data to be the start of a command
  120. if (c != GDB_STUB_START) {
  121. LOG_ERROR(Debug_GDBStub, "Invalid command buffer contents: {}", current_command.data());
  122. current_command.clear();
  123. SendStatus(GDB_STUB_NACK);
  124. return;
  125. }
  126. // Continue reading until command is complete
  127. while (CommandEnd() == current_command.end()) {
  128. const auto new_data{backend.ReadFromClient()};
  129. current_command.insert(current_command.end(), new_data.begin(), new_data.end());
  130. }
  131. // Execute and respond to GDB
  132. const auto command{DetachCommand()};
  133. if (command) {
  134. SendStatus(GDB_STUB_ACK);
  135. ExecuteCommand(*command, actions);
  136. } else {
  137. SendStatus(GDB_STUB_NACK);
  138. }
  139. }
  140. void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions) {
  141. LOG_TRACE(Debug_GDBStub, "Executing command: {}", packet);
  142. if (packet.length() == 0) {
  143. SendReply(GDB_STUB_REPLY_ERR);
  144. return;
  145. }
  146. if (packet.starts_with("vCont")) {
  147. HandleVCont(packet.substr(5), actions);
  148. return;
  149. }
  150. std::string_view command{packet.substr(1, packet.size())};
  151. switch (packet[0]) {
  152. case 'H': {
  153. Kernel::KThread* thread{nullptr};
  154. s64 thread_id{strtoll(command.data() + 1, nullptr, 16)};
  155. if (thread_id >= 1) {
  156. thread = GetThreadByID(thread_id);
  157. } else {
  158. thread = backend.GetActiveThread();
  159. }
  160. if (thread) {
  161. SendReply(GDB_STUB_REPLY_OK);
  162. backend.SetActiveThread(thread);
  163. } else {
  164. SendReply(GDB_STUB_REPLY_ERR);
  165. }
  166. break;
  167. }
  168. case 'T': {
  169. s64 thread_id{strtoll(command.data(), nullptr, 16)};
  170. if (GetThreadByID(thread_id)) {
  171. SendReply(GDB_STUB_REPLY_OK);
  172. } else {
  173. SendReply(GDB_STUB_REPLY_ERR);
  174. }
  175. break;
  176. }
  177. case 'Q':
  178. case 'q':
  179. HandleQuery(command);
  180. break;
  181. case '?':
  182. SendReply(arch->ThreadStatus(backend.GetActiveThread(), GDB_STUB_SIGTRAP));
  183. break;
  184. case 'k':
  185. LOG_INFO(Debug_GDBStub, "Shutting down emulation");
  186. actions.push_back(DebuggerAction::ShutdownEmulation);
  187. break;
  188. case 'g':
  189. SendReply(arch->ReadRegisters(backend.GetActiveThread()));
  190. break;
  191. case 'G':
  192. arch->WriteRegisters(backend.GetActiveThread(), command);
  193. SendReply(GDB_STUB_REPLY_OK);
  194. break;
  195. case 'p': {
  196. const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
  197. SendReply(arch->RegRead(backend.GetActiveThread(), reg));
  198. break;
  199. }
  200. case 'P': {
  201. const auto sep{std::find(command.begin(), command.end(), '=') - command.begin() + 1};
  202. const size_t reg{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
  203. arch->RegWrite(backend.GetActiveThread(), reg, std::string_view(command).substr(sep));
  204. break;
  205. }
  206. case 'm': {
  207. const auto sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
  208. const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
  209. const size_t size{static_cast<size_t>(strtoll(command.data() + sep, nullptr, 16))};
  210. if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
  211. std::vector<u8> mem(size);
  212. system.Memory().ReadBlock(addr, mem.data(), size);
  213. SendReply(Common::HexToString(mem));
  214. } else {
  215. SendReply(GDB_STUB_REPLY_ERR);
  216. }
  217. break;
  218. }
  219. case 'M': {
  220. const auto size_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
  221. const auto mem_sep{std::find(command.begin(), command.end(), ':') - command.begin() + 1};
  222. const size_t addr{static_cast<size_t>(strtoll(command.data(), nullptr, 16))};
  223. const size_t size{static_cast<size_t>(strtoll(command.data() + size_sep, nullptr, 16))};
  224. const auto mem_substr{std::string_view(command).substr(mem_sep)};
  225. const auto mem{Common::HexStringToVector(mem_substr, false)};
  226. if (system.Memory().IsValidVirtualAddressRange(addr, size)) {
  227. system.Memory().WriteBlock(addr, mem.data(), size);
  228. system.InvalidateCpuInstructionCacheRange(addr, size);
  229. SendReply(GDB_STUB_REPLY_OK);
  230. } else {
  231. SendReply(GDB_STUB_REPLY_ERR);
  232. }
  233. break;
  234. }
  235. case 's':
  236. actions.push_back(DebuggerAction::StepThreadLocked);
  237. break;
  238. case 'C':
  239. case 'c':
  240. actions.push_back(DebuggerAction::Continue);
  241. break;
  242. case 'Z': {
  243. const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
  244. const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
  245. if (system.Memory().IsValidVirtualAddress(addr)) {
  246. replaced_instructions[addr] = system.Memory().Read32(addr);
  247. system.Memory().Write32(addr, arch->BreakpointInstruction());
  248. system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
  249. SendReply(GDB_STUB_REPLY_OK);
  250. } else {
  251. SendReply(GDB_STUB_REPLY_ERR);
  252. }
  253. break;
  254. }
  255. case 'z': {
  256. const auto addr_sep{std::find(command.begin(), command.end(), ',') - command.begin() + 1};
  257. const size_t addr{static_cast<size_t>(strtoll(command.data() + addr_sep, nullptr, 16))};
  258. const auto orig_insn{replaced_instructions.find(addr)};
  259. if (system.Memory().IsValidVirtualAddress(addr) &&
  260. orig_insn != replaced_instructions.end()) {
  261. system.Memory().Write32(addr, orig_insn->second);
  262. system.InvalidateCpuInstructionCacheRange(addr, sizeof(u32));
  263. replaced_instructions.erase(addr);
  264. SendReply(GDB_STUB_REPLY_OK);
  265. } else {
  266. SendReply(GDB_STUB_REPLY_ERR);
  267. }
  268. break;
  269. }
  270. default:
  271. SendReply(GDB_STUB_REPLY_EMPTY);
  272. break;
  273. }
  274. }
  275. // Structure offsets are from Atmosphere
  276. // See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
  277. static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory,
  278. const Kernel::KThread* thread) {
  279. // Read thread type from TLS
  280. const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)};
  281. const VAddr argument_thread_type{thread->GetArgument()};
  282. if (argument_thread_type && tls_thread_type != argument_thread_type) {
  283. // Probably not created by nnsdk, no name available.
  284. return std::nullopt;
  285. }
  286. if (!tls_thread_type) {
  287. return std::nullopt;
  288. }
  289. const u16 version{memory.Read16(tls_thread_type + 0x26)};
  290. VAddr name_pointer{};
  291. if (version == 1) {
  292. name_pointer = memory.Read32(tls_thread_type + 0xe4);
  293. } else {
  294. name_pointer = memory.Read32(tls_thread_type + 0xe8);
  295. }
  296. if (!name_pointer) {
  297. // No name provided.
  298. return std::nullopt;
  299. }
  300. return memory.ReadCString(name_pointer, 256);
  301. }
  302. static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory,
  303. const Kernel::KThread* thread) {
  304. // Read thread type from TLS
  305. const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)};
  306. const VAddr argument_thread_type{thread->GetArgument()};
  307. if (argument_thread_type && tls_thread_type != argument_thread_type) {
  308. // Probably not created by nnsdk, no name available.
  309. return std::nullopt;
  310. }
  311. if (!tls_thread_type) {
  312. return std::nullopt;
  313. }
  314. const u16 version{memory.Read16(tls_thread_type + 0x46)};
  315. VAddr name_pointer{};
  316. if (version == 1) {
  317. name_pointer = memory.Read64(tls_thread_type + 0x1a0);
  318. } else {
  319. name_pointer = memory.Read64(tls_thread_type + 0x1a8);
  320. }
  321. if (!name_pointer) {
  322. // No name provided.
  323. return std::nullopt;
  324. }
  325. return memory.ReadCString(name_pointer, 256);
  326. }
  327. static std::optional<std::string> GetThreadName(Core::System& system,
  328. const Kernel::KThread* thread) {
  329. if (system.CurrentProcess()->Is64BitProcess()) {
  330. return GetNameFromThreadType64(system.Memory(), thread);
  331. } else {
  332. return GetNameFromThreadType32(system.Memory(), thread);
  333. }
  334. }
  335. static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) {
  336. switch (thread->GetWaitReasonForDebugging()) {
  337. case Kernel::ThreadWaitReasonForDebugging::Sleep:
  338. return "Sleep";
  339. case Kernel::ThreadWaitReasonForDebugging::IPC:
  340. return "IPC";
  341. case Kernel::ThreadWaitReasonForDebugging::Synchronization:
  342. return "Synchronization";
  343. case Kernel::ThreadWaitReasonForDebugging::ConditionVar:
  344. return "ConditionVar";
  345. case Kernel::ThreadWaitReasonForDebugging::Arbitration:
  346. return "Arbitration";
  347. case Kernel::ThreadWaitReasonForDebugging::Suspended:
  348. return "Suspended";
  349. default:
  350. return "Unknown";
  351. }
  352. }
  353. static std::string GetThreadState(const Kernel::KThread* thread) {
  354. switch (thread->GetState()) {
  355. case Kernel::ThreadState::Initialized:
  356. return "Initialized";
  357. case Kernel::ThreadState::Waiting:
  358. return fmt::format("Waiting ({})", GetThreadWaitReason(thread));
  359. case Kernel::ThreadState::Runnable:
  360. return "Runnable";
  361. case Kernel::ThreadState::Terminated:
  362. return "Terminated";
  363. default:
  364. return "Unknown";
  365. }
  366. }
  367. static std::string PaginateBuffer(std::string_view buffer, std::string_view request) {
  368. const auto amount{request.substr(request.find(',') + 1)};
  369. const auto offset_val{static_cast<u64>(strtoll(request.data(), nullptr, 16))};
  370. const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))};
  371. if (offset_val + amount_val > buffer.size()) {
  372. return fmt::format("l{}", buffer.substr(offset_val));
  373. } else {
  374. return fmt::format("m{}", buffer.substr(offset_val, amount_val));
  375. }
  376. }
  377. void GDBStub::HandleQuery(std::string_view command) {
  378. if (command.starts_with("TStatus")) {
  379. // no tracepoint support
  380. SendReply("T0");
  381. } else if (command.starts_with("Supported")) {
  382. SendReply("PacketSize=4000;qXfer:features:read+;qXfer:threads:read+;qXfer:libraries:read+;"
  383. "vContSupported+;QStartNoAckMode+");
  384. } else if (command.starts_with("Xfer:features:read:target.xml:")) {
  385. const auto target_xml{arch->GetTargetXML()};
  386. SendReply(PaginateBuffer(target_xml, command.substr(30)));
  387. } else if (command.starts_with("Offsets")) {
  388. Loader::AppLoader::Modules modules;
  389. system.GetAppLoader().ReadNSOModules(modules);
  390. const auto main = std::find_if(modules.begin(), modules.end(),
  391. [](const auto& key) { return key.second == "main"; });
  392. if (main != modules.end()) {
  393. SendReply(fmt::format("TextSeg={:x}", main->first));
  394. } else {
  395. SendReply(fmt::format("TextSeg={:x}",
  396. system.CurrentProcess()->PageTable().GetCodeRegionStart()));
  397. }
  398. } else if (command.starts_with("Xfer:libraries:read::")) {
  399. Loader::AppLoader::Modules modules;
  400. system.GetAppLoader().ReadNSOModules(modules);
  401. std::string buffer;
  402. buffer += R"(<?xml version="1.0"?>)";
  403. buffer += "<library-list>";
  404. for (const auto& [base, name] : modules) {
  405. buffer += fmt::format(R"(<library name="{}"><segment address="{:#x}"/></library>)",
  406. EscapeXML(name), base);
  407. }
  408. buffer += "</library-list>";
  409. SendReply(PaginateBuffer(buffer, command.substr(21)));
  410. } else if (command.starts_with("fThreadInfo")) {
  411. // beginning of list
  412. const auto& threads = system.GlobalSchedulerContext().GetThreadList();
  413. std::vector<std::string> thread_ids;
  414. for (const auto& thread : threads) {
  415. thread_ids.push_back(fmt::format("{:x}", thread->GetThreadID()));
  416. }
  417. SendReply(fmt::format("m{}", fmt::join(thread_ids, ",")));
  418. } else if (command.starts_with("sThreadInfo")) {
  419. // end of list
  420. SendReply("l");
  421. } else if (command.starts_with("Xfer:threads:read::")) {
  422. std::string buffer;
  423. buffer += R"(<?xml version="1.0"?>)";
  424. buffer += "<threads>";
  425. const auto& threads = system.GlobalSchedulerContext().GetThreadList();
  426. for (const auto* thread : threads) {
  427. auto thread_name{GetThreadName(system, thread)};
  428. if (!thread_name) {
  429. thread_name = fmt::format("Thread {:d}", thread->GetThreadID());
  430. }
  431. buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)",
  432. thread->GetThreadID(), thread->GetActiveCore(),
  433. EscapeXML(*thread_name), GetThreadState(thread));
  434. }
  435. buffer += "</threads>";
  436. SendReply(PaginateBuffer(buffer, command.substr(19)));
  437. } else if (command.starts_with("Attached")) {
  438. SendReply("0");
  439. } else if (command.starts_with("StartNoAckMode")) {
  440. no_ack = true;
  441. SendReply(GDB_STUB_REPLY_OK);
  442. } else {
  443. SendReply(GDB_STUB_REPLY_EMPTY);
  444. }
  445. }
  446. void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions) {
  447. if (command == "?") {
  448. // Continuing and stepping are supported
  449. // (signal is ignored, but required for GDB to use vCont)
  450. SendReply("vCont;c;C;s;S");
  451. return;
  452. }
  453. Kernel::KThread* stepped_thread{nullptr};
  454. bool lock_execution{true};
  455. std::vector<std::string> entries;
  456. boost::split(entries, command.substr(1), boost::is_any_of(";"));
  457. for (const auto& thread_action : entries) {
  458. std::vector<std::string> parts;
  459. boost::split(parts, thread_action, boost::is_any_of(":"));
  460. if (parts.size() == 1 && (parts[0] == "c" || parts[0].starts_with("C"))) {
  461. lock_execution = false;
  462. }
  463. if (parts.size() == 2 && (parts[0] == "s" || parts[0].starts_with("S"))) {
  464. stepped_thread = GetThreadByID(strtoll(parts[1].data(), nullptr, 16));
  465. }
  466. }
  467. if (stepped_thread) {
  468. backend.SetActiveThread(stepped_thread);
  469. actions.push_back(lock_execution ? DebuggerAction::StepThreadLocked
  470. : DebuggerAction::StepThreadUnlocked);
  471. } else {
  472. actions.push_back(DebuggerAction::Continue);
  473. }
  474. }
  475. Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
  476. const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
  477. for (auto* thread : threads) {
  478. if (thread->GetThreadID() == thread_id) {
  479. return thread;
  480. }
  481. }
  482. return nullptr;
  483. }
  484. std::vector<char>::const_iterator GDBStub::CommandEnd() const {
  485. // Find the end marker
  486. const auto end{std::find(current_command.begin(), current_command.end(), GDB_STUB_END)};
  487. // Require the checksum to be present
  488. return std::min(end + 2, current_command.end());
  489. }
  490. std::optional<std::string> GDBStub::DetachCommand() {
  491. // Slice the string part from the beginning to the end marker
  492. const auto end{CommandEnd()};
  493. // Extract possible command data
  494. std::string data(current_command.data(), end - current_command.begin() + 1);
  495. // Shift over the remaining contents
  496. current_command.erase(current_command.begin(), end + 1);
  497. // Validate received command
  498. if (data[0] != GDB_STUB_START) {
  499. LOG_ERROR(Debug_GDBStub, "Invalid start data: {}", data[0]);
  500. return std::nullopt;
  501. }
  502. u8 calculated = CalculateChecksum(std::string_view(data).substr(1, data.size() - 4));
  503. u8 received = static_cast<u8>(strtoll(data.data() + data.size() - 2, nullptr, 16));
  504. // Verify checksum
  505. if (calculated != received) {
  506. LOG_ERROR(Debug_GDBStub, "Checksum mismatch: calculated {:02x}, received {:02x}",
  507. calculated, received);
  508. return std::nullopt;
  509. }
  510. return data.substr(1, data.size() - 4);
  511. }
  512. void GDBStub::SendReply(std::string_view data) {
  513. const auto escaped{EscapeGDB(data)};
  514. const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END,
  515. CalculateChecksum(escaped))};
  516. LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output);
  517. // C++ string support is complete rubbish
  518. const u8* output_begin = reinterpret_cast<const u8*>(output.data());
  519. const u8* output_end = output_begin + output.size();
  520. backend.WriteToClient(std::span<const u8>(output_begin, output_end));
  521. }
  522. void GDBStub::SendStatus(char status) {
  523. if (no_ack) {
  524. return;
  525. }
  526. std::array<u8, 1> buf = {static_cast<u8>(status)};
  527. LOG_TRACE(Debug_GDBStub, "Writing status: {}", status);
  528. backend.WriteToClient(buf);
  529. }
  530. } // namespace Core