libanemo/include/libsdb/gdb_server.hh

201 lines
6.5 KiB
C++

#ifndef LIBSDB_GDB_SERVER_HH
#define LIBSDB_GDB_SERVER_HH
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <unordered_set>
#include <vector>
#include <libanemo/log.hh>
#include <libanemo/width.hh>
#include <libcpu/abstract_cpu.hh>
namespace libsdb {
class gdb_server_base {
public:
enum class action_t { continue_, step, kill };
virtual ~gdb_server_base();
protected:
gdb_server_base();
bool create_server(int port);
bool accept_client();
void close_client();
void close_server();
action_t process();
virtual std::vector<uint8_t> read_all_regs() = 0;
virtual std::vector<uint8_t> read_reg(size_t index) = 0;
virtual std::vector<uint8_t> read_mem(size_t addr, size_t len) = 0;
virtual void set_breakpoint(size_t addr) = 0;
virtual void clear_breakpoint(size_t addr) = 0;
virtual void on_kill() = 0;
bool signal_on_entry;
void send_packet(const char* data);
void send_packet(const std::string& data) { send_packet(data.c_str()); }
std::string recv_packet();
static void compute_checksum(const char* data, size_t len, char out[3]);
static void hex_encode_byte(uint8_t val, char out[2]);
void send_hex_data(const std::vector<uint8_t>& data);
void send_signal();
void process_breakpoint(const std::string& pkt, bool set);
int server_fd;
int client_fd;
};
template <typename WORD_T>
class gdb_server : public gdb_server_base {
public:
explicit gdb_server(libcpu::abstract_cpu<WORD_T>* cpu)
: cpu(cpu), reg_buffer((cpu->n_gpr() + 1) * sizeof(WORD_T)) {}
void listen(int port) {
#ifdef __unix__
if (!create_server(port)) {
libanemo::log_error("gdb_server", "Failed to create server socket on port %d\n", port);
return;
}
libanemo::log_info("gdb_server", "Listening on port %d\n", port);
if (!accept_client()) {
libanemo::log_error("gdb_server", "Failed to accept client connection\n");
close_server();
return;
}
bool running = true;
while (running) {
copy_cpu_to_regs();
signal_on_entry = true;
action_t action = process();
if (action == action_t::kill) {
running = false;
} else if (action == action_t::continue_) {
WORD_T pc = cpu->get_pc();
libanemo::log_info("gdb_server", "Continuing execution from 0x%llx\n", static_cast<unsigned long long>(pc));
bool skipped = breakpoints.erase(pc) > 0;
cpu->next_instruction();
if (skipped) breakpoints.insert(pc);
while (!cpu->stopped() && !check_breakpoint()) {
cpu->next_instruction();
}
if (cpu->stopped()) {
libanemo::log_info("gdb_server", "CPU stopped (ebreak/trap) at 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
}
} else if (action == action_t::step) {
WORD_T pc = cpu->get_pc();
libanemo::log_info("gdb_server", "Single stepping from 0x%llx\n", static_cast<unsigned long long>(pc));
bool skipped = breakpoints.erase(pc) > 0;
cpu->next_instruction();
if (skipped) breakpoints.insert(pc);
if (cpu->stopped()) {
libanemo::log_info("gdb_server", "CPU stopped (ebreak/trap) at 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
} else if (check_breakpoint()) {
// already logged
} else {
libanemo::log_info("gdb_server", "Stepped to 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
}
}
}
close_client();
libanemo::log_info("gdb_server", "GDB disconnected\n");
close_server();
#else
libanemo::log_critical("gdb_server", "gdbserver is not supported on this platform\n");
#endif
}
std::vector<uint8_t> read_all_regs() override {
copy_cpu_to_regs();
std::vector<uint8_t> result(reg_buffer.size());
std::memcpy(result.data(), reg_buffer.data(), reg_buffer.size());
return result;
}
std::vector<uint8_t> read_reg(size_t index) override {
size_t reg_width = sizeof(WORD_T);
size_t num_gpr = cpu->n_gpr();
if (index >= num_gpr + 1) {
return {};
}
copy_cpu_to_regs();
std::vector<uint8_t> result(reg_width);
std::memcpy(result.data(), &reg_buffer[index * reg_width], reg_width);
return result;
}
std::vector<uint8_t> read_mem(size_t addr, size_t len) override {
std::vector<uint8_t> result;
result.reserve(len);
for (size_t i = 0; i < len; ++i) {
auto val = cpu->vmem_peek(static_cast<WORD_T>(addr + i), libanemo::width_t::byte);
if (val.has_value()) {
result.push_back(static_cast<uint8_t>(val.value()));
} else {
result.push_back(0);
}
}
return result;
}
void set_breakpoint(size_t addr) override {
WORD_T waddr = static_cast<WORD_T>(addr);
if (breakpoints.insert(waddr).second) {
libanemo::log_info("gdb_server", "Breakpoint set at 0x%llx\n", static_cast<unsigned long long>(waddr));
}
}
void clear_breakpoint(size_t addr) override {
WORD_T waddr = static_cast<WORD_T>(addr);
if (breakpoints.erase(waddr) > 0) {
libanemo::log_info("gdb_server", "Breakpoint cleared at 0x%llx\n", static_cast<unsigned long long>(waddr));
}
}
void on_kill() override {
libanemo::log_info("gdb_server", "Kill session requested by GDB\n");
}
private:
libcpu::abstract_cpu<WORD_T>* cpu;
std::vector<char> reg_buffer;
std::unordered_set<WORD_T> breakpoints;
void copy_cpu_to_regs() {
const WORD_T* gpr = cpu->get_gpr();
size_t reg_width = sizeof(WORD_T);
size_t n = cpu->n_gpr();
for (size_t i = 0; i < n; ++i) {
std::memcpy(&reg_buffer[i * reg_width], &gpr[i], reg_width);
}
WORD_T pc = cpu->get_pc();
std::memcpy(&reg_buffer[n * reg_width], &pc, reg_width);
}
bool check_breakpoint() {
WORD_T pc = cpu->get_pc();
if (breakpoints.count(pc) > 0) {
libanemo::log_info("gdb_server", "Breakpoint hit at 0x%llx\n", static_cast<unsigned long long>(pc));
return true;
}
return false;
}
};
} // namespace libsdb
#endif