diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_arch.h | 8 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_arch_riscv.c | 155 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_rsp.c | 624 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_rsp.h | 10 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_utils.c | 131 | ||||
-rw-r--r-- | c_emulator/gdb_remote/gdb_utils.h | 87 | ||||
-rw-r--r-- | c_emulator/riscv_sim.c | 67 | ||||
-rw-r--r-- | debugger/Makefile | 10 | ||||
-rw-r--r-- | debugger/README.md | 53 | ||||
-rw-r--r-- | debugger/rot13.c | 25 | ||||
-rw-r--r-- | debugger/spike.lds | 8 |
12 files changed, 1167 insertions, 13 deletions
@@ -111,6 +111,8 @@ C_WARNINGS ?= #-Wall -Wextra -Wno-unused-label -Wno-unused-parameter -Wno-unused-but-set-variable -Wno-unused-function C_INCS = $(addprefix c_emulator/,riscv_prelude.h riscv_platform_impl.h riscv_platform.h riscv_softfloat.h) C_SRCS = $(addprefix c_emulator/,riscv_prelude.c riscv_platform_impl.c riscv_platform.c riscv_softfloat.c riscv_sim.c) +C_INCS += $(addprefix c_emulator/gdb_remote/,gdb_utils.h gdb_arch.h gdb_rsp.h) +C_SRCS += $(addprefix c_emulator/gdb_remote/,gdb_utils.c gdb_arch_riscv.c gdb_rsp.c) # portability for MacPorts/MacOS C_SYS_INCLUDES = -I /opt/local/include diff --git a/c_emulator/gdb_remote/gdb_arch.h b/c_emulator/gdb_remote/gdb_arch.h new file mode 100644 index 0000000..229a2a7 --- /dev/null +++ b/c_emulator/gdb_remote/gdb_arch.h @@ -0,0 +1,8 @@ +#ifndef _GDB_ARCH_H_ +#define _GDB_ARCH_H_ + +#include "gdb_utils.h" + +struct sail_arch *get_gdb_riscv_arch(); + +#endif // _GDB_ARCH_H_ diff --git a/c_emulator/gdb_remote/gdb_arch_riscv.c b/c_emulator/gdb_remote/gdb_arch_riscv.c new file mode 100644 index 0000000..de7f008 --- /dev/null +++ b/c_emulator/gdb_remote/gdb_arch_riscv.c @@ -0,0 +1,155 @@ +#include "riscv_platform.h" +#include "riscv_platform_impl.h" +#include "riscv_sail.h" +#include "gdb_utils.h" +#include "gdb_arch.h" + +mach_bits riscv_get_reg(struct rsp_conn *conn, struct sail_arch *arch, uint64_t regno) { + switch (regno) { + case 0: + return 0; + +#define case_reg(rno) \ + case rno: \ + return zx ## rno \ + + case_reg(1); + case_reg(2); + case_reg(3); + case_reg(4); + case_reg(5); + case_reg(6); + case_reg(7); + case_reg(8); + case_reg(9); + case_reg(10); + case_reg(11); + case_reg(12); + case_reg(13); + case_reg(14); + case_reg(15); + case_reg(16); + case_reg(17); + case_reg(18); + case_reg(19); + case_reg(20); + case_reg(21); + case_reg(22); + case_reg(23); + case_reg(24); + case_reg(25); + case_reg(26); + case_reg(27); + case_reg(28); + case_reg(29); + case_reg(30); + case_reg(31); + +#undef case_reg + + case 32: + return zPC; + + default: + dprintf(conn->log_fd, "unrecognized register number %ld\n", regno); + conn_exit(conn, 1); + return 0; + } +} + +void riscv_set_reg(struct rsp_conn *conn, struct sail_arch *arch, uint64_t regno, mach_bits regval) { + switch (regno) { + case 0: + dprintf(conn->log_fd, "ignoring attempt to write $zero\n"); + break; + +#define case_reg(reg) \ + case reg: \ + zx ## reg = regval; \ + break + + case_reg(1); + case_reg(2); + case_reg(3); + case_reg(4); + case_reg(5); + case_reg(6); + case_reg(7); + case_reg(8); + case_reg(9); + case_reg(10); + case_reg(11); + case_reg(12); + case_reg(13); + case_reg(14); + case_reg(15); + case_reg(16); + case_reg(17); + case_reg(18); + case_reg(19); + case_reg(20); + case_reg(21); + case_reg(22); + case_reg(23); + case_reg(24); + case_reg(25); + case_reg(26); + case_reg(27); + case_reg(28); + case_reg(29); + case_reg(30); + case_reg(31); + +#undef case_reg + + case 32: + zPC = regval; + break; + + default: + dprintf(conn->log_fd, "unrecognized register number %ld\n", regno); + exit(1); + } +} + +mach_bits riscv_get_pc(struct rsp_conn *conn, struct sail_arch *arch) { + return zPC; +} + +void riscv_set_pc(struct rsp_conn *conn, struct sail_arch *arch, mach_bits regval) { + zPC = regval; +} + +int riscv_step(struct rsp_conn *conn, struct sail_arch *arch) { + // step at current address + sail_int sail_step; + bool stepped; + CREATE(sail_int)(&sail_step); + CONVERT_OF(sail_int, mach_int)(&sail_step, conn->model.step_no); + stepped = zstep(sail_step); + KILL(sail_int)(&sail_step); + if (have_exception) { + dprintf(conn->log_fd, "internal Sail exception, exiting!\n"); + conn_exit(conn, 1); + } + if (stepped) conn->model.step_no++; + return 0; +} + +bool riscv_is_done(struct rsp_conn *conn, struct sail_arch *arch) { + // fixme: we may not have a legal zhtif! + return zhtif_done; +} + +struct sail_arch *get_gdb_riscv_arch() { + struct sail_arch *arch = (struct sail_arch *)malloc(sizeof(*arch)); + arch->archlen = (zxlen_val == 32) ? ARCH32 : ARCH64; // only RV32 or RV64 for now + arch->nregs = 32; + arch->get_reg = riscv_get_reg; + arch->get_pc = riscv_get_pc; + arch->set_reg = riscv_set_reg; + arch->set_pc = riscv_set_pc; + arch->step = riscv_step; + arch->is_done = riscv_is_done; + return arch; +} diff --git a/c_emulator/gdb_remote/gdb_rsp.c b/c_emulator/gdb_remote/gdb_rsp.c new file mode 100644 index 0000000..55a775a --- /dev/null +++ b/c_emulator/gdb_remote/gdb_rsp.c @@ -0,0 +1,624 @@ +#include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include "elf.h" +#include "sail.h" +#include "rts.h" +#include "gdb_utils.h" +#include "gdb_arch.h" + +// to develop and debug the protocol, set the debug flag on gdb: +// +// (gdb) set debug remote 1 +// (gdb) target remote localhost:<port> + +struct rsp_conn *gdb_server_init(int port, int log_fd) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + int opt = 1; + if (sock < 0) { + dprintf(log_fd, "Unable to open socket for port %d: %s\n", port, strerror(errno)); + return NULL; + } + if (setsockopt(sock, SOL_SOCKET, (SO_REUSEPORT | SO_REUSEADDR), (char *)&opt, sizeof(opt)) < 0) { + dprintf(log_fd, "Cannot set reuse option on socket: %s\n", strerror(errno)); + close(sock); + return NULL; + } + + struct sockaddr_in saddr; + memset(&saddr, 0, sizeof(saddr)); + saddr.sin_family = AF_INET; + saddr.sin_addr.s_addr = htonl(INADDR_ANY); + saddr.sin_port = htons(port); + if (bind(sock, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) { + dprintf(log_fd, "Unable to bind to port %d: %s\n", port, strerror(errno)); + close(sock); + return NULL; + } + if (listen(sock, 2) < 0) { + dprintf(log_fd, "Unable to listen on socket: %s\n", strerror(errno)); + close(sock); + return NULL; + } + struct rsp_conn *conn = (struct rsp_conn *)malloc(sizeof(*conn)); + if (conn == NULL) { + close(sock); + return NULL; + } + dprintf(log_fd, "listening on port %d\n", port); + conn->listen_fd = sock; + conn->log_fd = log_fd; + conn->proto.send_acks = 1; // start out sending acks + init_gdb_model(&conn->model); + return conn; +} + +void gdb_server_set_arch(struct rsp_conn *conn, struct sail_arch *arch) { + conn->arch = arch; +} + +// breakpoint utils +static int insert_breakpoint(struct rsp_conn *conn, uint64_t addr, uint64_t kind) { + assert(kind == 2 || kind == 4); + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + struct sw_breakpoint *bpt = &conn->model.breakpoints[i]; + if (!bpt->active) { + bpt->addr = addr; + bpt->kind = (int)kind; + bpt->active = 1; + return 0; + } + } + return -1; +} + +static int remove_breakpoint(struct rsp_conn *conn, uint64_t addr, uint64_t kind) { + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + struct sw_breakpoint *bpt = &conn->model.breakpoints[i]; + if (!bpt->active || bpt->addr != addr) continue; + if (bpt->kind != kind) { + dprintf(conn->log_fd, "mismatched breakpoint kind: have %d, was given %ld!\n", + bpt->kind, kind); + return -1; + } + bpt->active = 0; + return 0; + } + return -1; +} + +static int match_breakpoint(struct rsp_conn *conn, uint64_t addr) { + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + struct sw_breakpoint *bpt = &conn->model.breakpoints[i]; + if (bpt->active && bpt->addr == addr) return 1; + } + return 0; +} + +// request and response handling + +static struct rsp_buf req = { NULL, 0, 0 }; +static struct rsp_buf resp = { NULL, 0, 0 }; + +static int expect_interrupt_req = 0; +static int interrupt_reqs = 0; // interrupts are supposed to be queued + +static int read_req(struct rsp_conn *conn, struct rsp_buf *req) { + static char buf[BUFSZ+1]; + + int done = 0; + int saw_start = 0; + int chksum_bytes = 0; + int out_ofs = 0; + int nbytes; + + while (1) { + do_read: + nbytes = read(conn->conn_fd, buf, BUFSZ); + if (nbytes < 0) { + if (!saw_start && errno == EAGAIN) return 0; + dprintf(conn->log_fd, "[dbg] Error reading socket: %s\n", strerror(errno)); + conn_exit(conn, 1); + } + dprintf(conn->log_fd, "[dbg] <- "); + for (int i = 0; i < nbytes; i++) { + dprintf(conn->log_fd, "%c", buf[i]); + } + dprintf(conn->log_fd, "\n"); + + if (!saw_start && nbytes == 0) return 0; + + int ofs = 0; + if (!saw_start) { + /* wait for the '$' */ + while (buf[ofs] != '$') { + if (expect_interrupt_req && (buf[ofs] == 0x3)) { // 0x3 == ^c + interrupt_reqs++; + return 1; + } + ofs++; + if (ofs == nbytes) { + goto do_read; + } + } + saw_start = 1; + ofs++; + } + + /* start copying to cmd-buf until the 3-byte checksum field (including #)*/ + while (chksum_bytes < 3 && ofs < nbytes) { + if (out_ofs == req->bufsz) grow_rsp_buf(conn, req); + req->cmd_buf[out_ofs++] = buf[ofs]; + + if (buf[ofs] == '#') chksum_bytes = 1; // checksum started + else if (chksum_bytes > 0) chksum_bytes++; + ofs++; + } + if (chksum_bytes == 3) { + // todo: checksum check. is it really needed? + req->cmd_buf[out_ofs] = 0; + break; + } + } + return 1; +} + +static int match_req_cmd(struct rsp_buf *req, const char *cmd) { + return !strncmp(req->cmd_buf, cmd, strlen(cmd)); +} + +static void prepare_resp(struct rsp_conn *conn, struct rsp_buf *r) { + if (conn->proto.send_acks) { + append_rsp_buf_msg(conn, r, "+$"); + } else { + append_rsp_buf_msg(conn, r, "$"); + } +} +static void make_empty_resp(struct rsp_conn *conn, struct rsp_buf *r) { +} +static void make_ok_resp(struct rsp_conn *conn, struct rsp_buf *r) { + append_rsp_buf_msg(conn, r, "OK"); +} +static void make_error_resp(struct rsp_conn *conn, struct rsp_buf *r, unsigned char err) { + while (r->bufofs + 3 > r->bufsz) { + grow_rsp_buf(conn, r); + } + r->cmd_buf[r->bufofs++] = 'E'; + append_rsp_buf_hex_byte(conn, r, err); +} + +static void handle_query(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + if (match_req_cmd(req, "qSupported:")) { + append_rsp_buf_msg(conn, resp, "multiprocess-;swbreak-;hwbreak-;qRelocInsn-"); + append_rsp_buf_msg(conn, resp, ";fork-events-;vfork-events-;exec-events-"); + append_rsp_buf_msg(conn, resp, ";vContSupported-;QThreadEvents-;no-resumed-"); + append_rsp_buf_msg(conn, resp, ";PacketSize=1024"); + + // offer no-ack mode + append_rsp_buf_msg(conn, resp, ";QStartNoAckMode+"); + return; + } + if (match_req_cmd(req, "QStartNoAckMode")) { + make_ok_resp(conn, resp); + conn->proto.send_acks = 0; + return; + } + if (match_req_cmd(req, "qOffsets")) { + append_rsp_buf_msg(conn, resp, "Text=0;Data=0;Bss=0"); // shouldn't this come from the memory map? + return; + } + if (match_req_cmd(req, "qC")) { + append_rsp_buf_msg(conn, resp, "QC0"); + return; + } + if (match_req_cmd(req, "qAttached")) { + // make sure there is no unexpected pid + if (match_req_cmd(req, "qAttached") || match_req_cmd(req, "qAttached:1")) { + append_rsp_buf_msg(conn, resp, "1"); + return; + } else { + make_error_resp(conn, resp, 2); // no processes + } + } + if (match_req_cmd(req, "qfThreadInfo")) { + append_rsp_buf_msg(conn, resp, "m0"); // no threads + return; + } + if (match_req_cmd(req, "qsThreadInfo")) { + append_rsp_buf_msg(conn, resp, "l"); + return; + } + if (match_req_cmd(req, "qSymbol::")) { + make_ok_resp(conn, resp); + return; + } + make_empty_resp(conn, resp); +} + +static void handle_vmsgs(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + // The only one that makes sense to handle is 'vCtrlC', perhaps + // later. For now, treat all like we treat 'vMustReplyEmpty'. + make_empty_resp(conn, resp); + return; +} + +static void handle_set_context(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + if (match_req_cmd(req, "H")) { + // we do not support thread-specific operations + if (req->cmd_buf[2] == '0' || (req->cmd_buf[2] == '-' && req->cmd_buf[3] == '1')) { + make_ok_resp(conn, resp); + return; + } + } + make_error_resp(conn, resp, 1); +} + +static void handle_stop_reply(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + switch (conn->model.run_state) { + case M_RUN_START: + { + /* this is equivalent to the interrupted signal code: SIGINT -> 2 */ + append_rsp_buf_msg(conn, resp, "S"); + append_rsp_buf_hex_byte(conn, resp, 2); + } + break; + case M_RUN_BREAKPOINT: + { + /* this is equivalent to the interrupted signal code: SIGTRAP -> 5 */ + append_rsp_buf_msg(conn, resp, "S"); + append_rsp_buf_hex_byte(conn, resp, 5); + } + break; + case M_RUN_RUNNING: + default: + // We shouldn't be getting this if we are running. + dprintf(conn->log_fd, "Unhandled stop reply for state %d\n", conn->model.run_state); + conn_exit(conn, 1); + break; + } +} + +static void send_reg_val(struct rsp_conn *conn, struct rsp_buf *r, mach_bits reg) { + struct sail_arch *arch = conn->arch; + int nbytes = (arch->archlen == ARCH32) ? 4 : 8; // only RV32 or RV64 for now + for (int i = 0; i < nbytes; i++) { + unsigned char c = (unsigned char) (reg & 0xff); + append_rsp_buf_hex_byte(conn, r, c); + reg >>= 8; + } +} + +static void handle_regs_read(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + mach_bits regval; + struct sail_arch *arch = conn->arch; + for (uint64_t i = 0; i <= arch->nregs; i++) { + regval = conn->arch->get_reg(conn, conn->arch, i); + send_reg_val(conn, resp, regval); + } +} + +static void handle_reg_read(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + // extract regno,regval + int ofs = 1; // past 'p' + uint64_t regno = 0; + if (extract_hex_integer_be(conn, req, &ofs, '#', ®no) < 0) { + dprintf(conn->log_fd, "internal error: no 'p' packet terminator '#' found\n"); + exit(1); + } + + uint64_t regval = conn->arch->get_reg(conn, conn->arch, regno); + dprintf(conn->log_fd, "read reg %ld as 0x%016" PRIx64 "\n", regno, regval); + send_reg_val(conn, resp, regval); +} + +static void handle_reg_write(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + // extract regno,regval + int ofs = 1; // past 'P' + uint64_t regno = 0, regval = 0; + if (extract_hex_integer_be(conn, req, &ofs, '=', ®no) < 0) { + dprintf(conn->log_fd, "internal error: no 'P' packet terminator '=' found\n"); + exit(1); + } + ofs++; + if (extract_hex_integer_le(conn, req, &ofs, '#', ®val) < 0) { + dprintf(conn->log_fd, "internal error: no 'P' packet terminator '#' found\n"); + exit(1); + } + + dprintf(conn->log_fd, "setting reg %ld to 0x%016" PRIx64 "\n", regno, regval); + conn->arch->set_reg(conn, conn->arch, regno, regval); + + make_ok_resp(conn, resp); +} + +static void handle_step(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + if (match_req_cmd(req, "s#")) { + conn->arch->step(conn, conn->arch); + /* stopping after a single step is equivalent to the interrupted signal code: SIGTRAP -> 5 */ + append_rsp_buf_msg(conn, resp, "S"); + append_rsp_buf_hex_byte(conn, resp, 5); + return; + } + make_error_resp(conn, resp, 1); +} + +static void handle_read_mem(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + // extract addr,length + int ofs = 1; // past 'm' + uint64_t addr = 0, len = 0; + if (extract_hex_integer_be(conn, req, &ofs, ',', &addr) < 0) { + dprintf(conn->log_fd, "internal error: no 'm' packet terminator ',' found\n"); + exit(1); + } + ofs++; + if (extract_hex_integer_be(conn, req, &ofs, '#', &len) < 0) { + dprintf(conn->log_fd, "internal error: no 'm' packet terminator '#' found\n"); + exit(1); + } + ofs++; + // trust gdb for legal addr/len? + for (uint64_t i = 0; i < len; i++) { + unsigned char byte = (unsigned char) read_mem(addr+i); + append_rsp_buf_hex_byte(conn, resp, byte); + } +} + +static void handle_write_mem(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + // extract addr,length + int ofs = 1; // past 'M' + uint64_t addr = 0, len = 0; + if (extract_hex_integer_be(conn, req, &ofs, ',', &addr) < 0) { + dprintf(conn->log_fd, "internal error: no 'M' packet terminator ',' found\n"); + exit(1); + } + ofs++; + if (extract_hex_integer_be(conn, req, &ofs, ':', &len) < 0) { + dprintf(conn->log_fd, "internal error: no 'M' packet terminator ':' found\n"); + exit(1); + } + ofs++; + // trust gdb for legal addr/len? + for (uint64_t i = 0; i < len; i++) { + if (ofs >= req->bufsz) { + dprintf(conn->log_fd, "not enough payload in 'M' packet!\n"); + exit(1); + } + uint64_t byte = int_of_hex(req->cmd_buf[ofs++]); + byte <<= 4; + byte += int_of_hex(req->cmd_buf[ofs++]); + write_mem(addr++, byte); + } + make_ok_resp(conn, resp); +} + +static void handle_cont(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + if (match_req_cmd(req, "c#")) { // todo: handle pc argument + // allow interrupt requests + expect_interrupt_req = 1; + int step_cnt = 0; + conn->model.run_state = M_RUN_RUNNING; + struct sail_arch *arch = conn->arch; + // continue at current address + while (!arch->is_done(conn, arch)) { + if (arch->step(conn, arch)) { + dprintf(conn->log_fd, "unable to step model, exiting.\n"); + conn_exit(conn, 1); + } + + mach_bits pc = arch->get_pc(conn, arch); + if (match_breakpoint(conn, pc)) { + conn->model.run_state = M_RUN_BREAKPOINT; + handle_stop_reply(conn, req, resp); + break; + } + step_cnt++; + if (step_cnt == 50) { + step_cnt = 0; + if (read_req(conn, req)) { + if (interrupt_reqs > 0) { + --interrupt_reqs; + dprintf(conn->log_fd, "interrupted, breaking\n"); + } else { + dprintf(conn->log_fd, "got another request during cont, breaking\n"); + } + conn->model.run_state = M_RUN_BREAKPOINT; + // send a stop-reply as above + handle_stop_reply(conn, req, resp); + break; + } + } + } + // disable interrupt requests + expect_interrupt_req = 0; + return; + } + make_error_resp(conn, resp, 1); +} + +static void handle_sw_breakpoint(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + if (match_req_cmd(req, "Z0,") || match_req_cmd(req, "z0,")) { // software breakpoint + // extract address and kind + uint64_t addr = 0, kind = 0; + int ofs = 3; // past Z0, + if (extract_hex_integer_be(conn, req, &ofs, ',', &addr) < 0) { + dprintf(conn->log_fd, "internal error: no '{Zz}0' packet terminator ',' found\n"); + exit(1); + } + + ofs++; + switch (req->cmd_buf[ofs]) { + case '2': + kind = 2; + break; + case '4': + kind = 4; + break; + default: + dprintf(conn->log_fd, "error: unexpected '{Zz}0' kind found\n"); + make_error_resp(conn, resp, 1); + return; + } + + ofs++; + switch (req->cmd_buf[ofs]) { + case '#': + // expected case, no byte-coded condition triggers + break; + case ';': + default: + dprintf(conn->log_fd, "conditional triggers for breakpoints not supported\n"); + make_empty_resp(conn, resp); + return; + } + + if (req->cmd_buf[0] == 'Z') { + dprintf(conn->log_fd, "setting breakpoint at addr=0x%016" PRIx64 " of kind %ld\n", + addr, kind); + if (insert_breakpoint(conn, addr, kind) < 0) { + dprintf(conn->log_fd, "out of breakpoint slots!\n"); + make_error_resp(conn, resp, 1); + return; + } + } else { + dprintf(conn->log_fd, "removing breakpoint at addr=0x%016" PRIx64 " of kind %ld\n", + addr, kind); + if (remove_breakpoint(conn, addr, kind) < 0) { + make_error_resp(conn, resp, 1); + return; + } + } + + make_ok_resp(conn, resp); + return; + } + make_empty_resp(conn, resp); +} + +static void dispatch_req(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + prepare_resp(conn, resp); + switch (req->cmd_buf[0]) { + case 'q': + case 'Q': + handle_query(conn, req, resp); + break; + case 'v': + handle_vmsgs(conn, req, resp); + break; + case 'g': + handle_regs_read(conn, req, resp); + break; + case 'p': + handle_reg_read(conn, req, resp); + break; + case 'P': + handle_reg_write(conn, req, resp); + break; + case 'H': + handle_set_context(conn, req, resp); + break; + case '?': + handle_stop_reply(conn, req, resp); + break; + case 'D': + dprintf(conn->log_fd, "GDB disconnecting, exiting.\n"); + /* wait for another connection? */ + conn_exit(conn, 0); + break; + case 's': + handle_step(conn, req, resp); + break; + case 'm': + handle_read_mem(conn, req, resp); + break; + case 'M': + handle_write_mem(conn, req, resp); + break; + case 'c': + handle_cont(conn, req, resp); + break; + case 'Z': + case 'z': + handle_sw_breakpoint(conn, req, resp); + break; + case 'X': + // force use of 'M': send an empty response + make_empty_resp(conn, resp); + break; + default: + dprintf(conn->log_fd, "Unsupported cmd %c\n", req->cmd_buf[0]); + conn_exit(conn, 1); + } +} + +static void resp_set_checksum(struct rsp_conn *conn, struct rsp_buf *r) { + unsigned char chksum = 0; + int i = 0; + while (r->cmd_buf[i] != '$') { i++; assert(i < r->bufofs); } + for (i++; i < r->bufofs; i++) { + chksum += r->cmd_buf[i]; + } + while (r->bufofs + 3 > r->bufsz) { + grow_rsp_buf(conn, r); + } + r->cmd_buf[r->bufofs++] = '#'; + push_hex_byte(r->cmd_buf + r->bufofs, chksum); + r->bufofs += 2; +} + +static void send_resp(struct rsp_conn *conn, struct rsp_buf *r) { + int nbytes = 0; + while (nbytes < r->bufofs) { + int n = write(conn->conn_fd, r->cmd_buf + nbytes, r->bufofs - nbytes); + if (n < 0) { + dprintf(conn->log_fd, "[dbg] error sending response: %s\n", strerror(errno)); + conn_exit(conn, 1); + } + + dprintf(conn->log_fd, "[dbg] -> "); + for (int i = 0; i < n; i++) { + dprintf(conn->log_fd, "%c", r->cmd_buf[nbytes + i]); + } + dprintf(conn->log_fd, "\n"); + + nbytes += n; + } +} + +static void gdb_server_dispatch(struct rsp_conn *conn, struct rsp_buf *req, struct rsp_buf *resp) { + while (1) { + while (!read_req(conn, req)) // should move to a select loop :p + ; + + dispatch_req(conn, req, resp); + + resp_set_checksum(conn, resp); + send_resp(conn, resp); + + req->bufofs = 0; + resp->bufofs = 0; + } +} + +void gdb_server_run(struct rsp_conn *conn) { + if ((conn->conn_fd = accept(conn->listen_fd, (struct sockaddr *)NULL, NULL)) < 0) { + dprintf(conn->log_fd, "[dbg] error accepting connection: %s\n", strerror(errno)); + exit(1); + } + if (fcntl(conn->conn_fd, F_SETFL, O_NONBLOCK) < 0) { + dprintf(conn->log_fd, "[dbg] error making connection non-blocking: %s\n", strerror(errno)); + exit(1); + } + + grow_rsp_buf(conn, &req); + grow_rsp_buf(conn, &resp); + gdb_server_dispatch(conn, &req, &resp); +} diff --git a/c_emulator/gdb_remote/gdb_rsp.h b/c_emulator/gdb_remote/gdb_rsp.h new file mode 100644 index 0000000..043af7d --- /dev/null +++ b/c_emulator/gdb_remote/gdb_rsp.h @@ -0,0 +1,10 @@ +#ifndef _GDB_RSP_H_ +#define _GDB_RSP_H_ + +#include "gdb_utils.h" + +struct rsp_conn *gdb_server_init(int port, int log_fd); +void gdb_server_set_arch(struct rsp_conn *conn, struct sail_arch *arch); +void gdb_server_run(struct rsp_conn *conn); + +#endif diff --git a/c_emulator/gdb_remote/gdb_utils.c b/c_emulator/gdb_remote/gdb_utils.c new file mode 100644 index 0000000..844b8e3 --- /dev/null +++ b/c_emulator/gdb_remote/gdb_utils.c @@ -0,0 +1,131 @@ +#include <assert.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <string.h> +#include <unistd.h> + +#include "sail.h" +#include "rts.h" +#include "gdb_utils.h" + +// hex utils + +int int_of_hex(char c) { + if ('0' <= c && c <= '9') { return c - '0'; } + else if ('a' <= c && c <= 'f') { return c - 'a' + 10; } + else if ('A' <= c && c <= 'F') { return c - 'A' + 10; } + else { return 0; } +} + +char hex_of_int(unsigned val) { + assert(val < 16); + if (val < 10) { return val + '0'; } + else { return val - 10 + 'a'; } +} + +void push_hex_byte(char *buf, uint8_t byte) { + buf[0] = hex_of_int(byte >> 4); + buf[1] = hex_of_int(byte & 0xf); +} + +// big-endian hex +int extract_hex_integer_be(struct rsp_conn *conn, struct rsp_buf *req, int *start_ofs, char terminator, uint64_t *val) { + *val = 0; + int i = *start_ofs; + while (true) { + if (i >= req->bufsz) return -1; + if (req->cmd_buf[i] == terminator) break; + *val <<= 4; + *val |= int_of_hex(req->cmd_buf[i]) & 0xf; + i++; + } + *start_ofs = i; + return 0; +} + +// little-endian hex +int extract_hex_integer_le(struct rsp_conn *conn, struct rsp_buf *req, int *start_ofs, char terminator, uint64_t *val) { + *val = 0; + int i = *start_ofs; + int shft = 0; + while (true) { + uint8_t byte; + if (i >= req->bufsz) return -1; + if (req->cmd_buf[i] == terminator) break; + + byte = int_of_hex(req->cmd_buf[i++]); + byte <<= 4; + + if (i >= req->bufsz) return -1; + if (req->cmd_buf[i] == terminator) return -1; // unexpected terminator in middle of byte + + byte |= int_of_hex(req->cmd_buf[i++]) & 0xf; + + *val |= byte << shft; + shft +=8; + } + *start_ofs = i; + return 0; +} + +// protocol message utilities + +void grow_rsp_buf(struct rsp_conn *conn, struct rsp_buf *b) { + if (b->cmd_buf == NULL) { + if ((b->cmd_buf = (char *)malloc(BUFSZ*sizeof(char) + 1)) == NULL) { + dprintf(conn->log_fd, "[dbg] unable to init cmd_buf\n"); + conn_exit(conn, 1); + } + b->bufofs = 0; + b->bufsz = BUFSZ; + return; + } + + b->cmd_buf = (char *)realloc(b->cmd_buf, 2*b->bufsz + 1); + if (b->cmd_buf == NULL) { + dprintf(conn->log_fd, "[dbg] cannot realloc to %d bytes, quitting!\n", 2*b->bufsz + 1); + conn_exit(conn, 1); + } + b->bufsz *= 2; +} + +void append_rsp_buf_bytes(struct rsp_conn *conn, struct rsp_buf *b, const char *msg, int mlen) { + while (b->bufofs + mlen >= b->bufsz) { + grow_rsp_buf(conn, b); + } + memcpy(b->cmd_buf + b->bufofs, msg, mlen); + b->bufofs += mlen; + b->cmd_buf[b->bufofs] = 0; +} + +void append_rsp_buf_msg(struct rsp_conn *conn, struct rsp_buf *b, const char *msg) { + append_rsp_buf_bytes(conn, b, msg, strlen(msg)); +} + +void append_rsp_buf_hex_byte(struct rsp_conn *conn, struct rsp_buf *b, unsigned char byte) { + while (b->bufofs + 2 >= b->bufsz) { + grow_rsp_buf(conn, b); + } + push_hex_byte(b->cmd_buf + b->bufofs, byte); + b->bufofs += 2; +} + + +// other state and connection utils + +void init_gdb_model(struct model_state *model) { + memset(model, 0, sizeof(*model)); + model->run_state = M_RUN_START; + model->step_no = 0; +} + +void conn_exit(struct rsp_conn *conn, int code) { + close(conn->conn_fd); + close(conn->listen_fd); + close(conn->log_fd); + exit(code); +} + + diff --git a/c_emulator/gdb_remote/gdb_utils.h b/c_emulator/gdb_remote/gdb_utils.h new file mode 100644 index 0000000..953a904 --- /dev/null +++ b/c_emulator/gdb_remote/gdb_utils.h @@ -0,0 +1,87 @@ +#ifndef _GDB_UTILS_H_ +#define _GDB_UTILS_H_ + +// protocol and connection state + +struct proto_state { + int send_acks; +}; + +typedef enum { + M_RUN_START, + M_RUN_RUNNING, + M_RUN_BREAKPOINT, + M_RUN_HALT // currently unused +} model_run_state_t; + +struct sw_breakpoint { + int active; // 0 -> inactive + int kind; // either 2 (c.ebreak) or 4 (ebreak) + uint64_t addr; // pc +}; + +#define MAX_BREAKPOINTS 64 + +struct model_state { + model_run_state_t run_state; + mach_int step_no; + struct sw_breakpoint breakpoints[MAX_BREAKPOINTS]; +}; + +struct rsp_conn { + int listen_fd; + int conn_fd; + int log_fd; + struct proto_state proto; + struct model_state model; + struct sail_arch *arch; +}; + +void init_gdb_model(struct model_state *model); +void conn_exit(struct rsp_conn *conn, int code); + +// protocol message buffers + +#define BUFSZ 2048 +struct rsp_buf { + char *cmd_buf; + int bufofs; + int bufsz; +}; + +void grow_rsp_buf(struct rsp_conn *conn, struct rsp_buf *b); +void append_rsp_buf_bytes(struct rsp_conn *conn, struct rsp_buf *b, const char *msg, int mlen); +void append_rsp_buf_msg(struct rsp_conn *conn, struct rsp_buf *b, const char *msg); +void append_rsp_buf_hex_byte(struct rsp_conn *conn, struct rsp_buf *b, unsigned char byte); + +// hex utils + +int int_of_hex(char c); +char hex_of_int(unsigned val); +void push_hex_byte(char *buf, uint8_t byte); + +// integers from endian-specific hex-encodings +int extract_hex_integer_be(struct rsp_conn *c, struct rsp_buf *r, int *start_ofs, char terminator, uint64_t *val); +int extract_hex_integer_le(struct rsp_conn *conn, struct rsp_buf *req, int *start_ofs, char terminator, uint64_t *val); + +// basic interface to the sail model. + +typedef enum { + ARCH32, + ARCH64, +} archlen_t; + +struct sail_arch { + archlen_t archlen; + uint64_t nregs; // inclusive bound, and typically includes the PC register + mach_bits (*get_reg)(struct rsp_conn *conn, struct sail_arch *arch, uint64_t regno); + mach_bits (*get_pc)(struct rsp_conn *conn, struct sail_arch *arch); + void (*set_reg) (struct rsp_conn *conn, struct sail_arch *arch, uint64_t regno, mach_bits regval); + void (*set_pc) (struct rsp_conn *conn, struct sail_arch *arch, mach_bits regval); + + int (*step) (struct rsp_conn *conn, struct sail_arch *arch); + bool (*is_done) (struct rsp_conn *conn, struct sail_arch *arch); + void *arch_info; +}; + +#endif // _GDB_UTILS_H_ diff --git a/c_emulator/riscv_sim.c b/c_emulator/riscv_sim.c index 841b249..74bda19 100644 --- a/c_emulator/riscv_sim.c +++ b/c_emulator/riscv_sim.c @@ -17,6 +17,8 @@ #include "riscv_platform.h" #include "riscv_platform_impl.h" #include "riscv_sail.h" +#include "gdb_remote/gdb_arch.h" +#include "gdb_remote/gdb_rsp.h" #ifdef ENABLE_SPIKE #include "tv_spike_intf.h" @@ -57,6 +59,8 @@ static int rvfi_dii_port; static int rvfi_dii_sock; #endif +int gdb_port = -1; + unsigned char *spike_dtb = NULL; size_t spike_dtb_len = 0; @@ -98,6 +102,7 @@ static struct option options[] = { {"enable-misaligned", no_argument, 0, 'm'}, {"enable-pmp", no_argument, 0, 'P'}, {"ram-size", required_argument, 0, 'z'}, + {"ram-base", required_argument, 0, 'B'}, {"disable-compressed", no_argument, 0, 'C'}, {"disable-writable-misa", no_argument, 0, 'I'}, {"disable-fdext", no_argument, 0, 'F'}, @@ -114,6 +119,7 @@ static struct option options[] = { {"trace", optional_argument, 0, 'v'}, {"no-trace", optional_argument, 0, 'V'}, {"inst-limit", required_argument, 0, 'l'}, + {"gdb-port", required_argument, 0, 'g'}, {0, 0, 0, 0} }; @@ -194,8 +200,8 @@ static void read_dtb(const char *path) char *process_args(int argc, char **argv) { - int c; - uint64_t ram_size = 0; + int c, idx = 1; + uint64_t ram_size = 0, ram_base = 0; while(true) { c = getopt_long(argc, argv, "a" @@ -207,6 +213,7 @@ char *process_args(int argc, char **argv) "s" "p" "z:" + "B:" "b:" "t:" "h" @@ -215,8 +222,9 @@ char *process_args(int argc, char **argv) "V::" "v::" "l:" + "g:" "F:" - , options, NULL); + , options, &idx); if (c == -1) break; switch (c) { case 'a': @@ -258,7 +266,7 @@ char *process_args(int argc, char **argv) do_show_times = true; break; case 'z': - ram_size = atol(optarg); + ram_size = strtoull(optarg, NULL, 0); if (ram_size) { fprintf(stderr, "setting ram-size to %" PRIu64 " MB\n", ram_size); rv_ram_size = ram_size << 20; @@ -267,6 +275,17 @@ char *process_args(int argc, char **argv) exit(1); } break; + case 'B': + ram_base = strtoull(optarg, NULL, 0); + if (ram_base) { + fprintf(stderr, "setting ram-base to 0x%" PRIx64 "\n", ram_base); + rv_ram_base = ram_base; + } else { + fprintf(stderr, "invalid ram-base '%s' provided.\n", optarg); + exit(1); + } + break; + case 'b': dtb_file = strdup(optarg); fprintf(stderr, "using %s as DTB file.\n", dtb_file); @@ -298,6 +317,9 @@ char *process_args(int argc, char **argv) case 'l': insn_limit = atoi(optarg); break; + case 'g': + gdb_port = atoi(optarg); + break; case '?': print_usage(argv[0], 1); break; @@ -346,7 +368,9 @@ uint64_t load_sail(char *f) /* locate htif ports */ if (lookup_sym(f, "tohost", &rv_htif_tohost) < 0) { fprintf(stderr, "Unable to locate htif tohost port.\n"); - exit(1); + + if (gdb_port < 0) + exit(1); } fprintf(stderr, "tohost located at 0x%0" PRIx64 "\n", rv_htif_tohost); /* locate test-signature locations if any */ @@ -422,7 +446,7 @@ void tick_spike() #endif } -void init_sail_reset_vector(uint64_t entry) +void init_sail_reset_vector(uint64_t entry, bool with_gdb) { #define RST_VEC_SIZE 8 uint32_t reset_vec[RST_VEC_SIZE] = { @@ -479,8 +503,8 @@ void init_sail_reset_vector(uint64_t entry) /* set rom size */ rv_rom_size = rom_end - rv_rom_base; - /* boot at reset vector */ - zPC = rv_rom_base; + /* boot at reset vector (but with gdb, start at ELF entry)*/ + zPC = with_gdb ? entry : rv_rom_base; } void preinit_sail() @@ -488,7 +512,7 @@ void preinit_sail() model_init(); } -void init_sail(uint64_t elf_entry) +void init_sail(uint64_t elf_entry, bool with_gdb) { zinit_model(UNIT); #ifdef RVFI_DII @@ -504,7 +528,7 @@ void init_sail(uint64_t elf_entry) zPC = elf_entry; } else #endif - init_sail_reset_vector(elf_entry); + init_sail_reset_vector(elf_entry, with_gdb); // this is probably unnecessary now; remove if (!rv_enable_rvc) z_set_Misa_C(&zmisa, 0); @@ -515,7 +539,7 @@ void reinit_sail(uint64_t elf_entry) { model_fini(); model_init(); - init_sail(elf_entry); + init_sail(elf_entry, false); } int init_check(struct tv_spike_t *s) @@ -725,6 +749,7 @@ void run_sail(void) CREATE(sail_int)(&sail_step); CONVERT_OF(sail_int, mach_int)(&sail_step, step_no); stepped = zstep(sail_step); + KILL(sail_int)(&sail_step); if (have_exception) goto step_exception; flush_logs(); KILL(sail_int)(&sail_step); @@ -736,9 +761,9 @@ void run_sail(void) CREATE(sail_int)(&sail_step); CONVERT_OF(sail_int, mach_int)(&sail_step, step_no); stepped = zstep(sail_step); + KILL(sail_int)(&sail_step); if (have_exception) goto step_exception; flush_logs(); - KILL(sail_int)(&sail_step); } if (stepped) { step_no++; @@ -881,7 +906,7 @@ int main(int argc, char **argv) * until we roll our own. */ init_spike(file, entry, rv_ram_size); - init_sail(entry); + init_sail(entry, (gdb_port > 0)); if (!init_check(s)) finish(1); @@ -890,6 +915,22 @@ int main(int argc, char **argv) exit(1); } + if (gdb_port > 0) { + struct rsp_conn *conn = gdb_server_init(gdb_port, 2); + if (conn == NULL) { + fprintf(stderr, "Cannot initialize gdb server, exiting.\n"); + exit(1); + } + struct sail_arch *arch = get_gdb_riscv_arch(); + if (arch == NULL) { + fprintf(stderr, "Cannot initialize sail interface, exiting.\n"); + exit(1); + } + gdb_server_set_arch(conn, arch); + gdb_server_run(conn); + exit(0); + } + do { run_sail(); #ifndef RVFI_DII diff --git a/debugger/Makefile b/debugger/Makefile new file mode 100644 index 0000000..5648579 --- /dev/null +++ b/debugger/Makefile @@ -0,0 +1,10 @@ +.PHONE: all clean + +all: rot13-64 + +rot13-64: rot13.c + riscv64-unknown-linux-gnu-gcc -g -Og -o rot13-64.o -c rot13.c + riscv64-unknown-linux-gnu-gcc -g -Og -T spike.lds -nostartfiles -o rot13-64 rot13-64.o + +clean: + -rm rot13-64 rot13-64.o diff --git a/debugger/README.md b/debugger/README.md new file mode 100644 index 0000000..86f7c16 --- /dev/null +++ b/debugger/README.md @@ -0,0 +1,53 @@ +The C emulator implements a very basic GDB remote server, allowing it +to be used as a remote target for GDB. + +The files in this directory can be used to test this feature, using +the example used in the spike RISC-V simulator. Make sure you have +the RISCV toolchain in your path. If you don't have a toolchain +already, you may need to build this toolchain using `crosstool`. + +From the top-level `sail-riscv` directory: + +``` +$ make -C debugger +$ ./c_emulator/riscv_sim_RV64 -g 9333 -B 0x10000000 -z 0x20000 debugger/rot13-64 +``` + +In another terminal, start gdb (built from the RISC-V toolchain) and +connect it to the emulator: + +``` +$ riscv64-unknown-linux-gnu-gdb debugger/rot13-64 +There is NO WARRANTY, to the extent permitted by law. +Type "show copying" and "show warranty" for details. +This GDB was configured as "--host=x86_64-build_pc-linux-gnu --target=riscv64-unknown-linux-gnu". +Type "show configuration" for configuration details. +For bug reporting instructions, please see: +<http://www.gnu.org/software/gdb/bugs/>. +Find the GDB manual and other documentation resources online at: + <http://www.gnu.org/software/gdb/documentation/>. + +For help, type "help". +Type "apropos word" to search for commands related to "word"... +Reading symbols from debugger/rot13-64...done. +(gdb) target remote localhost:9333 +Remote debugging using localhost:9333 +main () at rot13.c:9 +9 ; +(gdb) print wait +$1 = 1 +(gdb) print wait=0 +$2 = 0 +(gdb) print text +$3 = "Vafgehpgvba frgf jnag gb or serr!" +(gdb) b done +Breakpoint 1 at 0x10010062: file rot13.c, line 22. +(gdb) c +Continuing. + +Breakpoint 1, main () at rot13.c:24 +24 ; +(gdb) p wait +$4 = 0 + +``` diff --git a/debugger/rot13.c b/debugger/rot13.c new file mode 100644 index 0000000..3a150c8 --- /dev/null +++ b/debugger/rot13.c @@ -0,0 +1,25 @@ +char text[] = "Vafgehpgvba frgf jnag gb or serr!"; + +// Don't use the stack, because sp isn't set up. +volatile int wait = 1; + +int main() +{ + while (wait) + ; + + // Doesn't actually go on the stack, because there are lots of GPRs. + int i = 0; + while (text[i]) { + char lower = text[i] | 32; + if (lower >= 'a' && lower <= 'm') + text[i] += 13; + else if (lower > 'm' && lower <= 'z') + text[i] -= 13; + i++; + } + + done: + while (!wait) + ; +} diff --git a/debugger/spike.lds b/debugger/spike.lds new file mode 100644 index 0000000..f45b1ca --- /dev/null +++ b/debugger/spike.lds @@ -0,0 +1,8 @@ +OUTPUT_ARCH( "riscv" ) + +SECTIONS +{ + . = 0x10010000; + .text : { *(.text) } + .data : { *(.data) } +} |