/*
 * 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 .
 */
#include 
#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"
            ""
            ""
            "aarch64"
            ""
            ""
            "";
        constexpr const char Aarch64CoreXml[] =
            "l\n"
            "\n"
            "\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\n"
            "\t\t\n"
            "\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "";
        constexpr const char Aarch64FpuXml[] =
            "l\n"
            "\n"
            "\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "";
        constexpr const char TargetXmlAarch32[] =
            "l"
            ""
            ""
            "arm"
            ""
            ""
            "";
        constexpr const char ArmCoreXml[] =
            "l\n"
            "\n"
            "\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\n";
        constexpr const char ArmVfpXml[] =
            "l\n"
            "\n"
            "\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\t\n"
            "\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(state) < util::size(MemoryStateNames)) {
                return MemoryStateNames[static_cast(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(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(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(value >> 0)), util::ConvertToBigEndian(static_cast(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 requires std::unsigned_integral
        util::optional 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 ParseGdbRegister32(const char *&src)   { return ParseGdbRegister(src); }
        ALWAYS_INLINE util::optional ParseGdbRegister64(const char *&src)   { return ParseGdbRegister(src); }
        ALWAYS_INLINE util::optional ParseGdbRegister128(const char *&src) { return ParseGdbRegister(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(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(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(-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(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(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(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(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(address), insn);
                                    }
                                    if (signal == GdbSignal_BreakpointTrap && !is_new_hb_nro) {
                                        AppendReplyFormat(reply_cur, reply_end, "T%02Xthread:p%lx.%lx;swbreak:;", static_cast(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(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(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(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(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(-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(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(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(-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(-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(-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(-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(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(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(DecodeHex(dot + 1));
                    thread_ix = FindThreadIdIndex(thread_ids, num_threads, static_cast(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(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(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(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(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(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(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(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::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, "");
            /* 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(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, "", module_name, m_debug_process.GetModuleBaseAddress(i));
                } else {
                    AppendReplyFormat(dst_cur, dst_end, "", module_name, m_debug_process.GetModuleBaseAddress(i));
                }
            }
            AppendReplyFormat(dst_cur, dst_end, "");
            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, "\n\n\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, "- \n%lu\n%s\n\n", d.info.create_process.process_id, d.info.create_process.name);
                        }
                    }
                }
                /* Set footer. */
                AppendReplyFormat(dst_cur, dst_end, "");
                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, "");
            /* 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, "", m_process_id.value, thread_ids[i], core, sanitized);
                }
            }
            AppendReplyFormat(dst_cur, dst_end, "");
            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");
        }
    }
}