massive feat: added a GDB stub for debugging

Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
2024-06-16 07:45:54 +05:30
parent c22333812e
commit a7d919eea0
15 changed files with 708 additions and 34 deletions

View File

@@ -21,7 +21,7 @@ jobs:
- name: setup - name: setup
run: nix develop .#matar-clang -c meson setup $BUILDDIR run: nix develop .#matar-clang -c meson setup $BUILDDIR -Dgdb_debug=true
- name: fmt - name: fmt
run: nix develop .#matar-clang -c ninja clang-format-check -C $BUILDDIR run: nix develop .#matar-clang -c ninja clang-format-check -C $BUILDDIR

View File

@@ -21,7 +21,7 @@ jobs:
- name: setup - name: setup
run: nix develop .#matar -c meson setup $BUILDDIR run: nix develop .#matar -c meson setup $BUILDDIR -Dgdb_debug=true
- name: build - name: build
run: nix develop .#matar -c ninja -C $BUILDDIR run: nix develop .#matar -c ninja -C $BUILDDIR

View File

@@ -31,7 +31,6 @@ class Bus {
uint32_t read_word(uint32_t, bool = true); uint32_t read_word(uint32_t, bool = true);
void write_word(uint32_t, uint32_t, bool = true); void write_word(uint32_t, uint32_t, bool = true);
// not sure what else to do? // not sure what else to do?
inline void internal_cycle() { cycles++; } inline void internal_cycle() { cycles++; }

View File

@@ -4,11 +4,19 @@
#include "bus.hh" #include "bus.hh"
#include "cpu/psr.hh" #include "cpu/psr.hh"
#include "thumb/instruction.hh" #include "thumb/instruction.hh"
#include <cstdint>
#include <memory> #include <memory>
#include <cstdint> #ifdef GDB_DEBUG
#include <unordered_set>
#endif
namespace matar { namespace matar {
#ifdef GDB_DEBUG
class GdbRsp;
#endif
class Cpu { class Cpu {
public: public:
Cpu(std::shared_ptr<Bus>) noexcept; Cpu(std::shared_ptr<Bus>) noexcept;
@@ -16,24 +24,12 @@ class Cpu {
void step(); void step();
void chg_mode(const Mode to); void chg_mode(const Mode to);
bool is_flushed = false; inline bool is_halted() { return halted; }
inline void flush_pipeline() { inline void resume() { halted = false; }
is_flushed = true;
if (cpsr.state() == State::Arm) {
opcodes[0] = bus->read_word(pc, false);
advance_pc_arm();
opcodes[1] = bus->read_word(pc);
advance_pc_arm();
} else {
opcodes[0] = bus->read_halfword(pc, false);
advance_pc_thumb();
opcodes[1] = bus->read_halfword(pc);
advance_pc_thumb();
}
sequential = true;
};
private: private:
bool halted = false;
friend void arm::Instruction::exec(Cpu& cpu); friend void arm::Instruction::exec(Cpu& cpu);
friend void thumb::Instruction::exec(Cpu& cpu); friend void thumb::Instruction::exec(Cpu& cpu);
@@ -93,5 +89,26 @@ class Cpu {
inline void advance_pc_arm() { pc += arm::INSTRUCTION_SIZE; }; inline void advance_pc_arm() { pc += arm::INSTRUCTION_SIZE; };
inline void advance_pc_thumb() { pc += thumb::INSTRUCTION_SIZE; } inline void advance_pc_thumb() { pc += thumb::INSTRUCTION_SIZE; }
bool is_flushed = false;
inline void flush_pipeline() {
if (cpsr.state() == State::Arm) {
opcodes[0] = bus->read_word(pc, false);
advance_pc_arm();
opcodes[1] = bus->read_word(pc);
advance_pc_arm();
} else {
opcodes[0] = bus->read_halfword(pc, false);
advance_pc_thumb();
opcodes[1] = bus->read_halfword(pc);
advance_pc_thumb();
}
sequential = true;
};
#ifdef GDB_DEBUG
friend class GdbRsp;
std::unordered_set<uint32_t> breakpoints = {};
#endif
}; };
} }

View File

@@ -7,8 +7,18 @@ project('matar', 'cpp',
'cpp_std=c++23', 'cpp_std=c++23',
'default_library=static']) 'default_library=static'])
lib_cpp_args = []
compiler = meson.get_compiler('cpp') compiler = meson.get_compiler('cpp')
if get_option('disassembler')
lib_cpp_args += '-DDISASSEMBLER'
endif
if get_option('gdb_debug')
lib_cpp_args += '-DGDB_DEBUG'
endif
subdir('include') subdir('include')
subdir('src') subdir('src')
subdir('apps') subdir('apps')

View File

@@ -1,2 +1,3 @@
option('tests', type : 'boolean', value : true, description: 'enable tests') option('tests', type : 'boolean', value : true, description: 'enable tests')
option('disassembler', type: 'boolean', value: true, description: 'enable disassembler') option('disassembler', type: 'boolean', value: true, description: 'enable disassembler')
option('gdb_debug', type: 'boolean', value: false, description: 'enable GDB RSP server')

View File

@@ -136,6 +136,7 @@ Bus::write(uint32_t address) {
#undef MATCHES #undef MATCHES
} }
glogger.error("Invalid memory region written");
return {}; return {};
} }

View File

@@ -129,6 +129,14 @@ Cpu::step() {
// word align // word align
rst_bit(pc, 1); rst_bit(pc, 1);
#ifdef GDB_DEBUG
if (breakpoints.contains(pc - 2 * arm::INSTRUCTION_SIZE)) {
glogger.info_bold("CPU halted");
halted = true;
return;
}
#endif
arm::Instruction instruction(opcodes[0]); arm::Instruction instruction(opcodes[0]);
opcodes[0] = opcodes[1]; opcodes[0] = opcodes[1];
@@ -148,6 +156,15 @@ Cpu::step() {
} else } else
advance_pc_arm(); advance_pc_arm();
} else { } else {
#ifdef GDB_DEBUG
if (breakpoints.contains(pc - 2 * thumb::INSTRUCTION_SIZE)) {
glogger.info_bold("CPU halted");
halted = true;
return;
}
#endif
thumb::Instruction instruction(opcodes[0]); thumb::Instruction instruction(opcodes[0]);
opcodes[0] = opcodes[1]; opcodes[0] = opcodes[1];

481
src/gdb_rsp.cc Normal file
View File

@@ -0,0 +1,481 @@
#include "gdb_rsp.hh"
#include "util/log.hh"
#include <csignal>
#include <numeric>
#include <regex>
#include <stdexcept>
#include <string>
namespace matar {
template<typename... Args>
static inline constexpr void
gdb_log(const std::format_string<Args...>& fmt, Args&&... args) {
glogger.debug("GDB: {}", std::format(fmt, std::forward<Args>(args)...));
}
static inline void
append_le(std::string& str, uint32_t value) {
// little endian only
str += std::format("{:02x}", value & 0xFF);
str += std::format("{:02x}", value >> 8 & 0xFF);
str += std::format("{:02x}", value >> 16 & 0xFF);
str += std::format("{:02x}", value >> 24 & 0xFF);
}
static inline std::string
be_to_le(std::string str) {
if (str.length() != 8)
throw std::out_of_range("string is supposed to be 8 bytes");
std::string current;
for (int i = 7; i >= 0; i -= 2) {
current += str[i - 1];
current += str[i];
}
return current;
}
GdbRsp::GdbRsp(std::shared_ptr<Cpu> cpu)
: cpu(cpu) {}
void
GdbRsp::start(const uint port) {
server.start(port);
server.run();
std::string msg;
while (!attached) {
step();
}
}
void
GdbRsp::satisfy_client() {
while (server.client_waiting() && attached) {
step();
}
}
void
GdbRsp::step() {
std::string msg = receive();
switch (msg[0]) {
case '+':
break;
case '-':
break;
case '\x03':
gdb_log("ctrl+c interrupt received");
cmd_halted();
break;
case '$': {
if (msg.starts_with("$qSupported")) {
acknowledge();
cmd_supported(msg);
} else if (msg.starts_with("$qAttached")) {
acknowledge();
cmd_attached();
} else {
switch (msg[1]) {
case '?':
acknowledge();
cmd_halted();
break;
case 'g':
acknowledge();
cmd_read_registers();
break;
case 'G':
acknowledge();
cmd_write_registers(msg);
break;
case 'p':
acknowledge();
cmd_read_register(msg);
break;
case 'P':
acknowledge();
cmd_write_register(msg);
break;
case 'm':
acknowledge();
cmd_read_memory(msg);
break;
case 'M':
acknowledge();
cmd_write_memory(msg);
break;
case 'z':
acknowledge();
cmd_rm_breakpoint(msg);
break;
case 'Z':
acknowledge();
cmd_add_breakpoint(msg);
break;
case 'c':
acknowledge();
cmd_continue();
break;
case 'D':
acknowledge();
cmd_detach();
break;
default:
gdb_log("unknown command");
send_empty();
}
}
break;
}
default:
gdb_log("unknown message received");
}
}
std::string
GdbRsp::receive() {
std::string msg = server.receive(1);
char ch = msg[0];
int checksum = 0;
if (ch == '$') {
while ((ch = server.receive(1)[0]) != '#') {
checksum += static_cast<uint>(ch);
msg += ch;
if (msg.length() > MAX_MSG_LEN) {
throw std::logic_error("GDB: received message is too long");
}
}
if (std::stoul(server.receive(2), nullptr, 16) != (checksum & 0xFF)) {
gdb_log("{}", msg);
throw std::logic_error("GDB: bad message checksum");
}
}
gdb_log("received message \"{}\"", msg);
return msg;
}
std::string
GdbRsp::make_packet(std::string raw) {
uint checksum = std::accumulate(raw.begin(), raw.end(), 0);
return std::format("${}#{:02x}", raw, checksum & 0xFF);
}
void
GdbRsp::acknowledge() {
server.send("+");
}
void
GdbRsp::send_empty() {
acknowledge();
server.send(make_packet(""));
}
void
GdbRsp::notify_breakpoint_reached() {
gdb_log("reached breakpoint, sending signal");
server.send(make_packet(std::format("S{:02x}", SIGTRAP)));
}
void
GdbRsp::cmd_attached() {
attached = true;
gdb_log("server is now attached");
server.send(make_packet("1"));
}
void
GdbRsp::cmd_supported(std::string msg) {
std::string response;
if (msg.find("hwbreak+;") != std::string::npos)
response += "hwbreak+;";
gdb_log("sending response for qSupported");
server.send(make_packet(response));
}
void
GdbRsp::cmd_halted() {
gdb_log("sending reason for upcoming halt");
server.send(make_packet(std::format("S{:02x}", SIGTRAP)));
}
void
GdbRsp::cmd_read_registers() {
std::string response;
for (int i = 0; i < cpu->GPR_COUNT - 1; i++)
append_le(response, cpu->gpr[i]);
// for some reason this PC needs to be the address of executing instruction
// i.e, two instructions behind actual PC
append_le(response,
cpu->pc - 2 * (cpu->cpsr.state() == State::Arm
? arm::INSTRUCTION_SIZE
: thumb::INSTRUCTION_SIZE));
gdb_log("sending register values");
server.send(make_packet(response));
}
void
GdbRsp::cmd_write_registers(std::string msg) {
static std::regex rgx("\\$G([0-9A-Fa-f]+)");
std::smatch sm;
regex_match(msg, sm, rgx);
if (sm.size() != 2 || sm[1].str().size() != 16 * 8) {
gdb_log("invalid arguments to write registers");
send_empty();
return;
}
try {
std::string values = sm[1].str();
for (uint i = 0, j = 0; i < values.length() - 8; i += 8, j++) {
cpu->gpr[i] = std::stoul(sm[i + 1].str(), nullptr, 16);
cpu->gpr[j] =
std::stoul(be_to_le(values.substr(i, 8)), nullptr, 16);
}
gdb_log("register values written");
server.send(OK_MSG);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_read_register(std::string msg) {
std::string response;
try {
uint reg = std::stoul(msg.substr(2), nullptr, 16);
// 25th register is CPSR in gdb ARM
if (reg == 25)
append_le(response, cpu->cpsr.raw());
else if (reg < cpu->GPR_COUNT)
append_le(response, cpu->gpr[reg]);
else
response += "xxxxxxxx";
gdb_log("sending single register value");
server.send(make_packet(response));
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_write_register(std::string msg) {
static std::regex rgx("\\$P([0-9A-Fa-f]+)\\=([0-9A-Fa-f]+)");
std::smatch sm;
regex_match(msg, sm, rgx);
if (sm.size() != 3 && sm[2].str().length() != 8) {
gdb_log("invalid arguments to write single register");
send_empty();
return;
}
try {
uint reg = std::stoul(sm[1].str(), nullptr, 16);
uint32_t value = std::stoul(be_to_le(sm[2].str()), nullptr, 16);
dbg(value);
if (reg == 25)
cpu->cpsr.set_all(value);
else if (reg < cpu->GPR_COUNT)
cpu->gpr[reg] = value;
gdb_log("single register value written");
server.send(OK_MSG);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_read_memory(std::string msg) {
std::string response;
bool sequential = false;
static std::regex rgx("\\$m([0-9A-Fa-f]+),([0-9A-Fa-f]+)");
std::smatch sm;
regex_match(msg, sm, rgx);
if (sm.size() != 3) {
gdb_log("invalid arguments to read memory");
send_empty();
return;
}
uint32_t address = 0, length = 0;
try {
address = std::stoul(sm[1].str(), nullptr, 16);
length = std::stoul(sm[2].str(), nullptr, 16);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
return;
}
for (uint i = 0; i < length; i++) {
response +=
std::format("{:02x}", cpu->bus->read_byte(address + i), sequential);
sequential = true;
}
cpu->sequential = false;
gdb_log("sending memory values values");
server.send(make_packet(response));
}
void
GdbRsp::cmd_write_memory(std::string msg) {
bool sequential = false;
static std::regex rgx("\\$M([0-9A-Fa-f]+),([0-9A-Fa-f]+):([0-9A-Fa-f]+)");
std::smatch sm;
regex_match(msg, sm, rgx);
if (sm.size() != 4) {
gdb_log("invalid arguments to write memory");
send_empty();
return;
}
try {
uint32_t address = std::stoul(sm[1].str(), nullptr, 16);
uint32_t length = std::stoul(sm[2].str(), nullptr, 16);
std::string values = sm[3].str();
for (uint i = 0, j = 0; i < length && j < values.size(); i++, j += 2) {
cpu->bus->write_byte(address + i,
std::stoul(values.substr(j, 2), nullptr, 16) &
0xFF,
sequential);
glogger.warn("hi {:02x}", cpu->bus->read_byte(address + i));
sequential = true;
}
cpu->sequential = false;
gdb_log("register values written");
server.send(OK_MSG);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_rm_breakpoint(std::string msg) {
static std::regex rgx("\\$z(0|1),([0-9A-Fa-f]+),(2|3|4)");
std::smatch sm;
regex_match(msg, sm, rgx);
if (sm.size() != 4) {
gdb_log("invalid arguments to remove breakpoint");
send_empty();
return;
}
if (sm[1].str() != "0" && sm[0].str() != "1") {
gdb_log("unrecognized breakpoint type encountered");
send_empty();
return;
}
if (sm[3].str() != "3" && sm[3].str() != "4") {
gdb_log("only 32 bit breakpoints supported");
send_empty();
return;
}
try {
uint32_t address = std::stoul(sm[2].str(), nullptr, 16);
cpu->breakpoints.erase(address);
gdb_log("breakpoint {:#08x} removed", address);
server.send(OK_MSG);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_add_breakpoint(std::string msg) {
static std::regex rgx("\\$Z(0|1),([0-9A-Fa-f]+),(2|3|4)");
std::smatch sm;
regex_match(msg, sm, rgx);
dbg(sm.size());
dbg(sm[0].str());
if (sm.size() != 4) {
gdb_log("invalid arguments to add breakpoint");
send_empty();
return;
}
if (sm[1].str() != "0" && sm[0].str() != "1") {
gdb_log("unrecognized breakpoint type encountered");
send_empty();
return;
}
if (sm[3].str() != "3" && sm[3].str() != "4") {
gdb_log("only 32 bit breakpoints supported");
send_empty();
return;
}
try {
uint32_t address = std::stoul(sm[2].str(), nullptr, 16);
cpu->breakpoints.insert(address);
gdb_log("breakpoint {:#08x} added", address);
server.send(OK_MSG);
} catch (const std::exception& e) {
gdb_log("{}", e.what());
send_empty();
}
}
void
GdbRsp::cmd_detach() {
attached = false;
cpu->resume();
gdb_log("detached");
server.send(OK_MSG);
}
void
GdbRsp::cmd_continue() {
cpu->resume();
gdb_log("continued");
server.send(OK_MSG);
while (true) {
cpu->step();
}
}
}

43
src/gdb_rsp.hh Normal file
View File

@@ -0,0 +1,43 @@
#include "cpu/cpu.hh"
#include "util/tcp_server.hh"
namespace matar {
class GdbRsp {
public:
GdbRsp(std::shared_ptr<Cpu> cpu);
void start(const uint port);
void satisfy_client();
void step();
private:
bool attached = false;
std::shared_ptr<Cpu> cpu;
net::TcpServer server;
std::string receive();
std::string make_packet(std::string raw);
void acknowledge();
void send_empty();
void notify_breakpoint_reached();
// Commands
void cmd_attached();
void cmd_supported(std::string msg);
void cmd_halted();
void cmd_read_registers();
void cmd_write_registers(std::string msg);
void cmd_read_register(std::string msg);
void cmd_write_register(std::string msg);
void cmd_read_memory(std::string msg);
void cmd_write_memory(std::string msg);
void cmd_rm_breakpoint(std::string msg);
void cmd_add_breakpoint(std::string msg);
void cmd_detach();
void cmd_continue();
static constexpr std::string ATTACHED_MSG = "$qAttached#8f";
static constexpr std::string OK_MSG = "+$OK#9a";
static constexpr uint MAX_MSG_LEN = 4096;
};
}

View File

@@ -2,16 +2,14 @@ lib_sources = files(
'bus.cc', 'bus.cc',
) )
if get_option('gdb_debug')
lib_sources += files('gdb_rsp.cc')
endif
subdir('util') subdir('util')
subdir('cpu') subdir('cpu')
subdir('io') subdir('io')
lib_cpp_args = []
if get_option('disassembler')
lib_cpp_args += '-DDISASSEMBLER'
endif
lib = library( lib = library(
meson.project_name(), meson.project_name(),
lib_sources, lib_sources,

View File

@@ -1,3 +1,8 @@
lib_sources += files( lib_sources += files(
'log.cc' 'log.cc',
'tcp_server.cc'
) )
if get_option('gdb_debug')
lib_sources += files('tcp_server.cc')
endif

80
src/util/tcp_server.cc Normal file
View File

@@ -0,0 +1,80 @@
#include "tcp_server.hh"
#include <cstring>
#include <format>
#include <sys/ioctl.h>
#include <unistd.h>
namespace net {
TcpServer::TcpServer()
: server_fd(0)
, client_fd(0) {}
TcpServer::~TcpServer() {
close(server_fd);
close(client_fd);
}
bool
TcpServer::client_waiting() {
int count = 0;
ioctl(client_fd, FIONREAD, &count);
return static_cast<bool>(count);
}
void
TcpServer::run() {
socklen_t cli_addr_size = sizeof(client_addr);
client_fd = ::accept(
server_fd, reinterpret_cast<sockaddr*>(&client_addr), &cli_addr_size);
if (client_fd == -1)
throw std::runtime_error("accept failed");
}
void
TcpServer::start(uint port) {
server_fd = socket(PF_INET, SOCK_STREAM, 0);
if (server_fd == -1) {
throw std::runtime_error("creating socket failed");
}
int option = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
std::memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port);
if (::bind(server_fd,
reinterpret_cast<sockaddr*>(&server_addr),
sizeof(server_addr)) == -1) {
throw std::runtime_error("binding socket failed");
}
if (::listen(server_fd, 1) == -1) {
throw std::runtime_error("listening failed");
}
}
void
TcpServer::send(std::string msg) {
if (::send(client_fd, msg.data(), msg.length(), 0) == -1) {
throw std::runtime_error(
std::format("failed to send message: {}\n", strerror(errno)));
}
}
std::string
TcpServer::receive(uint length) {
char msg[MAX_PACKET_SIZE];
ssize_t num_bytes = recv(client_fd, msg, length, 0);
msg[length] = '\0';
if (num_bytes < 0) {
throw std::runtime_error(
std::format("failed to receive messages: {}\n", strerror(errno)));
}
return std::string(msg);
}
}

26
src/util/tcp_server.hh Normal file
View File

@@ -0,0 +1,26 @@
#pragma once
#include <netinet/in.h>
#include <string>
namespace net {
class TcpServer {
public:
TcpServer();
~TcpServer();
void run();
void start(uint port);
void send(std::string msg);
std::string receive(uint length);
bool client_waiting();
private:
static constexpr uint MAX_PACKET_SIZE = 4096;
int server_fd;
int client_fd;
sockaddr_in server_addr;
sockaddr_in client_addr;
};
}

View File

@@ -9,15 +9,11 @@ tests_sources = files(
'bus.cc' 'bus.cc'
) )
tests_cpp_args = lib_cpp_args
subdir('cpu') subdir('cpu')
subdir('util') subdir('util')
tests_cpp_args = []
if get_option('disassembler')
tests_cpp_args += '-DDISASSEMBLER'
endif
catch2 = dependency('catch2', version: '>=3.4.0', static: true) catch2 = dependency('catch2', version: '>=3.4.0', static: true)
catch2_tests = executable( catch2_tests = executable(
'matar_tests', 'matar_tests',