From 07922abffc3f0c98fc47ca9f8fe340a22c8d20e0 Mon Sep 17 00:00:00 2001
From: Liam <byteslice@airmail.cc>
Date: Wed, 1 Jun 2022 10:54:44 -0400
Subject: [PATCH] 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<u8>(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 += "&amp;";
+            break;
+        case '"':
+            escaped += "&quot;";
+            break;
+        case '<':
+            escaped += "&lt;";
+            break;
+        case '>':
+            escaped += "&gt;";
+            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<DebuggerAction
     }
 }
 
+// Structure offsets are from Atmosphere
+// See osdbg_thread_local_region.os.horizon.hpp and osdbg_thread_type.os.horizon.hpp
+
+static std::optional<std::string> 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<std::string> 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<std::string> 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<?xml version="1.0"?>)";
+        buffer += R"(<?xml version="1.0"?>)";
         buffer += "<threads>";
 
         const auto& threads = system.GlobalSchedulerContext().GetThreadList();
-        for (const auto& thread : threads) {
-            buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)",
+        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 id="{:x}" core="{:d}" name="{}">{}</thread>)",
                                   thread->GetThreadID(), thread->GetActiveCore(),
-                                  thread->GetThreadID(), GetThreadState(thread));
+                                  EscapeXML(*thread_name), GetThreadState(thread));
         }
 
         buffer += "</threads>";
-        SendReply(buffer);
+
+        const auto offset{command.substr(19)};
+        const auto amount{command.substr(command.find(',') + 1)};
+
+        const auto offset_val{static_cast<u64>(strtoll(offset.data(), nullptr, 16))};
+        const auto amount_val{static_cast<u64>(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<std::string> 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<u8>(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<std::string> 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<void*>(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<KSynchronizationObject*> 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;