Virtualization · Hypervisors
The Fabricated Machine: ACPI Tables, SMBIOS, and PCI Configuration Space
How a hypervisor describes hardware that does not exist, which tables the guest kernel trusts for topology and interrupt routing, and why a wrong table entry survives every test until the guest boots on real silicon.
Everything covered so far concerns how a guest executes. None of it tells the guest what machine it is running on. A kernel does not discover its processor count, its interrupt routing, or its storage by probing; it reads descriptions that firmware prepared, and it trusts them. In a virtual machine the firmware is a hypervisor component that emits those descriptions from a configuration file, and the descriptions are the entire physical reality the guest will ever perceive.
The consequence is a peculiar failure mode. A wrong byte in a description table does not fail at the point of the error. It fails when the guest’s driver for a device that was described inconsistently initializes, or when a topology-aware scheduler makes a decision based on a distance table that describes a machine with no such topology. The guest runs on real hardware afterwards, and the same code path is correct, which makes the defect look like a hypervisor bug rather than a description bug.
What the guest is handed before it executes a line of its own code
A guest kernel starts with almost nothing. It has a small amount of memory, an entry point, and a promise that certain structures exist at certain addresses. The structures arrive in two waves.
The first wave is the boot-time handoff: a pointer to firmware tables, a memory map describing which physical ranges exist and which are usable, and a description of the interrupt controller. On a machine with ACPI, the pointer is to a root system description table whose first entry names the fixed-location tables the guest should read. The memory map is where the first lie appears: a guest is told it has a contiguous range of physical memory starting at zero, while the hypervisor has scattered that range across machine frames. The second-level translation tables are what make the lie consistent.
The second wave is device enumeration. The guest walks the PCI bus, finds devices at the addresses the bus reports, reads each device’s configuration space to learn its class, its BARs, and its interrupt pin, and binds a driver. Every one of those reads is answered by the hypervisor’s device model or by hardware passed through, and the answer must match the tables that described the device.
The two waves must agree. A guest kernel reads the interrupt routing from an ACPI table and the interrupt assignment from PCI configuration space, and if the two disagree, the driver programs a vector that no device will ever raise. Nothing detects the mismatch; the device simply never interrupts, and the guest waits.
ACPI: tables the guest parses as bytecode and as data
The ACPI tables come in two kinds, and the difference matters for how a hypervisor generates them.
The data tables are fixed-layout structures with a header that names the table, states its length, and carries a checksum over its bytes. The multiple APIC description table lists the interrupt controllers and the processors, so it is where the guest learns how many CPUs exist and which identifiers they have. The SRAT and SLIT tables describe memory affinity and inter-node distances, and a guest scheduler uses them to decide where to place work. Generating a data table is straightforward: the layout is defined, the fields are numbers, and the checksum is an eight-bit sum that must come out to zero.
The definition block is different. It holds AML, a bytecode that the guest’s interpreter executes to build the device tree and to evaluate methods. A hypervisor that wants to describe a device with power management, or a hotpluggable slot, or a battery, must emit AML, and emitting AML means either compiling it from source with a compiler that targets the specification or copying a block from a real machine and patching it. Both approaches appear in practice, and both have the same hazard: the block describes a machine, and if the machine the hypervisor builds differs from the one the block describes, the interpreter’s evaluation produces addresses and interrupt numbers that point at nothing.
The AML path is also where a guest can be attacked through its own firmware. A definition block is code, and a malicious hypervisor can hand a guest a block whose methods read arbitrary memory or write to ports. Guests that trust their firmware trust it completely, which is why the boundary between a confidential guest and its host has to cover the firmware interface and not just the memory.
The checksum is the one self-check the format offers. A table whose bytes sum to something other than zero is rejected, and the guest falls back to a default configuration or refuses to boot. That check catches corruption, not inconsistency: a table with a correct checksum and a wrong processor count passes and misleads.
PCI configuration space as the enumeration surface
The PCI bus is addressed by bus, device, and function numbers, and each function has a configuration space. In the original mechanism the guest writes an address to a pair of I/O ports and reads or writes four bytes of data, which means each configuration access is two port operations. Later mechanisms memory-map the configuration space, giving each function a region and allowing longer accesses.
A hypervisor’s device model must answer every configuration read for every function it presents, including the ones that are not there. The absent-device answer is all ones, which the guest interprets as “no device at this address”, so the model must know which addresses are empty and return that pattern rather than a zero. A model that returns zeros for absent functions makes the guest enumerate devices that do not exist and attempt to drive them.
The BARs are the interesting field. A device reports how much address space it needs by writing all ones to a BAR and reading back which bits are hardwired to zero, which encodes the size and alignment. The guest then assigns a base address. The hypervisor must honour the assignment, because the guest will program the device with that base, and the model must translate guest-physical accesses to that range into device operations. A model that reports a size the guest cannot satisfy, or that ignores the assignment and keeps its own base, produces a device the guest programs at an address the model does not watch.
Interrupts cross the same boundary. Each function reports an interrupt pin, and the ACPI tables map pins to interrupt controller inputs through a routing table that depends on the bus and slot numbers. The routing is a legacy artifact of physical wiring, and in a virtual machine there is no wiring, so the mapping is arbitrary but must be consistent. A hypervisor that assigns interrupt lines in the order devices are created and describes them in a table generated in a different order produces a guest that routes every interrupt to the wrong handler.
/* Answer a legacy configuration-space read for one function. */
static uint32_t pci_config_read(struct device *d, uint8_t offset, int size)
{
if (!d) /* no function at this address */
return size == 4 ? 0xffffffffu
: (0xffffffffu >> (32 - 8 * size));
if (offset == 0x3c && size == 4) {
/* Interrupt line and pin must match the routing table the guest read. */
return (uint32_t)d->irq_line << 24 | (uint32_t)d->irq_pin << 16;
}
return read_le(d->config + offset, size);
}
SMBIOS and the identity the guest believes it has
A third description layer reports the machine’s identity: manufacturer, product name, serial number, and the layout of memory modules and slots. It exists because management software wants to inventory physical hardware, and in a virtual machine the inventory describes hardware that was never manufactured.
Two things make it consequential. First, guest software sometimes keys behaviour off the product name, licensing being the usual reason, and a hypervisor that reports a generic string gets the generic code path while one that reports a specific string gets a path the hypervisor did not test. Second, the serial number and UUID are used to identify a machine across reboots, and a hypervisor that regenerates them on every start produces a guest that believes it has moved to new hardware each time, which invalidates cached entitlements and triggers reactivation logic.
The structures are a chain of variable-length records with a fixed header and a string table terminated by a double null. Generating them is mechanical; deciding what to put in them is policy, and the policy leaks. A guest can compare the serial number it read at boot with the one it reads after a migration, and the two should match if the migration was meant to be invisible.
Topology that the guest will act on
The tables that cause the most subtle problems are the ones describing relationships rather than devices.
A processor’s identifier in the interrupt controller table is not its index. Guests use the identifier to address inter-processor interrupts, and a hypervisor that assigns identifiers densely from zero and a topology that assumes they are contiguous will work on the machines the hypervisor was tested on and fail on one where a virtual CPU was removed and its identifier reused.
The NUMA distance table describes the cost of accessing memory attached to another node, as a number from 10 upward, where 10 means local. A guest scheduler reads those numbers and places work accordingly. A hypervisor that emits a distance table describing two nodes but maps all memory to one produces a scheduler that spreads work across nodes with identical costs, which is not wrong but is pure overhead. A hypervisor that emits a distance table with a value of zero for a remote node, which the specification does not permit, produces undefined behaviour in a guest that divides by it.
Cache and topology information adds a third dimension. Guests that are aware of which virtual CPUs share a cache or a core will place cooperating threads together, and the hypervisor’s scheduling decides whether that placement is real. Describing a topology the scheduler does not honour is worse than describing none, because the guest’s optimizations become pessimizations that the hypervisor cannot see.
Why these descriptions are the hardest part to get right
The mechanisms in the earlier articles have observable failure modes. A wrong page table causes a fault. A wrong exit classification hangs a virtual CPU. A wrong table causes nothing at all until a specific driver, on a specific topology, evaluates a specific method.
That latency is the design constraint. Firmware descriptions are a compatibility surface with no feedback channel: the guest cannot report that a table is wrong, only misbehave later. Hypervisors therefore converge on emitting descriptions copied from real machines, patched minimally, because a description that a real operating system has already been tested against is more likely to be consistent than one generated from first principles. The cost is that the virtual machine inherits the quirks of the machine it copied, including devices the hypervisor does not implement and slots that do not exist, and the guest’s drivers spend time probing for hardware that the description promised and the device model does not provide.
The residue is a rule that applies to the whole cluster. Every layer that hides the machine must describe the machine it is pretending to be, and the description is part of the interface. A hypervisor that virtualizes the processor perfectly and describes the platform carelessly produces guests that fail for reasons no amount of exit-count tuning will fix.
References
- Advanced Configuration and Power Interface (ACPI) Specification — UEFI Forum
- System Management BIOS (SMBIOS) Reference Specification — DMTF
- PCI Local Bus Specification — PCI Special Interest Group
- PCI Express Base Specification — PCI Special Interest Group