diff options
Diffstat (limited to 'src/core/debugger/gdbstub.cpp')
| -rw-r--r-- | src/core/debugger/gdbstub.cpp | 382 |
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 | |||
| 25 | namespace Core { | ||
| 26 | |||
| 27 | constexpr char GDB_STUB_START = '$'; | ||
| 28 | constexpr char GDB_STUB_END = '#'; | ||
| 29 | constexpr char GDB_STUB_ACK = '+'; | ||
| 30 | constexpr char GDB_STUB_NACK = '-'; | ||
| 31 | constexpr char GDB_STUB_INT3 = 0x03; | ||
| 32 | constexpr int GDB_STUB_SIGTRAP = 5; | ||
| 33 | |||
| 34 | constexpr char GDB_STUB_REPLY_ERR[] = "E01"; | ||
| 35 | constexpr char GDB_STUB_REPLY_OK[] = "OK"; | ||
| 36 | constexpr char GDB_STUB_REPLY_EMPTY[] = ""; | ||
| 37 | |||
| 38 | GDBStub::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 | |||
| 47 | GDBStub::~GDBStub() = default; | ||
| 48 | |||
| 49 | void GDBStub::Connected() {} | ||
| 50 | |||
| 51 | void GDBStub::Stopped(Kernel::KThread* thread) { | ||
| 52 | SendReply(arch->ThreadStatus(thread, GDB_STUB_SIGTRAP)); | ||
| 53 | } | ||
| 54 | |||
| 55 | std::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 | |||
| 66 | void 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 | |||
| 109 | void 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 | |||
| 251 | void 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 | |||
| 312 | Kernel::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 | |||
| 323 | std::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 | |||
| 331 | std::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 | |||
| 360 | u8 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 | |||
| 365 | void 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 | |||
| 376 | void 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 | ||