Skip to content

Model-Specific Registers

A model-specific register is a control register that, unlike EAX or CR0, has no dedicated instruction or fixed encoding of its own; instead, the entire space of MSRs is reached through exactly two general-purpose instructions, RDMSR and WRMSR, which select which register to touch by number rather than by name. Long Mode’s EFER and the STAR/LSTAR/FMASK registers System Calls configures for the fast syscall path are both MSRs, and both articles use them without covering the mechanism itself, which this article fills in.

Ordinary registers are addressed directly in an instruction’s encoding, which only scales to a small, fixed set: x86-64 has sixteen general-purpose registers, and adding a seventeenth would mean widening every instruction that could reference one. MSRs sidestep this entirely by moving the selector into data rather than encoding: ECX holds a 32-bit index chosen at the moment of the call, so the same two-instruction encoding reaches thousands of possible registers without any of them needing dedicated opcode space, at the cost of an extra register load before every access that a directly-encoded register wouldn’t need.

mov ecx, 0xC0000080 ; MSR index: IA32_EFER
rdmsr ; result: EDX:EAX = MSR value (64-bit, split across two 32-bit registers)
or eax, 1 << 8 ; set LME
wrmsr ; writes EDX:EAX back to the same MSR selected by ECX

RDMSR and WRMSR both operate on a 64-bit value despite EAX and EDX each only being 32 bits wide: the low half of the value goes in EAX, the high half in EDX, a split that predates 64-bit general-purpose registers existing at all and has stayed fixed for compatibility even now that a single 64-bit register could hold the whole value directly.

Each CPU core has its own independent copy of the entire MSR space; writing an MSR on one core has no effect on any other core’s copy of that same MSR index. This is a real, easy-to-miss consequence for anything set up once during boot on the bootstrap processor: EFER, the syscall configuration registers, and FS_BASE/GS_BASE all need to be written again on every additional core as it’s brought up under SMP, not copied from the boot core, since there is no shared state between cores here to copy from in the first place. FS_BASE and GS_BASE in particular are commonly used specifically because they’re per-core: a kernel points GS_BASE at a distinct per-core data structure on each core, then reaches it from any code path through a fixed offset from GS, without needing a separate mechanism to determine which core is currently executing.

Unlike CPUID, which silently returns whatever the highest supported leaf provides when queried out of range, RDMSR or WRMSR against an index the current CPU doesn’t implement raises #GP (general protection fault), an ordinary exception a kernel’s IDT already handles, rather than any special-cased behavior. This is a second, independent reason (beyond checking CPUID feature bits directly) to verify a feature is present before touching the MSR that configures it: a wrmsr to IA32_EFER’s LME bit on a CPU that doesn’t support long mode at all faults exactly the same way as a wrmsr to a completely made-up, nonexistent index would, with no distinction in the fault itself between “wrong index” and “right index, unsupported feature.”

MSRIndexPurpose
IA32_EFER0xC0000080Long mode enable/active, NX enable
IA32_APIC_BASE0x1BLocal APIC’s physical base address and enable bit
IA32_FS_BASE0xC0000100Base address FS-relative addressing resolves against
IA32_GS_BASE0xC0000101Base address GS-relative addressing resolves against
IA32_STAR0xC0000081Segment selectors loaded on syscall/sysret
IA32_LSTAR0xC0000082Entry point address syscall jumps to
IA32_FMASK0xC0000084EFLAGS bits cleared on syscall entry

Note the split between the two numbering ranges above: indices below roughly 0x4000 are architectural, defined identically across implementations, while the 0xC000_0000-prefixed range holds registers AMD introduced for x86-64 and Intel later adopted for compatibility, a naming convention (IA32_ prefix regardless of the numeric range) that has more to do with vendor history than with any functional difference visible from software.

RDMSR and WRMSR are both ring-0-only instructions with no equivalent of the TSS I/O permission bitmap port I/O has for extending access to ring 3; there is no mechanism to let unprivileged code touch an MSR directly at all, which is part of why per-core data reached via GS_BASE above is normally exposed to userspace only indirectly, through a system call or a kernel-populated read-only page, rather than by letting ring 3 read the MSR itself. A kernel probing which MSRs a given CPU actually implements has no direct enumeration instruction to call, unlike CPUID’s leaf structure; the practical approach is checking the specific CPUID feature bit associated with each MSR’s functionality (the syscall MSRs, for instance, are only meaningful once CPUID has already confirmed the instruction itself is supported) rather than probing MSR indices directly and handling the resulting faults.

  1. ^ Intel, Intel 64 and IA-32 Architectures Software Developer’s Manual, Volume 4: the complete architectural MSR reference.
  2. ^ AMD, AMD64 Architecture Programmer’s Manual, Volume 2, Appendix A: the AMD-side MSR listing, including the STAR/LSTAR/FMASK syscall registers.
  • Long Mode: the article whose EFER MSR use this one explains the general mechanism behind.
  • System Calls: the STAR/LSTAR/FMASK MSRs configuring the fast syscall path.
  • CPUID: the companion instruction for checking a feature is supported before touching the MSR that configures it.