mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2024-12-30 16:33:17 +01:00
2473 lines
107 KiB
C++
2473 lines
107 KiB
C++
/*
|
|
* Copyright (c) Atmosphère-NX
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <stratosphere.hpp>
|
|
#include "dmnt2_debug_log.hpp"
|
|
#include "dmnt2_gdb_server_impl.hpp"
|
|
|
|
namespace ams::dmnt {
|
|
|
|
namespace {
|
|
|
|
constexpr const u32 SdkBreakPoint = 0xE7FFFFFF;
|
|
constexpr const u32 SdkBreakPointMask = 0xFFFFFFFF;
|
|
|
|
constexpr const u32 ArmBreakPoint = 0xE7FFDEFE;
|
|
constexpr const u32 ArmBreakPointMask = 0xFFFFFFFF;
|
|
|
|
constexpr const u32 A64BreakPoint = 0xD4200000;
|
|
constexpr const u32 A64BreakPointMask = 0xFFE0001F;
|
|
|
|
constexpr const u32 A64Halt = 0xD4400000;
|
|
constexpr const u32 A64HaltMask = 0xFFE0001F;
|
|
|
|
constexpr const u32 A32BreakPoint = 0xE1200070;
|
|
constexpr const u32 A32BreakPointMask = 0xFFF000F0;
|
|
|
|
constexpr const u32 T16BreakPoint = 0x0000BE00;
|
|
constexpr const u32 T16BreakPointMask = 0x0000FF00;
|
|
|
|
constexpr const char TargetXmlAarch64[] =
|
|
"l<?xml version=\"1.0\"?>"
|
|
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
|
|
"<target>"
|
|
"<architecture>aarch64</architecture>"
|
|
"<xi:include href=\"aarch64-core.xml\"/>"
|
|
"<xi:include href=\"aarch64-fpu.xml\"/>"
|
|
"</target>";
|
|
|
|
constexpr const char Aarch64CoreXml[] =
|
|
"l<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n"
|
|
"<feature name=\"org.gnu.gdb.aarch64.core\">\n"
|
|
"\t<reg name=\"x0\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x1\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x2\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x3\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x4\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x5\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x6\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x7\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x8\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x9\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x10\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x11\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x12\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x13\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x14\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x15\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x16\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x17\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x18\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x19\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x20\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x21\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x22\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x23\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x24\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x25\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x26\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x27\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x28\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x29\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"x30\" bitsize=\"64\"/>\n"
|
|
"\t<reg name=\"sp\" bitsize=\"64\" type=\"data_ptr\"/>\n"
|
|
"\t<reg name=\"pc\" bitsize=\"64\" type=\"code_ptr\"/>\n"
|
|
"\t<flags id=\"cpsr_flags\" size=\"4\">\n"
|
|
"\t\t<field name=\"SP\" start=\"0\" end=\"0\" /><!-- Stack Pointer selection -->\n"
|
|
"\t\t<field name=\"EL\" start=\"2\" end=\"3\" /><!-- Exception level -->\n"
|
|
"\t\t<field name=\"nRW\" start=\"4\" end=\"4\" /><!-- Execution state (not Register Width) 0=64-bit 1-32-bit -->\n"
|
|
"\n"
|
|
"\t\t<field name=\"F\" start=\"6\" end=\"6\" /><!-- FIQ mask -->\n"
|
|
"\t\t<field name=\"I\" start=\"7\" end=\"7\" /><!-- IRQ mask -->\n"
|
|
"\t\t<field name=\"A\" start=\"8\" end=\"8\" /><!-- SError interrupt mask -->\n"
|
|
"\t\t<field name=\"D\" start=\"9\" end=\"9\" /><!-- Debug mask bit -->\n"
|
|
"\n"
|
|
"\t\t<field name=\"SSBS\" start=\"12\" end=\"12\"/><!-- Speculative Store Bypass Safe -->\n"
|
|
"\n"
|
|
"\t\t<field name=\"IL\" start=\"20\" end=\"20\"/><!-- Illegal execution state -->\n"
|
|
"\t\t<field name=\"SS\" start=\"21\" end=\"21\"/><!-- Software step -->\n"
|
|
"\t\t<field name=\"PAN\" start=\"22\" end=\"22\"/><!-- v8.1 Privileged Access Never -->\n"
|
|
"\t\t<field name=\"UAO\" start=\"23\" end=\"23\"/><!-- v8.2 User Access Override -->\n"
|
|
"\t\t<field name=\"DIT\" start=\"24\" end=\"24\"/><!-- v8.4 Data Independent Timing -->\n"
|
|
"\t\t<field name=\"TCO\" start=\"25\" end=\"25\"/><!-- v8.5 Tag Check Override -->\n"
|
|
"\n"
|
|
"\t\t<field name=\"V\" start=\"28\" end=\"28\"/><!-- oVerflow condition flag -->\n"
|
|
"\t\t<field name=\"C\" start=\"29\" end=\"29\"/><!-- Carry condition flag -->\n"
|
|
"\t\t<field name=\"Z\" start=\"30\" end=\"30\"/><!-- Zero condition flag -->\n"
|
|
"\t\t<field name=\"N\" start=\"31\" end=\"31\"/><!-- Negative condition flag -->\n"
|
|
"\t</flags>\n"
|
|
"\t<reg name=\"cpsr\" bitsize=\"32\" type=\"cpsr_flags\"/>\n"
|
|
"</feature>";
|
|
|
|
constexpr const char Aarch64FpuXml[] =
|
|
"l<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE feature SYSTEM \"gdb-target.dtd\">\n"
|
|
"<feature name=\"org.gnu.gdb.aarch64.fpu\">\n"
|
|
"\t<vector id=\"v2d\" type=\"ieee_double\" count=\"2\"/>\n"
|
|
"\t<vector id=\"v2u\" type=\"uint64\" count=\"2\"/>\n"
|
|
"\t<vector id=\"v2i\" type=\"int64\" count=\"2\"/>\n"
|
|
"\t<vector id=\"v4f\" type=\"ieee_single\" count=\"4\"/>\n"
|
|
"\t<vector id=\"v4u\" type=\"uint32\" count=\"4\"/>\n"
|
|
"\t<vector id=\"v4i\" type=\"int32\" count=\"4\"/>\n"
|
|
"\t<!-- <vector id=\"v8f\" type=\"ieee_half\" count=\"8\"/> -->\n"
|
|
"\t<vector id=\"v8u\" type=\"uint16\" count=\"8\"/>\n"
|
|
"\t<vector id=\"v8i\" type=\"int16\" count=\"8\"/>\n"
|
|
"\t<vector id=\"v16u\" type=\"uint8\" count=\"16\"/>\n"
|
|
"\t<vector id=\"v16i\" type=\"int8\" count=\"16\"/>\n"
|
|
"\t<vector id=\"v1u\" type=\"uint128\" count=\"1\"/>\n"
|
|
"\t<vector id=\"v1i\" type=\"int128\" count=\"1\"/>\n"
|
|
"\t<union id=\"vnd\">\n"
|
|
"\t\t<field name=\"f\" type=\"v2d\"/>\n"
|
|
"\t\t<field name=\"u\" type=\"v2u\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"v2i\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<union id=\"vns\">\n"
|
|
"\t\t<field name=\"f\" type=\"v4f\"/>\n"
|
|
"\t\t<field name=\"u\" type=\"v4u\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"v4i\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<union id=\"vnh\">\n"
|
|
"\t\t<!-- <field name=\"f\" type=\"v8f\"/> -->\n"
|
|
"\t\t<field name=\"u\" type=\"v8u\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"v8i\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<union id=\"vnb\">\n"
|
|
"\t\t<field name=\"u\" type=\"v16u\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"v16i\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<union id=\"vnq\">\n"
|
|
"\t\t<field name=\"u\" type=\"v1u\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"v1i\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<union id=\"aarch64v\">\n"
|
|
"\t\t<field name=\"d\" type=\"vnd\"/>\n"
|
|
"\t\t<field name=\"s\" type=\"vns\"/>\n"
|
|
"\t\t<field name=\"h\" type=\"vnh\"/>\n"
|
|
"\t\t<field name=\"b\" type=\"vnb\"/>\n"
|
|
"\t\t<field name=\"q\" type=\"vnq\"/>\n"
|
|
"\t</union>\n"
|
|
"\t<reg name=\"v0\" bitsize=\"128\" type=\"aarch64v\" regnum=\"34\"/>\n"
|
|
"\t<reg name=\"v1\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v2\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v3\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v4\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v5\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v6\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v7\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v8\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v9\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v10\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v11\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v12\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v13\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v14\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v15\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v16\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v17\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v18\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v19\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v20\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v21\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v22\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v23\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v24\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v25\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v26\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v27\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v28\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v29\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v30\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"v31\" bitsize=\"128\" type=\"aarch64v\"/>\n"
|
|
"\t<reg name=\"fpsr\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"fpcr\" bitsize=\"32\"/>\n"
|
|
"</feature>";
|
|
|
|
constexpr const char TargetXmlAarch32[] =
|
|
"l<?xml version=\"1.0\"?>"
|
|
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">"
|
|
"<target>"
|
|
"<architecture>arm</architecture>"
|
|
"<xi:include href=\"arm-core.xml\"/>"
|
|
"<xi:include href=\"arm-vfp.xml\"/>"
|
|
"</target>";
|
|
|
|
constexpr const char ArmCoreXml[] =
|
|
"l<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
|
|
"<feature name=\"org.gnu.gdb.arm.core\">\n"
|
|
"\t<reg name=\"r0\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r1\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r2\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r3\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r4\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r5\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r6\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r7\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r8\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r9\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r10\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r11\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"r12\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"sp\" bitsize=\"32\" type=\"data_ptr\"/>\n"
|
|
"\t<reg name=\"lr\" bitsize=\"32\"/>\n"
|
|
"\t<reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\"/>\n"
|
|
"\t<reg name=\"cpsr\" bitsize=\"32\" regnum=\"25\"/>\n"
|
|
"</feature>\n";
|
|
|
|
constexpr const char ArmVfpXml[] =
|
|
"l<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
|
|
"<feature name=\"org.gnu.gdb.arm.vfp\">\n"
|
|
"\t<reg name=\"d0\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d1\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d2\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d3\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d4\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d5\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d6\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d7\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d8\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d9\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d10\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d11\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d12\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d13\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d14\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d15\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d16\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d17\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d18\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d19\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d20\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d21\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d22\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d23\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d24\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d25\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d26\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d27\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d28\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d29\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d30\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"d31\" bitsize=\"64\" type=\"ieee_double\"/>\n"
|
|
"\t<reg name=\"fpscr\" bitsize=\"32\" type=\"int\" group=\"float\"/>\n"
|
|
"</feature>\n";
|
|
|
|
constexpr const char * const MemoryStateNames[svc::MemoryState_Coverage + 1] = {
|
|
"----- Free -----", /* svc::MemoryState_Free */
|
|
"Io ", /* svc::MemoryState_Io */
|
|
"Static ", /* svc::MemoryState_Static */
|
|
"Code ", /* svc::MemoryState_Code */
|
|
"CodeData ", /* svc::MemoryState_CodeData */
|
|
"Normal ", /* svc::MemoryState_Normal */
|
|
"Shared ", /* svc::MemoryState_Shared */
|
|
"Alias ", /* svc::MemoryState_Alias */
|
|
"AliasCode ", /* svc::MemoryState_AliasCode */
|
|
"AliasCodeData ", /* svc::MemoryState_AliasCodeData */
|
|
"Ipc ", /* svc::MemoryState_Ipc */
|
|
"Stack ", /* svc::MemoryState_Stack */
|
|
"ThreadLocal ", /* svc::MemoryState_ThreadLocal */
|
|
"Transfered ", /* svc::MemoryState_Transfered */
|
|
"SharedTransfered", /* svc::MemoryState_SharedTransfered */
|
|
"SharedCode ", /* svc::MemoryState_SharedCode */
|
|
"Inaccessible ", /* svc::MemoryState_Inaccessible */
|
|
"NonSecureIpc ", /* svc::MemoryState_NonSecureIpc */
|
|
"NonDeviceIpc ", /* svc::MemoryState_NonDeviceIpc */
|
|
"Kernel ", /* svc::MemoryState_Kernel */
|
|
"GeneratedCode ", /* svc::MemoryState_GeneratedCode */
|
|
"CodeOut ", /* svc::MemoryState_CodeOut */
|
|
"Coverage ", /* svc::MemoryState_Coverage */
|
|
};
|
|
|
|
constexpr const char *GetMemoryStateName(svc::MemoryState state) {
|
|
if (static_cast<size_t>(state) < util::size(MemoryStateNames)) {
|
|
return MemoryStateNames[static_cast<size_t>(state)];
|
|
} else {
|
|
return "Unknown ";
|
|
}
|
|
}
|
|
|
|
constexpr const char *GetMemoryPermissionString(const svc::MemoryInfo &info) {
|
|
if (info.state == svc::MemoryState_Free) {
|
|
return " ";
|
|
} else {
|
|
switch (info.permission) {
|
|
case svc::MemoryPermission_ReadExecute:
|
|
return "r-x";
|
|
case svc::MemoryPermission_Read:
|
|
return "r--";
|
|
case svc::MemoryPermission_ReadWrite:
|
|
return "rw-";
|
|
default:
|
|
return "---";
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ParsePrefix(char *&packet, const char *prefix) {
|
|
const auto len = std::strlen(prefix);
|
|
if (std::strncmp(packet, prefix, len) == 0) {
|
|
packet += len;
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void AppendReplyOk(char * &reply, char * const reply_end) {
|
|
AMS_UNUSED(reply_end);
|
|
std::strcpy(reply, "OK");
|
|
reply += 2;
|
|
}
|
|
|
|
void AppendReplyError(char * reply, char * const reply_end, const char *err) {
|
|
AMS_UNUSED(reply_end);
|
|
std::strcpy(reply, err);
|
|
reply += std::strlen(err);
|
|
}
|
|
|
|
void AppendReplyFormat(char * &reply, char * const reply_end, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
|
|
|
|
void AppendReplyFormat(char * &reply, char * const reply_end, const char *fmt, ...) {
|
|
if (const auto remaining_size = reply_end - reply; remaining_size > 0) {
|
|
std::va_list vl;
|
|
va_start(vl, fmt);
|
|
reply += util::VSNPrintf(reply, remaining_size, fmt, vl);
|
|
va_end(vl);
|
|
}
|
|
}
|
|
|
|
constexpr int DecodeHex(char c) {
|
|
if ('a' <= c && c <= 'f') {
|
|
return 10 + (c - 'a');
|
|
} else if ('A' <= c && c <= 'F') {
|
|
return 10 + (c - 'A');
|
|
} else if ('0' <= c && c <= '9') {
|
|
return 0 + (c - '0');
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
constexpr u64 DecodeHex(const char *s) {
|
|
u64 value = 0;
|
|
|
|
while (true) {
|
|
const char c = *(s++);
|
|
|
|
if (int v = DecodeHex(c); v >= 0) {
|
|
value <<= 4;
|
|
value |= v & 0xF;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
void MemoryToHex(char *dst, char * const dst_end, const void *mem, size_t size) {
|
|
const u8 *mem_u8 = static_cast<const u8 *>(mem);
|
|
|
|
while (size-- > 0 && dst < dst_end - 1) {
|
|
const u8 v = *(mem_u8++);
|
|
*(dst++) = "0123456789abcdef"[v >> 4];
|
|
*(dst++) = "0123456789abcdef"[v & 0xF];
|
|
}
|
|
*dst = 0;
|
|
}
|
|
|
|
void HexToMemory(void *dst, const char *src, size_t size) {
|
|
u8 *dst_u8 = static_cast<u8 *>(dst);
|
|
|
|
for (size_t i = 0; i < size; ++i) {
|
|
u8 v = DecodeHex(*(src++)) << 4;
|
|
v |= DecodeHex(*(src++)) & 0xF;
|
|
|
|
*(dst_u8++) = v;
|
|
}
|
|
}
|
|
|
|
void ParseOffsetLength(const char *packet, u32 &offset, u32 &length) {
|
|
/* Default to zero. */
|
|
offset = 0;
|
|
length = 0;
|
|
|
|
bool parsed_offset = false;
|
|
while (*packet) {
|
|
const char c = *(packet++);
|
|
if (c == ',') {
|
|
parsed_offset = true;
|
|
} else if (auto hex = DecodeHex(c); hex >= 0) {
|
|
if (parsed_offset) {
|
|
length <<= 4;
|
|
length |= hex;
|
|
} else {
|
|
offset <<= 4;
|
|
offset |= hex;
|
|
}
|
|
}
|
|
}
|
|
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Offset/Length %x/%x\n", offset, length);
|
|
}
|
|
|
|
s32 FindThreadIdIndex(u64 *thread_ids, s32 num_threads, u64 thread_id) {
|
|
for (auto i = 0; i < num_threads; ++i) {
|
|
if (thread_ids[i] == thread_id) {
|
|
return i;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void SetGdbRegister32(char * &dst, char * const dst_end, u32 value) {
|
|
if (value != 0) {
|
|
AppendReplyFormat(dst, dst_end, "%08x", util::ConvertToBigEndian(value));
|
|
} else {
|
|
AppendReplyFormat(dst, dst_end, "0*\"00");
|
|
}
|
|
}
|
|
|
|
void SetGdbRegister64(char * &dst, char * const dst_end, u64 value) {
|
|
if (value != 0) {
|
|
AppendReplyFormat(dst, dst_end, "%016lx", util::ConvertToBigEndian(value));
|
|
} else {
|
|
AppendReplyFormat(dst, dst_end, "0*,");
|
|
}
|
|
}
|
|
|
|
void SetGdbRegister128(char * &dst, char * const dst_end, u128 value) {
|
|
if (value != 0) {
|
|
AppendReplyFormat(dst, dst_end, "%016lx%016lx", util::ConvertToBigEndian(static_cast<u64>(value >> 0)), util::ConvertToBigEndian(static_cast<u64>(value >> BITSIZEOF(u64))));
|
|
} else {
|
|
AppendReplyFormat(dst, dst_end, "0*<");
|
|
}
|
|
}
|
|
|
|
void SetGdbRegisterPacket(char * &dst, char * const dst_end, const svc::ThreadContext &thread_context, bool is_64_bit) {
|
|
/* Clear packet. */
|
|
dst[0] = 0;
|
|
|
|
if (is_64_bit) {
|
|
/* Copy general purpose registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.r); ++i) {
|
|
SetGdbRegister64(dst, dst_end, thread_context.r[i]);
|
|
}
|
|
|
|
/* Copy special registers. */
|
|
SetGdbRegister64(dst, dst_end, thread_context.fp);
|
|
SetGdbRegister64(dst, dst_end, thread_context.lr);
|
|
SetGdbRegister64(dst, dst_end, thread_context.sp);
|
|
SetGdbRegister64(dst, dst_end, thread_context.pc);
|
|
|
|
SetGdbRegister32(dst, dst_end, thread_context.pstate);
|
|
|
|
/* Copy FPU registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.v); ++i) {
|
|
SetGdbRegister128(dst, dst_end, thread_context.v[i]);
|
|
}
|
|
|
|
SetGdbRegister32(dst, dst_end, thread_context.fpsr);
|
|
SetGdbRegister32(dst, dst_end, thread_context.fpcr);
|
|
} else {
|
|
/* Copy general purpose registers. */
|
|
for (size_t i = 0; i < 15; ++i) {
|
|
SetGdbRegister32(dst, dst_end, thread_context.r[i]);
|
|
}
|
|
|
|
/* Copy special registers. */
|
|
SetGdbRegister32(dst, dst_end, thread_context.pc);
|
|
SetGdbRegister32(dst, dst_end, thread_context.pstate);
|
|
|
|
/* Copy FPU registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.v) / 2; ++i) {
|
|
SetGdbRegister128(dst, dst_end, thread_context.v[i]);
|
|
}
|
|
|
|
const u32 fpscr = (thread_context.fpsr & 0xF80000FF) | (thread_context.fpcr & 0x07FFFF00);
|
|
SetGdbRegister32(dst, dst_end, fpscr);
|
|
}
|
|
}
|
|
|
|
void SetGdbRegisterPacket(char * &dst, char * const dst_end, const svc::ThreadContext &thread_context, u64 reg_num, bool is_64_bit) {
|
|
/* Clear packet. */
|
|
dst[0] = 0;
|
|
|
|
union {
|
|
u32 v32;
|
|
u64 v64;
|
|
u128 v128;
|
|
} v;
|
|
size_t reg_size = 0;
|
|
|
|
if (is_64_bit) {
|
|
if (reg_num < 29) {
|
|
v.v64 = thread_context.r[reg_num];
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 29) {
|
|
v.v64 = thread_context.fp;
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 30) {
|
|
v.v64 = thread_context.lr;
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 31) {
|
|
v.v64 = thread_context.sp;
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 32) {
|
|
v.v64 = thread_context.pc;
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 33) {
|
|
v.v32 = thread_context.pstate;
|
|
reg_size = sizeof(u32);
|
|
} else if (reg_num < 66) {
|
|
v.v128 = thread_context.v[reg_num - 34];
|
|
reg_size = sizeof(u128);
|
|
} else if (reg_num == 66) {
|
|
v.v32 = thread_context.fpsr;
|
|
reg_size = sizeof(u32);
|
|
} else if (reg_num == 67) {
|
|
v.v32 = thread_context.fpcr;
|
|
reg_size = sizeof(u32);
|
|
}
|
|
} else {
|
|
if (reg_num < 15) {
|
|
v.v32 = thread_context.r[reg_num];
|
|
reg_size = sizeof(u32);
|
|
} else if (reg_num == 15) {
|
|
v.v32 = thread_context.pc;
|
|
reg_size = sizeof(u32);
|
|
} else if (reg_num == 25) {
|
|
v.v32 = thread_context.pstate;
|
|
reg_size = sizeof(u32);
|
|
} else if (26 <= reg_num && reg_num < 58) {
|
|
const union {
|
|
u64 v64[2];
|
|
u128 v128;
|
|
} fpu_reg = { .v128 = thread_context.v[(reg_num - 26) / 2] };
|
|
|
|
v.v64 = fpu_reg.v64[(reg_num - 26) % 2];
|
|
reg_size = sizeof(u64);
|
|
} else if (reg_num == 58) {
|
|
const u32 fpscr = (thread_context.fpsr & 0xF80000FF) | (thread_context.fpcr & 0x07FFFF00);
|
|
|
|
v.v32 = fpscr;
|
|
reg_size = sizeof(u32);
|
|
}
|
|
}
|
|
|
|
switch (reg_size) {
|
|
case sizeof(u32): SetGdbRegister32 (dst, dst_end, v.v32 ); break;
|
|
case sizeof(u64): SetGdbRegister64 (dst, dst_end, v.v64 ); break;
|
|
case sizeof(u128): SetGdbRegister128(dst, dst_end, v.v128); break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
}
|
|
|
|
u64 Aarch32RegisterToAarch64Register(u64 reg_num) {
|
|
if (reg_num < 15) {
|
|
return reg_num;
|
|
} else if (reg_num == 15) {
|
|
return 32;
|
|
} else if (reg_num == 25) {
|
|
return 33;
|
|
} else if (26 <= reg_num && reg_num <= 57) {
|
|
return 34 + (reg_num - 26);
|
|
} else if (reg_num == 58) {
|
|
return 66;
|
|
} else {
|
|
AMS_ABORT("Unknown register number %lu\n", reg_num);
|
|
}
|
|
}
|
|
|
|
template<typename IntType> requires std::unsigned_integral<IntType>
|
|
util::optional<IntType> ParseGdbRegister(const char *&src) {
|
|
union {
|
|
IntType v;
|
|
u8 bytes[sizeof(v)];
|
|
} reg;
|
|
for (size_t i = 0; i < util::size(reg.bytes); ++i) {
|
|
const auto high = DecodeHex(*(src++));
|
|
const auto low = DecodeHex(*(src++));
|
|
if (high < 0 || low < 0) {
|
|
return util::nullopt;
|
|
}
|
|
reg.bytes[i] = (high << 4) | low;
|
|
}
|
|
|
|
return reg.v;
|
|
}
|
|
|
|
ALWAYS_INLINE util::optional<u32> ParseGdbRegister32(const char *&src) { return ParseGdbRegister<u32>(src); }
|
|
ALWAYS_INLINE util::optional<u64> ParseGdbRegister64(const char *&src) { return ParseGdbRegister<u64>(src); }
|
|
ALWAYS_INLINE util::optional<u128> ParseGdbRegister128(const char *&src) { return ParseGdbRegister<u128>(src); }
|
|
|
|
void ParseGdbRegisterPacket(svc::ThreadContext &thread_context, const char *src, bool is_64_bit) {
|
|
if (is_64_bit) {
|
|
/* Copy general purpose registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.r); ++i) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.r[i] = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Copy special registers. */
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.fp = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.lr = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.sp = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.pc = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pstate = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
/* Copy FPU registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.v); ++i) {
|
|
if (const auto v = ParseGdbRegister128(src); v.has_value()) {
|
|
thread_context.v[i] = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpsr = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpcr = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
/* Copy general purpose registers. */
|
|
for (size_t i = 0; i < 15; ++i) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.r[i] = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Copy special registers. */
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pc = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pstate = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
/* Copy FPU registers. */
|
|
for (size_t i = 0; i < util::size(thread_context.v) / 2; ++i) {
|
|
if (const auto v = ParseGdbRegister128(src); v.has_value()) {
|
|
thread_context.v[i] = *v;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpsr = *v & 0xF80000FF;
|
|
thread_context.fpcr = *v & 0x07FFFF00;
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ParseGdbRegisterPacket(svc::ThreadContext &thread_context, const char *src, u64 reg_num, bool is_64_bit) {
|
|
if (is_64_bit) {
|
|
if (reg_num < 29) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.r[reg_num] = *v;
|
|
}
|
|
} else if (reg_num == 29) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.fp = *v;
|
|
}
|
|
} else if (reg_num == 30) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.lr = *v;
|
|
}
|
|
} else if (reg_num == 31) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.sp = *v;
|
|
}
|
|
} else if (reg_num == 32) {
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
thread_context.pc = *v;
|
|
}
|
|
} else if (reg_num == 33) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pstate = *v;
|
|
}
|
|
} else if (reg_num < 66) {
|
|
if (const auto v = ParseGdbRegister128(src); v.has_value()) {
|
|
thread_context.v[reg_num - 34] = *v;
|
|
}
|
|
} else if (reg_num == 66) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpsr = *v;
|
|
}
|
|
} else if (reg_num == 67) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpcr = *v;
|
|
}
|
|
}
|
|
} else {
|
|
if (reg_num < 15) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.r[reg_num] = *v;
|
|
}
|
|
} else if (reg_num == 15) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pc = *v;
|
|
}
|
|
} else if (reg_num == 25) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.pstate = *v;
|
|
}
|
|
} else if (26 <= reg_num && reg_num < 58) {
|
|
union {
|
|
u64 v64[2];
|
|
u128 v128;
|
|
} fpu_reg = { .v128 = thread_context.v[(reg_num - 26) / 2] };
|
|
|
|
if (const auto v = ParseGdbRegister64(src); v.has_value()) {
|
|
fpu_reg.v64[(reg_num - 26) % 2] = *v;
|
|
thread_context.v[(reg_num - 26) / 2] = fpu_reg.v128;
|
|
}
|
|
} else if (reg_num == 58) {
|
|
if (const auto v = ParseGdbRegister32(src); v.has_value()) {
|
|
thread_context.fpsr = *v & 0xF80000FF;
|
|
thread_context.fpcr = *v & 0x07FFFF00;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 RegisterToContextFlags(u64 reg_num, bool is_64_bit) {
|
|
/* Convert register number. */
|
|
if (!is_64_bit) {
|
|
reg_num = Aarch32RegisterToAarch64Register(reg_num);
|
|
}
|
|
|
|
/* Get flags. */
|
|
u32 flags = 0;
|
|
if (reg_num < 29) {
|
|
flags = svc::ThreadContextFlag_General;
|
|
} else if (reg_num < 34) {
|
|
flags = svc::ThreadContextFlag_Control;
|
|
} else if (reg_num < 66) {
|
|
flags = svc::ThreadContextFlag_Fpu;
|
|
} else if (reg_num == 66) {
|
|
flags = svc::ThreadContextFlag_FpuControl;
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
constinit os::SdkMutex g_annex_buffer_lock;
|
|
constinit char g_annex_buffer[2 * GdbPacketBufferSize];
|
|
|
|
enum AnnexBufferContents {
|
|
AnnexBufferContents_Invalid,
|
|
AnnexBufferContents_Processes,
|
|
AnnexBufferContents_Threads,
|
|
AnnexBufferContents_Libraries,
|
|
};
|
|
|
|
constinit AnnexBufferContents g_annex_buffer_contents = AnnexBufferContents_Invalid;
|
|
|
|
void GetAnnexBufferContents(char * &dst, u32 offset, u32 length) {
|
|
const u32 annex_len = std::strlen(g_annex_buffer);
|
|
if (offset <= annex_len) {
|
|
if (offset + length < annex_len) {
|
|
dst[0] = 'm';
|
|
std::memcpy(dst + 1, g_annex_buffer + offset, length);
|
|
dst[1 + length] = 0;
|
|
|
|
dst += 1 + length;
|
|
} else {
|
|
const auto size = annex_len - offset;
|
|
|
|
dst[0] = 'l';
|
|
std::memcpy(dst + 1, g_annex_buffer + offset, size);
|
|
dst[1 + size] = 0;
|
|
|
|
dst += 1 + size;
|
|
}
|
|
} else {
|
|
dst[0] = '1';
|
|
dst[1] = 0;
|
|
|
|
dst += 1;
|
|
}
|
|
}
|
|
|
|
constinit os::SdkMutex g_event_request_lock;
|
|
constinit os::SdkMutex g_event_lock;
|
|
constinit os::SdkConditionVariable g_event_request_cv;
|
|
constinit os::SdkConditionVariable g_event_done_cv;
|
|
|
|
}
|
|
|
|
GdbServerImpl::GdbServerImpl(int socket, void *stack, size_t stack_size) : m_socket(socket), m_session(socket), m_packet_io(), m_state(State::Initial), m_debug_process(), m_event(os::EventClearMode_AutoClear) {
|
|
/* Create and start the events thread. */
|
|
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_events_thread), DebugEventsThreadEntry, this, stack, stack_size, os::HighestThreadPriority - 1));
|
|
os::StartThread(std::addressof(m_events_thread));
|
|
|
|
/* Set our state. */
|
|
m_state = State::Running;
|
|
}
|
|
|
|
GdbServerImpl::~GdbServerImpl() {
|
|
/* Set ourselves as killed. */
|
|
m_killed = true;
|
|
|
|
/* Signal to our events thread. */
|
|
{
|
|
std::scoped_lock lk(g_event_request_lock);
|
|
g_event_request_cv.Signal();
|
|
}
|
|
|
|
/* Signal our event. */
|
|
m_event.Signal();
|
|
|
|
/* Wait for our thread to finish. */
|
|
os::WaitThread(std::addressof(m_events_thread));
|
|
os::DestroyThread(std::addressof(m_events_thread));
|
|
|
|
/* Clear our state. */
|
|
m_state = State::Destroyed;
|
|
|
|
/* Detach. */
|
|
if (this->HasDebugProcess()) {
|
|
m_debug_process.Detach();
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::DebugEventsThread() {
|
|
/* Process events. */
|
|
{
|
|
std::scoped_lock lk(g_event_lock);
|
|
|
|
/* Loop while we're not killed. */
|
|
while (!m_killed) {
|
|
/* Wait for a request to come in. */
|
|
g_event_request_cv.Wait(g_event_lock);
|
|
|
|
/* Check that we're not killed now. */
|
|
if (m_killed) {
|
|
break;
|
|
}
|
|
|
|
/* Detach. */
|
|
m_debug_process.Detach();
|
|
|
|
/* Check if we need to start the process. */
|
|
const bool start_process = m_process_id == m_wait_process_id;
|
|
{
|
|
/* Clear our wait process id. */
|
|
ON_SCOPE_EXIT { if (start_process) { m_wait_process_id = os::InvalidProcessId; } };
|
|
|
|
/* If we have a process id, attach. */
|
|
if (R_FAILED(m_debug_process.Attach(m_process_id, m_process_id == m_wait_process_id))) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Failed to attach to %016lx\n", m_process_id.value);
|
|
g_event_done_cv.Signal();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Set our process id. */
|
|
m_process_id = m_debug_process.GetProcessId();
|
|
|
|
/* Signal that we're done attaching. */
|
|
g_event_done_cv.Signal();
|
|
|
|
/* Process debug events without the lock held. */
|
|
{
|
|
g_event_lock.Unlock();
|
|
this->ProcessDebugEvents();
|
|
g_event_lock.Lock();
|
|
}
|
|
|
|
/* Clear our process id and detach. */
|
|
m_process_id = os::InvalidProcessId;
|
|
m_debug_process.Detach();
|
|
}
|
|
}
|
|
|
|
/* Set our state. */
|
|
m_state = State::Exited;
|
|
}
|
|
|
|
void GdbServerImpl::ProcessDebugEvents() {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Processing debug events for %016lx\n", m_process_id.value);
|
|
|
|
u64 new_hb_nro_addr = 0;
|
|
u32 new_hb_nro_insn = 0;
|
|
|
|
while (true) {
|
|
/* Wait for an event to come in. */
|
|
const Result wait_result = [&] ALWAYS_INLINE_LAMBDA {
|
|
std::scoped_lock lk(g_event_lock);
|
|
|
|
s32 dummy = -1;
|
|
svc::Handle handle = m_debug_process.GetHandle();
|
|
R_RETURN(svc::WaitSynchronization(std::addressof(dummy), std::addressof(handle), 1, TimeSpan::FromMilliSeconds(20).GetNanoSeconds()));
|
|
}();
|
|
|
|
/* Check if we're killed. */
|
|
if (m_killed || !m_debug_process.IsValid()) {
|
|
break;
|
|
}
|
|
|
|
/* If we didn't get an event, try again. */
|
|
if (svc::ResultTimedOut::Includes(wait_result)) {
|
|
continue;
|
|
}
|
|
|
|
/* Try to get the event. */
|
|
svc::DebugEventInfo d;
|
|
if (R_FAILED(m_debug_process.GetProcessDebugEvent(std::addressof(d)))) {
|
|
continue;
|
|
}
|
|
|
|
/* Process the event. */
|
|
GdbSignal signal;
|
|
char send_buffer[GdbPacketBufferSize];
|
|
u64 thread_id = d.thread_id;
|
|
m_debug_process.ClearStep();
|
|
|
|
char * reply_cur = send_buffer;
|
|
char * const reply_end = std::end(send_buffer);
|
|
|
|
send_buffer[0] = 0;
|
|
switch (d.type) {
|
|
case svc::DebugEvent_Exception:
|
|
{
|
|
switch (d.info.exception.type) {
|
|
case svc::DebugException_BreakPoint:
|
|
{
|
|
signal = GdbSignal_BreakpointTrap;
|
|
|
|
const uintptr_t address = d.info.exception.address;
|
|
const bool is_instr = d.info.exception.specific.break_point.type == svc::BreakPointType_HardwareInstruction;
|
|
AMS_DMNT2_GDB_LOG_DEBUG("BreakPoint %lx, addr=%lx, type=%s\n", thread_id, address, is_instr ? "Instr" : "Data");
|
|
|
|
if (is_instr) {
|
|
AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;hwbreak:;", static_cast<u32>(signal), m_process_id.value, thread_id);
|
|
} else {
|
|
bool read = false, write = false;
|
|
const char *type = "watch";
|
|
if (R_SUCCEEDED(m_debug_process.GetWatchPointInfo(address, read, write))) {
|
|
if (read && write) {
|
|
type = "awatch";
|
|
} else if (read) {
|
|
type = "rwatch";
|
|
}
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("GetWatchPointInfo FAIL %lx, addr=%lx, type=%s\n", thread_id, address, is_instr ? "Instr" : "Data");
|
|
}
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;%s:%lx;", static_cast<u32>(signal), m_process_id.value, thread_id, type, address);
|
|
}
|
|
}
|
|
break;
|
|
case svc::DebugException_UserBreak:
|
|
{
|
|
const uintptr_t address = d.info.exception.address;
|
|
const auto &info = d.info.exception.specific.user_break;
|
|
AMS_DMNT2_GDB_LOG_DEBUG("UserBreak %lx, addr=%lx, reason=%x, data=0x%lx, size=0x%lx\n", thread_id, address, info.break_reason, info.address, info.size);
|
|
|
|
/* Check reason. */
|
|
/* TODO: libnx/Nintendo provide addresses in different ways, but we could optimize to avoid iterating all memory repeatedly. */
|
|
if ((info.break_reason & svc::BreakReason_NotificationOnlyFlag) != 0) {
|
|
const auto reason = info.break_reason & ~svc::BreakReason_NotificationOnlyFlag;
|
|
if (reason == svc::BreakReason_PostLoadDll || reason == svc::BreakReason_PostUnloadDll) {
|
|
/* Re-collect the process's modules. */
|
|
m_debug_process.CollectModules();
|
|
}
|
|
|
|
if (m_debug_process.GetOverrideStatus().IsHbl() && reason == svc::BreakReason_PostLoadDll) {
|
|
if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_hb_nro_insn), info.address, sizeof(new_hb_nro_insn)))) {
|
|
const u32 break_insn = SdkBreakPoint;
|
|
if (R_SUCCEEDED(m_debug_process.WriteMemory(std::addressof(break_insn), info.address, sizeof(break_insn)))) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Set automatic break on new homebrew NRO (%lx, %lx)\n", info.address, info.size);
|
|
|
|
new_hb_nro_addr = info.address;
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Failed to set automatic break on new homebrew NRO (%lx, %lx)\n", info.address, info.size);
|
|
}
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Failed to read first insn on new homebrew NRO (%lx, %lx)\n", info.address, info.size);
|
|
}
|
|
}
|
|
|
|
/* This was just a notification, so we should continue. */
|
|
m_debug_process.Continue();
|
|
continue;
|
|
}
|
|
|
|
/* Check if we should automatically continue. */
|
|
svc::ThreadContext ctx;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) {
|
|
u32 insn = 0;
|
|
if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(insn), ctx.pc, sizeof(insn)))) {
|
|
constexpr u32 Aarch64SvcBreakValue = 0xD4000001 | (svc::SvcId_Break << 5);
|
|
constexpr u32 Aarch32SvcBreakValue = 0xEF000000 | (svc::SvcId_Break);
|
|
bool is_svc_break = m_debug_process.Is64Bit() ? (insn == Aarch64SvcBreakValue) : (insn == Aarch32SvcBreakValue);
|
|
|
|
if (!is_svc_break) {
|
|
AMS_DMNT2_GDB_LOG_ERROR("UserBreak from non-SvcBreak (%08x)\n", insn);
|
|
m_debug_process.Continue();
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
signal = GdbSignal_BreakpointTrap;
|
|
}
|
|
break;
|
|
case svc::DebugException_DebuggerBreak:
|
|
{
|
|
signal = GdbSignal_Interrupt;
|
|
|
|
thread_id = m_debug_process.GetPreferredDebuggerBreakThreadId();
|
|
svc::ThreadContext ctx;
|
|
if (thread_id == 0 || thread_id == static_cast<u64>(-1) || R_FAILED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
AMS_DMNT2_GDB_LOG_DEBUG("DebuggerBreak %lx, last=%lx\n", thread_id, m_debug_process.GetLastThreadId());
|
|
|
|
m_debug_process.SetLastThreadId(thread_id);
|
|
m_debug_process.SetThreadIdOverride(thread_id);
|
|
}
|
|
break;
|
|
case svc::DebugException_UndefinedInstruction:
|
|
{
|
|
signal = GdbSignal_IllegalInstruction;
|
|
|
|
uintptr_t address = d.info.exception.address;
|
|
const u32 insn = d.info.exception.specific.undefined_instruction.insn;
|
|
u32 new_insn = 0;
|
|
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Undefined Instruction %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn);
|
|
|
|
bool is_new_hb_nro = false;
|
|
|
|
svc::ThreadContext ctx;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) {
|
|
bool insn_changed = false;
|
|
if (ctx.pstate & 0x20) {
|
|
/* Thumb mode. */
|
|
address &= ~1;
|
|
|
|
if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_insn), address, 2))) {
|
|
switch ((new_insn >> 11) & 0x1F) {
|
|
case 0x1D:
|
|
case 0x1E:
|
|
case 0x1F:
|
|
{
|
|
if (R_SUCCEEDED(m_debug_process.ReadMemory(reinterpret_cast<u8 *>(std::addressof(new_insn)) + 2, address + 2, 2))) {
|
|
insn_changed = (new_insn != insn);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
insn_changed = (new_insn != insn);
|
|
break;
|
|
}
|
|
|
|
if ((insn & T16BreakPointMask) == T16BreakPoint) {
|
|
signal = GdbSignal_BreakpointTrap;
|
|
}
|
|
}
|
|
} else {
|
|
/* Non-thumb. */
|
|
if (R_SUCCEEDED(m_debug_process.ReadMemory(std::addressof(new_insn), address, sizeof(new_insn)))) {
|
|
insn_changed = (new_insn != insn);
|
|
}
|
|
|
|
if (((insn & SdkBreakPointMask) == SdkBreakPoint) ||
|
|
((insn & ArmBreakPointMask) == ArmBreakPoint) ||
|
|
((insn & A64BreakPointMask) == A64BreakPoint) ||
|
|
((insn & A32BreakPointMask) == A32BreakPoint) ||
|
|
((insn & A64HaltMask) == A64Halt))
|
|
{
|
|
signal = GdbSignal_BreakpointTrap;
|
|
}
|
|
|
|
if (m_debug_process.GetOverrideStatus().IsHbl() && address == new_hb_nro_addr && insn == SdkBreakPoint) {
|
|
if (R_SUCCEEDED(m_debug_process.WriteMemory(std::addressof(new_hb_nro_insn), new_hb_nro_addr, sizeof(new_hb_nro_insn)))) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Did automatic break on new homebrew NRO (%lx)\n", address);
|
|
|
|
new_hb_nro_addr = 0;
|
|
new_hb_nro_insn = 0;
|
|
|
|
is_new_hb_nro = true;
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_ERROR("Failed to restore instruction for new homebrew NRO (%lx)!\n", address);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (insn_changed) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Instruction Changed %lx, address=%p, insn=%08x, new_insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn, new_insn);
|
|
}
|
|
}
|
|
|
|
if (signal == GdbSignal_IllegalInstruction) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Illegal Instruction %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn);
|
|
} else if (signal == GdbSignal_BreakpointTrap && ((insn & SdkBreakPointMask) != SdkBreakPoint)) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Non-SDK BreakPoint %lx, address=%p, insn=%08x\n", thread_id, reinterpret_cast<void *>(address), insn);
|
|
}
|
|
|
|
if (signal == GdbSignal_BreakpointTrap && !is_new_hb_nro) {
|
|
AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;swbreak:;", static_cast<u32>(signal), m_process_id.value, thread_id);
|
|
}
|
|
|
|
m_debug_process.ClearStep();
|
|
}
|
|
break;
|
|
default:
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Unhandled Exception %u %lx\n", static_cast<u32>(d.info.exception.type), thread_id);
|
|
signal = GdbSignal_SegmentationFault;
|
|
break;
|
|
}
|
|
|
|
if (reply_cur == send_buffer) {
|
|
AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;", static_cast<u32>(signal), m_process_id.value, thread_id);
|
|
}
|
|
|
|
m_debug_process.SetLastThreadId(thread_id);
|
|
m_debug_process.SetLastSignal(signal);
|
|
}
|
|
break;
|
|
case svc::DebugEvent_CreateThread:
|
|
{
|
|
AMS_DMNT2_GDB_LOG_DEBUG("CreateThread %lx\n", thread_id);
|
|
|
|
if (m_debug_process.IsValid()) {
|
|
m_debug_process.Continue();
|
|
} else {
|
|
AppendReplyFormat(reply_cur, reply_end, "W00");
|
|
}
|
|
}
|
|
break;
|
|
case svc::DebugEvent_ExitThread:
|
|
{
|
|
AMS_DMNT2_GDB_LOG_DEBUG("ExitThread %lx\n", thread_id);
|
|
|
|
if (m_debug_process.IsValid()) {
|
|
m_debug_process.Continue();
|
|
} else {
|
|
AppendReplyFormat(reply_cur, reply_end, "W00");
|
|
}
|
|
}
|
|
break;
|
|
case svc::DebugEvent_ExitProcess:
|
|
{
|
|
m_killed = true;
|
|
AMS_DMNT2_GDB_LOG_DEBUG("ExitProcess\n");
|
|
|
|
if (d.info.exit_process.reason == svc::ProcessExitReason_ExitProcess) {
|
|
AppendReplyFormat(reply_cur, reply_end, "W00");
|
|
} else {
|
|
AppendReplyFormat(reply_cur, reply_end, "X%02X", GdbSignal_Killed);
|
|
}
|
|
|
|
m_debug_process.Detach();
|
|
}
|
|
break;
|
|
default:
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Unhandled ProcessEvent %u %lx\n", static_cast<u32>(d.type), thread_id);
|
|
m_debug_process.Continue();
|
|
break;
|
|
}
|
|
|
|
if (reply_cur != send_buffer) {
|
|
bool do_break;
|
|
this->SendPacket(std::addressof(do_break), send_buffer);
|
|
if (do_break) {
|
|
m_debug_process.Break();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::AppendStopReplyPacket(GdbSignal signal) {
|
|
/* Set the signal. */
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "T%02X", static_cast<u32>(signal));
|
|
|
|
/* Get the last thread id. */
|
|
const u64 thread_id = m_debug_process.GetLastThreadId();
|
|
|
|
/* Get the thread context. */
|
|
svc::ThreadContext thread_context = {};
|
|
m_debug_process.GetThreadContext(std::addressof(thread_context), thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control);
|
|
|
|
/* Add important registers. */
|
|
/* TODO: aarch32 */
|
|
{
|
|
if (thread_context.fp != 0) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "1d:%016lx", util::ConvertToBigEndian(thread_context.fp));
|
|
} else {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "1d:0*,");
|
|
}
|
|
|
|
if (thread_context.sp != 0) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";1f:%016lx", util::ConvertToBigEndian(thread_context.sp));
|
|
} else {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";1f:0*,");
|
|
}
|
|
|
|
if (thread_context.pc != 0) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";20:%016lx", util::ConvertToBigEndian(thread_context.pc));
|
|
} else {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";20:0*,");
|
|
}
|
|
}
|
|
|
|
/* Add the thread id. */
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";thread:p%lx.%lx", m_process_id.value, thread_id);
|
|
|
|
/* Add the thread core. */
|
|
{
|
|
u32 core = 0;
|
|
m_debug_process.GetThreadCurrentCore(std::addressof(core), thread_id);
|
|
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";core:%u;", core);
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::LoopProcess() {
|
|
/* Process packets. */
|
|
while (m_session.IsValid()) {
|
|
/* Receive a packet. */
|
|
bool do_break = false;
|
|
char recv_buf[GdbPacketBufferSize];
|
|
char *packet = this->ReceivePacket(std::addressof(do_break), recv_buf, sizeof(recv_buf));
|
|
|
|
if (!do_break && packet != nullptr) {
|
|
/* Process the packet. */
|
|
char reply_buffer[GdbPacketBufferSize];
|
|
this->ProcessPacket(packet, reply_buffer);
|
|
|
|
/* Send packet. */
|
|
this->SendPacket(std::addressof(do_break), reply_buffer);
|
|
}
|
|
|
|
/* If we should, break the process. */
|
|
if (do_break) {
|
|
m_debug_process.Break();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::ProcessPacket(char *receive, char *reply) {
|
|
/* Set our fields. */
|
|
m_receive_packet = receive;
|
|
m_reply_cur = reply;
|
|
m_reply_end = reply + GdbPacketBufferSize;
|
|
|
|
/* Log the packet we're processing. */
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Receive: %s\n", m_receive_packet);
|
|
|
|
/* Clear our reply packet. */
|
|
reply[0] = 0;
|
|
|
|
/* Handle the received packet. */
|
|
switch (m_receive_packet[0]) {
|
|
case 'D':
|
|
this->D();
|
|
break;
|
|
case 'G':
|
|
this->G();
|
|
break;
|
|
case 'H':
|
|
this->H();
|
|
break;
|
|
case 'M':
|
|
this->M();
|
|
break;
|
|
case 'P':
|
|
this->P();
|
|
break;
|
|
case 'Q':
|
|
this->Q();
|
|
break;
|
|
case 'T':
|
|
this->T();
|
|
break;
|
|
case 'Z':
|
|
this->Z();
|
|
break;
|
|
case 'c':
|
|
this->c();
|
|
break;
|
|
case 'k':
|
|
this->k();
|
|
break;
|
|
case 'g':
|
|
if (!this->g()) {
|
|
m_killed = true;
|
|
}
|
|
break;
|
|
case 'm':
|
|
this->m();
|
|
break;
|
|
case 'p':
|
|
this->p();
|
|
break;
|
|
case 'v':
|
|
this->v();
|
|
break;
|
|
case 'q':
|
|
this->q();
|
|
break;
|
|
case 'z':
|
|
this->z();
|
|
break;
|
|
case '!':
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
break;
|
|
case '?':
|
|
this->QuestionMark();
|
|
break;
|
|
default:
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented: %s\n", m_receive_packet);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::D() {
|
|
m_debug_process.Detach();
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
}
|
|
|
|
void GdbServerImpl::G() {
|
|
/* Get thread id. */
|
|
u32 thread_id = m_debug_process.GetThreadIdOverride();
|
|
if (thread_id == 0 || thread_id == static_cast<u32>(-1)) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
/* Get thread context. */
|
|
svc::ThreadContext ctx;
|
|
if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_All))) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Update the thread context. */
|
|
ParseGdbRegisterPacket(ctx, m_receive_packet, m_debug_process.Is64Bit());
|
|
|
|
/* Set the thread context. */
|
|
if (R_SUCCEEDED(m_debug_process.SetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_All))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::H() {
|
|
if (this->HasDebugProcess()) {
|
|
if (ParsePrefix(m_receive_packet, "Hg") || ParsePrefix(m_receive_packet, "HG")) {
|
|
this->Hg();
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::Hg() {
|
|
bool success = false;
|
|
s64 thread_id;
|
|
if (const char *dot = std::strchr(m_receive_packet, '.'); dot != nullptr) {
|
|
thread_id = std::strcmp(dot + 1, "-1") == 0 ? -1 : static_cast<s64>(DecodeHex(dot + 1));
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Set thread id = %lx\n", thread_id);
|
|
|
|
u64 thread_ids[DebugProcess::ThreadCountMax];
|
|
s32 num_threads;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) {
|
|
if (thread_id == 0) {
|
|
thread_id = thread_ids[0];
|
|
}
|
|
for (auto i = 0; i < num_threads; ++i) {
|
|
if (thread_id == -1 || static_cast<u64>(thread_id) == thread_ids[i]) {
|
|
svc::ThreadContext context;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(context), thread_ids[i], svc::ThreadContextFlag_Control))) {
|
|
success = true;
|
|
if (thread_id != -1) {
|
|
m_debug_process.SetThreadIdOverride(thread_ids[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (success) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::M() {
|
|
++m_receive_packet;
|
|
|
|
/* Validate format. */
|
|
char *comma = std::strchr(m_receive_packet, ',');
|
|
if (comma == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
*comma = 0;
|
|
|
|
char *colon = std::strchr(comma + 1, ':');
|
|
if (colon == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
*colon = 0;
|
|
|
|
/* Parse address/length. */
|
|
const u64 address = DecodeHex(m_receive_packet);
|
|
const u64 length = DecodeHex(comma + 1);
|
|
if (length >= sizeof(m_buffer)) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Decode the memory. */
|
|
HexToMemory(m_buffer, colon + 1, length);
|
|
|
|
/* Write the memory. */
|
|
if (R_SUCCEEDED(m_debug_process.WriteMemory(m_buffer, address, length))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::P() {
|
|
++m_receive_packet;
|
|
|
|
/* Validate format. */
|
|
char *equal = std::strchr(m_receive_packet, '=');
|
|
if (equal == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
*equal = 0;
|
|
|
|
/* Decode the register. */
|
|
const u64 reg_num = DecodeHex(m_receive_packet);
|
|
|
|
/* Get the flags. */
|
|
const u32 flags = RegisterToContextFlags(reg_num, m_debug_process.Is64Bit());
|
|
|
|
/* Determine thread id. */
|
|
u32 thread_id = m_debug_process.GetThreadIdOverride();
|
|
if (thread_id == 0 || thread_id == static_cast<u32>(-1)) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
/* Update the register. */
|
|
svc::ThreadContext ctx;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, flags))) {
|
|
ParseGdbRegisterPacket(ctx, equal + 1, reg_num, m_debug_process.Is64Bit());
|
|
|
|
if (R_SUCCEEDED(m_debug_process.SetThreadContext(std::addressof(ctx), thread_id, flags))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::Q() {
|
|
if (false) {
|
|
/* TODO: QStartNoAckMode? */
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented Q: %s\n", m_receive_packet);
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::T() {
|
|
if (const char *dot = std::strchr(m_receive_packet, '.'); dot != nullptr) {
|
|
const u64 thread_id = DecodeHex(dot + 1);
|
|
|
|
svc::ThreadContext ctx;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, svc::ThreadContextFlag_Control))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::Z() {
|
|
/* Increment past the 'Z'. */
|
|
++m_receive_packet;
|
|
|
|
/* Decode the type. */
|
|
if (!('0' <= m_receive_packet[0] && m_receive_packet[0] <= '4') || m_receive_packet[1] != ',') {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
const auto type = m_receive_packet[0] - '0';
|
|
m_receive_packet += 2;
|
|
|
|
/* Decode the address/length. */
|
|
const char *comma = std::strchr(m_receive_packet, ',');
|
|
if (comma == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Parse address/length. */
|
|
const u64 address = DecodeHex(m_receive_packet);
|
|
const u64 length = DecodeHex(comma + 1);
|
|
|
|
switch (type) {
|
|
case 0: /* SW */
|
|
{
|
|
if (length == 2 || length == 4) {
|
|
if (R_SUCCEEDED(m_debug_process.SetBreakPoint(address, length, false))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 1: /* HW */
|
|
{
|
|
if (length == 2 || length == 4) {
|
|
if (R_SUCCEEDED(m_debug_process.SetHardwareBreakPoint(address, length, false))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 2: /* Watch-W */
|
|
{
|
|
if (m_debug_process.IsValidWatchPoint(address, length)) {
|
|
if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, false, true))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 3: /* Watch-R */
|
|
{
|
|
if (m_debug_process.IsValidWatchPoint(address, length)) {
|
|
if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, true, false))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 4: /* Watch-A */
|
|
{
|
|
if (m_debug_process.IsValidWatchPoint(address, length)) {
|
|
if (R_SUCCEEDED(m_debug_process.SetWatchPoint(address, length, true, true))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::c() {
|
|
/* Get thread id. */
|
|
u64 thread_id = m_debug_process.GetThreadIdOverride();
|
|
if (thread_id == 0 || thread_id == static_cast<u64>(-1)) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
/* Continue the thread. */
|
|
Result result;
|
|
if (thread_id == m_debug_process.GetLastThreadId()) {
|
|
result = m_debug_process.Continue(thread_id);
|
|
} else {
|
|
result = m_debug_process.Continue();
|
|
}
|
|
|
|
if (R_SUCCEEDED(result)) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
bool GdbServerImpl::g() {
|
|
/* Get thread id. */
|
|
u64 thread_id = m_debug_process.GetThreadIdOverride();
|
|
if (thread_id == 0 || thread_id == static_cast<u64>(-1)) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
/* Get thread context. */
|
|
svc::ThreadContext thread_context;
|
|
if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(thread_context), thread_id, svc::ThreadContextFlag_All))) {
|
|
return false;
|
|
}
|
|
|
|
/* Populate reply packet. */
|
|
SetGdbRegisterPacket(m_reply_cur, m_reply_end, thread_context, m_debug_process.Is64Bit());
|
|
|
|
return true;
|
|
}
|
|
|
|
void GdbServerImpl::m() {
|
|
++m_receive_packet;
|
|
|
|
/* Validate format. */
|
|
const char *comma = std::strchr(m_receive_packet, ',');
|
|
if (comma == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Parse address/length. */
|
|
const u64 address = DecodeHex(m_receive_packet);
|
|
const u64 length = DecodeHex(comma + 1);
|
|
if (length >= sizeof(m_buffer)) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Read the memory. */
|
|
/* TODO: Detect partial readability? */
|
|
if (R_FAILED(m_debug_process.ReadMemory(m_buffer, address, length))) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Encode the memory. */
|
|
MemoryToHex(m_reply_cur, m_reply_end, m_buffer, length);
|
|
}
|
|
|
|
void GdbServerImpl::k() {
|
|
m_debug_process.Terminate();
|
|
m_killed = true;
|
|
}
|
|
|
|
void GdbServerImpl::p() {
|
|
++m_receive_packet;
|
|
|
|
/* Decode the register. */
|
|
const u64 reg_num = DecodeHex(m_receive_packet);
|
|
|
|
/* Get the flags. */
|
|
const u32 flags = RegisterToContextFlags(reg_num, m_debug_process.Is64Bit());
|
|
|
|
/* Determine thread id. */
|
|
u32 thread_id = m_debug_process.GetThreadIdOverride();
|
|
if (thread_id == 0 || thread_id == static_cast<u32>(-1)) {
|
|
thread_id = m_debug_process.GetLastThreadId();
|
|
}
|
|
|
|
/* Get the register. */
|
|
svc::ThreadContext ctx;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadContext(std::addressof(ctx), thread_id, flags))) {
|
|
SetGdbRegisterPacket(m_reply_cur, m_reply_end, ctx, reg_num, m_debug_process.Is64Bit());
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::v() {
|
|
if (ParsePrefix(m_receive_packet, "vAttach;")) {
|
|
this->vAttach();
|
|
} else if (ParsePrefix(m_receive_packet, "vCont")) {
|
|
this->vCont();
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented v: %s\n", m_receive_packet);
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::vAttach() {
|
|
if (!this->HasDebugProcess()) {
|
|
/* Get the process id. */
|
|
if (const u64 process_id = DecodeHex(m_receive_packet); process_id != 0) {
|
|
/* Set our process id. */
|
|
m_process_id = { process_id };
|
|
|
|
/* Wait for us to be attached. */
|
|
{
|
|
std::scoped_lock lk(g_event_request_lock);
|
|
g_event_request_cv.Signal();
|
|
if (!g_event_done_cv.TimedWait(g_event_request_lock, TimeSpan::FromSeconds(2))) {
|
|
m_event.Signal();
|
|
}
|
|
}
|
|
|
|
/* If we're attached, send a stop reply packet. */
|
|
if (m_debug_process.IsValid()) {
|
|
/* Set the stop reply packet. */
|
|
this->AppendStopReplyPacket(m_debug_process.GetLastSignal());
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::vCont() {
|
|
/* Check if this is a query about what we support. */
|
|
if (ParsePrefix(m_receive_packet, "?")) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "vCont;c;C;s;S;");
|
|
return;
|
|
}
|
|
|
|
/* We want to parse semicolon separated fields repeatedly. */
|
|
char *saved;
|
|
char *token = strtok_r(m_receive_packet, ";", std::addressof(saved));
|
|
|
|
/* Validate the initial token. */
|
|
if (token == nullptr) {
|
|
return;
|
|
}
|
|
|
|
/* Prepare to parse threads. */
|
|
u64 thread_ids[DebugProcess::ThreadCountMax] = {};
|
|
u8 continue_modes[DebugProcess::ThreadCountMax] = {};
|
|
|
|
s32 num_threads;
|
|
if (R_FAILED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) {
|
|
AMS_DMNT2_GDB_LOG_ERROR("vCont: Failed to get thread list\n");
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Handle each token. */
|
|
Result result = ResultSuccess();
|
|
DebugProcess::ContinueMode default_continue_mode = DebugProcess::ContinueMode_Stopped;
|
|
while (token != nullptr && R_SUCCEEDED(result)) {
|
|
result = this->ParseVCont(token, thread_ids, continue_modes, num_threads, default_continue_mode);
|
|
token = strtok_r(nullptr, ";", std::addressof(saved));
|
|
}
|
|
|
|
AMS_DMNT2_GDB_LOG_DEBUG("vCont: NumThreads=%d, Default Continue Mode=%d\n", num_threads, static_cast<int>(default_continue_mode));
|
|
|
|
/* Act on all threads. */
|
|
s64 thread_id = -1;
|
|
for (auto i = 0; i < num_threads; ++i) {
|
|
if (continue_modes[i] == DebugProcess::ContinueMode_Step || (continue_modes[i] == DebugProcess::ContinueMode_Stopped && default_continue_mode == DebugProcess::ContinueMode_Step)) {
|
|
thread_id = thread_ids[i];
|
|
result = m_debug_process.Step(thread_ids[i]);
|
|
}
|
|
}
|
|
|
|
/* Continue the last thread. */
|
|
if (static_cast<u64>(thread_id) == m_debug_process.GetLastThreadId() && default_continue_mode != DebugProcess::ContinueMode_Continue) {
|
|
result = m_debug_process.Continue(thread_id);
|
|
} else {
|
|
result = m_debug_process.Continue();
|
|
}
|
|
|
|
/* Set reply. */
|
|
if (R_SUCCEEDED(result)) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_ERROR("vCont: Failed %08x\n", result.GetValue());
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
Result GdbServerImpl::ParseVCont(char * const token, u64 *thread_ids, u8 *continue_modes, s32 num_threads, DebugProcess::ContinueMode &default_continue_mode) {
|
|
/* Parse the thread id. */
|
|
s64 thread_id = -1;
|
|
s32 signal = -1;
|
|
s32 thread_ix = -1;
|
|
|
|
if (token[0] && token[1]) {
|
|
if (char *colon = std::strchr(token, ':'); colon != nullptr) {
|
|
*colon = 0;
|
|
if (char *dot = std::strchr(colon + 1, '.'); dot != nullptr) {
|
|
*dot = 0;
|
|
thread_id = std::strcmp(dot + 1, "-1") == 0 ? -1 : static_cast<s64>(DecodeHex(dot + 1));
|
|
thread_ix = FindThreadIdIndex(thread_ids, num_threads, static_cast<u64>(thread_id));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check that we don't already have a default mode. */
|
|
if (thread_id == -1 && default_continue_mode != DebugProcess::ContinueMode_Stopped) {
|
|
AMS_DMNT2_GDB_LOG_ERROR("vCont: Too many defaults specified\n");
|
|
}
|
|
|
|
/* Handle the action. */
|
|
switch (token[0]) {
|
|
case 'c':
|
|
if (thread_id > 0) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("vCont: Continue %lx\n", static_cast<u64>(thread_id));
|
|
continue_modes[thread_ix] = DebugProcess::ContinueMode_Continue;
|
|
} else {
|
|
default_continue_mode = DebugProcess::ContinueMode_Continue;
|
|
}
|
|
break;
|
|
case 'C':
|
|
if (token[1]) {
|
|
signal = std::strcmp(token + 1, "-1") == 0 ? -1 : static_cast<s32>(DecodeHex(token + 1));
|
|
}
|
|
AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring C, signal=%d\n", signal);
|
|
if (thread_id > 0) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("vCont: Continue %lx, signal=%d\n", static_cast<u64>(thread_id), signal);
|
|
continue_modes[thread_ix] = DebugProcess::ContinueMode_Continue;
|
|
} else {
|
|
default_continue_mode = DebugProcess::ContinueMode_Continue;
|
|
}
|
|
break;
|
|
case 's':
|
|
if (thread_id > 0) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("vCont: Step %lx\n", static_cast<u64>(thread_id));
|
|
continue_modes[thread_ix] = DebugProcess::ContinueMode_Step;
|
|
} else {
|
|
default_continue_mode = DebugProcess::ContinueMode_Step;
|
|
}
|
|
break;
|
|
case 'S':
|
|
if (token[1]) {
|
|
signal = std::strcmp(token + 1, "-1") == 0 ? -1 : static_cast<s32>(DecodeHex(token + 1));
|
|
}
|
|
AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring S, signal=%d\n", signal);
|
|
if (thread_id > 0) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("vCont: Step %lx, signal=%d\n", static_cast<u64>(thread_id), signal);
|
|
continue_modes[thread_ix] = DebugProcess::ContinueMode_Step;
|
|
} else {
|
|
default_continue_mode = DebugProcess::ContinueMode_Step;
|
|
}
|
|
break;
|
|
default:
|
|
AMS_DMNT2_GDB_LOG_WARN("vCont: Ignoring %c\n", token[0]);
|
|
break;
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
void GdbServerImpl::q() {
|
|
if (ParsePrefix(m_receive_packet, "qAttached:")) {
|
|
this->qAttached();
|
|
} else if (ParsePrefix(m_receive_packet, "qC")) {
|
|
this->qC();
|
|
} else if (ParsePrefix(m_receive_packet, "qRcmd,")) {
|
|
this->qRcmd();
|
|
} else if (ParsePrefix(m_receive_packet, "qSupported:")) {
|
|
this->qSupported();
|
|
} else if (ParsePrefix(m_receive_packet, "qXfer:")) {
|
|
this->qXfer();
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented q: %s\n", m_receive_packet);
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qAttached() {
|
|
if (this->HasDebugProcess()) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "1");
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qC() {
|
|
if (this->HasDebugProcess()) {
|
|
/* Send the thread id. */
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "QCp%lx.%lx", m_process_id.value, m_debug_process.GetLastThreadId());
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qRcmd() {
|
|
/* Decode the command. */
|
|
const auto packet_len = std::strlen(m_receive_packet);
|
|
HexToMemory(m_buffer, m_receive_packet, packet_len);
|
|
m_buffer[packet_len / 2] = '\x00';
|
|
|
|
/* Convert our response to hex, on complete. */
|
|
ON_SCOPE_EXIT {
|
|
/* Convert response to hex. */
|
|
MemoryToHex(m_reply_cur, m_reply_end, m_buffer, std::strlen(m_buffer));
|
|
};
|
|
|
|
char *reply_cur = m_buffer;
|
|
char *reply_end = m_buffer + sizeof(m_buffer);
|
|
|
|
/* Parse the command. */
|
|
char *command = reinterpret_cast<char *>(m_buffer);
|
|
if (ParsePrefix(command, "help")) {
|
|
AppendReplyFormat(reply_cur, reply_end, "get info\n"
|
|
"get mappings\n"
|
|
"get mappings {address}\n"
|
|
"get mapping {address}\n"
|
|
"wait application\n"
|
|
"wait {program id}\n"
|
|
"wait homebrew\n");
|
|
} else if (ParsePrefix(command, "get base") || ParsePrefix(command, "get info") || ParsePrefix(command, "get modules")) {
|
|
if (!this->HasDebugProcess()) {
|
|
AppendReplyFormat(reply_cur, reply_end, "Not attached.\n");
|
|
return;
|
|
}
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, "Process: 0x%lx (%s)\n"
|
|
"Program Id: 0x%016lx\n"
|
|
"Application: %d\n"
|
|
"Hbl: %d\n"
|
|
"Layout:\n", m_process_id.value, m_debug_process.GetProcessName(), m_debug_process.GetProgramLocation().program_id.value, m_debug_process.IsApplication(), m_debug_process.GetOverrideStatus().IsHbl());
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, " Alias: 0x%010lx - 0x%010lx\n"
|
|
" Heap: 0x%010lx - 0x%010lx\n"
|
|
" Aslr: 0x%010lx - 0x%010lx\n"
|
|
" Stack: 0x%010lx - 0x%010lx\n"
|
|
"Modules:\n", m_debug_process.GetAliasRegionAddress(), m_debug_process.GetAliasRegionAddress() + m_debug_process.GetAliasRegionSize() - 1,
|
|
m_debug_process.GetHeapRegionAddress(), m_debug_process.GetHeapRegionAddress() + m_debug_process.GetHeapRegionSize() - 1,
|
|
m_debug_process.GetAslrRegionAddress(), m_debug_process.GetAslrRegionAddress() + m_debug_process.GetAslrRegionSize() - 1,
|
|
m_debug_process.GetStackRegionAddress(), m_debug_process.GetStackRegionAddress() + m_debug_process.GetStackRegionSize() - 1);
|
|
|
|
/* Get the module list. */
|
|
for (size_t i = 0; i < m_debug_process.GetModuleCount(); ++i) {
|
|
const char *module_name = m_debug_process.GetModuleName(i);
|
|
const auto name_len = std::strlen(module_name);
|
|
if (name_len < 5 || (std::strcmp(module_name + name_len - 4, ".elf") != 0 && std::strcmp(module_name + name_len - 4, ".nss") != 0)) {
|
|
AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s.elf\n", m_debug_process.GetModuleBaseAddress(i), m_debug_process.GetModuleBaseAddress(i) + m_debug_process.GetModuleSize(i) - 1, module_name);
|
|
} else {
|
|
AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s\n", m_debug_process.GetModuleBaseAddress(i), m_debug_process.GetModuleBaseAddress(i) + m_debug_process.GetModuleSize(i) - 1, module_name);
|
|
}
|
|
}
|
|
} else if (ParsePrefix(command, "get mappings")) {
|
|
if (!this->HasDebugProcess()) {
|
|
AppendReplyFormat(reply_cur, reply_end, "Not attached.\n");
|
|
return;
|
|
}
|
|
|
|
uintptr_t cur_addr = 0;
|
|
if (ParsePrefix(command, " ")) {
|
|
ParsePrefix(command, "0x");
|
|
|
|
cur_addr = DecodeHex(command);
|
|
}
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, "Mappings (starting from 0x%010lx):\n", cur_addr);
|
|
|
|
while (true) {
|
|
/* Check if we have room for a mapping. */
|
|
if (reply_end - reply_cur < 0x48 * 2) {
|
|
AppendReplyFormat(reply_cur, reply_end, "<-- Send `monitor get mappings 0x%010lx` to continue -->\n", cur_addr);
|
|
break;
|
|
}
|
|
|
|
/* Get mapping. */
|
|
svc::MemoryInfo mem_info;
|
|
if (R_FAILED(m_debug_process.QueryMemory(std::addressof(mem_info), cur_addr))) {
|
|
break;
|
|
}
|
|
|
|
if (mem_info.state != svc::MemoryState_Inaccessible || mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) {
|
|
const char *state = GetMemoryStateName(mem_info.state);
|
|
const char *perm = GetMemoryPermissionString(mem_info);
|
|
|
|
const char l = (mem_info.attribute & svc::MemoryAttribute_Locked) ? 'L' : '-';
|
|
const char i = (mem_info.attribute & svc::MemoryAttribute_IpcLocked) ? 'I' : '-';
|
|
const char d = (mem_info.attribute & svc::MemoryAttribute_DeviceShared) ? 'D' : '-';
|
|
const char u = (mem_info.attribute & svc::MemoryAttribute_Uncached) ? 'U' : '-';
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, " 0x%010lx - 0x%010lx %s %s %c%c%c%c [%d, %d]\n", mem_info.base_address, mem_info.base_address + mem_info.size - 1, perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count);
|
|
}
|
|
|
|
/* Advance. */
|
|
const uintptr_t next_address = mem_info.base_address + mem_info.size;
|
|
if (next_address <= cur_addr) {
|
|
break;
|
|
}
|
|
|
|
cur_addr = next_address;
|
|
}
|
|
} else if (ParsePrefix(command, "get mapping ")) {
|
|
if (!this->HasDebugProcess()) {
|
|
AppendReplyFormat(reply_cur, reply_end, "Not attached.\n");
|
|
return;
|
|
}
|
|
|
|
/* Allow optional "0x" prefix. */
|
|
ParsePrefix(command, "0x");
|
|
|
|
/* Decode address. */
|
|
const u64 address = DecodeHex(command);
|
|
|
|
/* Get mapping. */
|
|
svc::MemoryInfo mem_info;
|
|
if (R_FAILED(m_debug_process.QueryMemory(std::addressof(mem_info), address))) {
|
|
AppendReplyFormat(reply_cur, reply_end, "0x%016lx: No mapping.\n", address);
|
|
}
|
|
|
|
const char *state = GetMemoryStateName(mem_info.state);
|
|
const char *perm = GetMemoryPermissionString(mem_info);
|
|
|
|
const char l = (mem_info.attribute & svc::MemoryAttribute_Locked) ? 'L' : '-';
|
|
const char i = (mem_info.attribute & svc::MemoryAttribute_IpcLocked) ? 'I' : '-';
|
|
const char d = (mem_info.attribute & svc::MemoryAttribute_DeviceShared) ? 'D' : '-';
|
|
const char u = (mem_info.attribute & svc::MemoryAttribute_Uncached) ? 'U' : '-';
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, "0x%010lx - 0x%010lx %s %s %c%c%c%c [%d, %d]\n", mem_info.base_address, mem_info.base_address + mem_info.size - 1, perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count);
|
|
} else if (ParsePrefix(command, "wait application") || ParsePrefix(command, "wait app")) {
|
|
/* Wait for an application process. */
|
|
{
|
|
/* Get hook to creation of application process. */
|
|
os::NativeHandle h;
|
|
R_ABORT_UNLESS(pm::dmnt::HookToCreateApplicationProcess(std::addressof(h)));
|
|
|
|
/* Wait for event. */
|
|
os::SystemEvent hook_event(h, true, os::InvalidNativeHandle, false, os::EventClearMode_AutoClear);
|
|
hook_event.Wait();
|
|
}
|
|
|
|
/* Get application process id. */
|
|
R_ABORT_UNLESS(pm::dmnt::GetApplicationProcessId(std::addressof(m_wait_process_id)));
|
|
|
|
/* Note that we're attaching. */
|
|
AppendReplyFormat(reply_cur, reply_end, "Send `attach 0x%lx` to attach.\n", m_wait_process_id.value);
|
|
} else if (ParsePrefix(command, "wait ")) {
|
|
/* Allow optional "0x" prefix. */
|
|
ParsePrefix(command, "0x");
|
|
|
|
/* Decode program id. */
|
|
const u64 program_id = DecodeHex(command);
|
|
|
|
AppendReplyFormat(reply_cur, reply_end, "[TODO] wait for program id 0x%lx\n", program_id);
|
|
} else {
|
|
std::memcpy(m_reply_cur, command, std::strlen(command) + 1);
|
|
AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur);
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qSupported() {
|
|
/* Current string from devkita64-none-elf-gdb: */
|
|
/* qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+ */
|
|
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "PacketSize=%lx", GdbPacketBufferSize - 1);
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";multiprocess+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:osdata:read+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:features:read+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:libraries:read+");
|
|
// TODO: AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:libraries-svr4:read+");
|
|
// TODO: AppendReplyFormat(m_reply_cur, m_reply_end, ";augmented-libraries-svr4-read+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:threads:read+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";qXfer:exec-file:read+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";swbreak+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";hwbreak+");
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, ";vContSupported+");
|
|
}
|
|
|
|
void GdbServerImpl::qXfer() {
|
|
/* Check for osdata. */
|
|
if (ParsePrefix(m_receive_packet, "osdata:read:")) {
|
|
this->qXferOsdataRead();
|
|
} else {
|
|
/* All other qXfer require debug process. */
|
|
if (!this->HasDebugProcess()) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Process. */
|
|
if (ParsePrefix(m_receive_packet, "features:read:")) {
|
|
this->qXferFeaturesRead();
|
|
} else if (ParsePrefix(m_receive_packet, "threads:read::")) {
|
|
if (!this->qXferThreadsRead()) {
|
|
m_killed = true;
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
} else if (ParsePrefix(m_receive_packet, "libraries:read::")) {
|
|
this->qXferLibrariesRead();
|
|
} else if (ParsePrefix(m_receive_packet, "exec-file:read:")) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "l%s", m_debug_process.GetProcessName());
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer: %s\n", m_receive_packet);
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qXferFeaturesRead() {
|
|
/* Handle the qXfer. */
|
|
u32 offset, length;
|
|
|
|
if (ParsePrefix(m_receive_packet, "target.xml:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Send the desired xml. */
|
|
std::strncpy(m_reply_cur, (this->Is64Bit() ? TargetXmlAarch64 : TargetXmlAarch32) + offset, length);
|
|
m_reply_cur[length] = 0;
|
|
m_reply_cur += std::strlen(m_reply_cur);
|
|
} else if (ParsePrefix(m_receive_packet, "aarch64-core.xml:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Send the desired xml. */
|
|
std::strncpy(m_reply_cur, Aarch64CoreXml + offset, length);
|
|
m_reply_cur[length] = 0;
|
|
m_reply_cur += std::strlen(m_reply_cur);
|
|
} else if (ParsePrefix(m_receive_packet, "aarch64-fpu.xml:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Send the desired xml. */
|
|
std::strncpy(m_reply_cur, Aarch64FpuXml + offset, length);
|
|
m_reply_cur[length] = 0;
|
|
m_reply_cur += std::strlen(m_reply_cur);
|
|
} else if (ParsePrefix(m_receive_packet, "arm-core.xml:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Send the desired xml. */
|
|
std::strncpy(m_reply_cur, ArmCoreXml + offset, length);
|
|
m_reply_cur[length] = 0;
|
|
m_reply_cur += std::strlen(m_reply_cur);
|
|
} else if (ParsePrefix(m_receive_packet, "arm-vfp.xml:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Send the desired xml. */
|
|
std::strncpy(m_reply_cur, ArmVfpXml + offset, length);
|
|
m_reply_cur[length] = 0;
|
|
m_reply_cur += std::strlen(m_reply_cur);
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet);
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::qXferLibrariesRead() {
|
|
/* Handle the qXfer. */
|
|
u32 offset, length;
|
|
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Acquire access to the annex buffer. */
|
|
std::scoped_lock lk(g_annex_buffer_lock);
|
|
|
|
/* If doing a fresh read, generate the module list. */
|
|
if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Libraries) {
|
|
/* Prepare to write to annex buffer. */
|
|
char *dst_cur = g_annex_buffer;
|
|
char *dst_end = g_annex_buffer + sizeof(g_annex_buffer);
|
|
|
|
/* Set header. */
|
|
AppendReplyFormat(dst_cur, dst_end, "<library-list>");
|
|
|
|
/* Get the module list. */
|
|
for (size_t i = 0; i < m_debug_process.GetModuleCount(); ++i) {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Module[%zu]: %p, %s\n", i, reinterpret_cast<void *>(m_debug_process.GetModuleBaseAddress(i)), m_debug_process.GetModuleName(i));
|
|
|
|
const char *module_name = m_debug_process.GetModuleName(i);
|
|
const auto name_len = std::strlen(module_name);
|
|
if (name_len < 5 || (std::strcmp(module_name + name_len - 4, ".elf") != 0 && std::strcmp(module_name + name_len - 4, ".nss") != 0)) {
|
|
AppendReplyFormat(dst_cur, dst_end, "<library name=\"%s.elf\"><segment address=\"0x%lx\" /></library>", module_name, m_debug_process.GetModuleBaseAddress(i));
|
|
} else {
|
|
AppendReplyFormat(dst_cur, dst_end, "<library name=\"%s\"><segment address=\"0x%lx\" /></library>", module_name, m_debug_process.GetModuleBaseAddress(i));
|
|
}
|
|
}
|
|
|
|
AppendReplyFormat(dst_cur, dst_end, "</library-list>");
|
|
|
|
g_annex_buffer_contents = AnnexBufferContents_Libraries;
|
|
}
|
|
|
|
/* Copy out the module list. */
|
|
GetAnnexBufferContents(m_reply_cur, offset, length);
|
|
}
|
|
|
|
void GdbServerImpl::qXferOsdataRead() {
|
|
/* Handle the qXfer. */
|
|
u32 offset, length;
|
|
|
|
if (ParsePrefix(m_receive_packet, "processes:")) {
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Acquire access to the annex buffer. */
|
|
std::scoped_lock lk(g_annex_buffer_lock);
|
|
|
|
/* If doing a fresh read, generate the process list. */
|
|
if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Processes) {
|
|
/* Prepare to write to annex buffer. */
|
|
char *dst_cur = g_annex_buffer;
|
|
char *dst_end = g_annex_buffer + sizeof(g_annex_buffer);
|
|
|
|
/* Clear the process list buffer. */
|
|
dst_cur[0] = 0;
|
|
|
|
/* Set header. */
|
|
AppendReplyFormat(dst_cur, dst_end, "<?xml version=\"1.0\"?>\n<!DOCTYPE target SYSTEM \"osdata.dtd\">\n<osdata type=\"processes\">\n");
|
|
|
|
/* Get all processes. */
|
|
{
|
|
/* Get all process ids. */
|
|
u64 process_ids[0x50];
|
|
s32 num_process_ids;
|
|
R_ABORT_UNLESS(svc::GetProcessList(std::addressof(num_process_ids), process_ids, util::size(process_ids)));
|
|
|
|
/* Send all processes. */
|
|
for (s32 i = 0; i < num_process_ids; ++i) {
|
|
svc::Handle handle;
|
|
if (R_SUCCEEDED(svc::DebugActiveProcess(std::addressof(handle), process_ids[i]))) {
|
|
ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(handle)); };
|
|
|
|
/* Get the create process event. */
|
|
svc::DebugEventInfo d;
|
|
R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), handle));
|
|
AMS_ABORT_UNLESS(d.type == svc::DebugEvent_CreateProcess);
|
|
|
|
AppendReplyFormat(dst_cur, dst_end, "<item>\n<column name=\"pid\">%lu</column>\n<column name=\"command\">%s</column>\n</item>\n", d.info.create_process.process_id, d.info.create_process.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set footer. */
|
|
AppendReplyFormat(dst_cur, dst_end, "</osdata>");
|
|
|
|
g_annex_buffer_contents = AnnexBufferContents_Processes;
|
|
}
|
|
|
|
/* Copy out the process list. */
|
|
GetAnnexBufferContents(m_reply_cur, offset, length);
|
|
} else {
|
|
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet);
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
|
|
bool GdbServerImpl::qXferThreadsRead() {
|
|
/* Handle the qXfer. */
|
|
u32 offset, length;
|
|
|
|
/* Parse offset/length. */
|
|
ParseOffsetLength(m_receive_packet, offset, length);
|
|
|
|
/* Acquire access to the annex buffer. */
|
|
std::scoped_lock lk(g_annex_buffer_lock);
|
|
|
|
/* If doing a fresh read, generate the thread list. */
|
|
if (offset == 0 || g_annex_buffer_contents != AnnexBufferContents_Threads) {
|
|
/* Prepare to write to annex buffer. */
|
|
char *dst_cur = g_annex_buffer;
|
|
char *dst_end = g_annex_buffer + sizeof(g_annex_buffer);
|
|
|
|
/* Set header. */
|
|
AppendReplyFormat(dst_cur, dst_end, "<threads>");
|
|
|
|
/* Get the thread list. */
|
|
u64 thread_ids[DebugProcess::ThreadCountMax];
|
|
s32 num_threads;
|
|
if (R_SUCCEEDED(m_debug_process.GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids)))) {
|
|
for (auto i = 0; i < num_threads; ++i) {
|
|
/* Check that we can get the thread context. */
|
|
{
|
|
svc::ThreadContext dummy_context;
|
|
if (R_FAILED(m_debug_process.GetThreadContext(std::addressof(dummy_context), thread_ids[i], svc::ThreadContextFlag_All))) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Get the thread core. */
|
|
u32 core = 0;
|
|
m_debug_process.GetThreadCurrentCore(std::addressof(core), thread_ids[i]);
|
|
|
|
/* Get the thread name. */
|
|
char name[os::ThreadNameLengthMax + 1];
|
|
m_debug_process.GetThreadName(name, thread_ids[i]);
|
|
name[sizeof(name) - 1] = '\x00';
|
|
|
|
/* Sanitize the thread name. */
|
|
char sanitized[os::ThreadNameLengthMax * 4 + 1];
|
|
{
|
|
char *cur = sanitized;
|
|
for (size_t i = 0; i < util::size(name); ++i) {
|
|
if (name[i] == '<') {
|
|
*(cur++) = '&';
|
|
*(cur++) = 'l';
|
|
*(cur++) = 't';
|
|
*(cur++) = ';';
|
|
} else if (name[i] == '>') {
|
|
*(cur++) = '&';
|
|
*(cur++) = 'g';
|
|
*(cur++) = 't';
|
|
*(cur++) = ';';
|
|
} else if (name[i] == '*' || name[i] == '#' || name[i] == '$' || name[i] == '}') {
|
|
*(cur++) = '}';
|
|
*(cur++) = name[i] ^ 0x20;
|
|
} else {
|
|
*(cur++) = name[i];
|
|
|
|
if (name[i] == '\x00') {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Set the thread entry */
|
|
AppendReplyFormat(dst_cur, dst_end, "<thread id=\"p%lx.%lx\" core=\"%u\" name=\"%s\" />", m_process_id.value, thread_ids[i], core, sanitized);
|
|
}
|
|
}
|
|
|
|
AppendReplyFormat(dst_cur, dst_end, "</threads>");
|
|
|
|
g_annex_buffer_contents = AnnexBufferContents_Threads;
|
|
}
|
|
|
|
/* Copy out the threads list. */
|
|
GetAnnexBufferContents(m_reply_cur, offset, length);
|
|
|
|
return true;
|
|
}
|
|
|
|
void GdbServerImpl::z() {
|
|
/* Increment past the 'z'. */
|
|
++m_receive_packet;
|
|
|
|
/* Decode the type. */
|
|
if (!('0' <= m_receive_packet[0] && m_receive_packet[0] <= '4') || m_receive_packet[1] != ',') {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
const auto type = m_receive_packet[0] - '0';
|
|
m_receive_packet += 2;
|
|
|
|
/* Decode the address/length. */
|
|
const char *comma = std::strchr(m_receive_packet, ',');
|
|
if (comma == nullptr) {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
return;
|
|
}
|
|
|
|
/* Parse address/length. */
|
|
const u64 address = DecodeHex(m_receive_packet);
|
|
const u64 length = DecodeHex(comma + 1);
|
|
|
|
switch (type) {
|
|
case 0: /* SW */
|
|
{
|
|
if (R_SUCCEEDED(m_debug_process.ClearBreakPoint(address, length))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
break;
|
|
case 1: /* HW */
|
|
{
|
|
if (R_SUCCEEDED(m_debug_process.ClearHardwareBreakPoint(address, length))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
break;
|
|
case 2: /* Watch-W */
|
|
case 3: /* Watch-R */
|
|
case 4: /* Watch-A */
|
|
{
|
|
if (R_SUCCEEDED(m_debug_process.ClearWatchPoint(address, length))) {
|
|
AppendReplyOk(m_reply_cur, m_reply_end);
|
|
} else {
|
|
AppendReplyError(m_reply_cur, m_reply_end, "E01");
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void GdbServerImpl::QuestionMark() {
|
|
if (m_debug_process.IsValid()) {
|
|
if (m_debug_process.GetLastThreadId() == 0) {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "X01");
|
|
} else {
|
|
this->AppendStopReplyPacket(m_debug_process.GetLastSignal());
|
|
}
|
|
} else {
|
|
AppendReplyFormat(m_reply_cur, m_reply_end, "W00");
|
|
}
|
|
}
|
|
|
|
}
|