From 07922abffc3f0c98fc47ca9f8fe340a22c8d20e0 Mon Sep 17 00:00:00 2001 From: Liam Date: Wed, 1 Jun 2022 10:54:44 -0400 Subject: core/debugger: Support reading guest thread names --- src/core/debugger/gdbstub.cpp | 171 ++++++++++++++++++++++++++++++++++++--- src/core/debugger/gdbstub.h | 1 - src/core/hle/kernel/k_thread.cpp | 4 + src/core/hle/kernel/k_thread.h | 10 +++ 4 files changed, 172 insertions(+), 14 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"; constexpr char GDB_STUB_REPLY_OK[] = "OK"; constexpr char GDB_STUB_REPLY_EMPTY[] = ""; +static u8 CalculateChecksum(std::string_view data) { + return std::accumulate(data.begin(), data.end(), u8{0}, + [](u8 lhs, u8 rhs) { return static_cast(lhs + rhs); }); +} + +static std::string EscapeGDB(std::string_view data) { + std::string escaped; + escaped.reserve(data.size()); + + for (char c : data) { + switch (c) { + case '#': + escaped += "}\x03"; + break; + case '$': + escaped += "}\x04"; + break; + case '*': + escaped += "}\x0a"; + break; + case '}': + escaped += "}\x5d"; + break; + default: + escaped += c; + break; + } + } + + return escaped; +} + +static std::string EscapeXML(std::string_view data) { + std::string escaped; + escaped.reserve(data.size()); + + for (char c : data) { + switch (c) { + case '&': + escaped += "&"; + break; + case '"': + escaped += """; + break; + case '<': + escaped += "<"; + break; + case '>': + escaped += ">"; + break; + default: + escaped += c; + break; + } + } + + return escaped; +} + GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) : DebuggerFrontend(backend_), system{system_} { if (system.CurrentProcess()->Is64BitProcess()) { @@ -255,6 +314,80 @@ void GDBStub::ExecuteCommand(std::string_view packet, std::vector GetNameFromThreadType32(Core::Memory::Memory& memory, + const Kernel::KThread* thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read32(thread->GetTLSAddress() + 0x1fc)}; + const VAddr argument_thread_type{thread->GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x26)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read32(tls_thread_type + 0xe4); + } else { + name_pointer = memory.Read32(tls_thread_type + 0xe8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +static std::optional GetNameFromThreadType64(Core::Memory::Memory& memory, + const Kernel::KThread* thread) { + // Read thread type from TLS + const VAddr tls_thread_type{memory.Read64(thread->GetTLSAddress() + 0x1f8)}; + const VAddr argument_thread_type{thread->GetArgument()}; + + if (argument_thread_type && tls_thread_type != argument_thread_type) { + // Probably not created by nnsdk, no name available. + return std::nullopt; + } + + if (!tls_thread_type) { + return std::nullopt; + } + + const u16 version{memory.Read16(tls_thread_type + 0x46)}; + VAddr name_pointer{}; + if (version == 1) { + name_pointer = memory.Read64(tls_thread_type + 0x1a0); + } else { + name_pointer = memory.Read64(tls_thread_type + 0x1a8); + } + + if (!name_pointer) { + // No name provided. + return std::nullopt; + } + + return memory.ReadCString(name_pointer, 256); +} + +static std::optional GetThreadName(Core::System& system, + const Kernel::KThread* thread) { + if (system.CurrentProcess()->Is64BitProcess()) { + return GetNameFromThreadType64(system.Memory(), thread); + } else { + return GetNameFromThreadType32(system.Memory(), thread); + } +} + static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { switch (thread->GetWaitReasonForDebugging()) { case Kernel::ThreadWaitReasonForDebugging::Sleep: @@ -332,20 +465,36 @@ void GDBStub::HandleQuery(std::string_view command) { } else if (command.starts_with("sThreadInfo")) { // end of list SendReply("l"); - } else if (command.starts_with("Xfer:threads:read")) { + } else if (command.starts_with("Xfer:threads:read::")) { std::string buffer; - buffer += R"(l)"; + buffer += R"()"; buffer += ""; const auto& threads = system.GlobalSchedulerContext().GetThreadList(); - for (const auto& thread : threads) { - buffer += fmt::format(R"({})", + for (const auto* thread : threads) { + auto thread_name{GetThreadName(system, thread)}; + if (!thread_name) { + thread_name = fmt::format("Thread {:d}", thread->GetThreadID()); + } + + buffer += fmt::format(R"({})", thread->GetThreadID(), thread->GetActiveCore(), - thread->GetThreadID(), GetThreadState(thread)); + EscapeXML(*thread_name), GetThreadState(thread)); } buffer += ""; - SendReply(buffer); + + const auto offset{command.substr(19)}; + const auto amount{command.substr(command.find(',') + 1)}; + + const auto offset_val{static_cast(strtoll(offset.data(), nullptr, 16))}; + const auto amount_val{static_cast(strtoll(amount.data(), nullptr, 16))}; + + if (offset_val + amount_val > buffer.size()) { + SendReply("l" + buffer.substr(offset_val)); + } else { + SendReply("m" + buffer.substr(offset_val, amount_val)); + } } else if (command.starts_with("Attached")) { SendReply("0"); } else if (command.starts_with("StartNoAckMode")) { @@ -438,14 +587,10 @@ std::optional GDBStub::DetachCommand() { return data.substr(1, data.size() - 4); } -u8 GDBStub::CalculateChecksum(std::string_view data) { - return std::accumulate(data.begin(), data.end(), u8{0}, - [](u8 lhs, u8 rhs) { return static_cast(lhs + rhs); }); -} - void GDBStub::SendReply(std::string_view data) { - const auto output{ - fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))}; + const auto escaped{EscapeGDB(data)}; + const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END, + CalculateChecksum(escaped))}; LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); // C++ string support is complete rubbish diff --git a/src/core/debugger/gdbstub.h b/src/core/debugger/gdbstub.h index aa1f7de6c..1bb638187 100644 --- a/src/core/debugger/gdbstub.h +++ b/src/core/debugger/gdbstub.h @@ -34,7 +34,6 @@ private: std::optional DetachCommand(); Kernel::KThread* GetThreadByID(u64 thread_id); - static u8 CalculateChecksum(std::string_view data); void SendReply(std::string_view data); void SendStatus(char status); diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index ab9ce6a86..940334f59 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp @@ -198,6 +198,10 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s resource_limit_release_hint = false; cpu_time = 0; + // Set debug context. + stack_top = user_stack_top; + argument = arg; + // Clear our stack parameters. std::memset(static_cast(std::addressof(GetStackParameters())), 0, sizeof(StackParameters)); diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 60ae0da78..f4d83f99a 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h @@ -660,6 +660,14 @@ public: void IfDummyThreadTryWait(); void IfDummyThreadEndWait(); + [[nodiscard]] uintptr_t GetArgument() const { + return argument; + } + + [[nodiscard]] VAddr GetUserStackTop() const { + return stack_top; + } + private: static constexpr size_t PriorityInheritanceCountMax = 10; union SyncObjectBuffer { @@ -791,6 +799,8 @@ private: std::vector wait_objects_for_debugging; VAddr mutex_wait_address_for_debugging{}; ThreadWaitReasonForDebugging wait_reason_for_debugging{}; + uintptr_t argument; + VAddr stack_top; public: using ConditionVariableThreadTreeType = ConditionVariableThreadTree; -- cgit v1.2.3