Neyko's devlog

QEMU RISC-V Virt Bare Bones

📅 2026-08-11

Goals:

  • Compile a simple application for rv64 ( serial port output/echo )
  • Run it on QEMU's "virt" platform ( generic platform not matching any real hw)
  • compile and run again, but for rv32i

Required files and dependencies:

    riscv64-elf-ubuntu-24.04-gcc.tar.xz
    riscv32-elf-ubuntu-24.04-gcc.tar.xz

Instructions:

  • extract riscv toolchains
$ mkdir risc64-elf && cd risc64-elf && tar xvf riscv64-elf-ubuntu-24.04-gcc.tar.xz

$ mkdir risc32-elf && cd risc32-elf && tar xvf riscv32-elf-ubuntu-24.04-gcc.tar.xz
  • alias required binaries (from toolchains), so that they are easier to use
$ alias riscv64-elf-gcc="./riscv64-elf/riscv/bin/riscv64-unknown-elf-gcc"
$ alias riscv64-elf-as="./riscv64-elf/riscv/bin/riscv64-unknown-elf-as"
$ alias riscv64-elf-ld="./riscv64-elf/riscv/bin/riscv64-unknown-elf-ld"

$ alias riscv32-elf-gcc="./riscv32-elf/riscv/bin/riscv32-unknown-elf-gcc"
$ alias riscv32-elf-as="./riscv32-elf/riscv/bin/riscv32-unknown-elf-as"
$ alias riscv32-elf-ld="./riscv32-elf/riscv/bin/riscv32-unknown-elf-ld"
  • compile, assemble, link
### FOR RV64 ###
$ riscv64-elf-gcc -Wall -Wextra -c -mcmodel=medany kernel.c -o kernel.o -ffreestanding
    
$ riscv64-elf-as -c entry.S -o entry.o
    
$ riscv64-elf-ld -T linker.ld -lgcc -nostdlib kernel.o entry.o -o kernel.elf 
    # i got "riscv64-unknown-elf-ld: cannot find -lgcc: no error", 
    # so had to modify the command a bit (to reference it manually):
$ riscv64-elf-ld -T linker.ld -lgcc -nostdlib kernel.o entry.o -o kernel.elf 
    --static -L riscv64-elf/riscv/lib/gcc/riscv64-unknown-elf/16.1.0/

### FOR RV32 ###
$ riscv32-elf-gcc -Wall -Wextra -c -mcmodel=medany kernel.c -o kernel32.o -ffreestanding
    
$ riscv32-elf-as -c entry32.S -o entry32.o
    # entry32.S is just entry.S with instruction sd changed to sw (no sd in rv32)
    
$ riscv32-elf-ld -T linker.ld -lgcc -nostdlib kernel32.o entry32.o -o kernel32.elf 
    --static -L riscv32-elf/riscv/lib/gcc/riscv32-unknown-elf/16.1.0/
  • load elfs in qemu
### FOR RV64 ###
$ qemu-system-riscv64 -machine virt -bios none -kernel kernel.elf -serial mon:stdio

### FOR RV32 ###
$ qemu-system-riscv32 -machine virt -bios none -kernel kernel32.elf -serial mon:stdio

# both were generated from the same kernel.c, so for both:
    # qemu will launch, running kmain()
    # void kmain(void) {              // 
    #     print("Hello world!\r\n");  // this will print first
    #     while(1) {                  // 
    #     putchar(*uart);             // then any input in console will echo
    # }                               // inf loop, so last char will print many times
    # return;                         // to exit QEMU: Ctrl+A, X
    #}                               //