From 9580a9a8daa4426914f396a694903ab880ecd3f2 Mon Sep 17 00:00:00 2001 From: Oxore Date: Sat, 27 Aug 2022 15:12:08 +0300 Subject: Add initial gdbremote implementation --- CMakeLists.txt | 7 +++ gdbremote.cpp | 100 ++++++++++++++++++++++++++++++++++++++ gdbremote_parser.cpp | 119 +++++++++++++++++++++++++++++++++++++++++++++ gdbremote_parser.hpp | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 359 insertions(+) create mode 100644 gdbremote.cpp create mode 100644 gdbremote_parser.cpp create mode 100644 gdbremote_parser.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d6456c7..6c5bc39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address -fn set(emulator_sources bus.cpp emulator.cpp + gdbremote_parser.cpp ) set(musashi_m68k_sources musashi-m68k/m68kcpu.c @@ -48,6 +49,12 @@ target_compile_definitions(emulator PRIVATE DEBUG_TRACE_INSTRUCTIONS=1 ) +# Target for GDB Remote Debugging protocol implementation testing +add_executable(gdbremote + gdbremote.cpp + gdbremote_parser.cpp + ) + include_directories( . ) diff --git a/gdbremote.cpp b/gdbremote.cpp new file mode 100644 index 0000000..98a0e2a --- /dev/null +++ b/gdbremote.cpp @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "gdbremote_parser.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#define MESSAGE_BUFFER_SIZE 1 + +char msg_buf[MESSAGE_BUFFER_SIZE]; + +static int set_socket_reuseaddr(int socket_fd) +{ + const int val = 1; + const socklen_t len = sizeof(val); + return setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, len); +} + +static inline struct sockaddr_in sockaddr_in_any_ip_with_port(uint16_t port) { + struct sockaddr_in server{}; + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; + server.sin_port = htons(port); + return server; +} + +static int setup_socket(uint16_t port) +{ + // This creates the socket - or quits + const int socket_fd = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd == -1) { + perror("Could not create socket"); + return -1; + } + // Make TCP port reusable in case of kill or crash. + // Deliberate explanation: https://stackoverflow.com/a/3233022 + if (set_socket_reuseaddr(socket_fd) == -1) { + perror("setsockopt failed"); + close(socket_fd); + return -1; + } + puts("Socket created"); + const struct sockaddr_in server = sockaddr_in_any_ip_with_port(port); + if (bind(socket_fd, (struct sockaddr *)&server, sizeof(server)) == -1) { + perror("Bind failed"); + close(socket_fd); + return -1; + } + printf("Binding to 0.0.0.0:%u done\n", port); + return socket_fd; +} + +int main(int argc, char *argv[]) +{ + const int port = argc > 1 ? atoi(argv[1]) : 3333; + const int socket_fd = setup_socket(port); + if (socket_fd == -1) + return EXIT_FAILURE; + printf("Listening TCP 0.0.0.0:%u\n", port); + listen(socket_fd, 4); // Mark socket as listener + struct sockaddr client_address; + socklen_t address_len; + const int conn_fd = accept(socket_fd, &client_address, &address_len); + if (conn_fd == -1) { + perror("Accept failed"); + close(socket_fd); + return EXIT_FAILURE; + } + puts("Connection accepted"); + GDBRemote::ExchangeContext exchange_ctx{}; + ssize_t read_size; + while ((read_size = recv(conn_fd, msg_buf, MESSAGE_BUFFER_SIZE, 0)) > 0) { + for (size_t i = 0; i < static_cast(read_size); i++) { + const auto res = exchange_ctx.Consume(msg_buf[i]); + if (res != nullptr) { + if (res->packet.length() > 0) { + printf("<- \"%s\"\n", res->packet.c_str()); + } + if (res->response.length() > 0) { + printf("-> \"%s\"\n", res->response.c_str()); + if (send(conn_fd, &res->response[0], res->response.length(), 0) == -1) + perror("Send failed"); + } + } + } + memset(msg_buf, '\0', MESSAGE_BUFFER_SIZE); + } + if (read_size == 0) { + puts("Client disconnected"); + } else if (read_size == -1) { + perror("Recv failed"); + } + close(socket_fd); +} diff --git a/gdbremote_parser.cpp b/gdbremote_parser.cpp new file mode 100644 index 0000000..e3f4aae --- /dev/null +++ b/gdbremote_parser.cpp @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#include "gdbremote_parser.hpp" + +using namespace GDBRemote; + +static inline bool is_hex_digit(uint8_t byte) +{ + return byte >= '0' && byte <= '9' + || byte >= 'A' && byte < 'F' + || byte >= 'a' && byte <= 'f'; +} + +static inline uint8_t parseChecksumFromHexChars(uint8_t first, uint8_t second) +{ + // Assume, that given bytes are valid hex digits in ASCII + first = ((first & 0x40) ? (first + 9) : first) & 0x0f; + second = ((second & 0x40) ? (second + 9) : second) & 0x0f; + return ((first << 4) | second) & 0xff; +} + +std::unique_ptr ExchangeContext::Consume(uint8_t byte) +{ + switch (_parsing_state) { + case ParsingState::kIdle: + if (byte == '$') { + transit(ParsingState::kPacketData); + } + break; + case ParsingState::kPacketData: + if (byte == '#') { + transit(ParsingState::kChecksum1); + } else { + _checksum = (_checksum + byte) & 0xff; + _packet_data += byte; + } + break; + case ParsingState::kPacketDataBinSecond: + // TODO escaped and binary data + break; + case ParsingState::kChecksum1: + if (is_hex_digit(byte)) { + transit(ParsingState::kChecksum2); + _given_checksum_first_byte = byte; + } else { + transit(ParsingState::kIdle); + return ExchangeResult::Nak(); + } + break; + case ParsingState::kChecksum2: + const uint8_t checksum = _checksum; + std::string packet_data(std::move(_packet_data)); + transit(ParsingState::kIdle); + if (is_hex_digit(byte)) { + const uint8_t given_checksum = + parseChecksumFromHexChars(_given_checksum_first_byte, byte); + if (given_checksum == checksum) { + return ExchangeResult::Ack(packet_data); + } else { + printf( + "Checksum mismatch received: %02X, expected: %02X\n", + given_checksum, checksum); + printf("Packet data: \"%s\"\n", packet_data.c_str()); + return ExchangeResult::Nak(); + } + } else { + return ExchangeResult::Nak(); + } + break; + } + return nullptr; +} + +std::string ExchangeContext::WrapDataToSend(Packet packet) +{ + (void) packet; + return std::string{}; +} + +void ExchangeContext::transit(ParsingState new_state) +{ + printf( + "GDBRemote ParsingState: %s -> %s\n", + parsingStateToString(_parsing_state), + parsingStateToString(new_state)); + switch (new_state) { + case ParsingState::kIdle: + _checksum = 0; + _packet_data.clear(); + break; + case ParsingState::kPacketData: + break; + case ParsingState::kPacketDataBinSecond: + break; + case ParsingState::kChecksum1: + break; + case ParsingState::kChecksum2: + break; + } + _parsing_state = new_state; +} + +const char* ExchangeContext::parsingStateToString(ParsingState state) +{ + switch (state) { + case ParsingState::kIdle: + return "Idle"; + case ParsingState::kPacketData: + return "PacketData"; + case ParsingState::kPacketDataBinSecond: + return "PacketDataBinSecond"; + case ParsingState::kChecksum1: + return "Checksum1"; + case ParsingState::kChecksum2: + return "Checksum2"; + } + return "?"; +} diff --git a/gdbremote_parser.hpp b/gdbremote_parser.hpp new file mode 100644 index 0000000..0e2f375 --- /dev/null +++ b/gdbremote_parser.hpp @@ -0,0 +1,133 @@ +/* SPDX-License-Identifier: Unlicense + */ + +#pragma once + +#include +#include +#include +#include + +namespace GDBRemote { + +enum class FeatureSupportMark: uint8_t { + kNone = 0, + kPlus, + kMinus, + kQuestion, +}; + +enum class FeatureName: int { + kUnknown = 0, + kMultiprocess, + kSoftwareBreak, + kHardwareBreak, + kQRelocInsn, + kForkEvents, + kVForkEvents, + kExecEvents, + kVContSuppurted, + kQThreadEvents, + kNoResumed, + kMemoryTagging, + kPacketSize, +}; + +struct Feature { + const FeatureName name{}; + const FeatureSupportMark mark{}; + const bool has_value{}; + const int value{}; + const std::string raw{}; +}; + +enum class PacketType: int { + kNone = 0, + kQuerySupported, + kQueryC, + kQueryFThreadInfo, + kQuerySThreadInfo, + kH, + kMustReplyEmpty, +}; + +struct PacketData { + PacketData() = delete; + PacketData(const PacketData&) = delete; + PacketData(PacketData&&) = delete; + virtual ~PacketData() {} +}; + +struct PacketDataSupportedFeatures: public PacketData { + std::vector features{}; + virtual ~PacketDataSupportedFeatures() {} +}; + +struct Packet { + const PacketType type{}; + const std::unique_ptr data; + + /** Convert raw packet data into a Packet + * + * Packet data is the data extracted by ExchangeContext::Consume function. + */ + static Packet Parse(std::string packet_data); +}; + +struct ExchangeResult { + const std::string packet{}; + const std::string response{}; + + ExchangeResult(std::string a_packet, std::string a_response) + : packet(a_packet), response(a_response) {} + + static std::unique_ptr Nak(std::string data=std::string{}) + { + return std::make_unique(data, std::string{"-"}); + } + + static std::unique_ptr Ack(std::string data=std::string{}) + { + return std::make_unique(data, std::string{"+"}); + } +}; + +class ExchangeContext { +public: + /** Consume next byte from input stream from GDB client + * + * Returns packet data and acknowledge response in case if a valid packet + * fully received. + * + * Returns nullptr if is in progress. + */ + std::unique_ptr Consume(uint8_t byte); + + /** Creates raw packet from packet data to be sent into socket directly + * + * It is not static nor const because after sending a packet an + * ExchangeContext must be set into awaiting acknowledge response state + * (i.e. '+' or '-'). Otherwise we will be unable to resend packet in case + * if '-' negative acknowledge is received. + */ + std::string WrapDataToSend(Packet packet=Packet{}); + +private: + enum class ParsingState { + kIdle, + kPacketData, + kPacketDataBinSecond, + kChecksum1, + kChecksum2, + }; + + void transit(ParsingState); + static const char* parsingStateToString(ParsingState state); + + ParsingState _parsing_state{ParsingState::kIdle}; + std::string _packet_data{}; + uint8_t _checksum{}; + uint8_t _given_checksum_first_byte{}; +}; + +} -- cgit v1.2.3