Timeline
Timeline
2025-11-21
- init
This article introduces a method to reverse-locate the initialization process of the RISC-V architecture virt Machine in QEMU through GDB debugging, discussing in detail from qemu_init creating machine class and instantiating object's QOM process, virt_machine_init function initializing CPU cores and devices and generating the device tree mechanism, and summarizes the machine_done stage loading OpenSBI firmware and setting the vCPU to execute the first instruction's source code implementation details.
Environment
1 | wget https://download.qemu.org/qemu-10.1.2.tar.xz |
Creating .clangd
1 | CompileFlags: |
virt Machine Initialization
The most convenient method is to reverse-locate the source code through GDB.
According to the previous explanation of QOM, the virt Machine must belong to a Qobject. We can set a breakpoint at the source code location of its class initialization or object instantiation to observe the call stack. Here, we first search for code related to typeinfo in the virt Machine source code:
virt_machine_typeinfo
1 | static const TypeInfo virt_machine_typeinfo = { |
Therefore, we can set two breakpoints forvirt_machine_class_init()andvirt_machine_instance_init()respectively.
Use the following command to debug QEMU with gdb:
1 | $ gdb ./build/qemu-system-riscv64 -ex "set args -M virt -nographic" |
virt_machine_class_init
First, what we trace isvirt_machine_class_initfunction
hw/riscv/virt.c
1 | static void virt_machine_class_init(ObjectClass *oc, const void *data) |
Call stack:

virt_machine_instance_init
hw/riscv/virt.c
1 | static void virt_machine_instance_init(Object *obj) |
Call stack:

Here we can see that the main function first calls qemu_init function, and then calls qemu_create_machine function to create the machine:
1 | main() → qemu_init() → qemu_create_machine() |
Then it first creates the class, and then instantiates the qobject, which is consistent with the process described earlier for QOM.
virt_machine_init
Forvirt_machine_class_init, it assigns the init of the defined MachineClass tovirt_machine_init
1 | MachineClass *mc = MACHINE_CLASS(oc); |
hw/riscv/virt.c
1 | static void virt_machine_init(MachineState *machine) |
Don’t be fooled by the length of this function; what it does is actually very simple:
1 | virt_machine_init() |
CPU socket mainly allows creating multiple groups of CPU cores according to clusters, then initialize various devices according to the address space, and finally generate a device tree in memory for virt to facilitate running the Linux kernel.
1 | ┌───────────────────────────────────────────────────────────────┐ |
So, at what location is the OpenSBI binary program loaded?
Load guest program
One thing is clear: the device model storing OpenSBI must be created and initialized first before the guest program binary data can be loaded. Following this logic, we can find the following code (in fact, QEMU starts loading the guest program only after the entire virt Machine is ready, which is implemented in machine_done):
virt_machine_done
1 | static void virt_machine_done(Notifier *notifier, void *data) |
- firmware_name is copied
riscv_default_firmware_name(&s->soc[0]) - start_addr is assigned
s->memmap[VIRT_DRAM].base
riscv_default_firmware_name
hw/riscv/boot.c
1 | const char *riscv_default_firmware_name(RISCVHartArrayState *harts) |
Ininclude/hw/riscv/boot.cis defined in
1 |
Therefore firmware_name is assigned asopensbi-riscv64-generic-fw_dynamic.bin
riscv_find_and_load_firmware
riscv_find_and_load_firmwarethe second parameter is default_machine_firmware, a string type, and what we pass isopensbi-riscv64-generic-fw_dynamic.bin, this function callsriscv_find_firmwareto get the firmware string
hw/riscv/boot.c
1 | target_ulong riscv_find_and_load_firmware(MachineState *machine, |
riscv_find_firmware
hw/riscv/boot.c
1 | char *riscv_find_firmware(const char *firmware_filename, |
Here we see that if we do not specify a BIOS via -bios, the default will be loaded._machine_The firmware is OpenSBI.
riscv_load_firmware
riscv_load_firmwareThe firmware will be loaded.
hw/riscv/boot.c
1 | target_ulong riscv_load_firmware(const char *firmware_filename, |
The first instruction executed by the vCPU.
Run qemu-system-riscv64.
1 | $ ./build/qemu-system-riscv64 -M virt -s -S -nographic |
This command makes QEMU create a virt machine, running in nographic mode,with serial output to the terminal.,**The default BIOS uses OpenSBI,**and enables the gdbstub remote debugging feature, allowing riscv64-gdb to debug the guest program. The default port number is 1234, and it stops at the first instruction, waiting for a gdb connection.
Then,
1 | $ gdb -ex "set architecture riscv64" -ex "target remote localhost:1234" |

The corresponding source code initialization location is:
target/riscv/cpu.c
1 | static void riscv_cpu_reset_hold(Object *obj, ResetType type) |
It can be seen that env->pc is assigned the value of env->resetvec, and the default value of resetvec is:
1 | //cpu_bits.h |
Reference:
