Newer
Older
Atmosphere / stratosphere / dmnt.gen2 / source / dmnt2_gdb_server_impl.cpp
@Michael Scire Michael Scire on 12 Sep 2021 41 KB dmnt: add attach support to gdbstub
/*
 * Copyright (c) 2018-2020 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 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>"
            "<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";

        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 SetReplyOk(char *reply) {
            std::strcpy(reply, "OK");
        }

        void SetReplyError(char *reply, const char *err) {
            AMS_DMNT2_GDB_LOG_ERROR("Reply Error: %s\n", err);
            std::strcpy(reply, err);
        }

        void SetReply(char *reply, const char *fmt, ...) __attribute__((format(printf, 2, 3)));

        void SetReply(char *reply, const char *fmt, ...) {
            std::va_list vl;
            va_start(vl, fmt);
            util::VSNPrintf(reply, GdbPacketBufferSize, fmt, vl);
            va_end(vl);
        }

        void AppendReply(char *reply, const char *fmt, ...) __attribute__((format(printf, 2, 3)));

        void AppendReply(char *reply, const char *fmt, ...) {
            const auto len = std::strlen(reply);

            std::va_list vl;
            va_start(vl, fmt);
            util::VSNPrintf(reply + len, GdbPacketBufferSize - len, 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, const void *mem, size_t size) {
            const u8 *mem_u8 = static_cast<const u8 *>(mem);

            while (size-- > 0) {
                const u8 v = *(mem_u8++);
                *(dst++) = "0123456789abcdef"[v >> 4];
                *(dst++) = "0123456789abcdef"[v & 0xF];
            }
            *dst = 0;
        }

        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);
        }

        constinit char g_process_list_buffer[0x2000];

        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_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 (m_attached) {
            R_ABORT_UNLESS(svc::CloseHandle(m_debug_handle));
        }
    }

    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;
                }

                /* Set ourselves as unattached. */
                m_attached = false;

                /* If we have a process id, attach. */
                if (R_FAILED(svc::DebugActiveProcess(std::addressof(m_debug_handle), m_process_id.value))) {
                    AMS_DMNT2_GDB_LOG_DEBUG("Failed to attach to %016lx\n", m_process_id.value);
                    m_debug_handle = svc::InvalidHandle;
                    g_event_done_cv.Signal();
                    continue;
                }

                /* Get the process id. */
                if (R_FAILED(svc::GetProcessId(std::addressof(m_process_id.value), m_debug_handle))) {
                    AMS_DMNT2_GDB_LOG_DEBUG("Failed to get process id for debug handle\n");
                    R_ABORT_UNLESS(svc::CloseHandle(m_debug_handle));
                    m_debug_handle = svc::InvalidHandle;
                    g_event_done_cv.Signal();
                    continue;
                }

                /* Set ourselves as attached. */
                m_attached = true;

                /* Get the create process event. */
                {
                    svc::DebugEventInfo d;
                    R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), m_debug_handle));
                    AMS_ABORT_UNLESS(d.type == svc::DebugEvent_CreateProcess);

                    m_create_process_info = d.info.create_process;
                }

                /* 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 ids. */
                m_process_id        = os::InvalidProcessId;
                m_target_process_id = os::InvalidProcessId;
            }
        }

        /* 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);

        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;
                return svc::WaitSynchronization(std::addressof(dummy), std::addressof(m_debug_handle), 1, TimeSpan::FromMilliSeconds(20).GetNanoSeconds());
            }();

            /* Check if we're killed. */
            if (m_killed) {
                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(svc::GetDebugEvent(std::addressof(d), m_debug_handle))) {
                continue;
            }

            /* Process the event. */
            switch (d.type) {
                default:
                    AMS_DMNT2_GDB_LOG_DEBUG("Unhandled ProcessEvent %u\n", static_cast<u32>(d.type));
                    break;
            }
        }
    }

    void GdbServerImpl::SetStopReplyPacket(GdbSignal signal) {
        /* Set the signal. */
        SetReply(m_reply_packet, "T%02X", static_cast<u32>(signal));

        /* Get the thread context. */
        svc::ThreadContext thread_context = {};
        svc::GetDebugThreadContext(std::addressof(thread_context), m_debug_handle, m_thread_id, svc::ThreadContextFlag_General | svc::ThreadContextFlag_Control);

        /* Add important registers. */
        /* TODO: aarch32 */
        {
            if (thread_context.fp != 0) {
                AppendReply(m_reply_packet, "1d:%016lx", util::ConvertToBigEndian(thread_context.fp));
            } else {
                AppendReply(m_reply_packet, "1d:0*,");
            }

            if (thread_context.sp != 0) {
                AppendReply(m_reply_packet, ";1f:%016lx", util::ConvertToBigEndian(thread_context.sp));
            } else {
                AppendReply(m_reply_packet, ";1f:0*,");
            }

            if (thread_context.pc != 0) {
                AppendReply(m_reply_packet, ";20:%016lx", util::ConvertToBigEndian(thread_context.pc));
            } else {
                AppendReply(m_reply_packet, ";20:0*,");
            }
        }

        /* Add the thread id. */
        AppendReply(m_reply_packet, ";thread:p%lx.%lx", m_target_process_id.value, m_thread_id);

        /* Add the thread core. */
        {
            u64 dummy;
            u32 core = 0;
            svc::GetDebugThreadParam(std::addressof(dummy), std::addressof(core), m_debug_handle, m_thread_id, svc::DebugThreadParam_CurrentCore);

            AppendReply(m_reply_packet, ";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);
            }
        }
    }

    void GdbServerImpl::ProcessPacket(char *receive, char *reply) {
        /* Set our fields. */
        m_receive_packet = receive;
        m_reply_packet   = reply;

        /* Log the packet we're processing. */
        AMS_DMNT2_GDB_LOG_DEBUG("Receive: %s\n", m_receive_packet);

        /* Clear our reply packet. */
        m_reply_packet[0] = 0;

        /* Handle the received packet. */
        switch (m_receive_packet[0]) {
            case 'H':
                this->H();
                break;
            case 'g':
                if (!this->g()) {
                    m_killed = true;
                }
                break;
            case 'm':
                this->m();
                break;
            case 'v':
                this->v();
                break;
            case 'q':
                this->q();
                break;
            case '!':
                SetReplyOk(m_reply_packet);
                break;
            case '?':
                this->QuestionMark();
                break;
            default:
                AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented: %s\n", m_receive_packet);
                break;
        }
    }

    void GdbServerImpl::H() {
        if (this->HasDebugProcess()) {
            if (ParsePrefix(m_receive_packet, "Hg") || ParsePrefix(m_receive_packet, "HG")) {
                this->Hg();
            } else {
                SetReplyError(m_reply_packet, "E01");
            }
        } else {
            SetReplyError(m_reply_packet, "E01");
        }
    }

    void GdbServerImpl::Hg() {
        if (const char *dot = std::strchr(m_receive_packet, '.'); dot != nullptr) {
            m_thread_id = DecodeHex(dot + 1);
            AMS_DMNT2_GDB_LOG_DEBUG("Set thread id = %lx\n", m_thread_id);

            /* TODO: Validate thread exists for the process? */
        }

        SetReplyOk(m_reply_packet);
    }

    bool GdbServerImpl::g() {
        /* TODO: aarch32 */

        /* Get thread context. */
        svc::ThreadContext thread_context;
        if (R_FAILED(svc::GetDebugThreadContext(std::addressof(thread_context), m_debug_handle, m_thread_id, svc::ThreadContextFlag_All))) {
            return false;
        }

        /* Copy general purpose registers. */
        for (size_t i = 0; i < util::size(thread_context.r); ++i) {
            if (thread_context.r[i] != 0) {
                AppendReply(m_reply_packet, "%016lx", util::ConvertToBigEndian(thread_context.r[i]));
            } else {
                AppendReply(m_reply_packet, "0*,");
            }
        }

        /* Copy special registers. */
        if (thread_context.fp != 0) {
            AppendReply(m_reply_packet, "%016lx", util::ConvertToBigEndian(thread_context.fp));
        } else {
            AppendReply(m_reply_packet, "0*,");
        }

        if (thread_context.lr != 0) {
            AppendReply(m_reply_packet, "%016lx", util::ConvertToBigEndian(thread_context.lr));
        } else {
            AppendReply(m_reply_packet, "0*,");
        }

        if (thread_context.sp != 0) {
            AppendReply(m_reply_packet, "%016lx", util::ConvertToBigEndian(thread_context.sp));
        } else {
            AppendReply(m_reply_packet, "0*,");
        }

        if (thread_context.pc != 0) {
            AppendReply(m_reply_packet, "%016lx", util::ConvertToBigEndian(thread_context.pc));
        } else {
            AppendReply(m_reply_packet, "0*,");
        }

        if (thread_context.pstate != 0) {
            AppendReply(m_reply_packet, "%08x", util::ConvertToBigEndian(thread_context.pstate));
        } else {
            AppendReply(m_reply_packet, "0*\"0");
        }

        /* Copy FPU registers. */
        for (size_t i = 0; i < util::size(thread_context.v); ++i) {
            if (thread_context.v[i] != 0) {
                AppendReply(m_reply_packet, "%016lx%016lx", util::ConvertToBigEndian(static_cast<u64>(thread_context.v[i] >> 0)), util::ConvertToBigEndian(static_cast<u64>(thread_context.v[i] >> BITSIZEOF(u64))));
            } else {
                AppendReply(m_reply_packet, "0*<");
            }
        }

        return true;
    }

    void GdbServerImpl::m() {
        ++m_receive_packet;

        /* Validate format. */
        const char *comma = std::strchr(m_receive_packet, ',');
        if (comma == nullptr) {
            SetReplyError(m_reply_packet, "E01");
            return;
        }

        /* Parse address/length. */
        const u64 address = DecodeHex(m_receive_packet);
        const u64 length  = DecodeHex(comma + 1);
        if (length >= sizeof(m_buffer)) {
            SetReplyError(m_reply_packet, "E01");
            return;
        }

        /* Read the memory. */
        /* TODO: Detect partial readability? */
        if (R_FAILED(svc::ReadDebugProcessMemory(reinterpret_cast<uintptr_t>(m_buffer), m_debug_handle, address, length))) {
            SetReplyError(m_reply_packet, "E01");
            return;
        }

        /* Encode the memory. */
        MemoryToHex(m_reply_packet, m_buffer, length);
    }

    void GdbServerImpl::v() {
        if (ParsePrefix(m_receive_packet, "vAttach;")) {
            this->vAttach();
        } 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 };
                m_target_process_id = { process_id };

                /* Wait for us to be attached. */
                bool 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();
                    }

                    attached = m_attached;
                }

                /* If we're attached, send a stop reply packet. */
                if (attached) {
                    /* Set our initial thread id. */
                    {
                        s32 dummy;
                        u64 thread_id = 0;
                        R_ABORT_UNLESS(svc::GetThreadList(std::addressof(dummy), std::addressof(thread_id), 1, m_debug_handle));

                        m_thread_id = thread_id;
                    }

                    /* Set the stop reply packet. */
                    this->SetStopReplyPacket(GdbSignal_Signal0);
                } else {
                    SetReplyError(m_reply_packet, "E01");
                }
            } else {
                SetReplyError(m_reply_packet, "E01");
            }
        } else {
            SetReplyError(m_reply_packet, "E01");
        }
    }

    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, "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()) {
            /* Parse/Save the requested process id */
            if (const char *colon = std::strchr(m_receive_packet, ':'); colon != nullptr) {
                m_target_process_id.value = DecodeHex(colon + 1);
                AMS_DMNT2_GDB_LOG_DEBUG("Queried for attached to %016lx\n", m_target_process_id.value);
            }
            SetReply(m_reply_packet, "1");
        } else {
            SetReplyError(m_reply_packet, "E01");
        }
    }

    void GdbServerImpl::qC() {
        if (this->HasDebugProcess()) {
            /* Potentially get our thread id. */
            if (m_thread_id == 0) {
                s32 dummy;
                u64 thread_id = 0;
                if (R_FAILED(svc::GetThreadList(std::addressof(dummy), std::addressof(thread_id), 1, m_debug_handle))) {
                    SetReplyError(m_reply_packet, "E01");
                    return;
                }

                m_thread_id = thread_id;
            }

            /* Send the thread id. */
            SetReply(m_reply_packet, "QCp%lx.%lx", m_target_process_id.value, m_thread_id);
        } else {
            SetReplyError(m_reply_packet, "E01");
        }
    }

    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+ */

        SetReply(m_reply_packet, "PacketSize=%lx", GdbPacketBufferSize - 1);
        AppendReply(m_reply_packet, ";multiprocess+");
        AppendReply(m_reply_packet, ";qXfer:osdata:read+");
        AppendReply(m_reply_packet, ";qXfer:features:read+");
        AppendReply(m_reply_packet, ";qXfer:libraries-svr4:read+");
        AppendReply(m_reply_packet, ";augmented-libraries-svr4-read+");
        AppendReply(m_reply_packet, ";qXfer:threads:read+");
        AppendReply(m_reply_packet, ";swbreak+");
        AppendReply(m_reply_packet, ";hwbreak+");
        AppendReply(m_reply_packet, ";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()) {
                SetReplyError(m_reply_packet, "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;
                    SetReplyError(m_reply_packet, "E01");
                }
            } else {
                AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer: %s\n", m_receive_packet);
                SetReplyError(m_reply_packet, "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. */
            /* TODO: Detection of debug-process as 64 bit or not. */
            std::strncpy(m_reply_packet, (this->Is64Bit() ? TargetXmlAarch64 : TargetXmlAarch32) + offset, length);
            m_reply_packet[length] = 0;
            m_reply_packet += std::strlen(m_reply_packet);
        } 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_packet, Aarch64CoreXml + offset, length);
            m_reply_packet[length] = 0;
            m_reply_packet += std::strlen(m_reply_packet);
        } 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_packet, Aarch64FpuXml + offset, length);
            m_reply_packet[length] = 0;
            m_reply_packet += std::strlen(m_reply_packet);
        }  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_packet, ArmCoreXml + offset, length);
            m_reply_packet[length] = 0;
            m_reply_packet += std::strlen(m_reply_packet);
        } 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_packet, ArmVfpXml + offset, length);
            m_reply_packet[length] = 0;
            m_reply_packet += std::strlen(m_reply_packet);
        } else {
            AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet);
            SetReplyError(m_reply_packet, "E01");
        }
    }

    void GdbServerImpl::qXferOsdataRead() {
        /* Handle the qXfer. */
        u32 offset, length;

        if (ParsePrefix(m_receive_packet, "processes:")) {
            /* Parse offset/length. */
            ParseOffsetLength(m_receive_packet, offset, length);

            /* If doing a fresh read, generate the process list. */
            if (offset == 0) {
                /* Clear the process list buffer. */
                g_process_list_buffer[0] = 0;

                /* Set header. */
                SetReply(g_process_list_buffer, "<?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);

                            AppendReply(g_process_list_buffer, "<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. */
                AppendReply(g_process_list_buffer, "</osdata>");
            }

            /* Copy out the process list. */
            const u32 annex_len = std::strlen(g_process_list_buffer);
            if (offset <= annex_len) {
                if (offset + length < annex_len) {
                    m_reply_packet[0] = 'm';
                    std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, length);
                    m_reply_packet[1 + length] = 0;
                } else {
                    const auto size = annex_len - offset;

                    m_reply_packet[0] = 'l';
                    std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, size);
                    m_reply_packet[1 + size] = 0;
                }
            } else {
                SetReply(m_reply_packet, "l");
            }
        } else {
            AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet);
            SetReplyError(m_reply_packet, "E01");
        }
    }

    bool GdbServerImpl::qXferThreadsRead() {
        /* Set header. */
        SetReply(m_reply_packet, "l<threads>");

        /* Get thread list. */
        s32 num_threads;
        u64 thread_ids[0x80];
        if (R_FAILED(svc::GetThreadList(std::addressof(num_threads), thread_ids, util::size(thread_ids), m_debug_handle))) {
            return false;
        }

        /* Cap thread limit at 128. */
        if (num_threads > 0x80) {
            num_threads = 0x80;
        }

        /* Add all threads. */
        for (s32 i = 0; i < num_threads; ++i) {
            const auto thread_id = thread_ids[num_threads - 1 - i];

            /* Check that we can get the thread context. */
            svc::ThreadContext thread_context;
            if (R_FAILED(svc::GetDebugThreadContext(std::addressof(thread_context), m_debug_handle, thread_id, svc::ThreadContextFlag_All))) {
                continue;
            }

            /* Get the thread core. */
            u32 core = 0;
            {
                u64 dummy;
                u32 val32;
                if (R_SUCCEEDED(svc::GetDebugThreadParam(std::addressof(dummy), std::addressof(val32), m_debug_handle, m_thread_id, svc::DebugThreadParam_CurrentCore))) {
                    core = val32;
                }
            }

            /* TODO: `name=\"%s\"`? */
            AppendReply(m_reply_packet, "<thread id=\"p%lx.%lx\" core=\"%u\"/>", m_target_process_id.value, thread_id, core);
        }

        AppendReply(m_reply_packet, "</threads>");

        return true;
    }

    void GdbServerImpl::QuestionMark() {
        /* TODO */
        AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented QuestionMark\n");
        SetReply(m_reply_packet, "W00");
    }

}