core/debugger: Support reading guest thread names
This commit is contained in:
parent
858f8ac6d9
commit
07922abffc
4 changed files with 172 additions and 14 deletions
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue