/* * 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" "GNU/Linux" "" "" ""; 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; } } void ParseOffsetLength(const char *packet, u32 &offset, u32 &length) { /* Default to zero. */ offset = 0; length = 0; bool parsed_offset = false; while (*packet) { const char c = *(packet++); if (c == ',') { parsed_offset = true; } else if (auto hex = DecodeHex(c); hex >= 0) { if (parsed_offset) { length <<= 4; length |= hex; } else { offset <<= 4; offset |= hex; } } } AMS_DMNT2_GDB_LOG_DEBUG("Offset/Length %x/%x\n", offset, length); } constinit char g_process_list_buffer[0x2000]; } GdbServerImpl::GdbServerImpl(int socket) : m_socket(socket), m_session(socket), m_packet_io() { /* ... */ } GdbServerImpl::~GdbServerImpl() { /* ... */ } 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 '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()) { /* TODO */ 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()) { /* TODO: Parse/Save the requested process id */ SetReply(m_reply_packet, "1"); } else { SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::qC() { if (this->HasDebugProcess()) { /* TODO */ SetReplyError(m_reply_packet, "E01"); } 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:threads:read+"); AppendReply(m_reply_packet, ";swbreak+"); AppendReply(m_reply_packet, ";hwbreak+"); } 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 { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "E01"); } } } void GdbServerImpl::qXferFeaturesRead() { /* Handle the qXfer. */ u32 offset, length; if (ParsePrefix(m_receive_packet, "target.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ /* TODO: Detection of debug-process as 64 bit or not. */ std::strncpy(m_reply_packet, (this->Is64Bit() ? TargetXmlAarch64 : TargetXmlAarch32) + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "aarch64-core.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, Aarch64CoreXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "aarch64-fpu.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, Aarch64FpuXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "arm-core.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, ArmCoreXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else if (ParsePrefix(m_receive_packet, "arm-vfp.xml:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* Send the desired xml. */ std::strncpy(m_reply_packet, ArmVfpXml + offset, length); m_reply_packet[length] = 0; m_reply_packet += std::strlen(m_reply_packet); } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::qXferOsdataRead() { /* Handle the qXfer. */ u32 offset, length; if (ParsePrefix(m_receive_packet, "processes:")) { /* Parse offset/length. */ ParseOffsetLength(m_receive_packet, offset, length); /* If doing a fresh read, generate the process list. */ if (offset == 0) { /* Clear the process list buffer. */ g_process_list_buffer[0] = 0; /* Set header. */ SetReply(g_process_list_buffer, "\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; while (true) { R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), handle)); if (d.type == svc::DebugEvent_CreateProcess) { AppendReply(g_process_list_buffer, "\n%lu\n%s\n\n", d.info.create_process.process_id, d.info.create_process.name); break; } } } } } /* Set footer. */ AppendReply(g_process_list_buffer, ""); } /* Copy out the process list. */ const u32 annex_len = std::strlen(g_process_list_buffer); if (offset <= annex_len) { if (offset + length < annex_len) { m_reply_packet[0] = 'm'; std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, length); m_reply_packet[1 + length] = 0; } else { const auto size = annex_len - offset; m_reply_packet[0] = 'l'; std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, size); m_reply_packet[1 + size] = 0; } } else { SetReply(m_reply_packet, "l"); } } else { AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet); SetReplyError(m_reply_packet, "E01"); } } void GdbServerImpl::QuestionMark() { /* TODO */ AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented QuestionMark\n"); SetReply(m_reply_packet, "W00"); } }