Skip to content

Implement GDB stub for remote debugging #45

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ build/gfx.wad
# built objects
build/rv32emu
build/arch-test
build/mini-gdbstub
*.o
*.o.d
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
21 changes: 21 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 := \
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <binary>
```

The `<binary>` 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 <binary>
(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.
Expand Down
25 changes: 25 additions & 0 deletions emulate.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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);
Expand Down
82 changes: 82 additions & 0 deletions gdbstub.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <assert.h>
#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,
};
21 changes: 20 additions & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions mini-gdbstub
Submodule mini-gdbstub added at 742bcd
5 changes: 5 additions & 0 deletions riscv.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
12 changes: 12 additions & 0 deletions riscv_private.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once
#include <stdbool.h>

#ifdef ENABLE_GDBSTUB
#include "mini-gdbstub/include/gdbstub.h"
#endif
#include "riscv.h"

#define RV_NUM_REGS 32
Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 26 additions & 0 deletions tests/gdbstub-test/main.sh
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions tests/gdbstub-test/remote-commands.gdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
file build/puzzle.elf
target remote :1234
break *0x10700
continue
print $pc
del 1
stepi
stepi
continue
quit