/* * Copyright (c) 2018-2020 Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include "dmnt2_debug_log.hpp" #include "dmnt2_gdb_server_impl.hpp" namespace ams::dmnt { namespace { 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" "" "" "" "" ""; 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"; bool ParsePrefix(char *&packet, const char *prefix) { const auto len = std::strlen(prefix); if (std::strncmp(packet, prefix, len) == 0) { packet += len; return true; } else { return false; } } void SetReplyOk(char *reply) { std::strcpy(reply, "OK"); } void SetReplyError(char *reply, const char *err) { AMS_DMNT2_GDB_LOG_ERROR("Reply Error: %s\n", err); std::strcpy(reply, err); } void SetReply(char *reply, const char *fmt, ...) __attribute__((format(printf, 2, 3))); void SetReply(char *reply, const char *fmt, ...) { std::va_list vl; va_start(vl, fmt); util::VSNPrintf(reply, GdbPacketBufferSize, fmt, vl); va_end(vl); } void AppendReply(char *reply, const char *fmt, ...) __attribute__((format(printf, 2, 3))); void AppendReply(char *reply, const char *fmt, ...) { const auto len = std::strlen(reply); std::va_list vl; va_start(vl, fmt); util::VSNPrintf(reply + len, GdbPacketBufferSize - len, fmt, vl); va_end(vl); } constexpr int DecodeHex(char c) { if ('a' <= c && c <= 'f') { return 10 + (c - 'a'); } else if ('A' <= c && c <= 'F') { return 10 + (c - 'A'); } else if ('0' <= c && c <= '9') { return 0 + (c - '0'); } else { return -1; } } constexpr u64 DecodeHex(const char *s) { u64 value = 0; while (true) { const char c = *(s++); if (int v = DecodeHex(c); v >= 0) { value <<= 4; value |= v & 0xF; } else { break; } } return value; } void MemoryToHex(char *dst, const void *mem, size_t size) { const u8 *mem_u8 = static_cast(mem); while (size-- > 0) { const u8 v = *(mem_u8++); *(dst++) = "0123456789abcdef"[v >> 4]; *(dst++) = "0123456789abcdef"[v & 0xF]; } *dst = 0; } void ParseOffsetLength(const char *packet, u32 &offset, u32 &length) { /* Default to zero. */ offset = 0; length = 0; bool parsed_offset = false; while (*packet) { const char c = *(packet++); if (c == ',') { parsed_offset = true; } else if (auto hex = DecodeHex(c); hex >= 0) { if (parsed_offset) { length <<= 4; length |= hex; } else { offset <<= 4; offset |= hex; } } } AMS_DMNT2_GDB_LOG_DEBUG("Offset/Length %x/%x\n", offset, length); } void SetGdbRegister32(char *dst, u32 value) { if (value != 0) { AppendReply(dst, "%08x", util::ConvertToBigEndian(value)); } else { AppendReply(dst, "0*\"00"); } } void SetGdbRegister64(char *dst, u64 value) { if (value != 0) { AppendReply(dst, "%016lx", util::ConvertToBigEndian(value)); } else { AppendReply(dst, "0*,"); } } void SetGdbRegister128(char *dst, u128 value) { if (value != 0) { AppendReply(dst, "%016lx%016lx", util::ConvertToBigEndian(static_cast(value >> 0)), util::ConvertToBigEndian(static_cast(value >> BITSIZEOF(u64)))); } else { AppendReply(dst, "0*<"); } } void SetGdbRegisterPacket(char *dst, 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, thread_context.r[i]); } /* Copy special registers. */ SetGdbRegister64(dst, thread_context.fp); SetGdbRegister64(dst, thread_context.lr); SetGdbRegister64(dst, thread_context.sp); SetGdbRegister64(dst, thread_context.pc); SetGdbRegister32(dst, thread_context.pstate); /* Copy FPU registers. */ for (size_t i = 0; i < util::size(thread_context.v); ++i) { SetGdbRegister128(dst, thread_context.v[i]); } SetGdbRegister32(dst, thread_context.fpsr); SetGdbRegister32(dst, thread_context.fpcr); } else { /* Copy general purpose registers. */ for (size_t i = 0; i < 15; ++i) { SetGdbRegister32(dst, thread_context.r[i]); } /* Copy special registers. */ SetGdbRegister32(dst, thread_context.pc); SetGdbRegister32(dst, thread_context.pstate); /* Copy FPU registers. */ for (size_t i = 0; i < util::size(thread_context.v); ++i) { SetGdbRegister128(dst, thread_context.v[i]); } const u32 fpscr = (thread_context.fpsr & 0xF80000FF) | (thread_context.fpcr & 0x07FFFF00); SetGdbRegister32(dst, fpscr); } } constinit os::SdkMutex g_annex_buffer_lock; constinit char g_annex_buffer[0x8000]; 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; } else { const auto size = annex_len - offset; dst[0] = 'l'; std::memcpy(dst + 1, g_annex_buffer + offset, size); dst[1 + size] = 0; } } else { dst[0] = '1'; dst[1] = 0; } } 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(); /* If we have a process id, attach. */ if (R_FAILED(m_debug_process.Attach(m_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); 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(); 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. */ switch (d.type) { default: AMS_DMNT2_GDB_LOG_DEBUG("Unhandled ProcessEvent %u\n", static_cast(d.type)); break; } } } void GdbServerImpl::SetStopReplyPacket(GdbSignal signal) { /* Set the signal. */ SetReply(m_reply_packet, "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) { AppendReply(m_reply_packet, "1d:%016lx", util::ConvertToBigEndian(thread_context.fp)); } else { AppendReply(m_reply_packet, "1d:0*,"); } if (thread_context.sp != 0) { AppendReply(m_reply_packet, ";1f:%016lx", util::ConvertToBigEndian(thread_context.sp)); } else { AppendReply(m_reply_packet, ";1f:0*,"); } if (thread_context.pc != 0) { AppendReply(m_reply_packet, ";20:%016lx", util::ConvertToBigEndian(thread_context.pc)); } else { AppendReply(m_reply_packet, ";20:0*,"); } } /* Add the thread id. */ AppendReply(m_reply_packet, ";thread:p%lx.%lx", m_process_id.value, thread_id); /* Add the thread core. */ { u32 core = 0; m_debug_process.GetThreadCurrentCore(std::addressof(core), thread_id); AppendReply(m_reply_packet, ";core:%u;", core); } } void GdbServerImpl::LoopProcess() { /* Process packets. */ while (m_session.IsValid()) { /* Receive a packet. */ bool do_break = false; char recv_buf[GdbPacketBufferSize]; char *packet = this->ReceivePacket(std::addressof(do_break), recv_buf, sizeof(recv_buf)); if (!do_break && packet != nullptr) { /* Process the packet. */ char reply_buffer[GdbPacketBufferSize]; this->ProcessPacket(packet, reply_buffer); /* Send packet. */ this->SendPacket(std::addressof(do_break), reply_buffer); } } } void GdbServerImpl::ProcessPacket(char *receive, char *reply) { /* Set our fields. */ m_receive_packet = receive; m_reply_packet = reply; /* Log the packet we're processing. */ AMS_DMNT2_GDB_LOG_DEBUG("Receive: %s\n", m_receive_packet); /* Clear our reply packet. */ m_reply_packet[0] = 0; /* Handle the received packet. */ switch (m_receive_packet[0]) { case 'H': this->H(); break; case 'g': if (!this->g()) { m_killed = true; } break; case 'm': this->m(); break; case 'v': this->v(); break; case 'q': this->q(); break; case '!': SetReplyOk(m_reply_packet); break; case '?': this->QuestionMark(); break; default: AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented: %s\n", m_receive_packet); break; } } void GdbServerImpl::H() { if (this->HasDebugProcess()) { if (ParsePrefix(m_receive_packet, "Hg") || ParsePrefix(m_receive_packet, "HG")) { this->Hg(); } else { SetReplyError(m_reply_packet, "E01"); } } else { SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::Hg() { 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) { SetReplyOk(m_reply_packet); } else { SetReplyError(m_reply_packet, "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_packet, 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) { SetReplyError(m_reply_packet, "E01"); return; } /* Parse address/length. */ const u64 address = DecodeHex(m_receive_packet); const u64 length = DecodeHex(comma + 1); if (length >= sizeof(m_buffer)) { SetReplyError(m_reply_packet, "E01"); return; } /* Read the memory. */ /* TODO: Detect partial readability? */ if (R_FAILED(m_debug_process.ReadMemory(m_buffer, address, length))) { SetReplyError(m_reply_packet, "E01"); return; } /* Encode the memory. */ MemoryToHex(m_reply_packet, m_buffer, length); } void GdbServerImpl::v() { if (ParsePrefix(m_receive_packet, "vAttach;")) { this->vAttach(); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented v: %s\n", m_receive_packet); } } void GdbServerImpl::vAttach() { if (!this->HasDebugProcess()) { /* Get the process id. */ if (const u64 process_id = DecodeHex(m_receive_packet); process_id != 0) { /* Set our process id. */ m_process_id = { process_id }; /* 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->SetStopReplyPacket(m_debug_process.GetLastSignal()); } else { SetReplyError(m_reply_packet, "E01"); } } else { SetReplyError(m_reply_packet, "E01"); } } else { SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::q() { if (ParsePrefix(m_receive_packet, "qAttached:")) { this->qAttached(); } else if (ParsePrefix(m_receive_packet, "qC")) { this->qC(); } else if (ParsePrefix(m_receive_packet, "qSupported:")) { this->qSupported(); } else if (ParsePrefix(m_receive_packet, "qXfer:")) { this->qXfer(); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented q: %s\n", m_receive_packet); } } void GdbServerImpl::qAttached() { if (this->HasDebugProcess()) { SetReply(m_reply_packet, "1"); } else { SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::qC() { if (this->HasDebugProcess()) { /* Send the thread id. */ SetReply(m_reply_packet, "QCp%lx.%lx", m_process_id.value, m_debug_process.GetLastThreadId()); } else { SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::qSupported() { /* Current string from devkita64-none-elf-gdb: */ /* qSupported:multiprocess+;swbreak+;hwbreak+;qRelocInsn+;fork-events+;vfork-events+;exec-events+;vContSupported+;QThreadEvents+;no-resumed+ */ SetReply(m_reply_packet, "PacketSize=%lx", GdbPacketBufferSize - 1); AppendReply(m_reply_packet, ";multiprocess+"); AppendReply(m_reply_packet, ";qXfer:osdata:read+"); AppendReply(m_reply_packet, ";qXfer:features:read+"); AppendReply(m_reply_packet, ";qXfer:libraries:read+"); AppendReply(m_reply_packet, ";qXfer:libraries-svr4:read+"); AppendReply(m_reply_packet, ";augmented-libraries-svr4-read+"); AppendReply(m_reply_packet, ";qXfer:threads:read+"); AppendReply(m_reply_packet, ";qXfer:exec-file:read+"); AppendReply(m_reply_packet, ";swbreak+"); AppendReply(m_reply_packet, ";hwbreak+"); AppendReply(m_reply_packet, ";vContSupported+"); } void GdbServerImpl::qXfer() { /* Check for osdata. */ if (ParsePrefix(m_receive_packet, "osdata:read:")) { this->qXferOsdataRead(); } else { /* All other qXfer require debug process. */ if (!this->HasDebugProcess()) { SetReplyError(m_reply_packet, "E01"); return; } /* Process. */ if (ParsePrefix(m_receive_packet, "features:read:")) { this->qXferFeaturesRead(); } else if (ParsePrefix(m_receive_packet, "threads:read::")) { if (!this->qXferThreadsRead()) { m_killed = true; SetReplyError(m_reply_packet, "E01"); } } else if (ParsePrefix(m_receive_packet, "libraries:read::")) { this->qXferLibrariesRead(); } else if (ParsePrefix(m_receive_packet, "exec-file:read:")) { SetReply(m_reply_packet, "l%s", m_debug_process.GetProcessName()); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "E01"); } } } void GdbServerImpl::qXferFeaturesRead() { /* Handle the qXfer. */ u32 offset, length; if (ParsePrefix(m_receive_packet, "target.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, (this->Is64Bit() ? TargetXmlAarch64 : TargetXmlAarch32) + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "aarch64-core.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, Aarch64CoreXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "aarch64-fpu.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, Aarch64FpuXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "arm-core.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, ArmCoreXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "arm-vfp.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, ArmVfpXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::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) { /* Set header. */ SetReply(g_annex_buffer, ""); /* 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.GetBaseAddress(i)), m_debug_process.GetModuleName(i)); AppendReply(g_annex_buffer, "", m_debug_process.GetModuleName(i), m_debug_process.GetBaseAddress(i)); } AppendReply(g_annex_buffer, ""); g_annex_buffer_contents = AnnexBufferContents_Libraries; } /* Copy out the module list. */ GetAnnexBufferContents(m_reply_packet, 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) { /* Clear the process list buffer. */ g_annex_buffer[0] = 0; /* Set header. */ SetReply(g_annex_buffer, "\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); AppendReply(g_annex_buffer, "\n%lu\n%s\n\n", d.info.create_process.process_id, d.info.create_process.name); } } } /* Set footer. */ AppendReply(g_annex_buffer, ""); g_annex_buffer_contents = AnnexBufferContents_Processes; } /* Copy out the process list. */ GetAnnexBufferContents(m_reply_packet, offset, length); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "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) { /* Set header. */ SetReply(g_annex_buffer, ""); /* 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]); /* TODO: `name=\"%s\"`? */ AppendReply(g_annex_buffer, "", m_process_id.value, thread_ids[i], core); } } AppendReply(g_annex_buffer, ""); g_annex_buffer_contents = AnnexBufferContents_Threads; } /* Copy out the threads list. */ GetAnnexBufferContents(m_reply_packet, offset, length); return true; } void GdbServerImpl::QuestionMark() { if (m_debug_process.IsValid()) { if (m_debug_process.GetLastThreadId() == 0) { SetReply(m_reply_packet, "X01"); } else { this->SetStopReplyPacket(m_debug_process.GetLastSignal()); } } else { SetReply(m_reply_packet, "W00"); } } }