|
|
@ -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) { |
|
|
|
|
|
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(), 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{ |
|
|
|
|
|
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); |
|
|
LOG_TRACE(Debug_GDBStub, "Writing reply: {}", output); |
|
|
|
|
|
|
|
|
// C++ string support is complete rubbish
|
|
|
// C++ string support is complete rubbish
|
|
|
|