aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--c_emulator/gdb_remote/gdb_arch.h8
-rw-r--r--c_emulator/gdb_remote/gdb_arch_riscv.c155
-rw-r--r--c_emulator/gdb_remote/gdb_rsp.c624
-rw-r--r--c_emulator/gdb_remote/gdb_rsp.h10
-rw-r--r--c_emulator/gdb_remote/gdb_utils.c131
-rw-r--r--c_emulator/gdb_remote/gdb_utils.h87
-rw-r--r--c_emulator/riscv_sim.c67
-rw-r--r--debugger/Makefile10
-rw-r--r--debugger/README.md53
-rw-r--r--debugger/rot13.c25
-rw-r--r--debugger/spike.lds8
12 files changed, 1167 insertions, 13 deletions
diff --git a/Makefile b/Makefile
index 1c38953..e4bf607 100644
--- a/Makefile
+++ b/Makefile
@@ -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, '#', &regno) < 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, '=', &regno) < 0) {
+ dprintf(conn->log_fd, "internal error: no 'P' packet terminator '=' found\n");
+ exit(1);
+ }
+ ofs++;
+ if (extract_hex_integer_le(conn, req, &ofs, '#', &regval) < 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) }
+}