This document records every fix applied to make the RELM hypervisor kernel module compile and boot a Linux guest on the Intel VMX backend, and the exact bug behind each one. It is organized as:
- The contract — the two cross-cutting architectural decisions that every
other fix depends on (authoritative
struct vcpu, therelm_archinclude mechanism, and the include-path convention). - Per-file fixes — grouped by file, each entry: what was wrong → why it broke the build/boot → what changed.
Build note. This tree is a Linux kernel module; it can only be compiled on a Linux host with kernel build headers (
make ARCH=x86 SUBTARGET=vmx). Fixes here are static (source-level) corrections; they were not compiler-verified in the authoring environment (macOS). Run a build on your Linux box to confirm.
The tree was mid-refactor: a generic architecture-independent layer
(include/relm/*, src/core/*) was being split out from the x86-VMX backend
(include/arch/x86/vmx/*, src/arch/x86/vmx/*), and the split was left
half-done. Two incompatible worlds coexisted. Every per-file fix below assumes
these decisions:
struct vcpuis defined once, ininclude/relm/vcpu.h. It holds arch-independent fields (vm,vpid,state,launched,halted,host_task,host_stack,stats,ops, …) and embeds the arch-specific state asstruct vcpu_arch arch;.struct vcpu_arch(x86 VMX) is defined once, ininclude/arch/x86/vmx/vcpu_arch.h. It holds all VMX-specific fields:vmcs,controls, bitmaps, MSR areas,regs(struct guest_regs),cr0..cr4,efer,gdtr/idtr,exit_reason,apic,cr3_cache, and (added by this work)host_rsp+vmentry_host_rsp.- The pre-refactor flat
struct vcpuand the duplicatedstruct guest_regs,enum vcpu_state,struct vcpu_stats, and (minimal)struct cr3_shadow_cachethat lived ininclude/arch/x86/vmx/vmx.handvcpu_arch.hare removed. They collided (redefinition errors) becausevcpu_arch.hincludesvmx.h, so both landed in one translation unit.
Consequence — the universal member rename applied throughout the arch code:
| Old (flat) access | New (generic) access |
|---|---|
vcpu->regs.* |
vcpu->arch.regs.* |
vcpu->efer |
vcpu->arch.efer |
vcpu->cr0/cr3/cr4 |
vcpu->arch.cr0/cr3/cr4 |
vcpu->vmentry_host_rsp |
vcpu->arch.vmentry_host_rsp |
vcpu->exit_reason |
vcpu->arch.exit_reason |
vm->ept |
vm->arch.ept |
vm->pml4_gpa |
vm->arch.pml4_gpa |
vm->kernel_entry_gpa |
vm->arch.kernel_entry_gpa |
vm->boot_params_gpa |
vm->arch.boot_params_gpa |
vm->iommu |
vm->arch.iommu |
vm->total_guest_ram |
vm->memory.total_guest_ram |
vcpu->vpid/vm/state/launched/halted/host_task/host_stack/stats/target_cpu_id/ops
stay as-is — those are genuine generic-struct vcpu members.
Generic headers deliberately do not hardcode the arch path; they include
<relm_arch/vcpu_arch.h>, <relm_arch/vm_arch.h>, <relm_arch/arch.h>.
Nothing mapped that prefix, so every one was a fatal file not found.
Fix: the Makefile now creates a symlink include/relm_arch →
arch/$(ARCH)/$(SUBTARGET) (for this build: arch/x86/vmx) before invoking
kbuild, and removes it on clean. With the existing -I$(src)/include,
<relm_arch/vcpu_arch.h> resolves to include/arch/x86/vmx/vcpu_arch.h. This
keeps the generic layer arch-agnostic (the whole point of the refactor); to add
a new backend you only re-point the symlink, not edit core headers.
The kbuild -I set is: $(src), $(src)/include, $(src)/utils,
$(src)/include/arch/x86/vmx, $(src)/include/arch/x86,
$(src)/include/arch/x86/decoder (+ the relm_arch symlink under
$(src)/include).
The pervasive broken form #include <include/vmx.h> resolved to
$(src)/include/vmx.h — which does not exist (the header is
include/arch/x86/vmx/vmx.h). Corrected forms:
| Broken | Fixed | Resolves via |
|---|---|---|
<include/vm.h> |
<relm/vm.h> |
-I$(src)/include |
<include/iommu.h> |
<relm/iommu.h> |
-I$(src)/include |
<include/vmx.h> |
<vmx.h> |
-I…/arch/x86/vmx |
<include/ept.h> |
<ept.h> |
-I…/arch/x86/vmx |
<include/apic.h> |
<apic.h> |
-I…/arch/x86/vmx |
<include/vmx_ops.h> |
<vmx_ops.h> |
-I…/arch/x86/vmx |
<include/vmexit.h> |
<vmexit.h> |
-I…/arch/x86/vmx |
<include/vmcs_state.h> |
<vmcs_state.h> |
-I…/arch/x86/vmx |
(The already-working forms <include/relm/…>, <include/firmware/…>,
<include/virtio/…>, <include/arch/x86/…>, <utils/utils.h> were left alone
to minimize churn.)
- Bug:
BOOT_OBJSlistedsrc/boot/linux_loader.oandsrc/boot/stub.o. Neither source exists — the real boot sources aresrc/arch/x86/boot/boot.candsrc/arch/x86/boot/stub.S. kbuild aborts with "No rule to make target 'src/boot/linux_loader.o'" before compiling anything. Fix: pointBOOT_OBJSatsrc/arch/x86/boot/boot.oandsrc/arch/x86/boot/stub.o. - Bug (link):
src/arch/x86/boot/{boot,stub}were never in the build graph (ARCH_SRC_DIRwildcards onlysrc/arch/x86/vmx), sorelm_boot_load,relm_boot_info, and therelm_idt_stub_templatesymbols were undefined at link even thoughrelm.ccalls them. Fix: same as above — explicitly listing the boot objects pulls them in. - Bug: the
<relm_arch/...>include prefix used by generic headers mapped to nothing → fatal file not found in every core translation unit. Fix: newRELM_ARCH_LINKrule createsinclude/relm_arch → arch/$(ARCH)/$(SUBTARGET)as a prerequisite ofmodules:, removed onclean; added to.gitignore. src/core/memory.c(0 bytes) is left in the build: an empty translation unit produces an empty object that links cleanly, and the guest-memory helpers that would nominally live there are already defined insrc/core/vm.c. No change needed — flagged so it is not mistaken for a missing file.
- Bug (compile): broken include paths
<include/vmx_ops.h>,<include/vmcs.h>,<include/ept.h>,<include/apic.h>. Fix: →<vmx_ops.h>,<vmcs.h>,<ept.h>,<apic.h>. - Bug (compile): defined a second, flat
struct vcpuplus duplicatestruct guest_regs,enum vcpu_state,struct vcpu_stats— colliding with the authoritative definitions in<relm/vcpu.h>/<vcpu_arch.h>(redefinition errors; this header is transitively included byvcpu_arch.h). Fix: removed all four; added astruct vcpu;forward declaration so the prototypes here still compile.struct cr3_target_entry,struct cr3_shadow_cache(the full CR3-cache implementation), andstruct host_cpuare retained. - Bug (compile):
#define RELM_MAX_MANAGED_MSRS 8collided with the value16invcpu_arch.h. Fix: removed here; the16invcpu_arch.his now the single definition.
- Bug (compile): redefined
struct cr3_shadow_cachewith different members than the one invmx.h(included just above), a hard redefinition error. Fix: removed the minimal local definition; the full one fromvmx.his used. - Bug (compile/link):
struct vcpu_archwas missinghost_rspandvmentry_host_rsp, both referenced asvcpu->arch.host_rsp(vmx.c) and (after the member rename)vcpu->arch.vmentry_host_rsp(vmexit.c). Fix: added bothuint64_tfields with a comment explaining their role in the VM-entry/exit RSP handoff.
- Bug (compile): includes
<include/vmx.h>and<include/vm.h>— broken paths, and they formed an include cyclevm.h → vm_arch.h → ept.h → vm.h. Fix: all uses here are through pointers, so replaced the includes with forward declarationsstruct relm_vm; struct vcpu;plus<linux/spinlock.h>(forspinlock_t). Cycle-free. - Bug (compile):
int relm_ept_create_guest_page_tables(struct relm_vm *vm)(line 139) andint relm_virtio_mmio_handle_ept_violation(...)(line 152) were missing their terminating semicolons, merging with the next declaration and breaking every file that includesept.h. Fix: added the semicolons. - Bug:
EPT_MEMTYPE_WCwas(1ULL << 4)which encodes memtype value 2, not the write-combining memtype 1. Fix:(1ULL << 3)(bits[5:3] = 001).
vmexit.h/vmx_ops.h— Bug (compile): broken<include/...>paths. Fix:<include/vm.h>→<relm/vm.h>, and<include/vmcs_state.h>,<include/vmx.h>,<include/vmx_ops.h>,<include/vmcs.h>→ their bare arch forms.vmx_ops.h_vmwrite()— Bug (boot): on a genuinevmwritefailure it readVMCS_INSTRUCTION_ERROR_FIELDand, when that read itself returned 0 (e.g. VMfailInvalid, no current VMCS), returned0— i.e. success. Every mis-set VMCS field was silently accepted, so setup bugs surfaced only as an undiagnosable VMLAUNCH failure. Fix: always return a negative error on failure (error_code ? -error_code : -1).vmx_ops.h_cpu_has_vpid()— Bug: testedCPUID.1:ECX[5], which is the VMX feature bit, not VPID. Fix: read the VPID allowed-1 bit (secondary control bit 5) from the high dword ofIA32_VMX_PROCBASED_CTLS2.vmx_ops.h_get_vmcs_size()— Bug:"0x%ll\n"truncated format specifier. Fix:"0x%llx\n".apic.h— Bug:enum virt_apic_timer_mode timer_mode; ;— trailing empty struct member (GNU-extension warning). Fix: removed the extra;.
- Bug (compile):
VCPU_STATE_UNINTIALIZED(misspelled) — the enum member isVCPU_STATE_UNINITIALIZED. Fix: corrected spelling. - Bug (compile):
free_pages(..., HOST_STACK_ORDER)at two sites —HOST_STACK_ORDERis not visible in this generic TU (the header definesRELM_HOST_STACK_ORDER, used for the matching alloc). Fix: →RELM_HOST_STACK_ORDER(also makes alloc/free orders match). - Bug (BOOT-CRITICAL): the vCPU loop tested
vcpu->ops->init(no such member) and then fell straight into the run loop without ever calling Phase-2vcpu_init— so VMCLEAR/VMPTRLD and every host+guest VMCS field write never happened, and VM-entry ran against a completely unprogrammed VMCS. The guest could never boot. Fix: testvcpu->ops->vcpu_init, then actually callret = vcpu->ops->vcpu_init(vcpu);on the pinned CPU and fail cleanly (jump to_out_arch_destroy) if it errors. This also makes the previously-unused_out_arch_destroylabel a livegototarget (removing the unused-label warning).
- Bug (compile): broken
<include/...>paths + no visibility of the fullstruct vcpu. Fix: paths corrected per §0.3 and added#include <relm/vcpu.h>. - Member rename:
vcpu->regs.*→vcpu->arch.regs.*(throughout),vcpu->efer→vcpu->arch.efer,vcpu->vmentry_host_rsp→vcpu->arch.vmentry_host_rsp. - Bug (BOOT-CRITICAL — CPUID results discarded):
emulate_cpuid()wrote the CPUID result intovcpu->arch.regsonly. But on VMRESUME the exit stub (vmx_asm.S) restores guest GPRs from the on-stack save block (struct stack_guest_gprs *guest_gprs), not fromvcpu->arch.regs— so the emulated result never reached the guest. Linux issues CPUID pervasively during early boot, so this alone stalled the boot. Fix:emulate_cpuid()now takesguest_gprsand writes the result into it (still mirroring intovcpu->arch.regsfor tracing); the call site passesguest_gprs. - Bug (BOOT-CRITICAL — fw_cfg IN corrupts guest IDT): on a fw_cfg
INthe read value was written with_vmwrite(0x6818, rax).0x6818isGUEST_IDTR_BASE, not a register — every fw_cfg read clobbered the guest IDT base, and the value never actually reached the guest (onlyvcpu->arch.regswas set). Fix: removed the bogus VMCS write; deliver the value viaguest_gprs->rax(the path that actually reaches the guest), mirrored intovcpu->arch.regs.rax. - Bug (BOOT-CRITICAL — long mode never activates): in the WRMSR-EFER path,
bool lma = lma && pg;usedlmain its own initializer (garbage);if(lma) val != (1ULL<<10);was a discarded comparison that never set EFER.LMA; and CR0.PG was tested at bit 32 instead of bit 31. Result: EFER.LMA and theIA32E_MODEentry control were driven from garbage, so the guest could not reliably enter 64-bit mode. Fix:lma = lme && pg;,pg = guest_cr0 & (1ULL<<31), andif(lma) val |= (1ULL<<10);. - Bug (BOOT-CRITICAL — VM-entry-failure check dead):
if(exit_reason & (1U << 32))— the failure flag is bit 31, and1U<<32is an undefined 32-bit shift. Fix: test(1ULL<<31)on the full exit-reason value before masking to 16 bits. - Bug (BOOT — HLT permanently stops the vCPU): HLT set
vcpu->state = VCPU_STATE_HALTED, which the generic loop treats as "state != RUNNING" and breaks out of — killing the vCPU on the first HLT. Linux's idle path HLTs expecting resume-on-interrupt. Fix: advance RIP, setvcpu->halted = true, keepstate == RUNNING, and return 0 so the loop sleeps on the wait queue until an interrupt is injected, then re-enters the guest. - Bug (BOOT — MMIO emulation stops the guest): the successful
relm_virtio_mmio_handle_ept_violationbranch setret = 0(= stop). Fix:ret = 1(resume via VMRESUME); the handler already advanced RIP past the faulting instruction. Killed the guest on its first virtio MMIO access before. - Bug (diagnostics/robustness): no
case EXIT_REASON_EPT_MISCONFIG— an EPT misconfig fell through to the generic "unhandled reason" stop. Fix: added a dedicated case that readsGUEST_PHYSICAL_ADDRESS, callsrelm_vcpu_handle_ept_misconfig(vcpu->vm)(prototype added toept.h), and stops with a specific diagnostic.
Compile
- Broken include paths (
<include/vmx.h>…<include/vmcs_state.h>) → bare arch forms; added#include <relm/vcpu.h>(this is what definesstruct vcpuwith.arch,.launched,.stats). - The
vmx_vcpu_opsinitializer referencedvmx_setup_mmu,vmx_inject_irq,vmx_read_msr,vmx_write_msrwith no forward declarations (defined much later) → added the fourstaticprototypes. relm_vmentry_asm(...)called with no C prototype (implicit declaration, fatal under-Werror) → addedextern int relm_vmentry_asm(void *guest_regs, int launched);, matching the asm (RDI=guest_regs, RSI=launched) and the callrelm_vmentry_asm(&vcpu->arch.regs, vcpu->launched).pr_err("…%d…\n"), atomic_read(&work.failed_cpus);— the)closed the call before its argument (%dhad no operand). Fixed to pass the argument.- Member map:
vcpu->arch.launched→vcpu->launched(4 sites —launchedis a genericstruct vcpumember, not invcpu_arch);vcpu->vm->pml4_gpa/kernel_entry_gpa/total_guest_ram→vcpu->vm->arch.*/vcpu->vm->memory.*. .dump_regs = vmx_dump_regstook the address of astatic-declared but never-defined function (fatal under-Werror). The real defined function isvmx_dump_vcpu; removed the dead declaration, prototypedvmx_dump_vcpu, and pointed.dump_regsat it.
Boot-critical
- HOST_IA32_PAT never written although
VM_EXIT_LOAD_IA32_PATis enabled → on the first VM-exit the host reloads PAT=0 ⇒ all host memory Uncacheable ⇒ host hang. Fix:CHECK_VMWRITE(HOST_IA32_PAT, __rdmsr1(MSR_IA32_CR_PAT));inrelm_setup_host_state(reads the real host PAT so host caching is preserved). - GUEST_IA32_PAT never written although
VM_ENTRY_LOAD_GUEST_PATis enabled → guest runs fully Uncacheable. Fix:CHECK_VMWRITE(GUEST_IA32_PAT, __rdmsr1(MSR_IA32_CR_PAT));in the Linux-boot guest-state path.
Correctness
relm_setup_guest_state_longmode:cr0/cr4/cr0_fixed*/cr4_fixed*declared but never assigned, thencr0/cr4read uninitialized in apr_info— and the function never actually VMWROTE GUEST_CR0/CR4. Fix: read the four VMX fixed-bit MSRs (CR0_FIXED0/10x486/7,CR4_FIXED0/10x488/9), compute long-mode-legalcr0/cr4((val|fixed0)&fixed1), VMWRITE GUEST_CR0/CR4, updatevcpu->arch.cr0/cr4, and print the real values.memset(cache, 0, sizeof(cache))inrelm_cr3_cache_initzeroed only pointer size →sizeof(*cache).- HOST_CS/SS/DS/ES selectors masked with
0xF8(clears index bits 8-15, corrupting__KERNEL_CS/DSon modern kernels ⇒ VM-entry failure) →0xFFF8. relm_get_max_cr3_targetstruncated a 9-bit field (max 256) via auint8_tcast (256→0) → cast touint32_t.
vm.c
- Removed the duplicate per-CPU
current_vcpublock (DEFINE_PER_CPU +relm_get_current_vcpu) — also defined invcpu.c, causing a multiple-definition link error. relm_generic_vm_opsinitializer:;→,(a;terminated the initializer mid-list, dropping later members);relm_generic_vm_ops->x→.x(it's a struct value, not a pointer).vm-<mem_ops->setup→vm->mem_ops->setup(-<typo).relm_run_vcpu(vm)→relm_vcpu_run(vcpu)(correct API, per-vcpu);relm_stop_vcpu(...)(1- and 2-arg, nonexistent) →relm_vcpu_stop(vcpu);relm_vcpu_destroy(...)→relm_vcpu_free(vm->vcpus[i])(the defined function).goto _out_free_epttargeted an undefined label → retargeted to_out_free_mmu(the correct cleanup at that failure point).relm_vm_allocate_guest_ram: page was stored inregion->pages[i]and then__free_page'd on map failure, and freed again by the cleanup loop (double free) → store only after a successful map.relm_vm_zero_guest_memory:regionused uninitialized on the first iteration → initialized toNULL.- Removed a duplicated
vcpu = vm->vcpus[i];line, a deadreturn NULL;after agoto, and moved declarations above statements where-Werror=declaration- after-statementwould fire.
iommu.c
- Broken includes fixed; every
vm->iommu.*→vm->arch.iommu.*,vm->ept→vm->arch.ept,vm->total_guest_ram→vm->memory.total_guest_ram. relm_iommu_destroy: inverted guardif(!enabled || domain)(returned when a domain existed, leaking it) →if(!enabled || !domain).- Device-detach loop used
list_for_each_entrywithlist_del+kfreeinside (use-after-free) →list_for_each_entry_safe(using the already-declaredtmp).
decoder.c
-ENOSYSused without errno → added#include <linux/errno.h>.
relm.c — no changes needed; the relm_boot_load call and all includes
resolve correctly.
Compile
- Broken includes fixed.
- Stray
bool relm_virtio_mmio_handlefragment (no params/body/;) beforerelm_vcpu_handle_ept_misconfigmade the whole file fail to parse → removed. relm_ept_create_guest_page_tables_vmx(vm)→relm_ept_create_guest_page_tables(the defined/declared name)._unlikely(!table)→unlikely(!table).pr_err("…: %d\n")missing itserrargument → added it.
Boot-critical
- EPT leaf forced WB memtype: the leaf writer OR-ed in
EPT_MEMTYPE_WBunconditionally, so MMIO/APIC-access pages (which pass UC) got WB ⇒ wrong device caching / EPT misconfig. Fix: leaf writer now trusts the caller'sflags(hpa | flags); RAM call sites (vmx_mem_map_pageRAM path, and the three guest page-table pages inrelm_ept_create_guest_page_tables) were updated to passEPT_MEMTYPE_WB, while device callers keep UC. Invariant: leaf writer trusts flags; RAM→WB, device→UC.
Correctness
relm_handle_ept_violation: inverted guardif(!vm || vm->arch.ept)(errored when EPT was valid) →if(!vm || !vm->arch.ept).free_page((unsigned)table_va)truncated a 64-bit pointer →(unsigned long).vm->total_guest_ram→vm->memory.total_guest_ram.
- Broken includes fixed; added
#include <relm/vcpu.h>. vm->ept→vm->arch.ept(two sites).- APIC member location (the audit had this backwards): since
vcpu_archis authoritative, the virtual APIC isvcpu->arch.apic. Thevcpu->arch.apicuses were already correct; the wrong ones werecandidate->apic.*/target->apic→->arch.apic, andcontainer_of(apic, struct vcpu, apic)(wrong —apicis a member ofstruct vcpu_arch) → the two-stepcontainer_of(container_of(apic, struct vcpu_arch, apic), struct vcpu, arch).
boot.c
<include/vm.h>→<relm/vm.h>;vm->total_guest_ram→vm->memory.total_guest_ram(3 sites).relm_boot_loaddefined withstruct device dev(by value) but the header prototype and caller usestruct device *→ changed the parameter to a pointer, which fixes the cascading!devandrelm_kernel_load(…, dev, …)uses.relm_install_daig_idt_gdt(typo "daig") at the call (~423) and the definition (~520) →relm_install_diag_idt_gdt, matching the header.
e820.c / fw_cfg.c — <include/vm.h>→<relm/vm.h>;
vm->total_guest_ram→vm->memory.total_guest_ram.
seabios.c — broken includes fixed; seabios_hva = seabios_va (void*→uint64_t)
→ (uint64_t)(uintptr_t)seabios_va; sibling seabios_hva = NULL→= 0.
include/firmware/fw_cfg.h — a second block re-#defined the FW_CFG_*
selector keys to different, shifted values (macro-redefinition, fatal under
CONFIG_WERROR, and wrong keys so SeaBIOS read the wrong items). Fix: removed
the conflicting block, keeping the single canonical QEMU/SeaBIOS key set
(KERNEL_CMDLINE=0x09, INITRD_ADDR=0x0A, INITRD_SIZE=0x0B, BOOT_DEVICE=0x0C,
MAX_CPUS=0x0F, …); verified every key used by fw_cfg.c still exists.
include/virtio/mmio.h — RELM_MAC_MMIO_REGIONS→RELM_MAX_MMIO_REGIONS
(used to size vm.h's mmio_regions[]); RELM_VIRTIO_MMIO_REGION→
RELM_VIRTIO_MMIO_REGION_SIZE.
include/virtio/virtio.h — __atrribute__→__attribute__ (×2);
uint16/uint32→uint16_t/uint32_t (several); last_avial_idx→
last_avail_idx; struct member op→ops; drivers_features_ack→
driver_features_ack; sizeof(struct vring_used_elem)→…vring_used_entry;
vring_avail_total_size ring term scaled by sizeof(__le16) (was undercounting
the avail ring by ~2*queue_size bytes); added a find_device_for_gpa prototype.
src/virtio/virtio.c — unsigned int;→unsigned int i;;
VIRTIO_MMIO_MAGIC_VALUE→VIRTIO_MMIO_VALUE (+ missing ;) — without a correct
MagicValue the guest rejects the device; VERSON→VERSION; write→is_write
(2 sites); DeviceFeaturesSel now latches on WRITE (was !is_write, breaking
feature negotiation); ?=→>=; if(!value == 0)→if(*value == 0) (the old
form was always true, so virtqueues never became ready); *value == vq->ready
→ *value = vq->ready (QueueReady read was discarded); notify→queue_notify;
case …STATUS_OFF;→:; missing ;; opd→ops; spin_unlock(%dev…)→&dev…;
made find_device_for_gpa non-static.
src/virtio/mmio.c — <include/arch/x86/apic.h>→<apic.h> (+ <relm/vcpu.h>);
relm_cm_reserve_mmio_region→relm_vm_reserve_mmio_region;
mem_regions_count/mem_region_count→mmio_region_count,
mmio_region[i]→mmio_regions[i]; overlap→overlaps (in-scope);
relm_virto_device→relm_virtio_device; insn_bytes→insn_buf; immediate
MMIO writes (0xC7) now use decoded.immediate instead of an uninitialized GPR
index; 1if (needs_irq)→if (needs_irq);
relm_apic_inject_irq(dev->vm, dev->irq) → the real API
relm_apic_inject_interrupt(&vcpu->arch.apic, (uint8_t)dev->irq, false).
include/arch/x86/decoder/insn.h — #include "inat.h"" (stray ") →
#include "inat.h".
src/arch/x86/decoder/inat.c includes
<include/arch/x86/decoder/inat-tables.c>, a generated file that is not
present anywhere in the repo (confirmed by search). Without it the symbols
inat_primary_table, inat_escape_tables, inat_group_tables,
inat_avx_tables, inat_xop_tables are undefined and the x86 instruction
decoder cannot link — which also blocks the virtio MMIO EPT-violation path
(relm_decode_instruction). This is data, not a code bug; it must be generated
from Linux kernel sources on your Linux build host:
awk -f <linux>/arch/x86/tools/gen-insn-attr-x86.awk \
<linux>/arch/x86/lib/x86-opcode-map.txt \
> include/arch/x86/decoder/inat-tables.cThe include was left in place with a documenting comment. Everything else is fixed at the source level.
make ARCH=x86 SUBTARGET=vmx # on a Linux host with kernel build headersNot compiled in the authoring environment (macOS). Expect to iterate on residual
warnings that only a real -Werror kernel build will surface; the structural
blockers (build graph, the two struct vcpu worlds, the relm_arch prefix, the
pervasive include breakage) and the boot-path logic bugs (unprogrammed VMCS,
CPUID/EFER/PAT/HLT/MMIO handling) are resolved above.