Writing a monitor console for emuriscv
As a follow up to the previous post Debugging things running in your emulator I thought it would be nice to write a monitor console for the emuriscv RISC-V emulator.
With a monitor console one can interact with whatever is running inside (an OS, a program).
I was inspired by the QEMU Monitor in the matter of features and the command syntax.
Monitor in action
The help command shows what it can do:
(emuriscv) help
help:
regs - dump registers
reg $reg - dump one register $reg (format: x7 or t0)
x $addr [n] - dump n words from virtual address $addr
xp $addr [n] - dump n words from physical address $addr
p $reg [n] - dump n words from virtual address pointed to by $reg
pp $reg [n] - dump n words from physical address pointed to by $reg
q - quit
Showing the register values during debugging:
(emuriscv) regs
zero ( x0) : 0x00000000 ra ( x1) : 0x00000000 sp ( x2) : 0x00000000 gp ( x3) : 0x00000000
tp ( x4) : 0x00000000 t0 ( x5) : 0x00000000 t1 ( x6) : 0x00000000 t2 ( x7) : 0x00000000
s0 ( x8) : 0x00000000 s1 ( x9) : 0x00000000 a0 (x10) : 0x0000002a a1 (x11) : 0x00000000
a2 (x12) : 0x00000000 a3 (x13) : 0x00000000 a4 (x14) : 0x00000000 a5 (x15) : 0x00000000
a6 (x16) : 0x00000000 a7 (x17) : 0x0000005d s2 (x18) : 0x00000000 s3 (x19) : 0x00000000
s4 (x20) : 0x00000000 s5 (x21) : 0x00000000 s6 (x22) : 0x00000000 s7 (x23) : 0x00000000
s8 (x24) : 0x00000000 s9 (x25) : 0x00000000 s10 (x26) : 0x00000000 s11 (x27) : 0x00000000
t3 (x28) : 0x00000000 t4 (x29) : 0x00000000 t5 (x30) : 0x00000000 t6 (x31) : 0x00000000
pc: 00000028 priv: U cycles: 6
We can examine the value of an individual register with the reg
command:
(emuriscv) reg a7
a7 (x17): 0000005d | 93 |
(emuriscv) reg x10
a0 (x10): 0000002a | 42 |
(emuriscv) reg pc
pc: 00000028 | 40 |
We can display an individual word in memory either by an address or an address from a register: (the 0x prefix with addresses is optional)
(emuriscv) p pc
0x0000000c: ffdff06f | -2101137 | ▀≡o╠╠╠╠╨⌠┌èÿ±╝
(emuriscv) x 0x16
0x00000016: 007305d0 | 7538128 |
(emuriscv) x 16
0x00000016: 007305d0 | 7538128 |
We can also display a memory range:
(emuriscv) x 0x10 6
0x00000010: 00000513 | 1299 |
0x00000014: 05d00893 | 97519763 | ô╠╠╠╠√.ëÄ4⌠Γ»C
0x00000018: 00000073 | 115 |
0x0000001c: 02a00513 | 44041491 | á╠╠╠╠√.ëÄ4⌠Γ»C
0x00000020: 05d00893 | 97519763 | ô╠╠╠╠√.ëÄ4⌠Γ»C
0x00000024: 00000073 | 115 |
Virtual and physical memory
Both the p
and x
commands have their pp
and xp
counterparts that peek into the physical memory.
A dump of binary being debugged, for the reference:
0000000000000000 <.data>:
0: 00c0006f j 0xc
4: 00000013 nop
8: 0140006f j 0x1c
c: ffdff06f j 0x8
10: 00000513 li a0,0
14: 05d00893 li a7,93
18: 00000073 ecall
1c: 02a00513 li a0,42
20: 05d00893 li a7,93
24: 00000073 ecall
Implementation
Today I learned how to use strtok
to tokenize a string:
char* line; //input line
...
size_t token_count = 0;
for (token_count = 0; token_count < 10; token_count++) {
char* arg = token_count == 0 ? line : NULL;
tokens[token_count] = strtok(arg, " ");
if (tokens[token_count] == NULL)
break;
}
Then used strcmp
to check the command and pass the parameters to the functions actually dumping the data.
else if (strcmp(tokens[0], "reg") == 0) {
if (token_count > 1)
dump_register(state, tokens[1]);
}
To nicely align the register values I used printf
width specifiers
, such as %5s
, that specifies that at least 5 characters will be printed:
void dump_registers(State* state) {
char xname[4]; //e.g. x12
for (int i = 0; i < REGISTERS; i++) {
sprintf(xname, "x%d", i);
printf("%5s (%3s) : 0x%08x ", reg_name[i], xname, state->x[i]);
if (i % 4 == 3)
printf("\n");
}
...
Missing features
It would be quite useful to implement disassembly of the memory regions and support more display formats (8-bit and 16-bit values).
Setting breakpoints and being able to watch a register/memory address (as in gdb) would make the debugging from inside the emulator easier.