diff --git a/.gitignore b/.gitignore index aab70dd0..345b4f13 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ build/gfx.wad # built objects build/rv32emu build/arch-test +build/mini-gdbstub *.o *.o.d diff --git a/.gitmodules b/.gitmodules index 4f1f3e2f..39cd81c9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "riscv-arch-test"] path = tests/riscv-arch-test url = https://github.com/riscv-non-isa/riscv-arch-test +[submodule "mini-gdbstub"] + path = mini-gdbstub + url = https://github.com/RinHizakura/mini-gdbstub diff --git a/Makefile b/Makefile index 577946eb..79483b35 100644 --- a/Makefile +++ b/Makefile @@ -55,6 +55,27 @@ $(OUT)/emulate.o: CFLAGS += -fno-gcse -fno-crossjumping endif endif +ENABLE_GDBSTUB ?= 1 +ifeq ("$(ENABLE_GDBSTUB)", "1") +MINI_GDBSTUB_OUT = $(abspath $(OUT)/mini-gdbstub) +GDBSTUB_COMM = 127.0.0.1:1234 +LIB_GDBSTUB += $(MINI_GDBSTUB_OUT)/libgdbstub.a +gdbstub-test: $(BIN) + ./tests/gdbstub-test/main.sh + +$(LIB_GDBSTUB): + git submodule update --init mini-gdbstub + $(MAKE) -C mini-gdbstub O=$(MINI_GDBSTUB_OUT) +$(OUT)/emulate.o: $(LIB_GDBSTUB) +OBJS_EXT += gdbstub.o +CFLAGS += -D ENABLE_GDBSTUB -D'GDBSTUB_COMM="$(GDBSTUB_COMM)"' +LDFLAGS += $(LIB_GDBSTUB) +endif + +# Clear the .DEFAULT_GOAL special variable, so that the following turns +# to the first target after .DEFAULT_GOAL is not set. +.DEFAULT_GOAL := + all: $(BIN) OBJS := \ diff --git a/README.md b/README.md index ef7947c5..0a0cdea6 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,45 @@ Detail in riscv-arch-test: Add `-D` to enable and `-U` to disable the specific ISA extensions. +## Debugging mode with GDB remote serial protocal + +By supporting a small set of +[GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html) +(GDBRSP), `rv32emu` is allowed to be run as gdbstub experimentally. To enable this feature, +you should first configure the `ENABLE_GDBSTUB` to 1 in the Makefile and build the emulator. +After that, you could run it with the following command. + +``` +./build/rv32emu --gdbstub +``` + +The `` should be the ELF file in riscv32 format. You are also recommended to compile +your program with `-g` option to generate debug information in your ELF file. + +If the emulator starts correctly without exit, you can then execute the riscv-gdb. Two GDB +commands are required first to provide the supported architecture of the emulator to GDB (also +provide debugging symbol if there's any) and then connect to the emulator. + +``` +$ riscv32-unknown-elf-gdb +(gdb) file +(gdb) target remote :1234 +``` + +If there's no error message from the riscv-gdb, congratulate! You can now interact with +the emulator from the GDB command line now! + +### Limitation +The support of GDB Remote Serial Protocol(GDBRSP) in `rv32emu` is still in the +development phase. There are some known restriction of this functionality due to the +incomplete design now. +* Since the 'G' packet is not supported yet, writing emulator registers with GDB is +not permitted. +* Consequently, the packets for binary download (the "X" packet) and memory writing +(the "M" packet) specified in GDBRSP are not allowed. It is forbidden to use GDB commands +such as "load" that aim to modify the emulator's internal memory. +* Due to the poor design of breakpoints handling, you can only set single breakpoint +during debugging ## External sources In `rv32emu` repository, there are some prebuilt ELF files for testing purpose. diff --git a/emulate.c b/emulate.c index 26720915..52d28514 100644 --- a/emulate.c +++ b/emulate.c @@ -13,6 +13,10 @@ static inline int isinff(float x) #endif #endif +#ifdef ENABLE_GDBSTUB +extern struct target_ops rv_ops; +#endif + #include "riscv.h" #include "riscv_private.h" @@ -1672,6 +1676,27 @@ static inline bool op_unimp(struct riscv_t *rv, uint32_t insn UNUSED) return false; } +#ifdef ENABLE_GDBSTUB +void rv_debug(struct riscv_t *rv) +{ + if (!gdbstub_init(&rv->gdbstub, &rv_ops, + (arch_info_t){ + .reg_num = 33, + .reg_byte = 4, + .target_desc = TARGET_RV32, + }, + GDBSTUB_COMM)) { + return; + } + + if (!gdbstub_run(&rv->gdbstub, (void *) rv)) { + return; + } + gdbstub_close(&rv->gdbstub); +} + +#endif /* ENABLE_GDBSTUB */ + void rv_step(struct riscv_t *rv, int32_t cycles) { assert(rv); diff --git a/gdbstub.c b/gdbstub.c new file mode 100644 index 00000000..1c4c27a5 --- /dev/null +++ b/gdbstub.c @@ -0,0 +1,82 @@ +#include +#include "mini-gdbstub/include/gdbstub.h" +#include "riscv_private.h" + +static size_t rv_read_reg(void *args, int regno) +{ + struct riscv_t *rv = (struct riscv_t *) args; + + if (regno < 32) { + return rv_get_reg(rv, regno); + } + + if (regno == 32) { + return rv_get_pc(rv); + } + + return -1; +} + +static void rv_read_mem(void *args, size_t addr, size_t len, void *val) +{ + struct riscv_t *rv = (struct riscv_t *) args; + + for (size_t i = 0; i < len; i++) + *((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr); +} + +static gdb_action_t rv_cont(void *args) +{ + struct riscv_t *rv = (struct riscv_t *) args; + const uint32_t cycles_per_step = 1; + + for (; !rv_has_halted(rv);) { + if (rv->breakpoint_specified && (rv_get_pc(rv) == rv->breakpoint_addr)) { + break; + } + rv_step(rv, cycles_per_step); + } + + return ACT_RESUME; +} + +static gdb_action_t rv_stepi(void *args) +{ + struct riscv_t *rv = (struct riscv_t *) args; + rv_step(rv, 1); + return ACT_RESUME; +} + +static bool rv_set_bp(void *args, size_t addr, bp_type_t type) +{ + struct riscv_t *rv = (struct riscv_t *) args; + if (type != BP_SOFTWARE || rv->breakpoint_specified) + return false; + + rv->breakpoint_specified = true; + rv->breakpoint_addr = addr; + return true;; +} + +static bool rv_del_bp(void *args, size_t addr, bp_type_t type) +{ + struct riscv_t *rv = (struct riscv_t *) args; + if (type != BP_SOFTWARE) + return false; + /* When there is no matched breakpoint, no further action is taken */ + if (!rv->breakpoint_specified || addr != rv->breakpoint_addr) + return true; + + rv->breakpoint_specified = false; + rv->breakpoint_addr = 0; + return true; +} + +struct target_ops rv_ops = { + .read_reg = rv_read_reg, + .read_mem = rv_read_mem, + .cont = rv_cont, + .stepi = rv_stepi, + .set_bp = rv_set_bp, + .del_bp = rv_del_bp, +}; diff --git a/main.c b/main.c index 591e2fc9..6361fba3 100644 --- a/main.c +++ b/main.c @@ -7,6 +7,10 @@ /* enable program trace mode */ static bool opt_trace = false; +#ifdef ENABLE_GDBSTUB +/* enable program gdbstub mode */ +static bool opt_gdbstub = false; +#endif /* RISCV arch-test */ static bool opt_arch_test = false; @@ -76,6 +80,9 @@ static void print_usage(const char *filename) "Usage: %s [options] [filename]\n" "Options:\n" " --trace : print executable trace\n" +#ifdef ENABLE_GDBSTUB + " --gdbstub : allow remote GDB connections (as gdbstub)\n" +#endif " --arch-test [filename] : dump signature to the given file, " "required by arch-test test\n", filename); @@ -94,6 +101,12 @@ static bool parse_args(int argc, char **args) opt_trace = true; continue; } +#ifdef ENABLE_GDBSTUB + if (!strcmp(arg, "--gdbstub")) { + opt_gdbstub = true; + continue; + } +#endif if (!strcmp(arg, "--arch-test")) { opt_arch_test = true; if (i + 1 >= argc) { @@ -199,7 +212,13 @@ int main(int argc, char **args) /* run based on the specified mode */ if (opt_trace) { run_and_trace(rv, elf); - } else { + } +#ifdef ENABLE_GDBSTUB + else if (opt_gdbstub) { + rv_debug(rv); + } +#endif + else { run(rv); } diff --git a/mini-gdbstub b/mini-gdbstub new file mode 160000 index 00000000..742bcd45 --- /dev/null +++ b/mini-gdbstub @@ -0,0 +1 @@ +Subproject commit 742bcd45b1b4f3dbd488be8f9f7b3a3b9053b910 diff --git a/riscv.h b/riscv.h index 9d29476f..d7576b78 100644 --- a/riscv.h +++ b/riscv.h @@ -99,6 +99,11 @@ void rv_delete(struct riscv_t *); /* reset the RISC-V processor */ void rv_reset(struct riscv_t *, riscv_word_t pc); +#ifdef ENABLE_GDBSTUB +/* Run the RISCV-emulator as gdbstub */ +void rv_debug(struct riscv_t *rv); +#endif + /* step the RISC-V emulator */ void rv_step(struct riscv_t *, int32_t cycles); diff --git a/riscv_private.h b/riscv_private.h index 552310ff..de84b8a3 100644 --- a/riscv_private.h +++ b/riscv_private.h @@ -1,6 +1,9 @@ #pragma once #include +#ifdef ENABLE_GDBSTUB +#include "mini-gdbstub/include/gdbstub.h" +#endif #include "riscv.h" #define RV_NUM_REGS 32 @@ -138,6 +141,15 @@ struct riscv_t { /* user provided data */ riscv_user_t userdata; +#ifdef ENABLE_GDBSTUB + /* gdbstub instance */ + gdbstub_t gdbstub; + + /* GDB instruction breakpoint */ + bool breakpoint_specified; + riscv_word_t breakpoint_addr; +#endif + #ifdef ENABLE_RV32F /* float registers */ union { diff --git a/tests/gdbstub-test/main.sh b/tests/gdbstub-test/main.sh new file mode 100755 index 00000000..17f3c2fc --- /dev/null +++ b/tests/gdbstub-test/main.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +r=$'\r' +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +build/rv32emu --gdbstub build/puzzle.elf & +PID=$! +# We should confirm rv32emu is still executing before running GDB +if ps -p $PID > /dev/null +then + riscv32-unknown-elf-gdb -q -x tests/gdbstub-test/remote-commands.gdb > /tmp/gdbstub-test.txt + + # check if we stop at the breakpoint + expected=$(grep -rw "Breakpoint 1 at" /tmp/gdbstub-test.txt | awk {'print $4'}) + ans=$(grep -r "$1 =" /tmp/gdbstub-test.txt | awk {'print $5'}) + if [ "$expected" != "$ans" ]; then + echo -e "${r}$expected != $ans... ${RED}pass${NC}" + exit 1 + fi + echo -e "${r}$expected == $ans... ${GREEN}pass${NC}" + exit 0 + wait $PID +fi + +exit 1 diff --git a/tests/gdbstub-test/remote-commands.gdb b/tests/gdbstub-test/remote-commands.gdb new file mode 100644 index 00000000..b4e5ada0 --- /dev/null +++ b/tests/gdbstub-test/remote-commands.gdb @@ -0,0 +1,10 @@ +file build/puzzle.elf +target remote :1234 +break *0x10700 +continue +print $pc +del 1 +stepi +stepi +continue +quit