|
|
|
@ -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 += "&";
|
|
|
|
|
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<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
|
|
|
|
|