201 lines
6.5 KiB
C++
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(), ®_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(®_buffer[i * reg_width], &gpr[i], reg_width);
|
|
}
|
|
WORD_T pc = cpu->get_pc();
|
|
std::memcpy(®_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
|