dmnt: add basic gdb packet receive logic

This commit is contained in:
Michael Scire 2021-07-16 00:31:17 -07:00
parent e96d9950d2
commit 6da88a436f
11 changed files with 752 additions and 8 deletions

View file

@ -24,11 +24,10 @@ namespace ams::dmnt {
namespace {
constexpr size_t NumLogBuffers = 16;
constexpr size_t NumLogBuffers = 0x200;
constexpr size_t LogBufferSize = 0x100;
/* TODO: This shouldn't be so high priority. Come up with a better number? */
constexpr inline auto LogThreadPriority = 10;
constexpr inline auto LogThreadPriority = os::HighestThreadPriority - 2;
constinit os::MessageQueueType g_buf_mq;
constinit os::MessageQueueType g_req_mq;

View file

@ -20,8 +20,13 @@ namespace ams::dmnt {
void InitializeDebugLog();
void DebugLog(const char *prefix, const char *fmt, ...);
void DebugLog(const char *prefix, const char *fmt, ...) __attribute((format(printf, 2, 3)));
#define AMS_DMNT2_DEBUG_LOG(fmt, ...) ::ams::dmnt::DebugLog("[dmnt2] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_INFO(fmt, ...) ::ams::dmnt::DebugLog("[gdb-i] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_WARN(fmt, ...) ::ams::dmnt::DebugLog("[gdb-w] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_ERROR(fmt, ...) ::ams::dmnt::DebugLog("[gdb-e] ", fmt, ## __VA_ARGS__)
#define AMS_DMNT2_GDB_LOG_DEBUG(fmt, ...) ::ams::dmnt::DebugLog("[gdb-d] ", fmt, ## __VA_ARGS__)
}

View file

@ -0,0 +1,194 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
constexpr char BreakCharacter = '\x03'; /* ctrl-c */
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 char EncodeHex(u8 v) {
return "0123456789abcdef"[v & 0xF];
}
}
void GdbPacketIo::SendPacket(bool *out_break, const char *src, HtcsSession *session) {
/* Default to not breaked. */
*out_break = false;
/* Send a packet. */
while (true) {
std::scoped_lock lk(m_mutex);
size_t len = 0;
u8 checksum = 0;
while (src[len] != 0) {
checksum += static_cast<u8>(src[len++]);
}
char buffer[1 + GdbPacketBufferSize + 4];
buffer[0] = '$';
std::memcpy(buffer + 1, src, len);
buffer[1 + len] = '#';
buffer[2 + len] = EncodeHex(checksum >> 4);
buffer[3 + len] = EncodeHex(checksum >> 0);
buffer[4 + len] = 0;
if (session->PutString(buffer) < 0) {
/* Log (truncated) copy of packet. */
AMS_DMNT2_GDB_LOG_ERROR("Failed to send packet %s\n", buffer);
return;
}
if (m_no_ack) {
return;
}
/* Check to see if we need to retransmit. */
bool retransmit = false;
do {
if (const auto char_holder = session->GetChar(); char_holder) {
switch (*char_holder) {
case BreakCharacter:
*out_break = true;
return;
case '+':
return;
case '-':
retransmit = true;
break;
default:
break;
}
} else {
/* Log (truncated) copy of packet. */
AMS_DMNT2_GDB_LOG_ERROR("Failed to receive ack for %s\n", buffer);
return;
}
} while (!retransmit);
}
}
char *GdbPacketIo::ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session) {
/* Default to not breaked. */
*out_break = false;
/* Receive a packet. */
while (true) {
/* Wait for data to be available. */
if (!session->WaitToBeReadable()) {
return nullptr;
}
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Verify that we still have data immediately available. */
if (!session->WaitToBeReadable(0)) {
continue;
}
/* Prepare to parse. */
enum class State {
Initial,
PacketData,
ChecksumHigh,
ChecksumLow
};
State state = State::Initial;
u8 checksum = 0;
int csum_high = -1, csum_low = -1;
size_t count = 0;
/* Read characters. */
while (true) {
if (const auto char_holder = session->GetChar(); char_holder) {
const auto c = *char_holder;
switch (state) {
case State::Initial:
if (c == '$') {
state = State::PacketData;
} else if (c == BreakCharacter) {
*out_break = true;
return nullptr;
}
break;
case State::PacketData:
/* TODO: Escaped characters. */
if (c == '#') {
dst[count] = 0;
state = State::ChecksumHigh;
} else {
AMS_ABORT_UNLESS(count < size - 1);
checksum += static_cast<u8>(c);
dst[count++] = c;
}
break;
case State::ChecksumHigh:
csum_high = DecodeHex(c);
state = State::ChecksumLow;
break;
case State::ChecksumLow:
csum_low = DecodeHex(c);
if (m_no_ack) {
return dst;
} else {
const u8 expectsum = (static_cast<u8>(csum_high) << 4) | (static_cast<u8>(csum_low) << 0);
if (csum_high < 0 || csum_low < 0 || checksum != expectsum) {
/* Request retransmission. */
state = State::Initial;
checksum = 0;
csum_high = -1;
csum_low = -1;
count = 0;
session->PutChar('-');
} else {
session->PutChar('+');
return dst;
}
}
break;
}
} else {
return nullptr;
}
}
}
}
}

View file

@ -0,0 +1,37 @@
/*
* 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_htcs_session.hpp"
namespace ams::dmnt {
static constexpr size_t GdbPacketBufferSize = 16_KB;
class GdbPacketIo {
private:
os::SdkMutex m_mutex;
bool m_no_ack;
public:
GdbPacketIo() : m_mutex(), m_no_ack(false) { /* ... */ }
void SetNoAck() { m_no_ack = true; }
void SendPacket(bool *out_break, const char *src, HtcsSession *session);
char *ReceivePacket(bool *out_break, char *dst, size_t size, HtcsSession *session);
};
}

View file

@ -0,0 +1,114 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "dmnt2_gdb_packet_io.hpp"
#include "dmnt2_gdb_server.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
namespace {
constexpr size_t ServerThreadStackSize = util::AlignUp(3 * GdbPacketBufferSize + os::MemoryPageSize, os::ThreadStackAlignment);
alignas(os::ThreadStackAlignment) constinit u8 g_server_thread_stack[ServerThreadStackSize];
constinit os::ThreadType g_server_thread;
constinit util::TypedStorage<HtcsSession> g_session;
void OnClientSocketAccepted(int fd) {
/* Create htcs session for the socket. */
util::ConstructAt(g_session, fd);
ON_SCOPE_EXIT { util::DestroyAt(g_session); };
HtcsSession *session = util::GetPointer(g_session);
/* 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) {
AMS_DMNT2_GDB_LOG_DEBUG("Received Packet %s\n", packet);
/* TODO: Process packets. */
packet_io.SendPacket(std::addressof(do_break), "OK", session);
}
}
}
void GdbServerThreadFunction(void *) {
/* Loop forever, servicing our gdb server. */
while (true) {
/* Get a socket. */
int fd;
while ((fd = htcs::Socket()) == -1) {
os::SleepThread(TimeSpan::FromSeconds(1));
}
/* Ensure we cleanup the socket when we're done with it. */
ON_SCOPE_EXIT {
htcs::Close(fd);
os::SleepThread(TimeSpan::FromSeconds(1));
};
/* Create a sock addr for our server. */
htcs::SockAddrHtcs addr;
addr.family = htcs::HTCS_AF_HTCS;
addr.peer_name = htcs::GetPeerNameAny();
std::strcpy(addr.port_name.name, "iywys@$gdb");
/* Bind. */
if (htcs::Bind(fd, std::addressof(addr)) == -1) {
continue;
}
/* Listen on our port. */
while (htcs::Listen(fd, 0) == 0) {
/* Continue accepting clients, so long as we can. */
int client_fd;
while (true) {
/* Try to accept a client. */
if (client_fd = htcs::Accept(fd, std::addressof(addr)); client_fd < 0) {
break;
}
/* Handle the client. */
OnClientSocketAccepted(client_fd);
/* Close the client socket. */
htcs::Close(client_fd);
}
}
}
}
}
void InitializeGdbServer() {
/* Create and start gdb server thread. */
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

@ -0,0 +1,23 @@
/*
* 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>
namespace ams::dmnt {
void InitializeGdbServer();
}

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "dmnt2_htcs_receive_buffer.hpp"
namespace ams::dmnt {
ssize_t HtcsReceiveBuffer::Read(void *dst, size_t size) {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that we're readable and valid. */
if (!(this->IsValid() && this->IsReadable())) {
return -1;
}
/* Check that we have data to read. */
const size_t readable = std::min(size, m_readable_size);
if (readable <= 0) {
return -1;
}
/* Copy the data. */
std::memcpy(dst, m_buffer + m_offset, readable);
/* Advance our pointers. */
m_readable_size -= readable;
m_offset += readable;
/* Handle the case where we're done consuming. */
if (m_readable_size == 0) {
m_offset = 0;
m_readable_event.Clear();
m_writable_event.Signal();
}
return readable;
}
ssize_t HtcsReceiveBuffer::Write(const void *src, size_t size) {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Check that we're readable and valid. */
if (!(this->IsValid() && this->IsWritable())) {
return -1;
}
/* Copy the data to our buffer. */
std::memcpy(m_buffer, src, size);
/* Set our fields. */
m_readable_size = size;
m_offset = 0;
m_writable_event.Clear();
m_readable_event.Signal();
return size;
}
bool HtcsReceiveBuffer::WaitToBeReadable() {
/* Check if we're already readable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsReadable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_readable_event.Clear();
}
}
/* Wait for us to be readable. */
m_readable_event.Wait();
return this->IsValid();
}
bool HtcsReceiveBuffer::WaitToBeReadable(TimeSpan timeout) {
/* Check if we're already readable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsReadable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_readable_event.Clear();
}
}
/* Wait for us to be readable. */
const bool res = m_readable_event.TimedWait(timeout);
return res && this->IsValid();
}
bool HtcsReceiveBuffer::WaitToBeWritable() {
/* Check if we're already writable. */
{
std::scoped_lock lk(m_mutex);
if (this->IsWritable()) {
return true;
} else if (!this->IsValid()) {
return false;
} else {
m_writable_event.Clear();
}
}
/* Wait for us to be writable. */
m_writable_event.Wait();
return this->IsValid();
}
void HtcsReceiveBuffer::Invalidate() {
/* Acquire exclusive access to ourselves. */
std::scoped_lock lk(m_mutex);
/* Set ourselves as invalid. */
m_valid = false;
/* Signal our events. */
m_readable_event.Signal();
m_writable_event.Signal();
}
}

View file

@ -0,0 +1,49 @@
/*
* 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>
namespace ams::dmnt {
class HtcsReceiveBuffer {
public:
static constexpr size_t ReceiveBufferSize = 4_KB;
private:
u8 m_buffer[ReceiveBufferSize];
os::Event m_readable_event;
os::Event m_writable_event;
os::SdkMutex m_mutex;
size_t m_readable_size;
size_t m_offset;
bool m_valid;
public:
HtcsReceiveBuffer() : m_readable_event(os::EventClearMode_ManualClear), m_writable_event(os::EventClearMode_ManualClear), m_mutex(), m_readable_size(), m_offset(), m_valid(true) { /* ... */ }
ALWAYS_INLINE bool IsReadable() const { return m_readable_size != 0; }
ALWAYS_INLINE bool IsWritable() const { return m_readable_size == 0; }
ALWAYS_INLINE bool IsValid() const { return m_valid; }
ssize_t Read(void *dst, size_t size);
ssize_t Write(const void *src, size_t size);
bool WaitToBeReadable();
bool WaitToBeReadable(TimeSpan timeout);
bool WaitToBeWritable();
void Invalidate();
};
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2018-2020 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "dmnt2_htcs_session.hpp"
#include "dmnt2_debug_log.hpp"
namespace ams::dmnt {
HtcsSession::HtcsSession(int fd) : m_socket(fd), m_valid(true) {
/* Create our thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_receive_thread), ReceiveThreadEntry, this, m_receive_thread_stack, sizeof(m_receive_thread_stack), os::HighestThreadPriority - 1));
/* Start our thread. */
os::StartThread(std::addressof(m_receive_thread));
/* Note that we connected. */
AMS_DMNT2_GDB_LOG_INFO("Created Session %d\n", m_socket);
}
HtcsSession::~HtcsSession() {
/* Note that we connected. */
AMS_DMNT2_GDB_LOG_INFO("Closing Session %d\n", m_socket);
/* Invalidate our receive buffer. */
m_receive_buffer.Invalidate();
/* Shutdown our socket. */
htcs::Shutdown(m_socket, htcs::HTCS_SHUT_RDWR);
/* Wait for our thread. */
os::WaitThread(std::addressof(m_receive_thread));
os::DestroyThread(std::addressof(m_receive_thread));
/* Close our socket. */
htcs::Close(m_socket);
}
bool HtcsSession::WaitToBeReadable() {
return m_receive_buffer.WaitToBeReadable();
}
bool HtcsSession::WaitToBeReadable(TimeSpan timeout) {
return m_receive_buffer.WaitToBeReadable(timeout);
}
util::optional<char> HtcsSession::GetChar() {
/* Wait for us to have data. */
m_receive_buffer.WaitToBeReadable();
/* Get our data. */
char c;
if (m_receive_buffer.Read(std::addressof(c), sizeof(c)) > 0) {
return c;
} else {
return util::nullopt;
}
}
ssize_t HtcsSession::PutChar(char c) {
/* Send the character. */
const auto sent = htcs::Send(m_socket, std::addressof(c), sizeof(c), 0);
if (sent < 0) {
m_valid = false;
}
return sent;
}
ssize_t HtcsSession::PutString(const char *str) {
/* Repeatedly send until all is sent. */
const size_t len = std::strlen(str);
size_t remaining = len;
while (remaining > 0) {
const auto sent = htcs::Send(m_socket, str, remaining, 0);
if (sent >= 0) {
remaining -= sent;
str += sent;
} else {
m_valid = false;
return sent;
}
}
return len;
}
void HtcsSession::ReceiveThreadFunction() {
/* Create temporary buffer. */
u8 buffer[HtcsReceiveBuffer::ReceiveBufferSize];
/* Loop receiving data. */
while (true) {
/* Receive data. */
const auto res = htcs::Recv(m_socket, buffer, sizeof(buffer), 0);
if (res > 0) {
/* Write the data to our buffer. */
m_receive_buffer.WaitToBeWritable();
m_receive_buffer.Write(buffer, res);
} else {
/* Otherwise, if we got an error other than "try again", we're done. */
if (htcs::GetLastError() != htcs::HTCS_EAGAIN) {
AMS_DMNT2_GDB_LOG_INFO("Session %d invalid, res=%ld, err=%d\n", m_socket, res, static_cast<int>(htcs::GetLastError()));
m_valid = false;
break;
}
}
}
/* Invalidate our receive buffer. */
m_receive_buffer.Invalidate();
}
}

View file

@ -0,0 +1,50 @@
/*
* 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_htcs_receive_buffer.hpp"
namespace ams::dmnt {
class HtcsSession {
private:
alignas(os::ThreadStackAlignment) u8 m_receive_thread_stack[util::AlignUp(os::MemoryPageSize + HtcsReceiveBuffer::ReceiveBufferSize, os::ThreadStackAlignment)];
HtcsReceiveBuffer m_receive_buffer;
os::ThreadType m_receive_thread;
int m_socket;
bool m_valid;
public:
HtcsSession(int fd);
~HtcsSession();
ALWAYS_INLINE bool IsValid() const { return m_valid; }
bool WaitToBeReadable();
bool WaitToBeReadable(TimeSpan timeout);
util::optional<char> GetChar();
ssize_t PutChar(char c);
ssize_t PutString(const char *str);
private:
static void ReceiveThreadEntry(void *arg) {
static_cast<HtcsSession *>(arg)->ReceiveThreadFunction();
}
void ReceiveThreadFunction();
};
}

View file

@ -15,6 +15,7 @@
*/
#include <stratosphere.hpp>
#include "dmnt2_debug_log.hpp"
#include "dmnt2_gdb_server.hpp"
extern "C" {
extern u32 __start__;
@ -143,13 +144,12 @@ int main(int argc, char **argv)
/* Initialize debug log thread. */
dmnt::InitializeDebugLog();
/* TODO: Start GdbServer. */
/* Start GdbServer. */
dmnt::InitializeGdbServer();
/* TODO */
u64 i = 0;
while (true) {
AMS_DMNT2_DEBUG_LOG("Log #%llu\n", i++);
os::SleepThread(TimeSpan::FromSeconds(1));
os::SleepThread(TimeSpan::FromDays(1));
}
return 0;