dmnt: refactor/add support for getting process list in gdb

This commit is contained in:
Michael Scire 2021-07-17 06:27:40 -07:00 committed by SciresM
parent a7f9729f63
commit 649a0052d0
5 changed files with 255 additions and 107 deletions

View file

@ -99,5 +99,12 @@
}, {
"type": "handle_table_size",
"value": 0
}]
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}]
}

View file

@ -1,42 +0,0 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
namespace ams::dmnt {
class GdbPacketParser {
private:
char *m_receive_packet;
char *m_reply_packet;
char m_buffer[GdbPacketBufferSize / 2];
bool m_64_bit;
bool m_no_ack;
public:
GdbPacketParser(char *recv, char *reply, bool is_64_bit) : m_receive_packet(recv), m_reply_packet(reply), m_64_bit(is_64_bit), m_no_ack(false) { /* ... */ }
void Process();
bool Is64Bit() const { return m_64_bit; }
private:
void q();
void qSupported();
void qXferFeaturesRead();
};
}

View file

@ -14,10 +14,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
#include "dmnt2_gdb_packet_parser.hpp"
#include "dmnt2_gdb_server.hpp"
#include "dmnt2_debug_log.hpp"
#include "dmnt2_gdb_server.hpp"
#include "dmnt2_gdb_server_impl.hpp"
namespace ams::dmnt {
@ -25,43 +24,13 @@ namespace ams::dmnt {
constexpr size_t ServerThreadStackSize = util::AlignUp(4 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment);
alignas(os::ThreadStackAlignment) constinit u8 g_server_thread32_stack[ServerThreadStackSize];
alignas(os::ThreadStackAlignment) constinit u8 g_server_thread64_stack[ServerThreadStackSize];
alignas(os::ThreadStackAlignment) constinit u8 g_server_thread_stack[ServerThreadStackSize];
constinit os::ThreadType g_server_thread32;
constinit os::ThreadType g_server_thread64;
constinit os::ThreadType g_server_thread;
constinit util::TypedStorage<HtcsSession> g_session32;
constinit util::TypedStorage<HtcsSession> g_session64;
constinit util::TypedStorage<GdbServerImpl> g_gdb_server;
void ProcessForHtcsSession(HtcsSession *session, bool is_64_bit) {
/* Create packet io handler. */
GdbPacketIo packet_io;
/* Process packets. */
while (session->IsValid()) {
/* Receive a packet. */
bool do_break = false;
char recv_buf[GdbPacketBufferSize];
char *packet = packet_io.ReceivePacket(std::addressof(do_break), recv_buf, sizeof(recv_buf), session);
if (!do_break && packet != nullptr) {
/* Create a packet parser. */
char reply_buffer[GdbPacketBufferSize];
GdbPacketParser parser(packet, reply_buffer, is_64_bit);
/* Process the packet. */
parser.Process();
/* Send packet. */
packet_io.SendPacket(std::addressof(do_break), reply_buffer, session);
}
}
}
template<bool Is64Bit>
void GdbServerThreadFunction() {
void GdbServerThreadFunction(void *) {
/* Loop forever, servicing our gdb server. */
while (true) {
/* Get a socket. */
@ -80,7 +49,7 @@ namespace ams::dmnt {
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
std::strcpy(addr.port_name.name, Is64Bit ? "iywys@$gdb-aarch64" : "iywys@$gdb-aarch32");
std::strcpy(addr.port_name.name, "iywys@$gdb");
/* Bind. */
if (htcs::Bind(fd, std::addressof(addr)) == -1) {
@ -97,13 +66,14 @@ namespace ams::dmnt {
break;
}
/* Create htcs session for the socket. */
auto &session_storage = Is64Bit ? g_session64 : g_session32;
util::ConstructAt(session_storage, client_fd);
ON_SCOPE_EXIT { util::DestroyAt(session_storage); };
{
/* Create gdb server for the socket. */
util::ConstructAt(g_gdb_server, client_fd);
ON_SCOPE_EXIT { util::DestroyAt(g_gdb_server); };
/* Process for the session. */
ProcessForHtcsSession(util::GetPointer(session_storage), Is64Bit);
/* Process for the server. */
util::GetReference(g_gdb_server).LoopProcess();
}
/* Close the client socket. */
htcs::Close(client_fd);
@ -112,22 +82,12 @@ namespace ams::dmnt {
}
}
void GdbServerThreadFunction64(void *) {
GdbServerThreadFunction<true>();
}
void GdbServerThreadFunction32(void *) {
GdbServerThreadFunction<false>();
}
}
void InitializeGdbServer() {
/* Create and start gdb server threads. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_server_thread64), GdbServerThreadFunction64, nullptr, g_server_thread64_stack, sizeof(g_server_thread64_stack), os::HighestThreadPriority - 1));
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_server_thread32), GdbServerThreadFunction32, nullptr, g_server_thread32_stack, sizeof(g_server_thread32_stack), os::HighestThreadPriority - 1));
os::StartThread(std::addressof(g_server_thread64));
os::StartThread(std::addressof(g_server_thread32));
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_server_thread), GdbServerThreadFunction, nullptr, g_server_thread_stack, sizeof(g_server_thread_stack), os::HighestThreadPriority - 1));
os::StartThread(std::addressof(g_server_thread));
}
}

View file

@ -15,7 +15,7 @@
*/
#include <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#include "dmnt2_gdb_packet_parser.hpp"
#include "dmnt2_gdb_server_impl.hpp"
namespace ams::dmnt {
@ -323,9 +323,38 @@ namespace ams::dmnt {
AMS_DMNT2_GDB_LOG_DEBUG("Offset/Length %x/%x\n", offset, length);
}
constinit char g_process_list_buffer[0x2000];
}
void GdbPacketParser::Process() {
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);
@ -334,29 +363,66 @@ namespace ams::dmnt {
/* 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 GdbPacketParser::q() {
if (ParsePrefix(m_receive_packet, "qSupported:")) {
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:features:read:")) {
this->qXferFeaturesRead();
} else if (ParsePrefix(m_receive_packet, "qXfer:")) {
this->qXfer();
} else {
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented q: %s\n", m_receive_packet);
}
}
void GdbPacketParser::qSupported() {
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+ */
@ -370,7 +436,29 @@ namespace ams::dmnt {
AppendReply(m_reply_packet, ";hwbreak+");
}
void GdbPacketParser::qXferFeaturesRead() {
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:")) {
@ -378,6 +466,7 @@ namespace ams::dmnt {
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);
@ -415,7 +504,83 @@ namespace ams::dmnt {
m_reply_packet += std::strlen(m_reply_packet);
} else {
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:features:read: %s\n", m_receive_packet);
SetReplyError(m_reply_packet, "E01");
}
}
void GdbServerImpl::qXferOsdataRead() {
/* Handle the qXfer. */
u32 offset, length;
if (ParsePrefix(m_receive_packet, "processes:")) {
/* Parse offset/length. */
ParseOffsetLength(m_receive_packet, offset, length);
/* If doing a fresh read, generate the process list. */
if (offset == 0) {
/* Clear the process list buffer. */
g_process_list_buffer[0] = 0;
/* Set header. */
SetReply(g_process_list_buffer, "<?xml version=\"1.0\"?>\n<!DOCTYPE target SYSTEM \"osdata.dtd\">\n<osdata type=\"processes\">\n");
/* Get all processes. */
{
/* Get all process ids. */
u64 process_ids[0x50];
s32 num_process_ids;
R_ABORT_UNLESS(svc::GetProcessList(std::addressof(num_process_ids), process_ids, util::size(process_ids)));
/* Send all processes. */
for (s32 i = 0; i < num_process_ids; ++i) {
svc::Handle handle;
if (R_SUCCEEDED(svc::DebugActiveProcess(std::addressof(handle), process_ids[i]))) {
ON_SCOPE_EXIT { R_ABORT_UNLESS(svc::CloseHandle(handle)); };
/* Get the create process event. */
svc::DebugEventInfo d;
while (true) {
R_ABORT_UNLESS(svc::GetDebugEvent(std::addressof(d), handle));
if (d.type == svc::DebugEvent_CreateProcess) {
AppendReply(g_process_list_buffer, "<item>\n<column name=\"pid\">%lu</column>\n<column name=\"command\">%s</column>\n</item>\n", d.info.create_process.process_id, d.info.create_process.name);
break;
}
}
}
}
}
/* Set footer. */
AppendReply(g_process_list_buffer, "</osdata>");
}
/* Copy out the process list. */
const u32 annex_len = std::strlen(g_process_list_buffer);
if (offset <= annex_len) {
if (offset + length < annex_len) {
m_reply_packet[0] = 'm';
std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, length);
m_reply_packet[1 + length] = 0;
} else {
const auto size = annex_len - offset;
m_reply_packet[0] = 'l';
std::memcpy(m_reply_packet + 1, g_process_list_buffer + offset, size);
m_reply_packet[1 + size] = 0;
}
} else {
SetReply(m_reply_packet, "l");
}
} else {
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented qxfer:osdata:read: %s\n", m_receive_packet);
SetReplyError(m_reply_packet, "E01");
}
}
void GdbServerImpl::QuestionMark() {
/* TODO */
AMS_DMNT2_GDB_LOG_DEBUG("Not Implemented QuestionMark\n");
SetReply(m_reply_packet, "W00");
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
namespace ams::dmnt {
class GdbServerImpl {
private:
int m_socket;
HtcsSession m_session;
GdbPacketIo m_packet_io;
char *m_receive_packet{nullptr};
char *m_reply_packet{nullptr};
char m_buffer[GdbPacketBufferSize / 2];
svc::Handle m_debug_handle{svc::InvalidHandle};
public:
GdbServerImpl(int socket);
~GdbServerImpl();
void LoopProcess();
private:
void ProcessPacket(char *receive, char *reply);
void SendPacket(bool *out_break, const char *src) { return m_packet_io.SendPacket(out_break, src, std::addressof(m_session)); }
char *ReceivePacket(bool *out_break, char *dst, size_t size) { return m_packet_io.ReceivePacket(out_break, dst, size, std::addressof(m_session)); }
private:
bool HasDebugProcess() const { return m_debug_handle != svc::InvalidHandle; }
bool Is64Bit() const { return true; /* TODO: Retrieve from debug process info. */ }
private:
void H();
void q();
void qAttached();
void qC();
void qSupported();
void qXfer();
void qXferFeaturesRead();
void qXferOsdataRead();
void QuestionMark();
};
}