Skip to content

Commit 1d10fa0

Browse files
committed
Add debug mode using mini-gdbstub submodules
This patch provide an experimental solution that lets rv32emu support a small set of GDB Remote Serial Protocol packet with the mini-gdbstub library. After this patch, we could connect our emulator to GDB and interact with some simple commands like printing current program counter(`print $pc`) or unpausing the execution(`continue`). It's worth mentioning that we only enable to use single breakpoint on rv32emu under this patch. It will be improved in the future. Close #22
1 parent 304f0af commit 1d10fa0

12 files changed

+234
-1
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ build/gfx.wad
88
# built objects
99
build/rv32emu
1010
build/arch-test
11+
build/mini-gdbstub
1112
*.o
1213
*.o.d

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "riscv-arch-test"]
22
path = tests/riscv-arch-test
33
url = https://github.com/riscv-non-isa/riscv-arch-test
4+
[submodule "mini-gdbstub"]
5+
path = mini-gdbstub
6+
url = https://github.com/RinHizakura/mini-gdbstub

Makefile

+21
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,27 @@ $(OUT)/emulate.o: CFLAGS += -fno-gcse -fno-crossjumping
5555
endif
5656
endif
5757

58+
ENABLE_GDBSTUB ?= 1
59+
ifeq ("$(ENABLE_GDBSTUB)", "1")
60+
MINI_GDBSTUB_OUT = $(abspath $(OUT)/mini-gdbstub)
61+
GDBSTUB_COMM = 127.0.0.1:1234
62+
LIB_GDBSTUB += $(MINI_GDBSTUB_OUT)/libgdbstub.a
63+
gdbstub-test: $(BIN)
64+
./tests/gdbstub-test/main.sh
65+
66+
$(LIB_GDBSTUB):
67+
git submodule update --init mini-gdbstub
68+
$(MAKE) -C mini-gdbstub O=$(MINI_GDBSTUB_OUT)
69+
$(OUT)/emulate.o: $(LIB_GDBSTUB)
70+
OBJS_EXT += gdbstub.o
71+
CFLAGS += -D ENABLE_GDBSTUB -D'GDBSTUB_COMM="$(GDBSTUB_COMM)"'
72+
LDFLAGS += $(LIB_GDBSTUB)
73+
endif
74+
75+
# Clear the .DEFAULT_GOAL special variable, so that the following turns
76+
# to the first target after .DEFAULT_GOAL is not set.
77+
.DEFAULT_GOAL :=
78+
5879
all: $(BIN)
5980

6081
OBJS := \

README.md

+38
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,44 @@ Detail in riscv-arch-test:
112112

113113
Add `-D` to enable and `-U` to disable the specific ISA extensions.
114114

115+
## Debugging mode with GDB remote serial protocal
116+
117+
By supporting a small set of
118+
[GDB Remote Serial Protocol](https://sourceware.org/gdb/onlinedocs/gdb/Remote-Protocol.html)
119+
(GDBRSP), `rv32emu` is allowed to be run as gdbstub experimentally. To enable this feature,
120+
you should first configure the `ENABLE_GDBSTUB` to 1 in the Makefile and build the emulator.
121+
After that, you could run it with the following command.
122+
123+
```
124+
./build/rv32emu --gdbstub <binary>
125+
```
126+
127+
The `<binary>` should be the ELF file in riscv32 format. You are also recommended to compile
128+
your program with `-g` option to generate debug information in your ELF file.
129+
130+
If the emulator starts correctly without exit, you can then execute the riscv-gdb. Two GDB
131+
commands are required first to provide the supported architecture of the emulator to GDB (also
132+
provide debugging symbol if there's any) and then connect to the emulator.
133+
134+
```
135+
$ riscv32-unknown-elf-gdb
136+
(gdb) file <binary>
137+
(gdb) target remote :1234
138+
```
139+
140+
If there's no error message from the riscv-gdb, congratulate! You can now interact with
141+
the emulator from the GDB command line now!
142+
143+
### Limitation
144+
The support of GDB Remote Serial Protocol in `rv32emu` is still in the development phase.
145+
There are some known restriction of this functionality due to the incomplete design now.
146+
* Since the 'G' packet is not supported yet, writing emulator registers with GDB is
147+
not permitted.
148+
* Consequently, the packets for binary download (the "X" packet) and memory writing
149+
(the "M" packet) specified in GDBRSP are not allowed. It is forbidden to use GDB commands
150+
such as "load" that aim to modify the emulator's internal memory.
151+
* Due to the poor design of breakpoints handling, you can only set single breakpoint
152+
during debugging
115153
## External sources
116154

117155
In `rv32emu` repository, there are some prebuilt ELF files for testing purpose.

emulate.c

+25
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ static inline int isinff(float x)
1313
#endif
1414
#endif
1515

16+
#ifdef ENABLE_GDBSTUB
17+
extern struct target_ops rv_ops;
18+
#endif
19+
1620
#include "riscv.h"
1721
#include "riscv_private.h"
1822

@@ -1672,6 +1676,27 @@ static inline bool op_unimp(struct riscv_t *rv, uint32_t insn UNUSED)
16721676
return false;
16731677
}
16741678

1679+
#ifdef ENABLE_GDBSTUB
1680+
void rv_debug(struct riscv_t *rv)
1681+
{
1682+
if (!gdbstub_init(&rv->gdbstub, &rv_ops,
1683+
(arch_info_t){
1684+
.reg_num = 33,
1685+
.reg_byte = 4,
1686+
.target_desc = TARGET_RV32,
1687+
},
1688+
GDBSTUB_COMM)) {
1689+
return;
1690+
}
1691+
1692+
if (!gdbstub_run(&rv->gdbstub, (void *) rv)) {
1693+
return;
1694+
}
1695+
gdbstub_close(&rv->gdbstub);
1696+
}
1697+
1698+
#endif /* ENABLE_GDBSTUB */
1699+
16751700
void rv_step(struct riscv_t *rv, int32_t cycles)
16761701
{
16771702
assert(rv);

gdbstub.c

+82
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <assert.h>
2+
#include "mini-gdbstub/include/gdbstub.h"
3+
#include "riscv_private.h"
4+
5+
static size_t rv_read_reg(void *args, int regno)
6+
{
7+
struct riscv_t *rv = (struct riscv_t *) args;
8+
9+
if (regno < 32) {
10+
return rv_get_reg(rv, regno);
11+
}
12+
13+
if (regno == 32) {
14+
return rv_get_pc(rv);
15+
}
16+
17+
return -1;
18+
}
19+
20+
static void rv_read_mem(void *args, size_t addr, size_t len, void *val)
21+
{
22+
struct riscv_t *rv = (struct riscv_t *) args;
23+
24+
for (size_t i = 0; i < len; i++)
25+
*((uint8_t *) val + i) = rv->io.mem_read_b(rv, addr);
26+
}
27+
28+
static gdb_action_t rv_cont(void *args)
29+
{
30+
struct riscv_t *rv = (struct riscv_t *) args;
31+
const uint32_t cycles_per_step = 1;
32+
33+
for (; !rv_has_halted(rv);) {
34+
if (rv->breakpoint_specified && (rv_get_pc(rv) == rv->breakpoint_addr)) {
35+
break;
36+
}
37+
rv_step(rv, cycles_per_step);
38+
}
39+
40+
return ACT_RESUME;
41+
}
42+
43+
static gdb_action_t rv_stepi(void *args)
44+
{
45+
struct riscv_t *rv = (struct riscv_t *) args;
46+
rv_step(rv, 1);
47+
return ACT_RESUME;
48+
}
49+
50+
static bool rv_set_bp(void *args, size_t addr, bp_type_t type)
51+
{
52+
struct riscv_t *rv = (struct riscv_t *) args;
53+
if (type != BP_SOFTWARE || rv->breakpoint_specified)
54+
return false;
55+
56+
rv->breakpoint_specified = true;
57+
rv->breakpoint_addr = addr;
58+
return true;;
59+
}
60+
61+
static bool rv_del_bp(void *args, size_t addr, bp_type_t type)
62+
{
63+
struct riscv_t *rv = (struct riscv_t *) args;
64+
if (type != BP_SOFTWARE)
65+
return false;
66+
// When there is no matched breakpoint, no further action is taken
67+
if (!rv->breakpoint_specified || addr != rv->breakpoint_addr)
68+
return true;
69+
70+
rv->breakpoint_specified = false;
71+
rv->breakpoint_addr = 0;
72+
return true;
73+
}
74+
75+
struct target_ops rv_ops = {
76+
.read_reg = rv_read_reg,
77+
.read_mem = rv_read_mem,
78+
.cont = rv_cont,
79+
.stepi = rv_stepi,
80+
.set_bp = rv_set_bp,
81+
.del_bp = rv_del_bp,
82+
};

main.c

+20-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
/* enable program trace mode */
99
static bool opt_trace = false;
10+
#ifdef ENABLE_GDBSTUB
11+
/* enable program gdbstub mode */
12+
static bool opt_gdbstub = false;
13+
#endif
1014

1115
/* RISCV arch-test */
1216
static bool opt_arch_test = false;
@@ -76,6 +80,9 @@ static void print_usage(const char *filename)
7680
"Usage: %s [options] [filename]\n"
7781
"Options:\n"
7882
" --trace : print executable trace\n"
83+
#ifdef ENABLE_GDBSTUB
84+
" --gdbstub : allow remote GDB connections (as gdbstub)\n"
85+
#endif
7986
" --arch-test [filename] : dump signature to the given file, "
8087
"required by arch-test test\n",
8188
filename);
@@ -94,6 +101,12 @@ static bool parse_args(int argc, char **args)
94101
opt_trace = true;
95102
continue;
96103
}
104+
#ifdef ENABLE_GDBSTUB
105+
if (!strcmp(arg, "--gdbstub")) {
106+
opt_gdbstub = true;
107+
continue;
108+
}
109+
#endif
97110
if (!strcmp(arg, "--arch-test")) {
98111
opt_arch_test = true;
99112
if (i + 1 >= argc) {
@@ -199,7 +212,13 @@ int main(int argc, char **args)
199212
/* run based on the specified mode */
200213
if (opt_trace) {
201214
run_and_trace(rv, elf);
202-
} else {
215+
}
216+
#ifdef ENABLE_GDBSTUB
217+
else if (opt_gdbstub) {
218+
rv_debug(rv);
219+
}
220+
#endif
221+
else {
203222
run(rv);
204223
}
205224

mini-gdbstub

Submodule mini-gdbstub added at 742bcd4

riscv.h

+5
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ void rv_delete(struct riscv_t *);
9999
/* reset the RISC-V processor */
100100
void rv_reset(struct riscv_t *, riscv_word_t pc);
101101

102+
#ifdef ENABLE_GDBSTUB
103+
/* Run the RISCV-emulator in debug mode */
104+
void rv_debug(struct riscv_t *rv);
105+
#endif
106+
102107
/* step the RISC-V emulator */
103108
void rv_step(struct riscv_t *, int32_t cycles);
104109

riscv_private.h

+12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#pragma once
22
#include <stdbool.h>
33

4+
#ifdef ENABLE_GDBSTUB
5+
#include "mini-gdbstub/include/gdbstub.h"
6+
#endif
47
#include "riscv.h"
58

69
#define RV_NUM_REGS 32
@@ -138,6 +141,15 @@ struct riscv_t {
138141
/* user provided data */
139142
riscv_user_t userdata;
140143

144+
#ifdef ENABLE_GDBSTUB
145+
/* gdbstub instance */
146+
gdbstub_t gdbstub;
147+
148+
/* GDB instruction breakpoint */
149+
bool breakpoint_specified;
150+
riscv_word_t breakpoint_addr;
151+
#endif
152+
141153
#ifdef ENABLE_RV32F
142154
/* float registers */
143155
union {

tests/gdbstub-test/main.sh

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
GREEN='\033[0;32m'
2+
RED='\033[0;31m'
3+
NC='\033[0m'
4+
5+
build/rv32emu --gdbstub build/puzzle.elf &
6+
riscv32-unknown-elf-gdb -q -x tests/gdbstub-test/remote-commands.gdb > /tmp/gdbstub-test.txt
7+
8+
# check if we stop at the breakpoint
9+
expect=$(grep -rw "Breakpoint 1 at" /tmp/gdbstub-test.txt | awk {'print $4'})
10+
ans=$(grep -r "$1 =" /tmp/gdbstub-test.txt | awk {'print $5'})
11+
if [ "$expect" != "$ans" ]; then
12+
echo "$expect != $ans... ${RED}pass${NC}"
13+
exit 1
14+
fi
15+
echo "$expect == $ans... ${GREEN}pass${NC}"
16+
exit 0
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
file build/puzzle.elf
2+
target remote :1234
3+
break *0x10700
4+
continue
5+
print $pc
6+
del 1
7+
stepi
8+
stepi
9+
continue
10+
quit

0 commit comments

Comments
 (0)