Running on QEMU¶
Installing QEMU¶
It is best to use the latest official QEMU version for the following exercises. As of today, it's v9.0 (see the release notes). The sources are on GitLab or in the official GitHub mirror.
To get the latest version of QEMU on your system it might be much easier to build it from sources rather than trying to find a pre-built version which suits your host type and operating system.
Detailed instructions for building on Linux are on QEMU's Wiki. First you need to install required additional packages, which depend on the Linux distribution used on the host.
For example, for Ubuntu 20.04 the following needs to be done:
$ apt install build-essential git libglib2.0-dev libfdt-dev \
libpixman-1-dev zlib1g-dev ninja-build python3-venv
For AlmaLinux 8 the following needs to be done:
$ dnf install git glib2-devel libfdt-devel pixman-devel zlib-devel \
bzip2 ninja-build python3 python38
After installation of prerequisites you can download sources, configure, and build in the following way:
# Clone v9.0 from GitHub repository
$ git clone --depth 1 -b stable-9.0 https://github.com/qemu/qemu.git
$ cd qemu
# Configure QEMU for 32-bit and 64-bit RISC-V targets
$ ./configure --target-list=riscv32-softmmu,riscv64-softmmu
# Build QEMU
$ make
After the build is complete qemu-system-riscv32
and qemu-system-riscv64
are placed in the
build
folder, which you can add to the PATH
environment variable for convenience, and use
the short binary name instead of the full path.
Code Example¶
Consider this code example which prints all passed command line arguments:
#include <stdio.h>
void __wrap__arcv_cache_enable() {}
int main(int argc, char *argv[]) {
for (int i = 0; i < argc; i++) {
printf("%d: %s\n", i, argv[i]);
}
return 0;
}
By default, ARC-V specific startup code turns on caches through
ARC-V specific CSR. However, it's not available in QEMU and to solve
this issue we added __wrap__arcv_cache_enable
to overwrite original
caches initialization function. Also, we will have to pass an extra
-Wl,--wrap=_arcv_cache_enable
option to GCC.
Also, code sections will be placed in 0x80000000
since in default
QEMU machine DDR is placed at 0x80000000
, where the board jumps on
start automatically.
Build and Run for Base RMX-100¶
Build the application:
$ riscv64-snps-elf-gcc \
-march=rv32ic_zcb_zcmp_zcmt_zba_zbb_zbs_zicsr \
-mabi=ilp32 \
-mtune=arc-v-rmx-100-series \
-Wl,--wrap=_arcv_cache_enable \
-Wl,-defsym=txtmem_addr=0x80000000 \
-Wl,-defsym=txtmem_len=1M \
-Wl,-defsym=datamem_addr=0x80200000 \
-Wl,-defsym=datamem_len=1M \
-specs=semihost.specs \
-specs=arcv.specs \
-T arcv.ld \
args.c -o args.elf
Run on QEMU:
$ qemu-system-riscv32 \
-semihosting \
-nographic \
-machine virt \
-cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
-bios none \
-kernel args.elf \
-append "one two three"
0: args.elf
1: one
2: two
3: three
Build and Run for Base RMX-500¶
Build the application:
$ riscv64-snps-elf-gcc \
-march=rv32ic_zcb_zcmp_zcmt_zba_zbb_zbs_zicsr \
-mabi=ilp32 \
-mtune=arc-v-rmx-500-series \
-Wl,--wrap=_arcv_cache_enable \
-Wl,-defsym=txtmem_addr=0x80000000 \
-Wl,-defsym=txtmem_len=1M \
-Wl,-defsym=datamem_addr=0x80200000 \
-Wl,-defsym=datamem_len=1M \
-specs=semihost.specs \
-specs=arcv.specs \
-T arcv.ld \
args.c -o args.elf
Run on QEMU:
$ qemu-system-riscv32 \
-semihosting \
-nographic \
-machine virt \
-cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
-bios none \
-kernel args.elf \
-append "one two three"
0: args.elf
1: one
2: two
3: three
Build and Run for Base RHX-100¶
Build the application:
$ riscv64-snps-elf-gcc \
-march=rv32imac_zcb_zcmp_zba_zbb_zbs_zicsr \
-mabi=ilp32 \
-mtune=arc-v-rhx-100-series \
-Wl,--wrap=_arcv_cache_enable \
-Wl,-defsym=txtmem_addr=0x80000000 \
-Wl,-defsym=txtmem_len=1M \
-Wl,-defsym=datamem_addr=0x80200000 \
-Wl,-defsym=datamem_len=1M \
-specs=semihost.specs \
-specs=arcv.specs \
-T arcv.ld \
args.c -o args.elf
Run on QEMU:
$ qemu-system-riscv32 \
-semihosting \
-nographic \
-machine virt \
-cpu rv32,f=off,zfa=off,d=off,m=on,a=on,zca=on,zcb=on,zcmp=on,zba=on,zbb=on,zbs=on \
-bios none \
-kernel args.elf \
-append "one two three"
0: args.elf
1: one
2: two
3: three
Build and Run for Base RPX-100¶
Build the application:
$ riscv64-snps-elf-gcc \
-march=rv64imac_zcb_zba_zbb_zbs_zicsr \
-mabi=lp64 \
-mtune=arc-v-rmx-100-series \
-mcmodel=medany \
-Wl,--wrap=_arcv_cache_enable \
-Wl,-defsym=txtmem_addr=0x80000000 \
-Wl,-defsym=txtmem_len=1M \
-Wl,-defsym=datamem_addr=0x80200000 \
-Wl,-defsym=datamem_len=1M \
-specs=semihost.specs \
-specs=arcv.specs \
-T arcv.ld \
args.c -o args.elf
Run on QEMU:
$ qemu-system-riscv64 \
-semihosting \
-nographic \
-machine virt \
-cpu rv64,f=off,zfa=off,d=off,m=on,a=on,zca=on,zcb=on,zba=on,zbb=on,zbs=on \
-bios none \
-kernel args.elf \
-append "one two three"
0: args.elf
1: one
2: two
3: three
Debugging Using GDB¶
You can debug an application that is being run on QEMU with help of the built-in GDB server of
QEMU. On the QEMU side, you need to enable the GDB sever and optionally instruct the QEMU not
to start the application execution automatically, but instead wait for user input. For that you
need to pass -s -S
commands to qemu-system-riscv32
or qemu-system-riscv64
.
Build the same example for RMX-100:
$ riscv64-snps-elf-gcc \
-march=rv32ic_zcb_zcmp_zcmt_zba_zbb_zbs_zicsr \
-mabi=ilp32 \
-mtune=arc-v-rmx-100-series \
-Wl,--wrap=_arcv_cache_enable \
-Wl,-defsym=txtmem_addr=0x80000000 \
-Wl,-defsym=txtmem_len=1M \
-Wl,-defsym=datamem_addr=0x80200000 \
-Wl,-defsym=datamem_len=1M \
-specs=semihost.specs \
-specs=arcv.specs \
-T arcv.ld \
-g \
args.c -o args.elf
Start GDB server (we use -gdb tcp::12345
instead of -s
to set the custom
GDB port number instead of the default 1234 port number):
$ qemu-system-riscv32 \
-semihosting \
-nographic \
-machine virt \
-cpu rv32,f=off,zfa=off,d=off,zce=on,zba=on,zbb=on,zbs=on \
-bios none \
-kernel args.elf \
-append "one two three" \
-gdb tcp::12345 -S
Start GDB session:
$ riscv64-snps-elf-gdb -q args.elf
Reading symbols from args.elf...
(gdb) target remote :12345
Remote debugging using :12345
0x00001000 in ?? ()
(gdb) b main
Breakpoint 1 at 0x800000d6: file args.c, line 8.
(gdb) c
Continuing.
Breakpoint 1, main (argc=4, argv=0x80200e78 <_argv>) at args.c:8
8 for (int i = 0; i < argc; i++) {
(gdb) step
9 printf("%d: %s\n", i, argv[i]);
(gdb) c
Continuing.
[Inferior 1 (process 1) exited normally]
Note that the first location where the CPU is waiting for commands is 0x00001000
. Itβs a
virt
board reset vector location. It is not possible to suppress execution of that reset
vector code as itβs an essential part of the QEMU board (think of it as a boot-ROM). You
can only bypass it if you load the target executable from the GDB client with the load command.
Then execution starts from the loaded application entry point.