From 963e00194602da589579c7e9f15d7aa826e98ed5 Mon Sep 17 00:00:00 2001 From: Konstantin Nazarov Date: Sun, 8 Dec 2024 23:32:45 +0000 Subject: [PATCH] Add a trivial gdb stub, runnable but not yet fully working --- CMakeLists.txt | 2 + rve.nix | 1 + src/debug.cpp | 217 +++++++++++++++++++++++++++++++++++++++++++++++++ src/debug.hpp | 46 +++++++++++ src/rve.cpp | 22 ++++- 5 files changed, 285 insertions(+), 3 deletions(-) create mode 100644 src/debug.cpp create mode 100644 src/debug.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ecdb48..86bd0a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,12 +12,14 @@ add_library(vm_lib) target_sources(vm_lib PRIVATE src/vm.cpp + src/debug.cpp PUBLIC FILE_SET HEADERS BASE_DIRS src FILES src/vm.hpp + src/debug.hpp ) add_executable(rve src/rve.cpp) diff --git a/rve.nix b/rve.nix index 6be2130..712500d 100644 --- a/rve.nix +++ b/rve.nix @@ -21,6 +21,7 @@ pkgs.gcc13Stdenv.mkDerivation rec { #riscv-pkgs.buildPackages.gcc pkgsCross.riscv32-embedded.buildPackages.gcc pkgsCross.riscv32-embedded.buildPackages.binutils + pkgsCross.riscv32-embedded.buildPackages.gdb ]; hardeningDisable = [ "all" ]; diff --git a/src/debug.cpp b/src/debug.cpp new file mode 100644 index 0000000..3395688 --- /dev/null +++ b/src/debug.cpp @@ -0,0 +1,217 @@ +#include "debug.hpp" + +GDBStub::GDBStub(int port) : server_fd(-1), client_fd(-1), running(false) { + server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd == -1) { + throw std::runtime_error("Failed to create socket."); + } + + sockaddr_in server_addr = {}; + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(port); + + if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) { + throw std::runtime_error("Failed to bind socket."); + } + + if (listen(server_fd, 1) == -1) { + throw std::runtime_error("Failed to listen on socket."); + } + + std::cout << "Waiting for GDB connection on port " << port << "..." << std::endl; + client_fd = accept(server_fd, nullptr, nullptr); + if (client_fd == -1) { + throw std::runtime_error("Failed to accept connection."); + } + + std::cout << "GDB connected!" << std::endl; + running = true; +} + +GDBStub::~GDBStub() { + if (client_fd != -1) { + close(client_fd); + } + if (server_fd != -1) { + close(server_fd); + } +} + +void GDBStub::run() { + try { + while (running) { + std::string packet = receive_packet(); + std::cout << "received packet: " << packet << "\n"; + handle_packet(packet); + } + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + } +} + +void GDBStub::stop() { + running = false; +} + +void GDBStub::send_ack() { + if (send(client_fd, "+", 1, 0) == -1) { + throw std::runtime_error("Failed to send acknowledgment."); + } +} + +void GDBStub::wait_ack() { + char ack; + ssize_t len = recv(client_fd, &ack, 1, 0); + if (len <= 0) { + throw std::runtime_error("Failed to receive acknowledgment from GDB."); + } + + if (ack != '+') { + throw std::runtime_error("Negative acknowledgment received."); + } +} + +void GDBStub::send_packet(const std::string &packet) { + uint8_t checksum = 0; + for (char c : packet) { + checksum += c; + } + + std::ostringstream response; + response << '$' << packet << '#' << std::hex << std::setfill('0') << std::setw(2) << (checksum & 0xFF); + + const std::string response_str = response.str(); + if (send(client_fd, response_str.c_str(), response_str.size(), 0) == -1) { + throw std::runtime_error("Failed to send packet to GDB."); + } + + wait_ack(); +} + +std::string GDBStub::receive_packet() { + char buffer[BUFFER_SIZE]; + std::string packet; + bool found_start = false; + + while (true) { + ssize_t len = recv(client_fd, buffer, sizeof(buffer), 0); + if (len <= 0) { + throw std::runtime_error("Connection closed or error in receiving."); + } + + for (ssize_t i = 0; i < len; ++i) { + char c = buffer[i]; + if (!found_start) { + if (c == '$') { + found_start = true; + packet.clear(); + } + } else { + if (c == '#') { + send_ack(); + // Ignore checksum for simplicity + return packet; + } else { + packet += c; + } + } + } + } +} + +void GDBStub::handle_packet(const std::string &packet) { + switch (packet[0]) { + case '?': + send_packet("S05"); // TODO: real stop reason + break; + + case 'g': // Read all registers + send_packet(read_registers()); + break; + + case 'p': { + // Getting specific register + int regnum = std::stoi(packet.substr(1), nullptr, 16); + + uint32_t reg_value = 0; + + std::ostringstream response; + response << std::hex << std::setfill('0') << std::setw(8) << reg_value; + + send_packet(response.str()); + break; + } + + case 'G': + send_packet("OK"); // TODO: write registers + break; + + case 'm': { + size_t addr, length; + if (parse_memory_request(packet, addr, length)) { + send_packet(read_memory(addr, length)); + } else { + send_packet("E01"); // Error + } + break; + } + + case 'M': + send_packet("OK"); // TODO: write memory + break; + + case 'c': + send_packet("S05"); // TODO: continue execution + break; + + case 's': + send_packet("S05"); // TODO: step execution + break; + + case 'q': + std::cout << "received q\n"; + if (packet.find("qSupported") == 0) { + std::cout << "received qSupported\n"; + send_packet("PacketSize=4096"); // Example capability + } else { + send_packet(""); // Empty response + } + break; + + default: + send_packet(""); // Unsupported packet + break; + } +} + +bool GDBStub::parse_memory_request(const std::string &packet, size_t &addr, size_t &length) { + size_t comma_pos = packet.find(','); + if (comma_pos == std::string::npos) { + return false; + } + + try { + addr = std::stoul(packet.substr(1, comma_pos - 1), nullptr, 16); + length = std::stoul(packet.substr(comma_pos + 1), nullptr, 16); + } catch (...) { + return false; + } + return true; +} + +std::string GDBStub::read_registers() { + std::ostringstream out; + for (int i = 0; i < 32; ++i) { + out << std::string(8, '0'); // TODO: stub + } + return out.str(); +} + +std::string GDBStub::read_memory(size_t addr, size_t length) { + std::ostringstream out; + for (size_t i = 0; i < length; ++i) { + out << "00"; // TODO: stub + } + return out.str(); +} diff --git a/src/debug.hpp b/src/debug.hpp new file mode 100644 index 0000000..7b49d3e --- /dev/null +++ b/src/debug.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class GDBStub { +public: + GDBStub(int port); + ~GDBStub(); + + void run(); + void stop(); + +private: + int server_fd; + int client_fd; + std::atomic running; + + // Buffer size for GDB packet handling + static constexpr size_t BUFFER_SIZE = 4096; + + void send_packet(const std::string &packet); + + void send_ack(); + + void wait_ack(); + + std::string receive_packet(); + + void handle_packet(const std::string &packet); + + bool parse_memory_request(const std::string &packet, size_t &addr, size_t &length); + + std::string read_registers(); + + std::string read_memory(size_t addr, size_t length); +}; diff --git a/src/rve.cpp b/src/rve.cpp index 7969cf5..a9f3b02 100644 --- a/src/rve.cpp +++ b/src/rve.cpp @@ -1,4 +1,6 @@ #include "vm.hpp" +#include "debug.hpp" + #include #include #include @@ -34,13 +36,20 @@ int main(int argc, char* argv[]) { const size_t MEMORY_SIZE = 128*1024; uint8_t memory[MEMORY_SIZE] = {0}; - if (argc <= 1) { + bool debug = false; + std::string program_filename = ""; + + for (int i = 1; i < argc; ++i) { + if (std::string(argv[i]) == "--debug") + debug = true; + else + program_filename = argv[i]; + } + if (program_filename == "") { std::cerr << "Expected filename of program to execute" << std::endl; return 1; } - std::string program_filename = argv[1]; - try { load_program(program_filename, memory, MEMORY_SIZE); } catch (const std::exception &e) { @@ -61,5 +70,12 @@ int main(int argc, char* argv[]) { std::cout << "result: " << *res << std::endl; + if (debug) { + // to debug, do: "set debug remote 1" in gdb + // and then "target remote :1234" + GDBStub stub(1234); + stub.run(); + } + return 0; }