For this post, we'll be looking at the basics of VMX and how to initialize it.
- VmxEnable PROC FRAME
- ; save arg
- push rcx
- .PUSHREG rcx
- ; save nonvolatile rbx
- push rbx
- .PUSHREG rbx
- .ENDPROLOG
- ; first, ensure VMX is supported
- mov eax, 1
- cpuid
- test cl, 020h
- jnz vmxSupported
- ; not supported
- mov eax, STATUS_NOT_IMPLEMENTED
- pop rbx
- pop rcx
- ret
The first thing to do of course is to make sure the CPU actually supports the VMX extensions; this is done through the cpuid instruction, with eax=1. VMX support is indicated by ecx bit 5. If this bit is clear, VMX is not supported on this processor, and STATUS_NOT_IMPLEMENTED is returned to the driver.
By the way, the push rcx is to save the single argument, the physical pointer to a VMXON region, which I'll explain later. The push rbx is needed because rbx is a non-volatile register which needs to be restored upon function return.
- ; ensure the IA32_FEATURE_CONTROL MSR is setup correctly
- mov ecx, IA32_FEATURE_CONTROL
- rdmsr
- test cl, 001h
- jnz msrSetUp
Next, we need to make sure that the IA32_FEATURE_CONTROL MSR (index 0x3a) is properly set up; more than likely, it'll already be pre-programmed by the BIOS, but just in case, we want to set it up ourselves.
- ; program the MSR
- or al, 007h ; enable VMXON inside/outside SMX operation, set lock bit
- wrmsr
- msrSetUp:
If it's not set up, we need to set 3 bits; bit 0 is a lock bit that prevents the MSR from being changed (and also indicates that it has been set up, which is what we check for earlier). The other two bits, bit 1 and bit 2, indicate whether the vmxon instruction is supported inside/outside SMX operation. We prefer both ;)
- ; test required bits
- mov rbx, cr0
- mov ecx, IA32_VMX_CR0_FIXED1
- rdmsr
- shl rdx, 16
- or rdx, rax
- not rdx
- ; all bits set in rdx (~FIXED1) must also be clear in rbx
- test rbx, rdx
- jnz badBits
- not rbx
- mov ecx, IA32_VMX_CR0_FIXED0
- rdmsr
- shl rdx, 16
- or rdx, rax
- ; all bits set in rdx must be clear in rbx (~cr0)
- test rbx, rdx
- jnz badBits
- mov rbx, cr4
- mov ecx, IA32_VMX_CR4_FIXED1
- rdmsr
- shl rdx, 16
- or rdx, rax
- not rdx
- ; all bits set in rdx (~FIXED1) must also be clear in rbx
- test rbx, rdx
- jnz badBits
- not rbx
- mov ecx, IA32_VMX_CR4_FIXED0
- rdmsr
- shl rdx, 16
- or rdx, rax
- ; ignore VMX bit
- btr rdx, 13
- ; all bits set in rdx must be clear in rbx (~cr4)
- test rbx, rdx
- jnz badBits
- jmp goodBits
- badBits:
- mov eax, STATUS_INVALID_DEVICE_STATE
- pop rbx
- pop rcx
- ret
- goodBits:
This next chunk is a bit lengthy, but it's basically two halves that do the same thing - check certain required bits in control registers cr0 and cr4. The MSRs IA32_VMX_CR0_FIXED0, IA32_VMX_CR0_FIXED1, IA32_VMX_CR4_FIXED0, and IA32_VMX_CR4_FIXED1 (indexes 0x486, 0x487, 0x488, and 0x489) indicate which bits need to be set or reset in the corresponding control registers. For now if any bit is not in a state required, then rather than trying to modify the control registers, we simply fail out with STATUS_INVALID_DEVICE_STATE.
- ; set VMX bit
- mov rax, cr4
- bts rax, 13
- mov cr4, rax
- jnc vmxEnabled
- ; already enabled
- mov eax, STATUS_RESOURCE_IN_USE
- pop rbx
- pop rcx
- ret
- vmxEnabled:
- ; enter VMX operation
- vmxon QWORD PTR [rsp + 8]
- jnc success
- ; something went wrong
- mov eax, STATUS_UNSUCCESSFUL
- pop rbx
- pop rcx
- ret
- success:
- xor eax, eax
- pop rbx
- pop rcx
- ret
- VmxEnable ENDP
If everything is setup correctly, we can go ahead and enable VMX by setting bit 13 of cr4, and actually entering VMX operation with the vmxon instruction. The QWORD PTR [rsp + 8] operand refers to the previous argument push, and points to the previously-mentioned VMXON region.
The reason for the STATUS_RESOURCE_IN_USE error if VMX is already enabled is because that means something else (such as VMWare) is already probably using VMX, in which case we just let it continue using VMX by itself and error out (I have yet to test if VMWare behaves similarly if it detects a hypervisor already running on the machine).
We then make one final check to make sure vmxon actually succeeded; if it didn't the carry flag will be set (which means we shouldn't try to disable VMX later on).
That's pretty much it! Now, the VMXON region I mentioned a few times earlier is an implementation-dependent region of host memory (at most 4KB) that VMX uses to manage VMs. The only initialization it needs is a 31-bit VMCS identifier at the beginning, which can be read via the IA32_FEATURE_CONTROL MSR (index 0x480), taking care to clear bit 31 (since VMXON uses it as a "shadow region" indicator, which we don't want).
Leaving VMX region is very simple:
- VmxDisable PROC
- ; leave VMX operation
- vmxoff
- ; clear VMX bit
- mov rax, cr4
- btr rax, 13
- mov cr4, rax
- ret
- VmxDisable ENDP
Whew! That was quite a lengthy post. I can continue doing post like these along with less-technical progress reports if people like them, or not - either way is fine with me. If you want to learn more about VMX, you can always consult the x86 programmer's manual here (volume 3, chapters 23-25 and chapter 30).
Edit: I've fixed a few bugs in the code; earlier I mentioned that bit 10 of ecx after the cpuid instruction indicated VMX support (even though the code tested bit 9), when in fact, it is bit 5 that indicates support.
Also, the required bit checking code clobbers the rbx register, which is non-volatile (i.e. it needs to be preserved across the function call). Finally, the vmxon operand was updated to reflect the newly-pushed register, and was actually tested for success (via the carry flag).
No comments:
Post a Comment