Files
lab-rv32i-freertos-c-queue/vendor/Hazard3/test/sim/tb_common/tb_gdb.cpp
T

680 lines
19 KiB
C++

#include "tb_gdb.h"
#include "tb_constants.h"
#include <algorithm>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <unistd.h>
static bool send_all(int fd, const uint8_t *buf, size_t len) {
size_t sent = 0;
while (sent < len) {
#ifdef MSG_NOSIGNAL
ssize_t n = send(fd, buf + sent, len - sent, MSG_NOSIGNAL);
#else
ssize_t n = send(fd, buf + sent, len - sent, 0);
#endif
if (n < 0) {
if (errno == EINTR)
continue;
return false;
}
sent += (size_t)n;
}
return true;
}
tb_gdb_server::tb_gdb_server(uint16_t port, tb_gdb_target &target)
: port_{port}, target_{target} {
target_xml_ =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE target SYSTEM \"gdb-target.dtd\">\n"
"<target>\n"
" <architecture>riscv:rv32</architecture>\n"
" <feature name=\"org.gnu.gdb.riscv.cpu\">\n"
" <reg name=\"x0\" bitsize=\"32\" type=\"int\" regnum=\"0\"/>\n"
" <reg name=\"x1\" bitsize=\"32\" type=\"int\" regnum=\"1\"/>\n"
" <reg name=\"x2\" bitsize=\"32\" type=\"int\" regnum=\"2\"/>\n"
" <reg name=\"x3\" bitsize=\"32\" type=\"int\" regnum=\"3\"/>\n"
" <reg name=\"x4\" bitsize=\"32\" type=\"int\" regnum=\"4\"/>\n"
" <reg name=\"x5\" bitsize=\"32\" type=\"int\" regnum=\"5\"/>\n"
" <reg name=\"x6\" bitsize=\"32\" type=\"int\" regnum=\"6\"/>\n"
" <reg name=\"x7\" bitsize=\"32\" type=\"int\" regnum=\"7\"/>\n"
" <reg name=\"x8\" bitsize=\"32\" type=\"int\" regnum=\"8\"/>\n"
" <reg name=\"x9\" bitsize=\"32\" type=\"int\" regnum=\"9\"/>\n"
" <reg name=\"x10\" bitsize=\"32\" type=\"int\" regnum=\"10\"/>\n"
" <reg name=\"x11\" bitsize=\"32\" type=\"int\" regnum=\"11\"/>\n"
" <reg name=\"x12\" bitsize=\"32\" type=\"int\" regnum=\"12\"/>\n"
" <reg name=\"x13\" bitsize=\"32\" type=\"int\" regnum=\"13\"/>\n"
" <reg name=\"x14\" bitsize=\"32\" type=\"int\" regnum=\"14\"/>\n"
" <reg name=\"x15\" bitsize=\"32\" type=\"int\" regnum=\"15\"/>\n"
" <reg name=\"x16\" bitsize=\"32\" type=\"int\" regnum=\"16\"/>\n"
" <reg name=\"x17\" bitsize=\"32\" type=\"int\" regnum=\"17\"/>\n"
" <reg name=\"x18\" bitsize=\"32\" type=\"int\" regnum=\"18\"/>\n"
" <reg name=\"x19\" bitsize=\"32\" type=\"int\" regnum=\"19\"/>\n"
" <reg name=\"x20\" bitsize=\"32\" type=\"int\" regnum=\"20\"/>\n"
" <reg name=\"x21\" bitsize=\"32\" type=\"int\" regnum=\"21\"/>\n"
" <reg name=\"x22\" bitsize=\"32\" type=\"int\" regnum=\"22\"/>\n"
" <reg name=\"x23\" bitsize=\"32\" type=\"int\" regnum=\"23\"/>\n"
" <reg name=\"x24\" bitsize=\"32\" type=\"int\" regnum=\"24\"/>\n"
" <reg name=\"x25\" bitsize=\"32\" type=\"int\" regnum=\"25\"/>\n"
" <reg name=\"x26\" bitsize=\"32\" type=\"int\" regnum=\"26\"/>\n"
" <reg name=\"x27\" bitsize=\"32\" type=\"int\" regnum=\"27\"/>\n"
" <reg name=\"x28\" bitsize=\"32\" type=\"int\" regnum=\"28\"/>\n"
" <reg name=\"x29\" bitsize=\"32\" type=\"int\" regnum=\"29\"/>\n"
" <reg name=\"x30\" bitsize=\"32\" type=\"int\" regnum=\"30\"/>\n"
" <reg name=\"x31\" bitsize=\"32\" type=\"int\" regnum=\"31\"/>\n"
" <reg name=\"pc\" bitsize=\"32\" type=\"code_ptr\" regnum=\"32\"/>\n"
" </feature>\n"
"</target>\n";
char mm_line[128];
snprintf(
mm_line,
sizeof(mm_line),
" <memory type=\"ram\" start=\"0x%08x\" length=\"0x%08x\"/>\n",
(unsigned)MEM_BASE,
(unsigned)MEM_SIZE
);
memory_map_xml_ =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" "
"\"http://sourceware.org/gdb/gdb-memory-map.dtd\">\n"
"<memory-map>\n" +
std::string(mm_line) +
"</memory-map>\n";
}
tb_gdb_server::~tb_gdb_server() {
close_client_();
if (server_fd_ >= 0) {
::close(server_fd_);
server_fd_ = -1;
}
}
bool tb_gdb_server::open_listen_socket_() {
server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd_ < 0) {
fprintf(stderr, "tb_gdb: socket() failed: %s\n", strerror(errno));
return false;
}
int opt = 1;
if (setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
fprintf(stderr, "tb_gdb: setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno));
return false;
}
#ifdef SO_REUSEPORT
(void)setsockopt(server_fd_, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
#endif
sockaddr_in addr{};
addr.sin_family = AF_INET;
// Debug services are local by default. Remote access belongs behind an
// explicit SSH tunnel, never an unauthenticated wildcard listener.
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(port_);
if (bind(server_fd_, (sockaddr *)&addr, sizeof(addr)) < 0) {
fprintf(stderr, "tb_gdb: bind(127.0.0.1:%u) failed: %s\n", (unsigned)port_, strerror(errno));
return false;
}
if (listen(server_fd_, 1) < 0) {
fprintf(stderr, "tb_gdb: listen() failed: %s\n", strerror(errno));
return false;
}
return true;
}
bool tb_gdb_server::accept_client_() {
sockaddr_in addr{};
socklen_t addr_len = sizeof(addr);
client_fd_ = accept(server_fd_, (sockaddr *)&addr, &addr_len);
if (client_fd_ < 0) {
fprintf(stderr, "tb_gdb: accept() failed: %s\n", strerror(errno));
return false;
}
int flag = 1;
(void)setsockopt(client_fd_, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
return true;
}
void tb_gdb_server::close_client_() {
if (client_fd_ >= 0) {
::close(client_fd_);
client_fd_ = -1;
}
no_ack_mode_ = false;
breakpoints_.clear();
ignore_breakpoint_pc_ = 0xffffffffu;
}
uint8_t tb_gdb_server::checksum_(const std::string &s) {
uint8_t sum = 0;
for (unsigned char c : s)
sum = (uint8_t)(sum + c);
return sum;
}
int tb_gdb_server::hex_val_(char c) {
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return 10 + (c - 'a');
if (c >= 'A' && c <= 'F')
return 10 + (c - 'A');
return -1;
}
std::string tb_gdb_server::to_hex_bytes_(const uint8_t *data, size_t len) {
static const char *hex = "0123456789abcdef";
std::string out;
out.reserve(len * 2);
for (size_t i = 0; i < len; ++i) {
out.push_back(hex[(data[i] >> 4) & 0xf]);
out.push_back(hex[data[i] & 0xf]);
}
return out;
}
bool tb_gdb_server::from_hex_bytes_(const std::string &hex, std::string &out_bytes) {
out_bytes.clear();
if (hex.size() % 2 != 0)
return false;
out_bytes.reserve(hex.size() / 2);
for (size_t i = 0; i < hex.size(); i += 2) {
int hi = hex_val_(hex[i]);
int lo = hex_val_(hex[i + 1]);
if (hi < 0 || lo < 0)
return false;
out_bytes.push_back((char)((hi << 4) | lo));
}
return true;
}
void tb_gdb_server::append_u32_le_hex_(std::string &out, uint32_t v) {
for (int i = 0; i < 4; ++i) {
const uint8_t b = (uint8_t)((v >> (8 * i)) & 0xffu);
static const char *hex = "0123456789abcdef";
out.push_back(hex[(b >> 4) & 0xf]);
out.push_back(hex[b & 0xf]);
}
}
bool tb_gdb_server::parse_u32_hex_(const std::string &s, uint32_t &out) {
if (s.empty())
return false;
uint32_t v = 0;
for (char c : s) {
int h = hex_val_(c);
if (h < 0)
return false;
v = (v << 4) | (uint32_t)h;
}
out = v;
return true;
}
bool tb_gdb_server::maybe_send_ack_(bool ok) {
if (no_ack_mode_)
return true;
const char c = ok ? '+' : '-';
return send_all(client_fd_, (const uint8_t *)&c, 1);
}
bool tb_gdb_server::send_packet_(const std::string &payload) {
std::string pkt;
pkt.reserve(payload.size() + 4);
pkt.push_back('$');
pkt += payload;
pkt.push_back('#');
uint8_t cksum = checksum_(payload);
static const char *hex = "0123456789abcdef";
pkt.push_back(hex[(cksum >> 4) & 0xf]);
pkt.push_back(hex[cksum & 0xf]);
return send_all(client_fd_, (const uint8_t *)pkt.data(), pkt.size());
}
bool tb_gdb_server::check_interrupt_() {
uint8_t c = 0;
ssize_t n = recv(client_fd_, &c, 1, MSG_DONTWAIT | MSG_PEEK);
if (n < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return false;
return true;
}
if (n == 0)
return true;
if (c != 0x03)
return false;
(void)recv(client_fd_, &c, 1, MSG_DONTWAIT);
return true;
}
bool tb_gdb_server::recv_packet_(std::string &out_payload, bool &got_interrupt) {
out_payload.clear();
got_interrupt = false;
while (true) {
uint8_t c = 0;
ssize_t n = recv(client_fd_, &c, 1, 0);
if (n == 0)
return false;
if (n < 0) {
if (errno == EINTR)
continue;
return false;
}
if (c == 0x03) {
got_interrupt = true;
return true;
}
if (c == '+' || c == '-') {
continue;
}
if (c != '$') {
continue;
}
std::string payload;
while (true) {
n = recv(client_fd_, &c, 1, 0);
if (n == 0)
return false;
if (n < 0) {
if (errno == EINTR)
continue;
return false;
}
if (c == '#')
break;
payload.push_back((char)c);
}
char ck[2];
for (int i = 0; i < 2; ++i) {
n = recv(client_fd_, &c, 1, 0);
if (n == 0)
return false;
if (n < 0) {
if (errno == EINTR) {
--i;
continue;
}
return false;
}
ck[i] = (char)c;
}
bool ok = true;
const int hi = hex_val_(ck[0]);
const int lo = hex_val_(ck[1]);
if (hi < 0 || lo < 0) {
ok = false;
} else {
const uint8_t expected = (uint8_t)((hi << 4) | lo);
ok = expected == checksum_(payload);
}
if (!maybe_send_ack_(ok))
return false;
if (!ok)
continue;
out_payload = std::move(payload);
return true;
}
}
std::string tb_gdb_server::stop_reply_(int signo) const {
char buf[8];
snprintf(buf, sizeof(buf), "S%02x", signo & 0xff);
return std::string(buf);
}
std::string tb_gdb_server::stop_reply_exit_() const {
char buf[8];
snprintf(buf, sizeof(buf), "W%02x", (unsigned)(target_.exit_code() & 0xffu));
return std::string(buf);
}
std::string tb_gdb_server::handle_command_(const std::string &cmd, bool &should_close) {
should_close = false;
if (cmd.empty())
return "";
// General queries
if (cmd == "?")
return stop_reply_(5); // SIGTRAP
if (cmd.rfind("qSupported", 0) == 0) {
return "PacketSize=4000;qXfer:features:read+;qXfer:memory-map:read+;QStartNoAckMode+;swbreak+;hwbreak+;vContSupported+;qRcmd+";
}
if (cmd == "QStartNoAckMode") {
no_ack_mode_ = true;
return "OK";
}
if (cmd == "qAttached")
return "1";
if (cmd == "qC")
return "QC1";
if (cmd == "qfThreadInfo")
return "m1";
if (cmd == "qsThreadInfo")
return "l";
if (cmd.rfind("H", 0) == 0)
return "OK";
if (cmd.rfind("qThreadExtraInfo", 0) == 0) {
const std::string s = "hart0";
return to_hex_bytes_((const uint8_t *)s.data(), s.size());
}
if (cmd.rfind("qSymbol", 0) == 0)
return "OK";
if (cmd == "qTStatus")
return "";
// GDB "monitor" commands (OpenOCD-style). Payload is hex-encoded bytes.
if (cmd.rfind("qRcmd,", 0) == 0) {
const std::string hex = cmd.substr(strlen("qRcmd,"));
std::string decoded;
if (!from_hex_bytes_(hex, decoded))
return "E01";
std::string console;
if (!target_.monitor_cmd(decoded, console))
return "";
if (!console.empty()) {
const std::string payload = "O" + to_hex_bytes_((const uint8_t *)console.data(), console.size());
(void)send_packet_(payload);
}
return "OK";
}
// Extended-remote mode request
if (cmd == "!")
return "OK";
// target.xml for riscv32 (x0..x31 + pc)
if (cmd.rfind("qXfer:features:read:target.xml:", 0) == 0) {
const std::string args = cmd.substr(strlen("qXfer:features:read:target.xml:"));
const size_t comma = args.find(',');
if (comma == std::string::npos)
return "E01";
uint32_t off = 0, len = 0;
if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
return "E01";
if (off >= target_xml_.size())
return "l";
const size_t end = std::min<size_t>(target_xml_.size(), (size_t)off + (size_t)len);
const bool more = end < target_xml_.size();
std::string out;
out.reserve(1 + (end - off));
out.push_back(more ? 'm' : 'l');
out.append(target_xml_, off, end - off);
return out;
}
// memory-map.xml (RAM region for disassembly / memory accessibility)
if (cmd.rfind("qXfer:memory-map:read::", 0) == 0) {
const std::string args = cmd.substr(strlen("qXfer:memory-map:read::"));
const size_t comma = args.find(',');
if (comma == std::string::npos)
return "E01";
uint32_t off = 0, len = 0;
if (!parse_u32_hex_(args.substr(0, comma), off) || !parse_u32_hex_(args.substr(comma + 1), len))
return "E01";
if (off >= memory_map_xml_.size())
return "l";
const size_t end = std::min<size_t>(memory_map_xml_.size(), (size_t)off + (size_t)len);
const bool more = end < memory_map_xml_.size();
std::string out;
out.reserve(1 + (end - off));
out.push_back(more ? 'm' : 'l');
out.append(memory_map_xml_, off, end - off);
return out;
}
// Registers
if (cmd == "g") {
std::string out;
out.reserve(33 * 8);
for (uint32_t r = 0; r < 33; ++r)
append_u32_le_hex_(out, target_.read_reg(r));
return out;
}
if (cmd.size() >= 2 && cmd[0] == 'p') {
uint32_t regno = 0;
if (!parse_u32_hex_(cmd.substr(1), regno))
return "E01";
std::string out;
out.reserve(8);
append_u32_le_hex_(out, target_.read_reg(regno));
return out;
}
if (cmd.size() >= 3 && cmd[0] == 'P') {
const size_t eq = cmd.find('=');
if (eq == std::string::npos)
return "E01";
uint32_t regno = 0;
if (!parse_u32_hex_(cmd.substr(1, eq - 1), regno))
return "E01";
std::string bytes;
if (!from_hex_bytes_(cmd.substr(eq + 1), bytes))
return "E01";
if (bytes.size() < 4)
return "E01";
uint32_t v = (uint8_t)bytes[0] | ((uint32_t)(uint8_t)bytes[1] << 8) | ((uint32_t)(uint8_t)bytes[2] << 16) |
((uint32_t)(uint8_t)bytes[3] << 24);
target_.write_reg(regno, v);
return "OK";
}
if (cmd.size() >= 1 && cmd[0] == 'G') {
std::string bytes;
if (!from_hex_bytes_(cmd.substr(1), bytes))
return "E01";
if (bytes.size() < 33 * 4)
return "E01";
for (uint32_t r = 0; r < 33; ++r) {
const size_t i = r * 4;
uint32_t v = (uint8_t)bytes[i + 0] | ((uint32_t)(uint8_t)bytes[i + 1] << 8) |
((uint32_t)(uint8_t)bytes[i + 2] << 16) | ((uint32_t)(uint8_t)bytes[i + 3] << 24);
target_.write_reg(r, v);
}
return "OK";
}
// Memory
if (cmd.size() >= 2 && cmd[0] == 'm') {
const size_t comma = cmd.find(',');
if (comma == std::string::npos)
return "E01";
uint32_t addr = 0, len = 0;
if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1), len))
return "E01";
if (len == 0)
return "";
std::string buf(len, '\0');
if (!target_.read_mem(addr, (uint8_t *)&buf[0], (size_t)len))
return "E01";
return to_hex_bytes_((const uint8_t *)buf.data(), buf.size());
}
if (cmd.size() >= 2 && cmd[0] == 'M') {
const size_t comma = cmd.find(',');
const size_t colon = cmd.find(':');
if (comma == std::string::npos || colon == std::string::npos || comma > colon)
return "E01";
uint32_t addr = 0, len = 0;
if (!parse_u32_hex_(cmd.substr(1, comma - 1), addr) || !parse_u32_hex_(cmd.substr(comma + 1, colon - comma - 1), len))
return "E01";
if (len == 0)
return "OK";
std::string bytes;
if (!from_hex_bytes_(cmd.substr(colon + 1), bytes))
return "E01";
if (bytes.size() < len)
return "E01";
if (!target_.write_mem(addr, (const uint8_t *)bytes.data(), len))
return "E01";
return "OK";
}
// Execute breakpoints.
//
// GDB's stock stepi implementation may place a temporary software
// breakpoint (Z0) or hardware breakpoint (Z1) at the predicted next PC,
// for example when stepping across an indirect jalr into a read-only text
// section. This testbench doesn't patch target memory, so both packet types
// are handled identically as execute breakpoints checked in the run loop.
if (cmd.rfind("Z0,", 0) == 0 || cmd.rfind("z0,", 0) == 0 ||
cmd.rfind("Z1,", 0) == 0 || cmd.rfind("z1,", 0) == 0) {
const bool set = cmd[0] == 'Z';
const size_t comma1 = cmd.find(',');
const size_t comma2 = cmd.find(',', comma1 + 1);
if (comma1 == std::string::npos || comma2 == std::string::npos)
return "E01";
uint32_t addr = 0;
if (!parse_u32_hex_(cmd.substr(comma1 + 1, comma2 - comma1 - 1), addr))
return "E01";
if (set)
breakpoints_.insert(addr);
else
breakpoints_.erase(addr);
return "OK";
}
// vCont
if (cmd == "vCont?")
return "vCont;c;s";
auto do_step = [&](bool single_step) -> std::string {
const uint32_t start_pc = target_.read_reg(32);
const bool ignore_start_break = single_step || start_pc == ignore_breakpoint_pc_;
const bool consume_ignore_pc = start_pc == ignore_breakpoint_pc_;
if (consume_ignore_pc)
ignore_breakpoint_pc_ = 0xffffffffu;
if (single_step) {
const tb_gdb_run_result r = target_.step_instruction();
if (r == tb_gdb_run_result::exited) {
// In extended-remote mode keep the connection alive after target exit,
// so the user can issue monitor commands such as "reset halt".
return stop_reply_exit_();
}
if (r == tb_gdb_run_result::timed_out) {
should_close = true;
return stop_reply_(14); // SIGALRM
}
ignore_breakpoint_pc_ = 0xffffffffu;
return stop_reply_(5); // SIGTRAP
}
bool first_iter = true;
while (true) {
if (check_interrupt_()) {
ignore_breakpoint_pc_ = 0xffffffffu;
return stop_reply_(2); // SIGINT
}
const uint32_t pc = target_.read_reg(32);
if (breakpoints_.count(pc) && !(first_iter && ignore_start_break && pc == start_pc)) {
ignore_breakpoint_pc_ = pc;
return stop_reply_(5); // SIGTRAP
}
const tb_gdb_run_result r = target_.step_instruction();
ignore_breakpoint_pc_ = 0xffffffffu;
if (r == tb_gdb_run_result::exited) {
// In extended-remote mode keep the connection alive after target exit,
// so the user can issue monitor commands such as "reset halt".
return stop_reply_exit_();
}
if (r == tb_gdb_run_result::timed_out) {
should_close = true;
return stop_reply_(14); // SIGALRM
}
first_iter = false;
}
};
if (cmd.size() >= 1 && (cmd[0] == 'c' || cmd[0] == 's')) {
// Optional resume address
if (cmd.size() > 1) {
uint32_t addr = 0;
if (!parse_u32_hex_(cmd.substr(1), addr))
return "E01";
target_.write_reg(32, addr);
}
return do_step(cmd[0] == 's');
}
if (cmd.rfind("vCont;", 0) == 0) {
if (cmd == "vCont;c")
return do_step(false);
if (cmd == "vCont;s")
return do_step(true);
return "";
}
// Detach / kill
if (cmd == "D") {
should_close = true;
return "OK";
}
if (cmd == "k") {
should_close = true;
return "OK";
}
// Unknown/unsupported -> empty response
return "";
}
tb_gdb_run_result tb_gdb_server::serve() {
if (!open_listen_socket_())
return tb_gdb_run_result::error;
fprintf(stderr, "tb_gdb: listening on 127.0.0.1:%u\n", (unsigned)port_);
if (!accept_client_())
return tb_gdb_run_result::error;
fprintf(stderr, "tb_gdb: client connected\n");
if (!send_packet_(stop_reply_(5)))
return tb_gdb_run_result::error;
while (true) {
std::string cmd;
bool got_interrupt = false;
if (!recv_packet_(cmd, got_interrupt))
break;
if (got_interrupt) {
if (!send_packet_(stop_reply_(2)))
break;
continue;
}
bool should_close = false;
std::string reply = handle_command_(cmd, should_close);
if (!send_packet_(reply))
break;
if (should_close) {
if (!reply.empty() && reply[0] == 'W')
return tb_gdb_run_result::exited;
break;
}
}
close_client_();
return tb_gdb_run_result::ok;
}