diff options
Diffstat (limited to 'src/core/debugger/gdbstub.cpp')
| -rw-r--r-- | src/core/debugger/gdbstub.cpp | 171 |
1 files changed, 158 insertions, 13 deletions
diff --git a/src/core/debugger/gdbstub.cpp b/src/core/debugger/gdbstub.cpp index 0c36069a6..682651a86 100644 --- a/src/core/debugger/gdbstub.cpp +++ b/src/core/debugger/gdbstub.cpp | |||
| @@ -34,6 +34,65 @@ constexpr char GDB_STUB_REPLY_ERR[] = "E01"; | |||
| 34 | constexpr char GDB_STUB_REPLY_OK[] = "OK"; | 34 | constexpr char GDB_STUB_REPLY_OK[] = "OK"; |
| 35 | constexpr char GDB_STUB_REPLY_EMPTY[] = ""; | 35 | constexpr char GDB_STUB_REPLY_EMPTY[] = ""; |
| 36 | 36 | ||
| 37 | static u8 CalculateChecksum(std::string_view data) { | ||
| 38 | return std::accumulate(data.begin(), data.end(), u8{0}, | ||
| 39 | [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); }); | ||
| 40 | } | ||
| 41 | |||
| 42 | static std::string EscapeGDB(std::string_view data) { | ||
| 43 | std::string escaped; | ||
| 44 | escaped.reserve(data.size()); | ||
| 45 | |||
| 46 | for (char c : data) { | ||
| 47 | switch (c) { | ||
| 48 | case '#': | ||
| 49 | escaped += "}\x03"; | ||
| 50 | break; | ||
| 51 | case '$': | ||
| 52 | escaped += "}\x04"; | ||
| 53 | break; | ||
| 54 | case '*': | ||
| 55 | escaped += "}\x0a"; | ||
| 56 | break; | ||
| 57 | case '}': | ||
| 58 | escaped += "}\x5d"; | ||
| 59 | break; | ||
| 60 | default: | ||
| 61 | escaped += c; | ||
| 62 | break; | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | return escaped; | ||
| 67 | } | ||
| 68 | |||
| 69 | static std::string EscapeXML(std::string_view data) { | ||
| 70 | std::string escaped; | ||
| 71 | escaped.reserve(data.size()); | ||
| 72 | |||
| 73 | for (char c : data) { | ||
| 74 | switch (c) { | ||
| 75 | case '&': | ||
| 76 | escaped += "&"; | ||
| 77 | break; | ||
| 78 | case '"': | ||
| 79 | escaped += """; | ||
| 80 | break; | ||
| 81 | case '<': | ||
| 82 | escaped += "<"; | ||
| 83 | break; | ||
| 84 | case '>': | ||
| 85 | escaped += ">"; | ||
| 86 | break; | ||
| 87 | default: | ||
| 88 | escaped += c; | ||
| 89 | break; | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | return escaped; | ||
| 94 | } | ||
| 95 | |||
| 37 | GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) | 96 | GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) |
| 38 | : DebuggerFrontend(backend_), system{system_} { | 97 | : DebuggerFrontend(backend_), system{system_} { |
| 39 | if (system.CurrentProcess()->Is64BitProcess()) { | 98 | if (system.CurrentProcess()->Is64BitProcess()) { |
| @@ -255,6 +314,80 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector<DebuggerAction | |||
| 255 | } | 314 | } |
| 256 | } | 315 | } |
| 257 | 316 | ||
| 317 | // Structure offsets are from Atmosphere | ||
| 318 | // See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp | ||
| 319 | |||
| 320 | static std::optional<std::string> GetNameFromThreadType32(Core::Memory::Memory& memory, | ||
| 321 | const Kernel::KThread* thread) { | ||
| 322 | // Read thread type from TLS | ||
| 323 | const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)}; | ||
| 324 | const VAddr argument_thread_type{thread->GetArgument()}; | ||
| 325 | |||
| 326 | if (argument_thread_type && tls_thread_type != argument_thread_type) { | ||
| 327 | // Probably not created by nnsdk, no name available. | ||
| 328 | return std::nullopt; | ||
| 329 | } | ||
| 330 | |||
| 331 | if (!tls_thread_type) { | ||
| 332 | return std::nullopt; | ||
| 333 | } | ||
| 334 | |||
| 335 | const u16 version{memory.Read16(tls_thread_type + 0x26)}; | ||
| 336 | VAddr name_pointer{}; | ||
| 337 | if (version == 1) { | ||
| 338 | name_pointer = memory.Read32(tls_thread_type + 0xe4); | ||
| 339 | } else { | ||
| 340 | name_pointer = memory.Read32(tls_thread_type + 0xe8); | ||
| 341 | } | ||
| 342 | |||
| 343 | if (!name_pointer) { | ||
| 344 | // No name provided. | ||
| 345 | return std::nullopt; | ||
| 346 | } | ||
| 347 | |||
| 348 | return memory.ReadCString(name_pointer, 256); | ||
| 349 | } | ||
| 350 | |||
| 351 | static std::optional<std::string> GetNameFromThreadType64(Core::Memory::Memory& memory, | ||
| 352 | const Kernel::KThread* thread) { | ||
| 353 | // Read thread type from TLS | ||
| 354 | const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)}; | ||
| 355 | const VAddr argument_thread_type{thread->GetArgument()}; | ||
| 356 | |||
| 357 | if (argument_thread_type && tls_thread_type != argument_thread_type) { | ||
| 358 | // Probably not created by nnsdk, no name available. | ||
| 359 | return std::nullopt; | ||
| 360 | } | ||
| 361 | |||
| 362 | if (!tls_thread_type) { | ||
| 363 | return std::nullopt; | ||
| 364 | } | ||
| 365 | |||
| 366 | const u16 version{memory.Read16(tls_thread_type + 0x46)}; | ||
| 367 | VAddr name_pointer{}; | ||
| 368 | if (version == 1) { | ||
| 369 | name_pointer = memory.Read64(tls_thread_type + 0x1a0); | ||
| 370 | } else { | ||
| 371 | name_pointer = memory.Read64(tls_thread_type + 0x1a8); | ||
| 372 | } | ||
| 373 | |||
| 374 | if (!name_pointer) { | ||
| 375 | // No name provided. | ||
| 376 | return std::nullopt; | ||
| 377 | } | ||
| 378 | |||
| 379 | return memory.ReadCString(name_pointer, 256); | ||
| 380 | } | ||
| 381 | |||
| 382 | static std::optional<std::string> GetThreadName(Core::System& system, | ||
| 383 | const Kernel::KThread* thread) { | ||
| 384 | if (system.CurrentProcess()->Is64BitProcess()) { | ||
| 385 | return GetNameFromThreadType64(system.Memory(), thread); | ||
| 386 | } else { | ||
| 387 | return GetNameFromThreadType32(system.Memory(), thread); | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 258 | static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { | 391 | static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { |
| 259 | switch (thread->GetWaitReasonForDebugging()) { | 392 | switch (thread->GetWaitReasonForDebugging()) { |
| 260 | case Kernel::ThreadWaitReasonForDebugging::Sleep: | 393 | case Kernel::ThreadWaitReasonForDebugging::Sleep: |
| @@ -332,20 +465,36 @@ void GDBStub::HandleQuery(std::string_view command) { | |||
| 332 | } else if (command.starts_with("sThreadInfo")) { | 465 | } else if (command.starts_with("sThreadInfo")) { |
| 333 | // end of list | 466 | // end of list |
| 334 | SendReply("l"); | 467 | SendReply("l"); |
| 335 | } else if (command.starts_with("Xfer:threads:read")) { | 468 | } else if (command.starts_with("Xfer:threads:read::")) { |
| 336 | std::string buffer; | 469 | std::string buffer; |
| 337 | buffer += R"(l<?xml version="1.0"?>)"; | 470 | buffer += R"(<?xml version="1.0"?>)"; |
| 338 | buffer += "<threads>"; | 471 | buffer += "<threads>"; |
| 339 | 472 | ||
| 340 | const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | 473 | const auto& threads = system.GlobalSchedulerContext().GetThreadList(); |
| 341 | for (const auto& thread : threads) { | 474 | for (const auto* thread : threads) { |
| 342 | buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)", | 475 | auto thread_name{GetThreadName(system, thread)}; |
| 476 | if (!thread_name) { | ||
| 477 | thread_name = fmt::format("Thread {:d}", thread->GetThreadID()); | ||
| 478 | } | ||
| 479 | |||
| 480 | buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="{}">{}</thread>)", | ||
| 343 | thread->GetThreadID(), thread->GetActiveCore(), | 481 | thread->GetThreadID(), thread->GetActiveCore(), |
| 344 | thread->GetThreadID(), GetThreadState(thread)); | 482 | EscapeXML(*thread_name), GetThreadState(thread)); |
| 345 | } | 483 | } |
| 346 | 484 | ||
| 347 | buffer += "</threads>"; | 485 | buffer += "</threads>"; |
| 348 | SendReply(buffer); | 486 | |
| 487 | const auto offset{command.substr(19)}; | ||
| 488 | const auto amount{command.substr(command.find(',') + 1)}; | ||
| 489 | |||
| 490 | const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))}; | ||
| 491 | const auto amount_val{static_cast<u64>(strtoll(amount.data(), nullptr, 16))}; | ||
| 492 | |||
| 493 | if (offset_val + amount_val > buffer.size()) { | ||
| 494 | SendReply("l" + buffer.substr(offset_val)); | ||
| 495 | } else { | ||
| 496 | SendReply("m" + buffer.substr(offset_val, amount_val)); | ||
| 497 | } | ||
| 349 | } else if (command.starts_with("Attached")) { | 498 | } else if (command.starts_with("Attached")) { |
| 350 | SendReply("0"); | 499 | SendReply("0"); |
| 351 | } else if (command.starts_with("StartNoAckMode")) { | 500 | } else if (command.starts_with("StartNoAckMode")) { |
| @@ -438,14 +587,10 @@ std::optional<std::string> GDBStub::DetachCommand() { | |||
| 438 | return data.substr(1, data.size() - 4); | 587 | return data.substr(1, data.size() - 4); |
| 439 | } | 588 | } |
| 440 | 589 | ||
| 441 | u8 GDBStub::CalculateChecksum(std::string_view data) { | ||
| 442 | return std::accumulate(data.begin(), data.end(), u8{0}, | ||
| 443 | [](u8 lhs, u8 rhs) { return static_cast<u8>(lhs + rhs); }); | ||
| 444 | } | ||
| 445 | |||
| 446 | void GDBStub::SendReply(std::string_view data) { | 590 | void GDBStub::SendReply(std::string_view data) { |
| 447 | const auto output{ | 591 | const auto escaped{EscapeGDB(data)}; |
| 448 | fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))}; | 592 | const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END, |
| 593 | CalculateChecksum(escaped))}; | ||
| 449 | LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); | 594 | LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); |
| 450 | 595 | ||
| 451 | // C++ string support is complete rubbish | 596 | // C++ string support is complete rubbish |