summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2022-08-27 15:12:08 +0300
committerOxore <oxore@protonmail.com>2022-08-27 15:13:13 +0300
commit9580a9a8daa4426914f396a694903ab880ecd3f2 (patch)
tree46394ea036316ffccf3357bcc35f1c18635ae890
parent903b8dbcaad887f6e14cc8cffc22ddda08dc9f85 (diff)
Add initial gdbremote implementation
-rw-r--r--CMakeLists.txt7
-rw-r--r--gdbremote.cpp100
-rw-r--r--gdbremote_parser.cpp119
-rw-r--r--gdbremote_parser.hpp133
4 files changed, 359 insertions, 0 deletions
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 <cstdlib>
+#include <cstdio>
+#include <cstdint>
+#include <cstring>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#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<size_t>(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<ExchangeResult> 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 <cstdint>
+#include <string>
+#include <vector>
+#include <memory>
+
+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<Feature> features{};
+ virtual ~PacketDataSupportedFeatures() {}
+};
+
+struct Packet {
+ const PacketType type{};
+ const std::unique_ptr<PacketData> 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<ExchangeResult> Nak(std::string data=std::string{})
+ {
+ return std::make_unique<ExchangeResult>(data, std::string{"-"});
+ }
+
+ static std::unique_ptr<ExchangeResult> Ack(std::string data=std::string{})
+ {
+ return std::make_unique<ExchangeResult>(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<ExchangeResult> 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{};
+};
+
+}