Merge pull request #8410 from liamwhite/thread-names
gdbstub: Support reading guest thread names
This commit is contained in:
		| @@ -34,6 +34,65 @@ constexpr char GDB_STUB_REPLY_ERR[] = "E01"; | |||||||
| constexpr char GDB_STUB_REPLY_OK[] = "OK"; | constexpr char GDB_STUB_REPLY_OK[] = "OK"; | ||||||
| constexpr char GDB_STUB_REPLY_EMPTY[] = ""; | 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 += "&"; | ||||||
|  |             break; | ||||||
|  |         case '"': | ||||||
|  |             escaped += """; | ||||||
|  |             break; | ||||||
|  |         case '<': | ||||||
|  |             escaped += "<"; | ||||||
|  |             break; | ||||||
|  |         case '>': | ||||||
|  |             escaped += ">"; | ||||||
|  |             break; | ||||||
|  |         default: | ||||||
|  |             escaped += c; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return escaped; | ||||||
|  | } | ||||||
|  |  | ||||||
| GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) | GDBStub::GDBStub(DebuggerBackend& backend_, Core::System& system_) | ||||||
|     : DebuggerFrontend(backend_), system{system_} { |     : DebuggerFrontend(backend_), system{system_} { | ||||||
|     if (system.CurrentProcess()->Is64BitProcess()) { |     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) { | static std::string_view GetThreadWaitReason(const Kernel::KThread* thread) { | ||||||
|     switch (thread->GetWaitReasonForDebugging()) { |     switch (thread->GetWaitReasonForDebugging()) { | ||||||
|     case Kernel::ThreadWaitReasonForDebugging::Sleep: |     case Kernel::ThreadWaitReasonForDebugging::Sleep: | ||||||
| @@ -332,20 +465,36 @@ void GDBStub::HandleQuery(std::string_view command) { | |||||||
|     } else if (command.starts_with("sThreadInfo")) { |     } else if (command.starts_with("sThreadInfo")) { | ||||||
|         // end of list |         // end of list | ||||||
|         SendReply("l"); |         SendReply("l"); | ||||||
|     } else if (command.starts_with("Xfer:threads:read")) { |     } else if (command.starts_with("Xfer:threads:read::")) { | ||||||
|         std::string buffer; |         std::string buffer; | ||||||
|         buffer += R"(l<?xml version="1.0"?>)"; |         buffer += R"(<?xml version="1.0"?>)"; | ||||||
|         buffer += "<threads>"; |         buffer += "<threads>"; | ||||||
|  |  | ||||||
|         const auto& threads = system.GlobalSchedulerContext().GetThreadList(); |         const auto& threads = system.GlobalSchedulerContext().GetThreadList(); | ||||||
|         for (const auto& thread : threads) { |         for (const auto* thread : threads) { | ||||||
|             buffer += fmt::format(R"(<thread id="{:x}" core="{:d}" name="Thread {:d}">{}</thread>)", |             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(), thread->GetActiveCore(), | ||||||
|                                   thread->GetThreadID(), GetThreadState(thread)); |                                   EscapeXML(*thread_name), GetThreadState(thread)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         buffer += "</threads>"; |         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")) { |     } else if (command.starts_with("Attached")) { | ||||||
|         SendReply("0"); |         SendReply("0"); | ||||||
|     } else if (command.starts_with("StartNoAckMode")) { |     } else if (command.starts_with("StartNoAckMode")) { | ||||||
| @@ -438,14 +587,10 @@ std::optional<std::string> GDBStub::DetachCommand() { | |||||||
|     return data.substr(1, data.size() - 4); |     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) { | void GDBStub::SendReply(std::string_view data) { | ||||||
|     const auto output{ |     const auto escaped{EscapeGDB(data)}; | ||||||
|         fmt::format("{}{}{}{:02x}", GDB_STUB_START, data, GDB_STUB_END, CalculateChecksum(data))}; |     const auto output{fmt::format("{}{}{}{:02x}", GDB_STUB_START, escaped, GDB_STUB_END, | ||||||
|  |                                   CalculateChecksum(escaped))}; | ||||||
|     LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); |     LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); | ||||||
|  |  | ||||||
|     // C++ string support is complete rubbish |     // C++ string support is complete rubbish | ||||||
|   | |||||||
| @@ -34,7 +34,6 @@ private: | |||||||
|     std::optional<std::string> DetachCommand(); |     std::optional<std::string> DetachCommand(); | ||||||
|     Kernel::KThread* GetThreadByID(u64 thread_id); |     Kernel::KThread* GetThreadByID(u64 thread_id); | ||||||
|  |  | ||||||
|     static u8 CalculateChecksum(std::string_view data); |  | ||||||
|     void SendReply(std::string_view data); |     void SendReply(std::string_view data); | ||||||
|     void SendStatus(char status); |     void SendStatus(char status); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -198,6 +198,10 @@ ResultCode KThread::Initialize(KThreadFunction func, uintptr_t arg, VAddr user_s | |||||||
|     resource_limit_release_hint = false; |     resource_limit_release_hint = false; | ||||||
|     cpu_time = 0; |     cpu_time = 0; | ||||||
|  |  | ||||||
|  |     // Set debug context. | ||||||
|  |     stack_top = user_stack_top; | ||||||
|  |     argument = arg; | ||||||
|  |  | ||||||
|     // Clear our stack parameters. |     // Clear our stack parameters. | ||||||
|     std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0, |     std::memset(static_cast<void*>(std::addressof(GetStackParameters())), 0, | ||||||
|                 sizeof(StackParameters)); |                 sizeof(StackParameters)); | ||||||
|   | |||||||
| @@ -660,6 +660,14 @@ public: | |||||||
|     void IfDummyThreadTryWait(); |     void IfDummyThreadTryWait(); | ||||||
|     void IfDummyThreadEndWait(); |     void IfDummyThreadEndWait(); | ||||||
|  |  | ||||||
|  |     [[nodiscard]] uintptr_t GetArgument() const { | ||||||
|  |         return argument; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     [[nodiscard]] VAddr GetUserStackTop() const { | ||||||
|  |         return stack_top; | ||||||
|  |     } | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     static constexpr size_t PriorityInheritanceCountMax = 10; |     static constexpr size_t PriorityInheritanceCountMax = 10; | ||||||
|     union SyncObjectBuffer { |     union SyncObjectBuffer { | ||||||
| @@ -791,6 +799,8 @@ private: | |||||||
|     std::vector<KSynchronizationObject*> wait_objects_for_debugging; |     std::vector<KSynchronizationObject*> wait_objects_for_debugging; | ||||||
|     VAddr mutex_wait_address_for_debugging{}; |     VAddr mutex_wait_address_for_debugging{}; | ||||||
|     ThreadWaitReasonForDebugging wait_reason_for_debugging{}; |     ThreadWaitReasonForDebugging wait_reason_for_debugging{}; | ||||||
|  |     uintptr_t argument; | ||||||
|  |     VAddr stack_top; | ||||||
|  |  | ||||||
| public: | public: | ||||||
|     using ConditionVariableThreadTreeType = ConditionVariableThreadTree; |     using ConditionVariableThreadTreeType = ConditionVariableThreadTree; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user