firecracker-microvm/firecracker · error

GDB requires KVM

Error message

GDB requires KVM

What it means

`expect("GDB requires KVM")` in the AArch64 GDB support (arch/aarch64.rs:68): `vmm.vm.as_kvm()` returns None when the Vmm was not built on a KVM-backed VM, and GDB stub memory access (read_address) has no fallback. Firecracker's GDB implementation is written exclusively against the KVM API (vcpu fds, KVM translations), hence the hard requirement.

Solutions

  1. Run the microvm with the KVM backend and confirm /dev/kvm exists and is accessible (rw for the user/kvm group)
  2. Gate the GDB feature: refuse to start the gdb thread unless `vmm.vm.as_kvm().is_some()`
  3. In code, convert the expect into an error: `vmm.vm.as_kvm().ok_or(GdbTargetError::RequiresKvm)?`

Example fix

// before
vmm.vm
    .as_kvm()
    .expect("GDB requires KVM")
    .guest_memory()
    .read(&mut buf, GuestAddress(address))?;

// after
let kvm = vmm.vm.as_kvm().ok_or(GdbTargetError::RequiresKvm)?;
kvm.guest_memory().read(&mut buf, GuestAddress(address))?;
Defensive patterns

Strategy: validation

Validate before calling

// Enable the gdb feature only on KVM-backed microvms
if cfg!(feature = "gdb") && vmm.vm.as_kvm().is_none() {
    return Err(GdbTargetError::RequiresKvm);
}

Type guard

fn is_kvm_backed(vmm: &Vmm) -> bool {
    vmm.vm.as_kvm().is_some()
}

Prevention

When it happens

Trigger: Enabling the GDB socket (`--gdb`/api config) and issuing a memory read (GDB `x`/`m` packet) on a Vmm whose `vm` wrapper is not a KVM Vm — alternate hypervisor backends, or test harnesses constructing Vmm with a mock/non-KVM vm object.

Common situations: Running the VMM under a non-KVM backend or in unit tests with fake Vm types while the gdb feature is compiled in; platforms where /dev/kvm is unavailable so a degraded backend was selected.

Related errors


AI-assisted analysis of firecracker-microvm/firecracker@0a745def42 (2026-08-19). Data as JSON: /api/errors/8506a4cc004f6007. Report an issue: GitHub.

Appendix: source

Thrown at src/vmm/src/gdb/arch/aarch64.rs:68

    get_sys_reg(PC_REG_ID, vcpu_fd)
}

/// Helper to extract a specific number of bits at an offset from a u64
macro_rules! extract_bits_64 {
    ($value: tt, $offset: tt, $length: tt) => {
        ($value >> $offset) & (!0u64 >> (64 - $length))
    };
}

/// Mask to clear the last 3 bits from the page table entry
const PTE_ADDRESS_MASK: u64 = !0b111u64;

/// Read a u64 value from a guest memory address
fn read_address(vmm: &Vmm, address: u64) -> Result<u64, GdbTargetError> {
    let mut buf = [0; 8];
    vmm.vm
        .as_kvm()
        .expect("GDB requires KVM")
        .guest_memory()
        .read(&mut buf, GuestAddress(address))?;

    Ok(u64::from_le_bytes(buf))
}

/// The grainsize used with 4KB paging
const GRAIN_SIZE: usize = 9;

/// Translates a virtual address according to the Vcpu's current address translation mode.
/// Returns the GPA (guest physical address)
///
/// To simplify the implementation we've made some assumptions about the paging setup.
/// Here we just assert firstly paging is setup and these assumptions are correct.
pub fn translate_gva(vcpu_fd: &VcpuFd, gva: u64, vmm: &Vmm) -> Result<u64, GdbTargetError> {
    // Check this virtual address is in kernel space
    if extract_bits_64!(gva, 55, 1) == 0 {
        return Err(GdbTargetError::GvaTranslateError);

View on GitHub (pinned to 0a745def42)