680 lines
19 KiB
C++
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;
|
|
}
|