VMCS and VMCB (Virtual Machine Control Structure)
Hardware-assisted virtualization needs one canonical, in-memory place to hold the entire processor worldview of a guest — every control register, segment register, the instruction pointer, the flags, the system descriptors — so that the CPU can swap from running the hypervisor to running the guest and back, atomically, without the hypervisor manually shuffling registers each time. On Intel this place is the VMCS (Virtual-Machine Control Structure); on AMD it is the VMCB (Virtual-Machine Control Block). Both are 4 KiB physical pages, but they embody two different design philosophies: Intel’s VMCS is opaque — software may touch it only through the
VMREADandVMWRITEinstructions, never by ordinary memory load/store, because the CPU is free to cache parts of it on-chip in an implementation-defined format (Intel SDM Vol 3C). AMD’s VMCB is a plain, documented struct that the hypervisor reads and writes with normalmovinstructions, its layout fixed by offset in the AMD architecture manual (AMD SVM Arch. Ref. Manual 33047). This note explains what lives inside each structure, why it must exist, how Linux’s KVM models it, and where the two vendors diverge. (Code and constants are pinned to Linux 6.12 LTS, x86, released 2024-11-17; the architectural facts come from the Intel SDM Vol 3C and AMD APM Vol 2 / the original SVM reference manual.)
The structure pairs with the entry/exit machinery: this note is the data, VM Entry and VM Exit Mechanics is the verb that consumes it, and Intel VMX and AMD SVM is the mode that the data describes. Read them together.
Mental Model
Think of the VMCS/VMCB as a save-game file for a whole CPU. When the hypervisor decides to run a guest, it does not push/pop dozens of registers by hand; instead it points the CPU at this 4 KiB page and issues a single instruction (VMLAUNCH/VMRESUME on Intel, VMRUN on AMD). The hardware then atomically spills the hypervisor’s CPU state into the structure’s host area, loads the guest’s CPU state from the structure’s guest area, and begins executing guest code. On the way back — a VM exit — the hardware does the reverse and stamps a reason code into the structure so the hypervisor knows why it regained control. The page therefore has four conceptual zones: guest state (loaded on entry, saved on exit), host state (loaded on exit), controls (the hypervisor’s policy: which guest instructions and events to trap), and exit information (filled by hardware, read by the hypervisor).
flowchart LR subgraph VMCS["VMCS / VMCB — one 4 KiB page per vCPU"] G["Guest-state area<br/>(CR0/3/4, RIP, RFLAGS,<br/>segments, system regs)"] H["Host-state area<br/>(where to return on exit)"] C["Control area<br/>(exec / entry / exit controls,<br/>intercept bitmaps, EPT/NPT ptr)"] X["Exit-information fields<br/>(reason, qualification,<br/>EXITINFO1/2)"] end HV["Hypervisor (KVM)"] CPU["CPU on VM entry / VM exit"] HV -->|"VMWRITE / mov: set controls + guest state"| C HV -->|"VMWRITE / mov"| G CPU -->|"entry: load guest, save host"| G CPU -->|"exit: save guest, load host, write reason"| X HV -->|"VMREAD / mov: read reason"| X
The four zones of the control structure. What it shows: the hypervisor populates the control and guest-state zones before entry; the CPU shuttles state between guest and host zones on each entry/exit and writes the exit reason into the exit-information zone; the hypervisor reads that reason to decide what to do. The insight to take: the structure exists so that one instruction can swap an entire CPU’s architectural state in one shot — the alternative (the hypervisor manually saving/restoring ~30 registers, MSRs, and descriptor tables on every transition) would be both slow and racy.
Why the Structure Must Exist
Before hardware virtualization, swapping CPU “worldviews” was the hypervisor’s manual job, and it was the central source of both complexity and bugs (see Why x86 Needed Hardware Virtualization and Shadow Page Tables). The processor running the guest must believe it owns ring 0, has its own CR3 page-table root, its own IDTR/GDTR, its own RFLAGS. The processor running the hypervisor needs its values back the instant the guest traps. If the hypervisor did this in software, every transition would be tens of loads and stores, and — worse — there would be a window where the CPU’s state is half guest, half host, during which any interrupt or fault would be catastrophic.
The VMCS/VMCB solves this by making the state swap a single, architecturally atomic hardware operation. The hypervisor writes the guest’s intended state and its own return state into the page once; thereafter each entry/exit is one instruction. This is the foundational data structure of VMX and SVM — without it, the new processor modes (Root Mode and Non-Root Mode) would have nothing to transition through.
A second reason it exists: it is the policy surface. The control fields decide which guest behaviours cause a trap. Should the guest’s HLT exit to the hypervisor or be allowed to run? Should CPUID be intercepted (it must, to lie about CPU features)? Should writes to CR3 trap, or is two-dimensional paging (Two-Dimensional Paging (EPT and NPT)) handling address translation so they need not? Every “should this guest action trap?” answer is a bit in the control area. The cost of running a guest is overwhelmingly the cost of these traps (VM Exit Reasons and Handling), so the control area is where a hypervisor tunes performance.
The Intel VMCS
Layout and the four region groups
Architecturally the VMCS is divided into six logical components (Intel SDM Vol 3C §25.1): the guest-state area, the host-state area, the VM-execution control fields, the VM-exit control fields, the VM-entry control fields, and the VM-exit information fields. The first 4 bytes of the page are not part of any field group: they hold the VMCS revision identifier (bits 0–30) plus a shadow-VMCS indicator (bit 31), and the next 4 bytes hold the VMCS abort indicator. KVM models exactly this preamble (arch/x86/kvm/vmx/vmcs.h, v6.12):
struct vmcs_hdr {
u32 revision_id:31;
u32 shadow_vmcs:1;
};
struct vmcs {
struct vmcs_hdr hdr; /* the 32-bit revision id + shadow bit */
u32 abort; /* the VMCS abort indicator */
char data[]; /* the rest — opaque, touched only by VMREAD/VMWRITE */
};The data[] member is deliberately an opaque flexible array: KVM never indexes into it. The CPU owns the format of everything past the first 8 bytes, and the only legal way to read or write those bytes is the VMREAD/VMWRITE instructions. The revision_id must match what the CPU reports in the IA32_VMX_BASIC MSR, or VMPTRLD (below) fails — this is how the CPU refuses to operate on a VMCS laid out for a different microarchitecture.
Field encoding — why fields have numbers, not offsets
Because the VMCS is opaque, fields are not named by byte offset; they are named by a 32-bit encoding. VMREAD reg, field_encoding reads, VMWRITE field_encoding, value writes. The encoding itself carries metadata, which KVM decodes with two small helpers (vmcs.h, v6.12):
static inline int vmcs_field_width(unsigned long field)
{
if (0x1 & field) /* bit 0 set -> a *_HIGH field, always 32 bit */
return VMCS_FIELD_WIDTH_U32;
return (field >> 13) & 0x3; /* bits 14:13 encode the width class */
}
static inline int vmcs_field_readonly(unsigned long field)
{
return (((field >> 10) & 0x3) == 1); /* bits 11:10 == 01b -> read-only data */
}Reading this is reading the encoding scheme directly: bits 14:13 select the width class (00b = 16-bit, 01b = 64-bit, 10b = 32-bit, 11b = natural-width i.e. 32/64 depending on mode), bits 11:10 select the type (00b = control, 01b = read-only data, 10b = guest state, 11b = host state), and bit 0 distinguishes the low and high halves of a 64-bit field. So the four “region groups” are not contiguous byte ranges you can memcpy; they are encoded into the field number itself. The actual encodings appear as the enum vmcs_field in the architecture header (arch/x86/include/asm/vmx.h, v6.12) — for example GUEST_CS_SELECTOR = 0x00000802 (type 10b = guest, width 00b = 16-bit), HOST_CS_SELECTOR = 0x00000c02 (type 11b = host), EPT_POINTER = 0x0000201a (control, 64-bit). That the read-only data fields exist as a distinct type is why VMWRITE to them fails with VMXERR_VMWRITE_READ_ONLY_VMCS_COMPONENT (error 13).
What the four groups hold, concretely
- Guest-state area — the architectural CPU state the hardware loads on VM entry and saves on VM exit:
CR0,CR3,CR4,RIP,RSP,RFLAGS, the segment registers (CS/SS/DS/ES/FS/GS) including their hidden base/limit/access-rights,LDTR,TR,GDTR,IDTR, and a pile of MSRs and the activity/interruptibility state. This is the guest’s whole worldview. - Host-state area —
CR0/CR3/CR4,RIP,RSP, the host segment selectors andFS/GSbases,GDTR/IDTRbases. This is where the CPU lands on a VM exit.HOST_RIPin particular is the address at which the hypervisor resumes — KVM points it at itsvmx_vmexitlabel, which is why control “magically” reappears there after the guest traps (see VM Entry and VM Exit Mechanics). - VM-execution / VM-entry / VM-exit control fields — the policy. Pin-based and processor-based execution controls decide which events trap (e.g.
CPU_BASED_HLT_EXITING,CPU_BASED_USE_MSR_BITMAPS,SECONDARY_EXEC_ENABLE_EPT); theEPT_POINTERnames the guest’s second-level page tables; theMSR_BITMAP/IO_BITMAP_A/Baddresses point at bitmaps that select which specific MSRs and I/O ports trap rather than all of them. Entry controls govern injection of a pending event on the way in; exit controls govern what state is saved/loaded on the way out. - VM-exit information fields — read-only, written by hardware on exit: the exit reason (e.g.
EXIT_REASON_EPT_VIOLATION = 48,EXIT_REASON_CPUID = 10,EXIT_REASON_HLT = 12,EXIT_REASON_IO_INSTRUCTION = 30), the exit qualification (a reason-specific detail, e.g. which CR was touched, or the faulting guest-physical address class for an EPT violation), and the VM-exit interruption-information fields. KVM reads these to dispatch the exit (VM Exit Reasons and Handling).
Active, current, and the on-chip cache
Here is the subtlety that the VMCS opacity exists to enable. A logical processor may have several active VMCSs at once, but only one current VMCS at any instant, and VMREAD/VMWRITE/VMLAUNCH/VMRESUME operate only on the current VMCS (Intel SDM Vol 3C; felixcloutier VMPTRLD). The hypervisor selects the current VMCS with VMPTRLD <phys-addr> (load VMCS pointer): “after execution, that VMCS is both active and current on the logical processor … loading the current-VMCS pointer with this operand” (felixcloutier VMPTRLD). It deactivates one with VMCLEAR <phys-addr>, which flushes any cached state back to memory and marks the VMCS launch-state “clear.” VMPTRST stores the current pointer.
Why this dance? Because the CPU is permitted to keep the current VMCS’s contents in implementation-specific on-chip memory for speed — a hidden cache. That is precisely why software must never mov into the VMCS page: the in-memory image may be stale relative to the on-chip copy. VMCLEAR is the instruction that says “I am done with this VMCS on this CPU; write the cache back to RAM.” This matters acutely when a vCPU migrates between physical CPUs: KVM must VMCLEAR on the old CPU (flush the cache to memory) before VMPTRLDing on the new one, or the new CPU would run with a half-stale VMCS.
KVM tracks all of this in struct loaded_vmcs, and keeps a per-CPU pointer to the live one (vmcs.h, v6.12):
DECLARE_PER_CPU(struct vmcs *, current_vmcs);
struct loaded_vmcs {
struct vmcs *vmcs;
struct vmcs *shadow_vmcs;
int cpu; /* which physical CPU this VMCS is loaded on (-1 = none) */
bool launched; /* has VMLAUNCH run? -> use VMRESUME next time */
bool nmi_known_unmasked;
...
struct list_head loaded_vmcss_on_cpu_link; /* all VMCSs loaded on this CPU */
struct vmcs_host_state host_state; /* write-through cache of host fields */
struct vmcs_controls_shadow controls_shadow; /* cached control-field values */
};The launched flag is the software memory of the VMCS launch state and is what decides VMLAUNCH vs VMRESUME (see VM Entry and VM Exit Mechanics). The loaded_vmcss_on_cpu_link list lets KVM VMCLEAR every VMCS still cached on a CPU that is going offline. The host_state and controls_shadow are software write-through caches: rather than issue a VMREAD (which is not free) to re-read a host field or a control word KVM already knows, KVM keeps a shadow copy in normal memory. The header comment is explicit: vmcs_host_state “is used as a write-through cache of the corresponding VMCS fields.” This is a direct consequence of VMCS opacity — because you cannot just dereference the field, caching what you wrote is a real optimization.
The AMD VMCB
One flat, documented struct
AMD took the opposite approach. The VMCB is a 4 KiB-aligned page whose layout is fixed and published by offset, and which the hypervisor reads and writes with ordinary memory accesses — no VMREAD/VMWRITE equivalent exists. The VMCB has just two areas: a control area (offset 0) and a state-save area (offset 0x400) (AMD SVM Arch. Ref. Manual 33047 §2.2). KVM mirrors this layout exactly as a packed C struct (arch/x86/include/asm/svm.h, v6.12):
struct vmcb {
struct vmcb_control_area control; /* 1024 bytes, offset 0x000 */
union {
struct vmcb_save_area save; /* 744 bytes, offset 0x400 */
struct sev_es_save_area host_sev_es_save; /* SEV-ES variant */
};
} __packed;
/* build-time assertions that lock the offsets to the architecture */
#define EXPECTED_VMCB_CONTROL_AREA_SIZE 1024
#define EXPECTED_VMCB_SAVE_AREA_SIZE 744
BUILD_BUG_ON(offsetof(struct vmcb, save) != EXPECTED_VMCB_CONTROL_AREA_SIZE); /* save @ 0x400 */The BUILD_BUG_ON is doing real work: it makes the compiler fail the build if the struct ever drifts from the architectural offset of 0x400 for the save area. That AMD lets KVM use offsetof at all — that the layout is a real C struct, not an opaque blob — is the whole difference from Intel.
The control area: intercepts and exit info
The control area is the AMD analogue of Intel’s control + exit-information groups. Its head is the intercept vector — six 32-bit words enumerating every guest event the hypervisor wants to trap, decoded in the kernel as an enum (asm/svm.h, v6.12): INTERCEPT_CPUID, INTERCEPT_HLT, INTERCEPT_IOIO_PROT, INTERCEPT_MSR_PROT, INTERCEPT_VMRUN, and so on, plus per-CR and per-DR read/write intercepts and a 32-bit exception-intercept word. After the intercepts come the I/O- and MSR-permission bitmap base addresses, the tsc_offset, the asid, the interrupt-control fields, and then the hardware-written exit fields:
struct __attribute__ ((__packed__)) vmcb_control_area {
u32 intercepts[MAX_INTERCEPT]; /* which guest events trap */
...
u64 iopm_base_pa; /* I/O permission bitmap */
u64 msrpm_base_pa; /* MSR permission bitmap */
u64 tsc_offset;
u32 asid; /* tagged-TLB address-space id */
...
u32 exit_code; /* <- hardware writes the exit reason */
u32 exit_code_hi;
u64 exit_info_1; /* <- EXITINFO1: reason-specific */
u64 exit_info_2; /* <- EXITINFO2 */
u32 exit_int_info;
...
u64 nested_ctl; /* nested-paging enable */
u64 nested_cr3; /* NPT root (AMD's EPT analogue) */
...
u64 next_rip; /* RIP after the intercepted insn */
u8 insn_len; /* length + bytes of the faulting insn */
u8 insn_bytes[15];
u64 vmsa_pa; /* SEV-ES encrypted save-area pointer */
...
};On a VM exit (#VMEXIT) the processor “saves the reason for exiting the guest in the VMCB’s EXITCODE field; additional information may be saved in the EXITINFO1 or EXITINFO2 fields, depending on the intercept” (AMD 33047 §2.3). The exit codes themselves are enumerated in the UAPI header (uapi/asm/svm.h, v6.12) — e.g. SVM_EXIT_CPUID = 0x072, SVM_EXIT_HLT = 0x078, SVM_EXIT_IOIO = 0x07b, SVM_EXIT_NPF = 0x400 (nested-page-fault, AMD’s EPT-violation analogue), SVM_EXIT_VMRUN = 0x080. A genuinely useful AMD touch, absent on Intel: next_rip, insn_len, and insn_bytes mean that for many intercepts the hardware hands the hypervisor the decoded faulting instruction, so KVM need not re-walk guest page tables and re-fetch/decode it.
The state-save area
The save area is the AMD analogue of Intel’s guest-state area: the segment registers (each a vmcb_seg of selector/attrib/limit/base), CR0/CR2/CR3/CR4, DR6/DR7, RFLAGS, RIP, RSP, RAX, EFER, the SYSENTER/STAR/LSTAR MSRs, CR2, and so on (asm/svm.h, v6.12). Crucially the host state does not live in the VMCB on AMD — VMRUN saves host state to a separate page pointed to by the VM_HSAVE_PA MSR, not into the VMCB. So the AMD VMCB is single-tenant: it describes the guest plus the trap policy, while host return-state is parked elsewhere. (Under SEV-ES the guest save area moves out of the VMCB entirely into an encrypted VMSA page; the VMCB save area is then reused only to save/load host state, hence the union above — see AMD SEV and SEV-SNP.)
Intel vs AMD — the same job, two philosophies
| Dimension | Intel VMCS | AMD VMCB |
|---|---|---|
| Size / alignment | 4 KiB, 4 KiB-aligned | 4 KiB, 4 KiB-aligned |
| Access method | only VMREAD/VMWRITE (opaque) | ordinary mov (documented offsets) |
| Layout | encoded field numbers, on-chip cache allowed | flat C struct, offsets fixed by spec |
| Host state | in the VMCS host-state area | in a separate VM_HSAVE_PA page, not the VMCB |
| Currency model | active/current; VMPTRLD/VMCLEAR swap | VMRUN rAX names the VMCB per call; no “current” latch |
| Faulting-insn help | re-decode from guest memory | next_rip + insn_bytes provided by HW |
The trade-off is real. Intel’s opacity buys the CPU freedom to cache and reorder the VMCS internally for speed, at the cost of forcing every access through a serializing instruction and forcing the hypervisor to maintain shadow caches (the host_state/controls_shadow in loaded_vmcs). AMD’s transparency makes the hypervisor’s life simpler — read a field, it is just a struct member — and the next_rip/insn_bytes gift removes a whole class of instruction-decode work, at the cost of a less abstracted hardware contract.
Uncertain
Verify: the exact byte offsets and the precise per-field membership of each VMCS group, and the claim that the VMCS abort indicator occupies bytes 4–7. Reason: these come from the Intel SDM Vol 3C §25.x, which was not fetched as a clean text extract during this task (the Intel PDF was located by search but not paragraph-verified); the KVM
vmcs.hconfirms the revision-id/shadow-bit/abort preamble and the encoding-decode helpers, but the full per-field offset table was corroborated via secondary references (OSDev VMX wiki, felixcloutier instruction pages) rather than the primary SDM tables. To resolve: read Intel SDM Vol 3C §25 “Virtual-Machine Control Structures” directly. The AMD-side offsets (control 1024 B at 0x000, save area 744 B at 0x400) are primary-verified against the kernelBUILD_BUG_ONs. uncertain
Uncertain
Verify: the AMD VMCB control-area size of 1024 bytes as an architectural guarantee vs a KVM implementation expectation. Reason:
EXPECTED_VMCB_CONTROL_AREA_SIZE = 1024is the kernel’s build-time assertion (primary,asm/svm.hv6.12); the AMD SVM manual fetched here (33047 Rev 3.02, 2005) documents the save area at offset 0x400 but the full Appendix C offset tables were not paragraph-extracted. The current canonical reference is AMD APM Vol 2 (newer than 33047). To resolve: cross-check AMD APM Vol 2 Appendix B “Layout of VMCB.” uncertain
Failure Modes and Pitfalls
moving into an Intel VMCS. A hypervisor author who treats the VMCS page like a struct (as on AMD) writes garbage: the CPU may be holding the live copy on-chip, so the in-memory write is silently ignored or, after a laterVMCLEAR, overwritten. The only correct access isVMWRITE. This is the single most common porting bug for people moving SVM code to VMX.- Forgetting to
VMCLEARon vCPU migration. If a vCPU thread is rescheduled to a different physical CPU while its VMCS is still “current/active” on the old CPU’s on-chip cache, the new CPU’sVMPTRLDloads a stale RAM image. KVM’sloaded_vmcs.cpufield and the per-CPUloaded_vmcss_on_cpu_linklist exist precisely to drive theVMCLEAR-then-VMPTRLDsequence correctly. - Wrong revision id. Copying a VMCS captured on one CPU model to another, or zeroing the page without writing the
IA32_VMX_BASICrevision id, makesVMPTRLDfail withVMXERR_VMPTRLD_INCORRECT_VMCS_REVISION_ID(error 11,uapi/asm/vmx.hv6.12). VMWRITEto a read-only field. Trying to write an exit-information field (type01b) fails with error 13. Thevmcs_field_readonly()helper exists so KVM can assert this in software.- AMD: stale host LDTR after VMEXIT. The AMD manual warns that “immediately after VMEXIT, the processor still contains the guest value for LDTR,” so the hypervisor must reload it; touching descriptor-table-relative state before doing so reads guest values (AMD 33047 §2.3). This is the kind of asymmetry the structures encode but do not fully automate.
Production Notes
KVM keeps the VMCS/VMCB hot. Beyond VMREAD avoidance via the controls_shadow/host_state write-through caches, modern Intel CPUs support VMCS shadowing (the shadow_vmcs bit in vmcs_hdr), which lets a nested L1 hypervisor’s VMREAD/VMWRITE hit a shadow VMCS directly instead of trapping to L0 — a large win for Nested Virtualization, where naive emulation would trap on every field access. On the AMD side, the next_rip/insn_bytes fields in the control area routinely save KVM an instruction-fetch-and-decode on common intercepts. When debugging a guest, trace-cmd/perf on the kvm:kvm_exit tracepoint shows the exit reason straight out of these structures (the symbolic names come from the VMX_EXIT_REASONS / SVM_EXIT_REASONS string tables in the UAPI headers), and the per-vCPU exit counters in vcpu->stat aggregate them — the universal first step in any “why is my VM slow” hunt is to read those reason codes, every one of which the CPU stamped into a VMCS/VMCB exit field.
See Also
- VM Entry and VM Exit Mechanics — the
VMLAUNCH/VMRESUME/VMRUNinstructions that load and save this structure - Intel VMX and AMD SVM — the hardware extensions and the modes the structure describes
- Root Mode and Non-Root Mode — the privilege axis the host/guest areas correspond to
- VM Exit Reasons and Handling — what KVM does with the exit-reason field this structure carries
- Two-Dimensional Paging (EPT and NPT) — the
EPT_POINTER/nested_cr3control fields point at the second-level page tables - KVM vCPU Run Loop — the software loop that drives the entry/exit this structure backs
- Nested Virtualization — VMCS shadowing and merging L1’s control structure with L0’s
- AMD SEV and SEV-SNP — why the SEV-ES guest save area leaves the VMCB for an encrypted VMSA page
- Linux Virtualization MOC — parent map