├── .gitattributes ├── .cargo └── config.toml ├── tests ├── samples.7z ├── bochs │ ├── bios │ │ ├── OVMF.fd │ │ └── VGABIOS-lgpl-latest │ ├── README.md │ ├── macos_intel.bxrc │ ├── linux_intel.bxrc │ └── windows_intel.bxrc └── startup.nsh ├── rust-toolchain.toml ├── hypervisor ├── src │ ├── intel_vt │ │ ├── mod.rs │ │ ├── hlat.rs │ │ ├── descriptors.rs │ │ ├── vmx.rs │ │ ├── epts.rs │ │ ├── run_vmx_vm.S │ │ ├── mtrr.rs │ │ └── vm.rs │ ├── switch_stack.S │ ├── serial_logger.rs │ ├── capture_registers.S │ ├── paging_structures.rs │ ├── switch_stack.rs │ ├── main.rs │ ├── allocator.rs │ ├── x86_instructions.rs │ └── hypervisor.rs ├── Cargo.toml └── README.md ├── os_client ├── src │ ├── vmcall.S │ └── main.rs └── Cargo.toml ├── docs └── architecture-instruction-set-extensions-programming-reference_v44.pdf ├── uefi_client ├── src │ ├── vmcall.S │ ├── paging_structures.rs │ ├── main.rs │ └── shell.rs └── Cargo.toml ├── .gitignore ├── xtask ├── Cargo.toml └── src │ ├── cargo.rs │ ├── main.rs │ └── vmtest │ ├── bochs.rs │ ├── mod.rs │ └── vmware.rs ├── README.md ├── LICENSE ├── .vscode └── tasks.json ├── Cargo.toml └── Cargo.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | xtask = "run --package xtask --" 3 | -------------------------------------------------------------------------------- /tests/samples.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/Hello-VT-rp/HEAD/tests/samples.7z -------------------------------------------------------------------------------- /tests/bochs/bios/OVMF.fd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/Hello-VT-rp/HEAD/tests/bochs/bios/OVMF.fd -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | profile = "default" 4 | targets = ["x86_64-unknown-uefi"] 5 | -------------------------------------------------------------------------------- /tests/bochs/bios/VGABIOS-lgpl-latest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/Hello-VT-rp/HEAD/tests/bochs/bios/VGABIOS-lgpl-latest -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/mod.rs: -------------------------------------------------------------------------------- 1 | mod descriptors; 2 | pub(crate) mod epts; 3 | pub(crate) mod hlat; 4 | mod mtrr; 5 | pub(crate) mod vm; 6 | pub(crate) mod vmx; 7 | -------------------------------------------------------------------------------- /tests/startup.nsh: -------------------------------------------------------------------------------- 1 | # Switch to the filesystem with vt-rp.efi. 2 | fs0: 3 | 4 | # Run vt-rp.efi. 5 | ecoh loading the driver 6 | load vt-rp.efi 7 | echo loaded the driver 8 | -------------------------------------------------------------------------------- /os_client/src/vmcall.S: -------------------------------------------------------------------------------- 1 | ;// Executes VMCALL instruction. 2 | ;// 3 | ;// extern "C" fn vmcall(number: u64, rdx: u64, r8: u64, r9: u64) -> u64; 4 | .global vmcall 5 | vmcall: 6 | vmcall 7 | ret 8 | -------------------------------------------------------------------------------- /docs/architecture-instruction-set-extensions-programming-reference_v44.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tandasat/Hello-VT-rp/HEAD/docs/architecture-instruction-set-extensions-programming-reference_v44.pdf -------------------------------------------------------------------------------- /uefi_client/src/vmcall.S: -------------------------------------------------------------------------------- 1 | ;// Executes VMCALL instruction. 2 | ;// 3 | ;// extern "C" fn vmcall(number: u64, rdx: u64, r8: u64, r9: u64) -> u64; 4 | .global vmcall 5 | vmcall: 6 | vmcall 7 | ret 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Will have compiled files and executables 4 | /target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # Project specifics 10 | **/samples/ 11 | **/NoOS/ 12 | **/bx_enh_dbg.ini 13 | -------------------------------------------------------------------------------- /hypervisor/src/switch_stack.S: -------------------------------------------------------------------------------- 1 | ;// The module containing the `switch_stack` function. 2 | 3 | ;// Jumps to the landing code with the new stack pointer. 4 | ;// 5 | ;// fn switch_stack(regs: &GuestRegisters, landing_code: usize, stack_base: u64) -> !; 6 | .global switch_stack 7 | switch_stack: 8 | xchg bx, bx 9 | mov rsp, r8 10 | jmp rdx 11 | -------------------------------------------------------------------------------- /os_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "os_client" 3 | description = "Demo client for an OS environment" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | readme.workspace = true 12 | 13 | [dependencies] 14 | -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.2" 4 | description = "A build and test assist program" 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | readme.workspace = true 12 | 13 | [lints] 14 | workspace = true 15 | 16 | [dependencies] 17 | cfg-if = "1.0" 18 | clap = { version = "4.3", features = ["derive"] } 19 | ctrlc = "3.4" 20 | wsl = "0.1" 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello-VT-rp 2 | 3 | A simple hypervisor demonstrating the use of the Intel VT-rp (redirect protection) technology. 4 | 5 | This repository is a complement of the [Intel VT-rp blog post series](https://tandasat.github.io/blog/2023/07/05/intel-vt-rp-part-1.html) and not meant for a general use. For the overview of Intel VT-rp, please read the post. 6 | 7 | 8 | ## The hypervisor 9 | 10 | The [hypervisor/](hypervisor/) directory contains a UEFI runtime-driver-based hypervisor. It is capable of booting a single-core Windows on Dell Latitude 7330 and enabling HLAT, PW, and GPV through hypercalls. 11 | -------------------------------------------------------------------------------- /uefi_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "uefi_client" 3 | description = "Demo client for a UEFI environment" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | readme.workspace = true 12 | 13 | # no_std cannot support `cargo test` and `cargo bench` 14 | [[bin]] 15 | name = "uefi_client" 16 | test = false 17 | bench = false 18 | 19 | [dependencies] 20 | bitfield.workspace = true 21 | uefi.workspace = true 22 | uefi-services = { version = "0.23", default-features = false } 23 | x86.workspace = true 24 | -------------------------------------------------------------------------------- /tests/bochs/README.md: -------------------------------------------------------------------------------- 1 | # Bochs Files 2 | Those files are used to start rhv on Bochs through the `cargo xtask bochs-intel` or `cargo xtask bochs-amd` command. 3 | 4 | 5 | ## Configuration Files 6 | The provided Bochs configuration files (.bxrc) are sufficient to run rhv on the OVMF provided UEFI shell environment. This can be useful to debug the types of failures that are difficult to diagnose on VMware or bare metal, for example, failure of the `VMLAUNCH` instruction. 7 | 8 | For more information about the configuration file, see the [Bochs User Manual](https://bochs.sourceforge.io/doc/docbook/user/bochsrc.html). 9 | 10 | 11 | ## BIOS Files 12 | Those are copy of BIOS files installed through `apt install ovmf vgabios`. Those copy exist to support macOS and native Windows environment. 13 | -------------------------------------------------------------------------------- /uefi_client/src/paging_structures.rs: -------------------------------------------------------------------------------- 1 | use bitfield::bitfield; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | #[repr(C, align(0x20_0000))] 5 | pub(crate) struct LargePage([u8; 0x20_0000]); 6 | 7 | #[derive(Debug, Clone, Copy)] 8 | pub(crate) struct Pml4(pub(crate) Table); 9 | 10 | #[derive(Debug, Clone, Copy)] 11 | pub(crate) struct Pdpt(pub(crate) Table); 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub(crate) struct Pd(pub(crate) Table); 15 | 16 | #[derive(Debug, Clone, Copy)] 17 | pub(crate) struct Pt(pub(crate) Table); 18 | 19 | #[derive(Debug, Clone, Copy)] 20 | #[repr(C, align(4096))] 21 | pub(crate) struct Table { 22 | pub(crate) entries: [Entry; 512], 23 | } 24 | 25 | bitfield! { 26 | #[derive(Clone, Copy)] 27 | pub struct Entry(u64); 28 | impl Debug; 29 | pub large, set_large: 7; 30 | pub pfn, set_pfn: 51, 12; 31 | } 32 | -------------------------------------------------------------------------------- /hypervisor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vt-rp" 3 | description = "A simple hypervisor demonstrating the use of the Intel VT-rp technology" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | repository.workspace = true 9 | keywords.workspace = true 10 | categories.workspace = true 11 | readme.workspace = true 12 | 13 | [lints] 14 | workspace = true 15 | 16 | # no_std cannot support `cargo test` and `cargo bench` 17 | [[bin]] 18 | name = "vt-rp" 19 | test = false 20 | bench = false 21 | 22 | [dependencies] 23 | bitfield.workspace = true 24 | derivative = { version = "2.2", features = ["use_core"] } 25 | log = "0.4" 26 | num-derive = { version = "0.4", default-features = false } 27 | num-traits = { version = "0.2", default-features = false } 28 | spin = "0.9" 29 | uefi.workspace = true 30 | x86.workspace = true 31 | 32 | [features] 33 | default = ["enable_vt_rp"] 34 | 35 | # Enable VT-rp. Disable this for testing with VMware or Bochs. 36 | enable_vt_rp = [] 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 - 2024 Satoshi Tanda 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /os_client/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{arch::global_asm, env}; 2 | 3 | fn main() { 4 | let args: Vec = env::args().collect(); 5 | if args.len() == 1 { 6 | println!("Specify a hypercall number and up to 3 parameters as needed."); 7 | println!(" >{} [parameter [...]]", args[0]); 8 | return; 9 | } 10 | 11 | let params: Vec = args 12 | .iter() 13 | .skip(1) 14 | .map(|arg| { 15 | u64::from_str_radix(arg.trim_start_matches("0x"), 16) 16 | .unwrap_or_else(|_| panic!("'{arg}' cannot be converted to u64")) 17 | }) 18 | .collect(); 19 | 20 | let number = params[0]; 21 | let rdx = *params.get(1).unwrap_or(&0); 22 | let r8 = *params.get(2).unwrap_or(&0); 23 | let r9 = *params.get(3).unwrap_or(&0); 24 | 25 | let status_code = unsafe { vmcall(number, rdx, r8, r9) }; 26 | if status_code != 0 { 27 | println!("VMCALL({number}, {rdx:#x?}, {r8:#x?}, {r9:#x?}) => {status_code:#x?}"); 28 | } 29 | } 30 | 31 | extern "C" { 32 | fn vmcall(number: u64, rdx: u64, r8: u64, r9: u64) -> u64; 33 | } 34 | global_asm!(include_str!("vmcall.S")); 35 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "rust: cargo xtask build", 7 | "command": "cargo xtask build", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": false 11 | } 12 | }, 13 | { 14 | "type": "shell", 15 | "label": "rust: cargo xtask clippy", 16 | "command": "cargo xtask clippy", 17 | "group": { 18 | "kind": "build", 19 | "isDefault": false 20 | } 21 | }, 22 | { 23 | "type": "shell", 24 | "label": "rust: cargo xtask bochs-intel", 25 | "command": "cargo xtask bochs-intel", 26 | "group": { 27 | "kind": "build", 28 | "isDefault": false 29 | } 30 | }, 31 | { 32 | "type": "shell", 33 | "label": "rust: cargo xtask vmware", 34 | "command": "cargo xtask vmware", 35 | "group": { 36 | "kind": "build", 37 | "isDefault": false 38 | } 39 | }, 40 | ] 41 | } -------------------------------------------------------------------------------- /hypervisor/src/serial_logger.rs: -------------------------------------------------------------------------------- 1 | //! The module containing the serial port logger implementation. 2 | 3 | use crate::x86_instructions::{inb, outb}; 4 | use core::{fmt, fmt::Write}; 5 | use spin::Mutex; 6 | 7 | /// Initializes the logger instance. 8 | pub(crate) fn init(level: log::LevelFilter) { 9 | log::set_logger(&SERIAL_LOGGER) 10 | .map(|()| log::set_max_level(level)) 11 | .unwrap(); 12 | } 13 | 14 | struct SerialLogger { 15 | port: Mutex, 16 | } 17 | impl SerialLogger { 18 | const fn new() -> Self { 19 | Self { 20 | port: Mutex::new(Serial {}), 21 | } 22 | } 23 | 24 | fn lock(&self) -> spin::MutexGuard<'_, Serial> { 25 | self.port.lock() 26 | } 27 | } 28 | impl log::Log for SerialLogger { 29 | fn enabled(&self, metadata: &log::Metadata<'_>) -> bool { 30 | metadata.level() <= log::Level::Trace 31 | } 32 | 33 | fn log(&self, record: &log::Record<'_>) { 34 | if self.enabled(record.metadata()) { 35 | let _ = writeln!(self.lock(), "{}: {}", record.level(), record.args()); 36 | } 37 | } 38 | 39 | fn flush(&self) {} 40 | } 41 | 42 | struct Serial; 43 | 44 | impl Write for Serial { 45 | // Writes bytes `string` to the serial port. 46 | fn write_str(&mut self, string: &str) -> Result<(), fmt::Error> { 47 | const UART_COM1: u16 = 0x3f8; 48 | const UART_OFFSET_TRANSMITTER_HOLDING_BUFFER: u16 = 0; 49 | const UART_OFFSET_LINE_STATUS: u16 = 5; 50 | 51 | for byte in string.bytes() { 52 | while (inb(UART_COM1 + UART_OFFSET_LINE_STATUS) & 0x20) == 0 {} 53 | outb(UART_COM1 + UART_OFFSET_TRANSMITTER_HOLDING_BUFFER, byte); 54 | } 55 | Ok(()) 56 | } 57 | } 58 | 59 | static SERIAL_LOGGER: SerialLogger = SerialLogger::new(); 60 | -------------------------------------------------------------------------------- /hypervisor/src/capture_registers.S: -------------------------------------------------------------------------------- 1 | ;// The module containing the `capture_registers` function. 2 | 3 | ;// Offsets to each field in the GuestRegisters struct. 4 | .set registers_rax, 0x0 5 | .set registers_rbx, 0x8 6 | .set registers_rcx, 0x10 7 | .set registers_rdx, 0x18 8 | .set registers_rdi, 0x20 9 | .set registers_rsi, 0x28 10 | .set registers_rbp, 0x30 11 | .set registers_r8, 0x38 12 | .set registers_r9, 0x40 13 | .set registers_r10, 0x48 14 | .set registers_r11, 0x50 15 | .set registers_r12, 0x58 16 | .set registers_r13, 0x60 17 | .set registers_r14, 0x68 18 | .set registers_r15, 0x70 19 | .set registers_rflags, 0x78 20 | .set registers_rsp, 0x80 21 | .set registers_rip, 0x88 22 | 23 | ;// Captures current general purpose registers, RFLAGS, RSP, and RIP. 24 | ;// 25 | ;// extern "efiapi" fn capture_registers(registers: &mut GuestRegisters); 26 | .global capture_registers 27 | capture_registers: 28 | ;// Capture general purpose registers. 29 | mov [rcx + registers_rax], rax 30 | mov [rcx + registers_rbx], rbx 31 | mov [rcx + registers_rcx], rcx 32 | mov [rcx + registers_rdx], rdx 33 | mov [rcx + registers_rsi], rsi 34 | mov [rcx + registers_rdi], rdi 35 | mov [rcx + registers_rbp], rbp 36 | mov [rcx + registers_r8], r8 37 | mov [rcx + registers_r9], r9 38 | mov [rcx + registers_r10], r10 39 | mov [rcx + registers_r11], r11 40 | mov [rcx + registers_r12], r12 41 | mov [rcx + registers_r13], r13 42 | mov [rcx + registers_r14], r14 43 | mov [rcx + registers_r15], r15 44 | 45 | ;// Capture RFLAGS, RSP, and RIP. 46 | pushfq 47 | pop rax 48 | mov [rcx + registers_rflags], rax 49 | 50 | mov rax, rsp 51 | add rax, 8 52 | mov [rcx + registers_rsp], rax 53 | 54 | mov rax, [rsp] 55 | mov [rcx + registers_rip], rax 56 | 57 | ret 58 | -------------------------------------------------------------------------------- /hypervisor/src/paging_structures.rs: -------------------------------------------------------------------------------- 1 | use bitfield::bitfield; 2 | use core::ptr::addr_of; 3 | use x86::current::paging::{BASE_PAGE_SHIFT, LARGE_PAGE_SIZE}; 4 | 5 | #[repr(C, align(4096))] 6 | pub(crate) struct PagingStructures { 7 | pml4: Pml4, 8 | pdpt: Pdpt, 9 | pd: [Pd; 512], 10 | } 11 | impl PagingStructures { 12 | pub(crate) fn build_identity(&mut self) { 13 | let pml4 = &mut self.pml4; 14 | pml4.0.entries[0].set_present(true); 15 | pml4.0.entries[0].set_writable(true); 16 | pml4.0.entries[0].set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); 17 | 18 | let mut pa = 0; 19 | for (i, pdpte) in self.pdpt.0.entries.iter_mut().enumerate() { 20 | pdpte.set_present(true); 21 | pdpte.set_writable(true); 22 | pdpte.set_pfn(addr_of!(self.pd[i]) as u64 >> BASE_PAGE_SHIFT); 23 | for pde in &mut self.pd[i].0.entries { 24 | pde.set_present(true); 25 | pde.set_writable(true); 26 | pde.set_large(true); 27 | pde.set_pfn(pa >> BASE_PAGE_SHIFT); 28 | pa += LARGE_PAGE_SIZE as u64; 29 | } 30 | } 31 | } 32 | } 33 | 34 | #[derive(Debug, Clone, Copy)] 35 | pub(crate) struct Pml4(pub(crate) Table); 36 | 37 | #[derive(Debug, Clone, Copy)] 38 | pub(crate) struct Pdpt(pub(crate) Table); 39 | 40 | #[derive(Debug, Clone, Copy)] 41 | pub(crate) struct Pd(pub(crate) Table); 42 | 43 | #[derive(Debug, Clone, Copy)] 44 | pub(crate) struct Pt(pub(crate) Table); 45 | 46 | #[derive(Debug, Clone, Copy)] 47 | #[repr(C, align(4096))] 48 | pub(crate) struct Table { 49 | pub(crate) entries: [Entry; 512], 50 | } 51 | 52 | bitfield! { 53 | #[derive(Clone, Copy)] 54 | pub struct Entry(u64); 55 | impl Debug; 56 | pub present, set_present: 0; 57 | pub writable, set_writable: 1; 58 | pub large, set_large: 7; 59 | pub restart, set_restart: 11; 60 | pub pfn, set_pfn: 51, 12; 61 | } 62 | -------------------------------------------------------------------------------- /tests/bochs/macos_intel.bxrc: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: usb_ohci=false, busmouse=false, voodoo=false, usb_uhci=false, serial=true, speaker=false, unmapped=true, parallel=true, biosdev=true, e1000=false, usb_ehci=false, extfpuirq=true, usb_xhci=false 3 | config_interface: textconfig 4 | display_library: nogui 5 | memory: host=1024, guest=1024 6 | romimage: file="bochs/bios/OVMF.fd", address=0xffe00000, options=none 7 | vgaromimage: file="bochs/bios/VGABIOS-lgpl-latest" 8 | boot: disk 9 | floppy_bootsig_check: disabled=0 10 | floppya: type=1_44 11 | # no floppyb 12 | ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 13 | ata0-master: type=disk, path="samples/bochs_disk.img", mode=flat 14 | ata0-slave: type=none 15 | ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 16 | ata1-master: type=none 17 | ata1-slave: type=none 18 | ata2: enabled=false 19 | ata3: enabled=false 20 | optromimage1: file=none 21 | optromimage2: file=none 22 | optromimage3: file=none 23 | optromimage4: file=none 24 | optramimage1: file=none 25 | optramimage2: file=none 26 | optramimage3: file=none 27 | optramimage4: file=none 28 | pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none 29 | vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin 30 | cpu: count=1, ips=20000000, model=tigerlake, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 31 | print_timestamps: enabled=0 32 | port_e9_hack: enabled=0 33 | private_colormap: enabled=0 34 | clock: sync=none, time0=local, rtc_sync=0 35 | # no cmosimage 36 | log: - 37 | logprefix: %d%e| 38 | debug: action=ignore 39 | info: action=report 40 | error: action=report 41 | panic: action=report 42 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 43 | mouse: type=ps2, enabled=false, toggle=ctrl+mbutton 44 | com1: enabled=true, mode=socket-server, dev="localhost:14449" 45 | com2: enabled=false 46 | com3: enabled=false 47 | com4: enabled=false 48 | parport1: enabled=true, file=none 49 | parport2: enabled=false 50 | magic_break: enabled=0 51 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "hypervisor", 4 | "xtask", 5 | "os_client", 6 | "uefi_client", 7 | ] 8 | resolver = "2" 9 | 10 | [workspace.package] 11 | version = "0.1.0" 12 | edition = "2021" 13 | authors = ["Satoshi Tanda "] 14 | description = "A simple hypervisor demonstrating the use of the Intel VT-rp (redirect protection) technology" 15 | license = "MIT" 16 | repository = "https://github.com/tandasat/Hello-VT-rp" 17 | keywords = ["VT-rp", "UEFI", "hypervisor"] 18 | categories = ["development-tools::testing", "no-std"] 19 | readme = "./README.md" 20 | 21 | [workspace.dependencies] 22 | bitfield = "0.14" 23 | uefi = { version = "0.26", default-features = false } 24 | x86 = "0.52" 25 | 26 | [workspace.lints.rust] 27 | # groups: https://doc.rust-lang.org/rustc/lints/groups.html 28 | future_incompatible = "warn" 29 | let_underscore = "warn" 30 | nonstandard_style = "warn" 31 | rust_2018_compatibility = "warn" 32 | rust_2018_idioms = "warn" 33 | rust_2021_compatibility = "warn" 34 | unused = "warn" 35 | 36 | # warnings that are not enabled by default or covered by groups 37 | # https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html 38 | macro_use_extern_crate = "warn" 39 | meta_variable_misuse = "warn" 40 | missing_abi = "warn" 41 | missing_copy_implementations = "warn" 42 | missing_debug_implementations = "warn" 43 | missing_docs = "warn" 44 | non_ascii_idents = "warn" 45 | noop_method_call = "warn" 46 | single_use_lifetimes = "warn" 47 | trivial_numeric_casts = "warn" 48 | unreachable_pub = "warn" 49 | unsafe_op_in_unsafe_fn = "warn" 50 | unused_crate_dependencies = "warn" 51 | unused_import_braces = "warn" 52 | unused_lifetimes = "warn" 53 | unused_qualifications = "warn" 54 | unused_results = "warn" 55 | 56 | # https://github.com/rust-lang/rust-clippy/blob/master/README.md 57 | [workspace.lints.clippy] 58 | pedantic = "warn" 59 | cargo = "warn" 60 | 61 | # https://doc.rust-lang.org/rustdoc/lints.html 62 | [workspace.lints.rustdoc] 63 | missing_crate_level_docs = "warn" 64 | private_doc_tests = "warn" 65 | invalid_html_tags = "warn" 66 | -------------------------------------------------------------------------------- /tests/bochs/linux_intel.bxrc: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: biosdev=true, busmouse=false, e1000=false, es1370=false, extfpuirq=true, parallel=true, sb16=false, serial=true, speaker=false, unmapped=true, usb_ehci=false, usb_ohci=false, usb_uhci=false, usb_xhci=false, voodoo=false 3 | config_interface: textconfig 4 | display_library: nogui 5 | memory: host=1024, guest=1024 6 | romimage: file="/usr/share/ovmf/OVMF.fd", address=0xffe00000, options=none 7 | vgaromimage: file="/usr/local/share/bochs/VGABIOS-lgpl-latest" 8 | boot: disk 9 | floppy_bootsig_check: disabled=0 10 | floppya: type=1_44 11 | # no floppyb 12 | ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 13 | ata0-master: type=disk, path="samples/bochs_disk.img", mode=flat 14 | ata0-slave: type=none 15 | ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 16 | ata1-master: type=none 17 | ata1-slave: type=none 18 | ata2: enabled=false 19 | ata3: enabled=false 20 | optromimage1: file=none 21 | optromimage2: file=none 22 | optromimage3: file=none 23 | optromimage4: file=none 24 | optramimage1: file=none 25 | optramimage2: file=none 26 | optramimage3: file=none 27 | optramimage4: file=none 28 | pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none 29 | vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin 30 | cpu: count=1, ips=20000000, model=tigerlake, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 31 | print_timestamps: enabled=0 32 | port_e9_hack: enabled=0 33 | private_colormap: enabled=0 34 | clock: sync=none, time0=local, rtc_sync=0 35 | # no cmosimage 36 | log: - 37 | logprefix: %d%e| 38 | debug: action=ignore 39 | info: action=report 40 | error: action=report 41 | panic: action=report 42 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 43 | mouse: type=ps2, enabled=false, toggle=ctrl+mbutton 44 | com1: enabled=true, mode=socket-server, dev="localhost:14449" 45 | com2: enabled=false 46 | com3: enabled=false 47 | com4: enabled=false 48 | parport1: enabled=true, file=none 49 | parport2: enabled=false 50 | magic_break: enabled=0 51 | -------------------------------------------------------------------------------- /tests/bochs/windows_intel.bxrc: -------------------------------------------------------------------------------- 1 | # configuration file generated by Bochs 2 | plugin_ctrl: voodoo=false, unmapped=true, biosdev=true, speaker=false, extfpuirq=true, parallel=true, serial=true, busmouse=false, e1000=false, es1370=false, gameport=true, ne2k=false, sb16=false, usb_uhci=false, usb_ohci=false, usb_ehci=false, usb_xhci=false 3 | config_interface: win32config 4 | # If you want to enable a debugger GUI, uncomment this line instead. 5 | #display_library: win32, options="gui_debug" 6 | display_library: nogui 7 | memory: host=1024, guest=1024 8 | romimage: file="bochs/bios/OVMF.fd", address=0xffe00000, options=none 9 | vgaromimage: file="bochs/bios/VGABIOS-lgpl-latest" 10 | boot: disk 11 | floppy_bootsig_check: disabled=0 12 | floppya: type=1_44 13 | # no floppyb 14 | ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14 15 | ata0-master: type=disk, path="samples\bochs_disk.img", mode=flat 16 | ata0-slave: type=none 17 | ata1: enabled=true, ioaddr1=0x170, ioaddr2=0x370, irq=15 18 | ata1-master: type=none 19 | ata1-slave: type=none 20 | ata2: enabled=false 21 | ata3: enabled=false 22 | optromimage1: file=none 23 | optromimage2: file=none 24 | optromimage3: file=none 25 | optromimage4: file=none 26 | optramimage1: file=none 27 | optramimage2: file=none 28 | optramimage3: file=none 29 | optramimage4: file=none 30 | pci: enabled=1, chipset=i440fx, slot1=none, slot2=none, slot3=none, slot4=none, slot5=none 31 | vga: extension=vbe, update_freq=5, realtime=1, ddc=builtin 32 | cpu: count=1, ips=20000000, model=tigerlake, reset_on_triple_fault=0, cpuid_limit_winnt=0, ignore_bad_msrs=1, mwait_is_nop=0 33 | print_timestamps: enabled=0 34 | port_e9_hack: enabled=0 35 | private_colormap: enabled=0 36 | clock: sync=none, time0=local, rtc_sync=0 37 | # no cmosimage 38 | log: - 39 | logprefix: %d%e| 40 | debug: action=ignore 41 | info: action=report 42 | error: action=report 43 | panic: action=report 44 | keyboard: type=mf, serial_delay=250, paste_delay=100000, user_shortcut=none 45 | mouse: type=ps2, enabled=false, toggle=ctrl+mbutton 46 | com1: enabled=true, mode=socket-server, dev="localhost:14449" 47 | com2: enabled=false 48 | com3: enabled=false 49 | com4: enabled=false 50 | parport1: enabled=true, file=none 51 | parport2: enabled=false 52 | magic_break: enabled=0 53 | -------------------------------------------------------------------------------- /xtask/src/cargo.rs: -------------------------------------------------------------------------------- 1 | use std::{env, fs, path::PathBuf, process::Command}; 2 | 3 | use crate::{output_dir, project_root_dir, DynError}; 4 | 5 | #[derive(Clone, Copy, Debug, PartialEq)] 6 | pub(crate) enum Action { 7 | Build, 8 | Clippy, 9 | } 10 | 11 | #[derive(Clone, Copy, Debug, PartialEq)] 12 | pub(crate) enum Package { 13 | Hypervisor, 14 | UefiClient, 15 | OsClient, 16 | Xtask, 17 | } 18 | 19 | #[derive(Clone, Copy, Debug, PartialEq)] 20 | pub(crate) enum Profile { 21 | Debug, 22 | Release, 23 | } 24 | 25 | pub(crate) fn cargo_run( 26 | action: Action, 27 | package: Package, 28 | profile: Profile, 29 | ) -> Result<(), DynError> { 30 | let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); 31 | let mut command = Command::new(cargo); 32 | let _ = command.arg(match action { 33 | Action::Build => "build", 34 | Action::Clippy => "clippy", 35 | }); 36 | if uefi_target(package) { 37 | let _ = command.args(["--target", "x86_64-unknown-uefi"]); 38 | } 39 | let _ = command.args(match package { 40 | Package::Hypervisor => ["--package", "vt-rp"], 41 | Package::UefiClient => ["--package", "uefi_client"], 42 | Package::OsClient => ["--package", "os_client"], 43 | Package::Xtask => ["--package", "xtask"], 44 | }); 45 | let release = profile == Profile::Release; 46 | if release { 47 | let _ = command.arg("--release"); 48 | } 49 | let ok = command.current_dir(project_root_dir()).status()?.success(); 50 | if !ok { 51 | Err("cargo build failed")?; 52 | } 53 | 54 | if action == Action::Build && package == Package::Hypervisor { 55 | let vtrp_efi = output_dir(release).join("vt-rp.efi"); 56 | transmute_to_runtime_driver(vtrp_efi)?; 57 | } 58 | 59 | Ok(()) 60 | } 61 | 62 | fn uefi_target(package: Package) -> bool { 63 | package == Package::Hypervisor || package == Package::UefiClient 64 | } 65 | 66 | fn transmute_to_runtime_driver(path: PathBuf) -> Result<(), DynError> { 67 | let mut data = fs::read(path.clone())?; 68 | data[0xd4] = 0xc; // IMAGE_OPTIONAL_HEADER.Subsystem = IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER 69 | fs::write(path, data)?; 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /hypervisor/src/switch_stack.rs: -------------------------------------------------------------------------------- 1 | use alloc::alloc::handle_alloc_error; 2 | use core::{alloc::Layout, arch::global_asm}; 3 | use log::debug; 4 | use uefi::{ 5 | proto::loaded_image::LoadedImage, 6 | table::{Boot, SystemTable}, 7 | }; 8 | 9 | use crate::{hypervisor::start_hypervisor, GuestRegisters, Page}; 10 | 11 | /// Installs the hypervisor on the current processor. 12 | pub(crate) fn virtualize_system(regs: &GuestRegisters, system_table: &SystemTable) -> ! { 13 | let bs = system_table.boot_services(); 14 | let loaded_image = bs 15 | .open_protocol_exclusive::(bs.image_handle()) 16 | .unwrap(); 17 | let (image_base, image_size) = loaded_image.info(); 18 | let image_base = image_base as usize; 19 | let image_range = image_base..image_base + image_size as usize; 20 | debug!("Image range: {image_range:#x?}"); 21 | 22 | // Prevent relocation by zapping the Relocation Table in the PE header. UEFI 23 | // keeps the list of runtime drivers and applies patches into their code and 24 | // data according with relocation information, as address translation switches 25 | // from physical-mode to virtual-mode when the OS starts. This causes a problem 26 | // with us because the host part keeps running under physical-mode, as the 27 | // host has its own page tables. Relocation ends up breaking the host code. 28 | // The easiest way is prevent this from happening is to nullify the relocation 29 | // table. 30 | unsafe { 31 | *((image_base + 0x128) as *mut u32) = 0; 32 | *((image_base + 0x12c) as *mut u32) = 0; 33 | } 34 | 35 | // Allocate separate stack space. This is never freed. 36 | let layout = Layout::array::(0x10).unwrap(); 37 | let stack = unsafe { alloc::alloc::alloc_zeroed(layout) }; 38 | if stack.is_null() { 39 | handle_alloc_error(layout); 40 | } 41 | let stack_base = stack as u64 + layout.size() as u64 - 0x10; 42 | debug!("Stack range: {:#x?}", (stack as u64..stack_base)); 43 | 44 | unsafe { switch_stack(regs, start_hypervisor as usize, stack_base) }; 45 | } 46 | 47 | extern "efiapi" { 48 | /// Jumps to the landing code with the new stack pointer. 49 | fn switch_stack(regs: &GuestRegisters, landing_code: usize, stack_base: u64) -> !; 50 | } 51 | global_asm!(include_str!("switch_stack.S")); 52 | -------------------------------------------------------------------------------- /hypervisor/README.md: -------------------------------------------------------------------------------- 1 | # Enabling VT-rp 2 | 3 | This document summarizes how this hypervisor enables each VT-rp features. 4 | 5 | 6 | ## Hypervisor-managed Linear Address Translation (HLAT) 7 | 8 | 1. In the host, create paging structures on memory as a hypervisor-managed ones with desired translations. All PML4es have the "Reset" bit set so that HLAT paging is always aborted. See [`intel_vt::hlat::PagingStructures::deactivate`]. 9 | 2. Enable HLAT by: 10 | 1. setting the "Activate tertiary controls" bit in the primary processor-based VM-execution controls. 11 | 2. setting the "Enable HLAT" bit in the tertiary processor-based VM-execution controls. 12 | 3. setting the GPA of (1) in the HLATP VMCS encoding. 13 | 4. leaving the HLAT prefix size VMCS encoding. This is to ensure all LA will be translated with HLAT paging, if the "Reset" bit is ever cleared. 14 | 15 | See [`intel_vt::vm::Vm::initialize`]. 16 | 3. On VMCALL 0, update (1) for the given LA. Specifically, remove the "Restart" bit so that HLAT paging completes for the LA (but only for that LA). See [`intel_vt::hlat::PagingStructures::enable_hlat_for_4kb`]. 17 | 18 | NB: 19 | - Normally, the hypervisor-managed paging structures should be mapped in GPA with the read-only permission. It is not done by default for demonstration. 20 | - When the hypervisor-managed paging structures are modified, translation caches (eg, TLB) must be invalidated with `INVVPID`. This is not done in this project as we do not enable VPID, and thus, all translation caches are invalidated on VM-exit and -entry. 21 | 22 | 23 | ## Paging Write (PW) 24 | 25 | 1. Enable PW by setting the "EPT paging-write control" bit in the tertiary processor-based VM-execution controls. See [`intel_vt::vm::Vm::initialize`]. 26 | 2. On VMCALL 2, locate the leaf EPT entry that corresponds to the GPA of the hypervisor-managed paging structures and set the "paging-write access" bit in the entry. See [`intel_vt::epts::Epts::make_2mb_pwa`]. 27 | 28 | 29 | ## Guest-Paging Verification (GPV) 30 | 31 | 1. Enable GPV by setting the "Guest-paging verification" bit in the tertiary processor-based VM-execution controls. See [`intel_vt::vm::Vm::initialize`]. 32 | 2. On VMCALL 3, set the "verify guest paging" bit in the leaf EPT entry that corresponds to the GPA protected by HLAT, and then, set the "paging-write access" bit in leaf EPT entry that corresponds to the GPA of the hypervisor-managed paging structures. 33 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | //! A build and test assist program. To show the usage, run 2 | //! 3 | //! ```shell 4 | //! cargo xtask 5 | //! ``` 6 | use cargo::{cargo_run, Action, Package, Profile}; 7 | use clap::{Parser, Subcommand}; 8 | use std::{ 9 | env, fs, 10 | path::{Path, PathBuf}, 11 | }; 12 | use vmtest::{ 13 | bochs::{Bochs, Cpu}, 14 | vmware::Vmware, 15 | }; 16 | 17 | mod cargo; 18 | mod vmtest; 19 | 20 | pub(crate) type DynError = Box; 21 | 22 | #[derive(Parser)] 23 | #[command(author, about, long_about = None)] 24 | struct Cli { 25 | /// Build the hypervisor with the release profile 26 | #[arg(short, long)] 27 | release: bool, 28 | 29 | #[command(subcommand)] 30 | command: Commands, 31 | } 32 | 33 | #[derive(Subcommand)] 34 | enum Commands { 35 | /// Build the entire workspace 36 | Build, 37 | /// Run clippy for the entire workspace 38 | Clippy, 39 | /// Start a Bochs VM with an Intel processor 40 | BochsIntel, 41 | /// Start a Bochs VM with an AMD processor 42 | BochsAmd, 43 | /// Start a VMware VM 44 | Vmware, 45 | } 46 | 47 | fn main() { 48 | let cli = Cli::parse(); 49 | let result = match &cli.command { 50 | Commands::Build => build(cli.release), 51 | Commands::Clippy => clippy(), 52 | Commands::BochsIntel => vmtest::run(&Bochs { cpu: Cpu::Intel }, cli.release), 53 | Commands::BochsAmd => vmtest::run(&Bochs { cpu: Cpu::Amd }, cli.release), 54 | Commands::Vmware => vmtest::run(&Vmware {}, cli.release), 55 | }; 56 | if let Err(e) = result { 57 | eprintln!("{e}"); 58 | std::process::exit(-1); 59 | } 60 | } 61 | 62 | fn build(release: bool) -> Result<(), DynError> { 63 | let profile = if release { 64 | Profile::Release 65 | } else { 66 | Profile::Debug 67 | }; 68 | cargo_run(Action::Build, Package::Hypervisor, profile)?; 69 | cargo_run(Action::Build, Package::UefiClient, profile)?; 70 | cargo_run(Action::Build, Package::OsClient, profile) 71 | } 72 | 73 | fn clippy() -> Result<(), DynError> { 74 | cargo_run(Action::Clippy, Package::Hypervisor, Profile::Debug)?; 75 | cargo_run(Action::Clippy, Package::UefiClient, Profile::Debug)?; 76 | cargo_run(Action::Clippy, Package::OsClient, Profile::Debug)?; 77 | cargo_run(Action::Clippy, Package::Xtask, Profile::Debug) 78 | } 79 | 80 | fn output_dir(release: bool) -> PathBuf { 81 | let mut out_dir = project_root_dir(); 82 | out_dir.extend(&["target", "x86_64-unknown-uefi"]); 83 | out_dir.extend(if release { &["release"] } else { &["debug"] }); 84 | fs::canonicalize(&out_dir).unwrap() 85 | } 86 | 87 | fn project_root_dir() -> PathBuf { 88 | // Get the path to the xtask directory and resolve its parent directory. 89 | let root_dir = Path::new(&env!("CARGO_MANIFEST_DIR")) 90 | .ancestors() 91 | .nth(1) 92 | .unwrap() 93 | .to_path_buf(); 94 | fs::canonicalize(root_dir).unwrap() 95 | } 96 | -------------------------------------------------------------------------------- /xtask/src/vmtest/bochs.rs: -------------------------------------------------------------------------------- 1 | use crate::DynError; 2 | use std::{ 3 | env, fmt, 4 | io::{BufRead, BufReader}, 5 | path::Path, 6 | process::{Command, Stdio}, 7 | sync::mpsc::channel, 8 | thread, 9 | time::{Duration, SystemTime}, 10 | }; 11 | 12 | use super::{copy_artifacts_to, TestVm, UnixCommand}; 13 | 14 | pub(crate) struct Bochs { 15 | pub(crate) cpu: Cpu, 16 | } 17 | 18 | impl TestVm for Bochs { 19 | fn deploy(&self, release: bool) -> Result<(), DynError> { 20 | copy_artifacts_to("./tests/samples/bochs_disk.img", release) 21 | } 22 | 23 | fn run(&self) -> Result<(), DynError> { 24 | // Start a threads that tries to connect to Bochs in an infinite loop. 25 | let _unused = thread::spawn(|| loop { 26 | let client = if env::consts::OS == "macos" { 27 | "nc" 28 | } else { 29 | "telnet" 30 | }; 31 | let output = UnixCommand::new(client) 32 | .args(["localhost", "14449"]) 33 | .stdout(Stdio::piped()) 34 | .stdin(Stdio::piped()) 35 | .spawn() 36 | .unwrap(); 37 | 38 | let now = SystemTime::now(); 39 | 40 | let reader = BufReader::new(output.stdout.unwrap()); 41 | reader 42 | .lines() 43 | .map_while(std::result::Result::ok) 44 | .for_each(|line| { 45 | println!( 46 | "{:>4}: {line}\r", 47 | now.elapsed().unwrap_or_default().as_secs() 48 | ); 49 | }); 50 | 51 | thread::sleep(Duration::from_secs(1)); 52 | }); 53 | 54 | let cpu_type = self.cpu.to_string().to_lowercase(); 55 | let _unused = thread::spawn(move || { 56 | // Start Bochs from the "tests" directory in background. 57 | let bochs = if cfg!(target_os = "windows") { 58 | r"C:\class\Bochs\bochs\obj-release\bochs.exe" 59 | } else { 60 | "bochs" 61 | }; 62 | let bxrc = format!("./bochs/{}_{cpu_type}.bxrc", env::consts::OS); 63 | let output = Command::new(bochs) 64 | .args(["-q", "-unlock", "-f", &bxrc]) 65 | .current_dir(Path::new("./tests")) 66 | .stdout(Stdio::piped()) 67 | .spawn() 68 | .unwrap(); 69 | 70 | // Read and print stdout as they come in. This does not return. 71 | let reader = BufReader::new(output.stdout.unwrap()); 72 | reader 73 | .lines() 74 | .map_while(std::result::Result::ok) 75 | .for_each(|line| println!("{line}\r")); 76 | }); 77 | 78 | let (tx, rx) = channel(); 79 | ctrlc::set_handler(move || tx.send(()).unwrap())?; 80 | rx.recv()?; 81 | 82 | Ok(()) 83 | } 84 | } 85 | 86 | #[derive(Debug)] 87 | pub(crate) enum Cpu { 88 | Intel, 89 | Amd, 90 | } 91 | impl fmt::Display for Cpu { 92 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 93 | fmt::Debug::fmt(self, f) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /hypervisor/src/main.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![no_main] 3 | #![no_std] 4 | #![allow(clippy::cast_possible_truncation)] 5 | 6 | extern crate alloc; 7 | 8 | mod allocator; 9 | mod hypervisor; 10 | mod intel_vt; 11 | mod paging_structures; 12 | mod serial_logger; 13 | mod switch_stack; 14 | mod x86_instructions; 15 | 16 | use core::arch::global_asm; 17 | use log::{debug, error, info}; 18 | use uefi::prelude::*; 19 | use x86::{cpuid::cpuid, current::paging::BASE_PAGE_SIZE}; 20 | 21 | use crate::{ 22 | hypervisor::{CPUID_VENDOR_AND_MAX_FUNCTIONS, HLAT_VENDOR_NAME}, 23 | switch_stack::virtualize_system, 24 | }; 25 | 26 | #[entry] 27 | fn main(image_handle: Handle, system_table: SystemTable) -> Status { 28 | serial_logger::init(log::LevelFilter::Debug); 29 | info!("Loading vt-rp.efi..."); 30 | 31 | allocator::init(&system_table); 32 | 33 | if is_hlat_hypervisor_present() { 34 | error!("The HLAT hypervisor is already present"); 35 | return Status::ABORTED; 36 | } 37 | 38 | if x86::cpuid::CpuId::new().get_vendor_info().unwrap().as_str() != "GenuineIntel" { 39 | error!("The system is not on the Intel processor"); 40 | return Status::ABORTED; 41 | } 42 | 43 | // Capture the register values to be used as an initial state of the VM. 44 | let mut regs = GuestRegisters::default(); 45 | unsafe { capture_registers(&mut regs) }; 46 | 47 | // Since we captured RIP just above, the VM will start running from here. 48 | // Check if our hypervisor is already loaded. If so, done, otherwise, continue 49 | // installing the hypervisor. 50 | if !is_hlat_hypervisor_present() { 51 | debug!("Virtualizing the system"); 52 | virtualize_system(®s, &system_table); 53 | } 54 | info!("The HLAT hypervisor has been installed successfully🔥"); 55 | Status::SUCCESS 56 | } 57 | 58 | /// Checks if this hypervisor is already installed. 59 | fn is_hlat_hypervisor_present() -> bool { 60 | let regs = cpuid!(CPUID_VENDOR_AND_MAX_FUNCTIONS); 61 | (regs.ebx == regs.ecx) && (regs.ecx == regs.edx) && (regs.edx == HLAT_VENDOR_NAME) 62 | } 63 | 64 | extern "efiapi" { 65 | /// Captures current general purpose registers, RFLAGS, RSP, and RIP. 66 | fn capture_registers(registers: &mut GuestRegisters); 67 | } 68 | global_asm!(include_str!("capture_registers.S")); 69 | 70 | /// The structure representing a single memory page (4KB). 71 | // 72 | // This does not _always_ have to be allocated at the page aligned address, but 73 | // very often it is, so let us specify the alignment. 74 | #[derive(Debug, Clone, Copy)] 75 | #[repr(C, align(4096))] 76 | struct Page([u8; BASE_PAGE_SIZE]); 77 | 78 | /// The collection of the guest general purpose register values. 79 | #[derive(Clone, Copy, Debug, Default)] 80 | #[repr(C)] 81 | struct GuestRegisters { 82 | rax: u64, 83 | rbx: u64, 84 | rcx: u64, 85 | rdx: u64, 86 | rdi: u64, 87 | rsi: u64, 88 | rbp: u64, 89 | r8: u64, 90 | r9: u64, 91 | r10: u64, 92 | r11: u64, 93 | r12: u64, 94 | r13: u64, 95 | r14: u64, 96 | r15: u64, 97 | rflags: u64, 98 | rsp: u64, 99 | rip: u64, 100 | } 101 | 102 | /// Handles panic. 103 | #[panic_handler] 104 | fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { 105 | error!("{info}"); 106 | loop { 107 | unsafe { 108 | x86::irq::disable(); 109 | x86::halt(); 110 | }; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /xtask/src/vmtest/mod.rs: -------------------------------------------------------------------------------- 1 | use std::{path::Path, process::Command}; 2 | 3 | use crate::{build, output_dir, project_root_dir, DynError}; 4 | 5 | pub(crate) mod bochs; 6 | pub(crate) mod vmware; 7 | 8 | pub(crate) trait TestVm { 9 | fn deploy(&self, release: bool) -> Result<(), DynError>; 10 | fn run(&self) -> Result<(), DynError>; 11 | } 12 | 13 | pub(crate) fn run(vm: &T, release: bool) -> Result<(), DynError> { 14 | build(release)?; 15 | extract_samples()?; 16 | vm.deploy(release)?; 17 | vm.run() 18 | } 19 | 20 | fn copy_artifacts_to(image: &str, release: bool) -> Result<(), DynError> { 21 | let vtrp_efi = unix_path(&output_dir(release)) + "/vt-rp.efi"; 22 | let startup_nsh = unix_path(&project_root_dir()) + "/tests/startup.nsh"; 23 | let files = [vtrp_efi, startup_nsh]; 24 | for file in &files { 25 | let output = UnixCommand::new("mcopy") 26 | .args(["-o", "-i", image, file, "::/"]) 27 | .output()?; 28 | if !output.status.success() { 29 | Err(format!("mcopy failed: {output:#?}"))?; 30 | } 31 | } 32 | Ok(()) 33 | } 34 | 35 | fn extract_samples() -> Result<(), DynError> { 36 | if !Path::new("./tests/samples/").exists() { 37 | println!("Extracting sample files..."); 38 | let output = UnixCommand::new("7z") 39 | .args(["x", "-o./tests/", "./tests/samples.7z"]) 40 | .output()?; 41 | if !output.status.success() { 42 | Err(format!("7z failed: {output:#?}"))?; 43 | } 44 | } 45 | Ok(()) 46 | } 47 | 48 | fn unix_path(path: &Path) -> String { 49 | if cfg!(target_os = "windows") { 50 | let path_str = path.to_str().unwrap().replace('\\', "\\\\"); 51 | let output = UnixCommand::new("wslpath") 52 | .args(["-a", &path_str]) 53 | .output() 54 | .unwrap(); 55 | std::str::from_utf8(&output.stdout) 56 | .unwrap() 57 | .trim() 58 | .to_string() 59 | } else { 60 | path.to_str().unwrap().to_string() 61 | } 62 | } 63 | 64 | // Defines [`UnixCommand`] that wraps [`Command`] with `wsl` command on Windows. 65 | // On non-Windows platforms, it is an alias of [`Command`]. 66 | cfg_if::cfg_if! { 67 | if #[cfg(windows)] { 68 | struct UnixCommand { 69 | wsl: Command, 70 | program: String, 71 | } 72 | 73 | impl UnixCommand { 74 | fn new(program: &str) -> Self { 75 | Self { 76 | wsl: Command::new("wsl"), 77 | program: program.to_string(), 78 | } 79 | } 80 | 81 | pub(crate) fn args(&mut self, args: I) -> &mut Command 82 | where 83 | I: IntoIterator, 84 | S: AsRef, 85 | { 86 | self.wsl.arg(self.program.clone()).args(args) 87 | } 88 | } 89 | } else { 90 | type UnixCommand = Command; 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use crate::unix_path; 97 | use std::path::Path; 98 | 99 | #[test] 100 | fn test_unix_path() { 101 | if cfg!(target_os = "windows") { 102 | assert_eq!(unix_path(Path::new(r"C:\")), "/mnt/c/"); 103 | assert_eq!(unix_path(Path::new("/tmp")), "/mnt/c/tmp"); 104 | } else { 105 | assert_eq!(unix_path(Path::new(r"C:\")), r"C:\"); 106 | assert_eq!(unix_path(Path::new("/tmp")), "/tmp"); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /uefi_client/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | #![no_std] 3 | 4 | extern crate alloc; 5 | 6 | mod paging_structures; 7 | mod shell; 8 | 9 | use alloc::{alloc::handle_alloc_error, vec::Vec}; 10 | use core::{alloc::Layout, arch::global_asm}; 11 | use uefi::prelude::*; 12 | use uefi_services::println; 13 | use x86::{ 14 | controlregs::{cr0, cr0_write, cr3, Cr0}, 15 | current::paging::BASE_PAGE_SHIFT, 16 | }; 17 | 18 | use crate::paging_structures::{LargePage, Pd, Pdpt, Pml4}; 19 | 20 | #[entry] 21 | fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { 22 | uefi_services::init(&mut system_table).unwrap(); 23 | 24 | let args = shell::get_args(); 25 | if args.len() == 1 { 26 | println!("Specify a hypercall number and up to 3 parameters as needed."); 27 | println!(" >{} [parameter [...]]", args[0]); 28 | println!(" >{} alias ", args[0]); 29 | return Status::INVALID_PARAMETER; 30 | } 31 | 32 | if args[1] == "alias" { 33 | demo_aliasing(u64::from_str_radix(args[2].trim_start_matches("0x"), 16).unwrap()); 34 | return Status::SUCCESS; 35 | } 36 | 37 | let params: Vec = args 38 | .iter() 39 | .skip(1) 40 | .map(|arg| { 41 | u64::from_str_radix(arg.trim_start_matches("0x"), 16) 42 | .unwrap_or_else(|_| panic!("'{arg}' cannot be converted to u64")) 43 | }) 44 | .collect(); 45 | 46 | let number = params[0]; 47 | let rdx = *params.get(1).unwrap_or(&0); 48 | let r8 = *params.get(2).unwrap_or(&0); 49 | let r9 = *params.get(3).unwrap_or(&0); 50 | 51 | let status_code = unsafe { vmcall(number, rdx, r8, r9) }; 52 | if status_code != 0 { 53 | println!("VMCALL({number}, {rdx:#x?}, {r8:#x?}, {r9:#x?}) => {status_code:#x?}"); 54 | } 55 | Status::SUCCESS 56 | } 57 | 58 | fn demo_aliasing(gpa: u64) { 59 | let layout = Layout::new::(); 60 | let alias_ptr = unsafe { alloc::alloc::alloc_zeroed(layout) }; 61 | if alias_ptr.is_null() { 62 | handle_alloc_error(layout); 63 | } 64 | 65 | let alias = alias_ptr as usize; 66 | let i4 = alias >> 39 & 0b1_1111_1111; 67 | let i3 = alias >> 30 & 0b1_1111_1111; 68 | let i2 = alias >> 21 & 0b1_1111_1111; 69 | 70 | // Locate PML4e, PDPTe and PDe used to translate the LA of RuntimeServices. 71 | let pml4 = (unsafe { cr3() } & !0xfff) as *mut Pml4; 72 | let pml4 = unsafe { &mut *pml4 }; 73 | let pdpt = (pml4.0.entries[i4].pfn() << BASE_PAGE_SHIFT) as *mut Pdpt; 74 | let pdpt = unsafe { &mut *pdpt }; 75 | let pd = (pdpt.0.entries[i3].pfn() << BASE_PAGE_SHIFT) as *mut Pd; 76 | let pd = unsafe { &mut *pd }; 77 | let pde = &mut pd.0.entries[i2]; 78 | assert!(pde.large()); 79 | 80 | // Update the PFN of the leaf entry to point to the specified GPA. Disable 81 | // write-protection as the paging structures are read-only on modern UEFI. 82 | unsafe { 83 | let cr0 = cr0(); 84 | cr0_write(cr0 & !Cr0::CR0_WRITE_PROTECT); 85 | pde.set_pfn(gpa >> BASE_PAGE_SHIFT); 86 | cr0_write(cr0); 87 | x86::tlb::flush(alias); 88 | } 89 | 90 | println!("Aliased GPA {gpa:#x?} onto LA {alias_ptr:#x?}"); 91 | } 92 | 93 | #[panic_handler] 94 | fn panic_handler(info: &core::panic::PanicInfo<'_>) -> ! { 95 | println!("[PANIC]: {}", info); 96 | loop { 97 | unsafe { 98 | x86::irq::disable(); 99 | x86::halt(); 100 | }; 101 | } 102 | } 103 | 104 | extern "C" { 105 | fn vmcall(number: u64, rdx: u64, r8: u64, r9: u64) -> u64; 106 | } 107 | global_asm!(include_str!("vmcall.S")); 108 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/hlat.rs: -------------------------------------------------------------------------------- 1 | use core::ptr::addr_of; 2 | use x86::current::paging::BASE_PAGE_SHIFT; 3 | 4 | use super::vm::vmread; 5 | use crate::paging_structures::{Pd, Pdpt, Pml4, Pt}; 6 | 7 | // The hypervisor-managed paging structures. The alignment is set to translate 8 | // all those structures only with a single EPT PDe for simpler implementation 9 | // and demonstration. 10 | #[repr(C, align(0x20_0000))] 11 | pub(crate) struct PagingStructures { 12 | pml4: Pml4, 13 | pdpt: Pdpt, 14 | pd: Pd, 15 | pt: Pt, 16 | } 17 | const _: () = assert!(core::mem::size_of::() == 0x20_0000); 18 | 19 | impl PagingStructures { 20 | pub(crate) fn deactivate(&mut self) { 21 | for pml4e in &mut self.pml4.0.entries { 22 | pml4e.set_present(true); 23 | pml4e.set_restart(true); 24 | } 25 | for pdpte in &mut self.pdpt.0.entries { 26 | pdpte.set_present(true); 27 | pdpte.set_restart(true); 28 | } 29 | for pde in &mut self.pd.0.entries { 30 | pde.set_present(true); 31 | pde.set_restart(true); 32 | } 33 | for pte in &mut self.pt.0.entries { 34 | pte.set_present(true); 35 | pte.set_restart(true); 36 | } 37 | } 38 | 39 | #[allow(clippy::similar_names)] 40 | // Prevent aliasing for the given LA by enabling HLAT paging for it. Returns 41 | // GPA corresponds to the given LA. 42 | pub(crate) fn enable_hlat_for_4kb(&mut self, la: u64) -> u64 { 43 | let la = la as usize; 44 | let i4 = la >> 39 & 0b1_1111_1111; 45 | let i3 = la >> 30 & 0b1_1111_1111; 46 | let i2 = la >> 21 & 0b1_1111_1111; 47 | let i1 = la >> 12 & 0b1_1111_1111; 48 | 49 | // Locate PML4e, PDPTe and PDe to be used to translate the LA from the guest 50 | // paging structures. 51 | let pml4 = (vmread(x86::vmx::vmcs::guest::CR3) & !0xfff) as *const Pml4; 52 | let pml4 = unsafe { &*pml4 }; 53 | let pml4e = pml4.0.entries[i4]; 54 | let pdpt = (pml4e.pfn() << BASE_PAGE_SHIFT) as *const Pdpt; 55 | let pdpt = unsafe { &*pdpt }; 56 | let pdpte = pdpt.0.entries[i3]; 57 | let pd = (pdpte.pfn() << BASE_PAGE_SHIFT) as *const Pd; 58 | let pd = unsafe { &*pd }; 59 | let pde = &pd.0.entries[i2]; 60 | 61 | // Then, copy the guest entry values into the hypervisor-managed paging 62 | // structure entries, clear the restart bit as OS may have used this bit, 63 | // and update PFN to point to the next hypervisor-managed paging structures. 64 | self.pml4.0.entries[i4].0 = pml4e.0; 65 | self.pml4.0.entries[i4].set_restart(false); 66 | self.pml4.0.entries[i4].set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); 67 | self.pdpt.0.entries[i3].0 = pdpte.0; 68 | self.pdpt.0.entries[i3].set_restart(false); 69 | self.pdpt.0.entries[i3].set_pfn(addr_of!(self.pd) as u64 >> BASE_PAGE_SHIFT); 70 | if pde.large() { 71 | self.pd.0.entries[i2].0 = pde.0; 72 | self.pd.0.entries[i2].set_restart(false); 73 | self.pd.0.entries[i2].pfn() << BASE_PAGE_SHIFT 74 | } else { 75 | // If it is not a large page, also process a PTe. 76 | let pt = (pde.pfn() << BASE_PAGE_SHIFT) as *const Pt; 77 | let pt = unsafe { &*pt }; 78 | let pte = &pt.0.entries[i1]; 79 | 80 | self.pd.0.entries[i2].0 = pde.0; 81 | self.pd.0.entries[i2].set_restart(false); 82 | self.pd.0.entries[i2].set_pfn(addr_of!(self.pt) as u64 >> BASE_PAGE_SHIFT); 83 | self.pt.0.entries[i1].0 = pte.0; 84 | self.pt.0.entries[i1].set_restart(false); 85 | self.pt.0.entries[i1].pfn() << BASE_PAGE_SHIFT 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /uefi_client/src/shell.rs: -------------------------------------------------------------------------------- 1 | //! The module containing the [`get_args`] function. 2 | 3 | // Wraps UEFI Shell protocols to gain command line parameters specified by an 4 | // user. Modern UEFI implements EFI_SHELL_PARAMETERS_PROTOCOL and not the other, 5 | // while some older system such as VMware UEFI implements EFI_SHELL_INTERFACE 6 | // and not the other. 7 | use alloc::{ 8 | string::{String, ToString}, 9 | vec::Vec, 10 | }; 11 | use core::ffi::c_void; 12 | use uefi::{ 13 | proto::{loaded_image::LoadedImage, Protocol}, 14 | table::boot::{OpenProtocolAttributes, OpenProtocolParams}, 15 | Char16, Handle, 16 | }; 17 | use uefi_services::system_table; 18 | 19 | /// Gets argc/argv using `EFI_SHELL_INTERFACE` or 20 | /// `EFI_SHELL_PARAMETERS_PROTOCOL`. 21 | pub(crate) fn get_args() -> Vec { 22 | match get_args_with_protocol::() { 23 | Ok(args) => args, 24 | Err(_) => get_args_with_protocol::().unwrap(), 25 | } 26 | } 27 | 28 | // Gets argc/argv using the given protocol. 29 | fn get_args_with_protocol() -> uefi::Result> { 30 | // Safety: Code is single threaded. 31 | let st = system_table(); 32 | let bs = st.boot_services(); 33 | let shell = unsafe { 34 | bs.open_protocol::( 35 | OpenProtocolParams { 36 | handle: bs.image_handle(), 37 | agent: bs.image_handle(), 38 | controller: None, 39 | }, 40 | OpenProtocolAttributes::GetProtocol, 41 | )? 42 | }; 43 | Ok(shell.args()) 44 | } 45 | 46 | // This protocol holds command line parameters. 47 | trait ShellProtocol { 48 | fn args(&self) -> Vec; 49 | } 50 | 51 | // `EFI_SHELL_INTERFACE` 52 | // 53 | #[repr(C)] 54 | #[uefi::proto::unsafe_protocol("47c7b223-c42a-11d2-8e57-00a0c969723b")] 55 | struct ShellInterface { 56 | image_handle: Handle, 57 | info: *const LoadedImage, 58 | argv: *const *const Char16, 59 | argc: usize, 60 | redir_argv: *const *const Char16, 61 | redir_argc: usize, 62 | stdin: *const c_void, 63 | stdout: *const c_void, 64 | stderr: *const c_void, 65 | arg_info: *const u32, 66 | echo_on: bool, 67 | } 68 | 69 | impl ShellProtocol for ShellInterface { 70 | fn args(&self) -> Vec { 71 | unsafe { 72 | let raw_args = core::slice::from_raw_parts(self.argv, self.argc); 73 | raw_args 74 | .iter() 75 | .map(|arg| uefi::CStr16::from_ptr(*arg).to_string()) 76 | .collect() 77 | } 78 | } 79 | } 80 | 81 | // `EFI_SHELL_PARAMETERS_PROTOCOL` 82 | // 83 | #[repr(C)] 84 | #[uefi::proto::unsafe_protocol("752f3136-4e16-4fdc-a22a-e5f46812f4ca")] 85 | struct ShellParametersProtocol { 86 | argv: *const *const Char16, 87 | argc: usize, 88 | stdin: *const c_void, 89 | stdout: *const c_void, 90 | stderr: *const c_void, 91 | } 92 | 93 | impl ShellProtocol for ShellParametersProtocol { 94 | fn args(&self) -> Vec { 95 | unsafe { 96 | let raw_args = core::slice::from_raw_parts(self.argv, self.argc); 97 | raw_args 98 | .iter() 99 | .map(|arg| uefi::CStr16::from_ptr(*arg).to_string()) 100 | .collect() 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /hypervisor/src/allocator.rs: -------------------------------------------------------------------------------- 1 | //! The module containing the [`BootTimeAllocator`] type. 2 | 3 | use core::{ 4 | alloc::{GlobalAlloc, Layout}, 5 | ffi::c_void, 6 | ptr, 7 | sync::atomic::{AtomicPtr, Ordering}, 8 | }; 9 | use uefi::table::{ 10 | boot::{AllocateType, MemoryType}, 11 | Boot, SystemTable, 12 | }; 13 | use x86::current::paging::BASE_PAGE_SHIFT; 14 | 15 | pub(crate) fn init(system_table: &SystemTable) { 16 | SYSTEM_TABLE.store(system_table.as_ptr().cast_mut(), Ordering::Release); 17 | } 18 | 19 | fn system_table() -> SystemTable { 20 | let ptr = SYSTEM_TABLE.load(Ordering::Acquire); 21 | unsafe { SystemTable::from_ptr(ptr) }.unwrap() 22 | } 23 | 24 | static SYSTEM_TABLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); 25 | 26 | /// The global allocator based on the UEFI boot services. Any memory allocated 27 | /// by this cannot be used after the `ExitBootServices` UEFI runtime service is 28 | /// called. This project never lets a boot loader call that service, so not an 29 | /// issue. 30 | struct BootTimeAllocator; 31 | 32 | #[allow(clippy::cast_ptr_alignment)] 33 | unsafe impl GlobalAlloc for BootTimeAllocator { 34 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 35 | let size = layout.size(); 36 | let align = layout.align(); 37 | 38 | // If the requested alignment is a multiple of 4KB, use `allocate_pages` 39 | // which allocates 4KB aligned memory with 4KB granularity. 40 | if (align % 0x1000) == 0 { 41 | system_table() 42 | .boot_services() 43 | .allocate_pages( 44 | AllocateType::AnyPages, 45 | MemoryType::RUNTIME_SERVICES_DATA, 46 | size_to_pages(size), 47 | ) 48 | .unwrap_or(0) as *mut u8 49 | } else if align > 8 { 50 | // Allocate more space for alignment. 51 | let Ok(ptr) = system_table() 52 | .boot_services() 53 | .allocate_pool(MemoryType::RUNTIME_SERVICES_DATA, size + align) 54 | else { 55 | return core::ptr::null_mut(); 56 | }; 57 | // Calculate align offset. 58 | let mut offset = ptr.align_offset(align); 59 | if offset == 0 { 60 | offset = align; 61 | } 62 | let return_ptr = unsafe { ptr.add(offset) }; 63 | // Store allocated pointer before the struct. 64 | unsafe { return_ptr.cast::<*mut u8>().sub(1).write(ptr) }; 65 | return_ptr 66 | } else { 67 | system_table() 68 | .boot_services() 69 | .allocate_pool(MemoryType::RUNTIME_SERVICES_DATA, size) 70 | .unwrap_or(core::ptr::null_mut()) 71 | } 72 | } 73 | 74 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 75 | if (layout.align() % 0x1000) == 0 { 76 | unsafe { 77 | system_table() 78 | .boot_services() 79 | .free_pages(ptr as u64, size_to_pages(layout.size())) 80 | .unwrap(); 81 | }; 82 | } else if layout.align() > 8 { 83 | let ptr = unsafe { ptr.cast::<*mut u8>().sub(1).read() }; 84 | unsafe { 85 | system_table().boot_services().free_pool(ptr).unwrap(); 86 | }; 87 | } else { 88 | unsafe { 89 | system_table().boot_services().free_pool(ptr).unwrap(); 90 | }; 91 | } 92 | } 93 | } 94 | 95 | fn size_to_pages(size: usize) -> usize { 96 | const PAGE_MASK: usize = 0xfff; 97 | 98 | (size >> BASE_PAGE_SHIFT) + usize::from((size & PAGE_MASK) != 0) 99 | } 100 | 101 | #[global_allocator] 102 | static ALLOCATOR: BootTimeAllocator = BootTimeAllocator; 103 | -------------------------------------------------------------------------------- /hypervisor/src/x86_instructions.rs: -------------------------------------------------------------------------------- 1 | //! The module containing wrapper functions for x86 instructions. 2 | //! 3 | //! Those instructions provided by the `x86` crate as `unsafe` functions, due to 4 | //! the fact that those require certain preconditions. The wrappers provided by 5 | //! this module encapsulate those `unsafe`-ness since this project always 6 | //! satisfies the preconditions and safe to call them at any context. 7 | 8 | use core::arch::asm; 9 | use x86::{ 10 | controlregs::{Cr0, Cr4, Xcr0}, 11 | current::rflags::RFlags, 12 | dtables::DescriptorTablePointer, 13 | segmentation::SegmentSelector, 14 | }; 15 | 16 | /// Reads an MSR. 17 | pub(crate) fn rdmsr(msr: u32) -> u64 { 18 | // Safety: this project runs at CPL0. 19 | unsafe { x86::msr::rdmsr(msr) } 20 | } 21 | 22 | /// Writes a value to an MSR. 23 | pub(crate) fn wrmsr(msr: u32, value: u64) { 24 | // Safety: this project runs at CPL0. 25 | unsafe { x86::msr::wrmsr(msr, value) }; 26 | } 27 | 28 | /// Reads the CR0 register. 29 | pub(crate) fn cr0() -> Cr0 { 30 | // Safety: this project runs at CPL0. 31 | unsafe { x86::controlregs::cr0() } 32 | } 33 | 34 | /// Writes a value to the CR0 register. 35 | pub(crate) fn cr0_write(val: Cr0) { 36 | // Safety: this project runs at CPL0. 37 | unsafe { x86::controlregs::cr0_write(val) }; 38 | } 39 | 40 | /// Reads the CR3 register. 41 | pub(crate) fn cr3() -> u64 { 42 | // Safety: this project runs at CPL0. 43 | unsafe { x86::controlregs::cr3() } 44 | } 45 | 46 | /// Reads the CR4 register. 47 | pub(crate) fn cr4() -> Cr4 { 48 | // Safety: this project runs at CPL0. 49 | unsafe { x86::controlregs::cr4() } 50 | } 51 | 52 | /// Writes a value to the CR4 register. 53 | pub(crate) fn cr4_write(val: Cr4) { 54 | // Safety: this project runs at CPL0. 55 | unsafe { x86::controlregs::cr4_write(val) }; 56 | } 57 | 58 | /// Reads the IDTR register. 59 | pub(crate) fn sidt() -> DescriptorTablePointer { 60 | let mut idtr = DescriptorTablePointer::::default(); 61 | // Safety: this project runs at CPL0. 62 | unsafe { x86::dtables::sidt(&mut idtr) }; 63 | idtr 64 | } 65 | 66 | /// Reads the GDTR. 67 | pub(crate) fn sgdt() -> DescriptorTablePointer { 68 | let mut gdtr = DescriptorTablePointer::::default(); 69 | // Safety: this project runs at CPL0. 70 | unsafe { x86::dtables::sgdt(&mut gdtr) }; 71 | gdtr 72 | } 73 | 74 | // 75 | pub(crate) fn lsl(selector: SegmentSelector) -> u32 { 76 | let flags: u64; 77 | let mut limit: u64; 78 | unsafe { 79 | asm!( 80 | "lsl {}, {}", 81 | "pushfq", 82 | "pop {}", 83 | out(reg) limit, 84 | in(reg) u64::from(selector.bits()), 85 | lateout(reg) flags 86 | ); 87 | }; 88 | assert!(RFlags::from_raw(flags).contains(RFlags::FLAGS_ZF)); 89 | limit as u32 90 | } 91 | 92 | /// LAR-Load Access Rights Byte 93 | pub(crate) fn lar(selector: SegmentSelector) -> u32 { 94 | let flags: u64; 95 | let mut access_rights: u64; 96 | unsafe { 97 | asm!( 98 | "lar {}, {}", 99 | "pushfq", 100 | "pop {}", 101 | out(reg) access_rights, 102 | in(reg) u64::from(selector.bits()), 103 | lateout(reg) flags 104 | ); 105 | }; 106 | assert!(RFlags::from_raw(flags).contains(RFlags::FLAGS_ZF)); 107 | access_rights as u32 108 | } 109 | 110 | /// Reads 8-bits from an IO port. 111 | pub(crate) fn inb(port: u16) -> u8 { 112 | // Safety: this project runs at CPL0. 113 | unsafe { x86::io::inb(port) } 114 | } 115 | 116 | /// Writes 8-bits to an IO port. 117 | pub(crate) fn outb(port: u16, val: u8) { 118 | // Safety: this project runs at CPL0. 119 | unsafe { x86::io::outb(port, val) }; 120 | } 121 | 122 | pub(crate) fn xsetbv(xcr: u32, val: Xcr0) { 123 | assert!(xcr == 0); 124 | unsafe { x86::controlregs::xcr0_write(val) }; 125 | } 126 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/descriptors.rs: -------------------------------------------------------------------------------- 1 | use alloc::{boxed::Box, vec::Vec}; 2 | use x86::{ 3 | dtables::DescriptorTablePointer, 4 | segmentation::{ 5 | cs, BuildDescriptor, CodeSegmentType, Descriptor, DescriptorBuilder, GateDescriptorBuilder, 6 | SegmentDescriptorBuilder, SegmentSelector, 7 | }, 8 | }; 9 | 10 | use crate::x86_instructions::sgdt; 11 | 12 | // UEFI does not set TSS in the GDT. This is incompatible to be both as VM and 13 | // hypervisor states. This struct supports creating a new GDT that does contain 14 | // the TSS. 15 | // 16 | // See: 27.2.3 Checks on Host Segment and Descriptor-Table Registers 17 | // See: 27.3.1.2 Checks on Guest Segment Registers 18 | pub(crate) struct Descriptors { 19 | gdt: Vec, 20 | pub(crate) gdtr: DescriptorTablePointer, 21 | pub(crate) cs: SegmentSelector, 22 | pub(crate) tr: SegmentSelector, 23 | pub(crate) tss: TaskStateSegment, 24 | } 25 | impl Default for Descriptors { 26 | fn default() -> Self { 27 | Self { 28 | gdt: Vec::new(), 29 | gdtr: DescriptorTablePointer::::default(), 30 | cs: SegmentSelector::from_raw(0), 31 | tr: SegmentSelector::from_raw(0), 32 | tss: TaskStateSegment::default(), 33 | } 34 | } 35 | } 36 | impl Descriptors { 37 | /// Creates a new GDT with TSS based on the current GDT. 38 | pub(crate) fn new_from_current() -> Self { 39 | // Get the current GDT. 40 | let current_gdtr = sgdt(); 41 | let current_gdt = unsafe { 42 | core::slice::from_raw_parts( 43 | current_gdtr.base.cast::(), 44 | usize::from(current_gdtr.limit + 1) / 8, 45 | ) 46 | }; 47 | 48 | // Copy the current GDT. 49 | let mut descriptors = Self { 50 | gdt: current_gdt.to_vec(), 51 | ..Default::default() 52 | }; 53 | 54 | // Append the TSS descriptor. Push extra 0 as it is 16 bytes. 55 | // See: 3.5.2 Segment Descriptor Tables in IA-32e Mode 56 | let tr_index = descriptors.gdt.len() as u16; 57 | descriptors 58 | .gdt 59 | .push(Self::task_segment_descriptor(&descriptors.tss).as_u64()); 60 | descriptors.gdt.push(0); 61 | 62 | descriptors.gdtr = DescriptorTablePointer::new_from_slice(&descriptors.gdt); 63 | descriptors.cs = cs(); 64 | descriptors.tr = SegmentSelector::new(tr_index, x86::Ring::Ring0); 65 | 66 | descriptors 67 | } 68 | 69 | /// Creates a new GDT with TSS from scratch for the host. 70 | pub(crate) fn new_for_host() -> Self { 71 | let mut descriptors = Self::default(); 72 | 73 | descriptors.gdt.push(0); 74 | descriptors 75 | .gdt 76 | .push(Self::code_segment_descriptor().as_u64()); 77 | descriptors 78 | .gdt 79 | .push(Self::task_segment_descriptor(&descriptors.tss).as_u64()); 80 | descriptors.gdt.push(0); 81 | 82 | descriptors.gdtr = DescriptorTablePointer::new_from_slice(&descriptors.gdt); 83 | descriptors.cs = SegmentSelector::new(1, x86::Ring::Ring0); 84 | descriptors.tr = SegmentSelector::new(2, x86::Ring::Ring0); 85 | 86 | descriptors 87 | } 88 | 89 | /// Builds a segment descriptor from the task state segment. 90 | fn task_segment_descriptor(tss: &TaskStateSegment) -> Descriptor { 91 | >::tss_descriptor(tss.base, tss.limit, true) 92 | .present() 93 | .dpl(x86::Ring::Ring0) 94 | .finish() 95 | } 96 | 97 | fn code_segment_descriptor() -> Descriptor { 98 | DescriptorBuilder::code_descriptor(0, u32::MAX, CodeSegmentType::ExecuteAccessed) 99 | .present() 100 | .dpl(x86::Ring::Ring0) 101 | .limit_granularity_4kb() 102 | .l() 103 | .finish() 104 | } 105 | } 106 | 107 | #[derive(derivative::Derivative)] 108 | #[derivative(Debug)] 109 | pub(crate) struct TaskStateSegment { 110 | pub(crate) base: u64, 111 | pub(crate) limit: u64, 112 | pub(crate) ar: u32, 113 | #[allow(dead_code)] 114 | #[derivative(Debug = "ignore")] 115 | segment: Box, 116 | } 117 | impl Default for TaskStateSegment { 118 | fn default() -> Self { 119 | let segment = Box::new(TaskStateSegmentRaw([0; 104])); 120 | Self { 121 | base: segment.as_ref() as *const _ as u64, 122 | limit: core::mem::size_of_val(segment.as_ref()) as u64 - 1, 123 | ar: 0x8b00, 124 | segment, 125 | } 126 | } 127 | } 128 | 129 | /// See: Figure 8-11. 64-Bit TSS Format 130 | #[allow(dead_code)] 131 | struct TaskStateSegmentRaw([u8; 104]); 132 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/vmx.rs: -------------------------------------------------------------------------------- 1 | use x86::{ 2 | controlregs::{Cr0, Cr4}, 3 | current::paging::BASE_PAGE_SIZE, 4 | }; 5 | 6 | use crate::x86_instructions::{cr0, cr0_write, cr4, cr4_write, rdmsr, wrmsr}; 7 | 8 | pub(crate) struct Vmx { 9 | vmxon_region: Vmxon, 10 | vmx_enabled: bool, 11 | } 12 | impl Vmx { 13 | pub(crate) fn new() -> Self { 14 | Self { 15 | vmxon_region: Vmxon::default(), 16 | vmx_enabled: false, 17 | } 18 | } 19 | 20 | pub(crate) fn enable(&mut self) { 21 | Self::adjust_cr0(); 22 | Self::adjust_cr4(); 23 | Self::adjust_feature_control_msr(); 24 | vmxon(&mut self.vmxon_region); 25 | self.vmx_enabled = true; 26 | } 27 | 28 | /// Updates the CR0 to satisfy the requirement for entering VMX operation. 29 | fn adjust_cr0() { 30 | // In order to enter VMX operation, some bits in CR0 (and CR4) have to be 31 | // set or cleared as indicated by the FIXED0 and FIXED1 MSRs. The rule is 32 | // summarized as below (taking CR0 as an example): 33 | // 34 | // IA32_VMX_CR0_FIXED0 IA32_VMX_CR0_FIXED1 Meaning 35 | // Bit X 1 (Always 1) The bit X of CR0 is fixed to 1 36 | // Bit X 0 1 The bit X of CR0 is flexible 37 | // Bit X (Always 0) 0 The bit X of CR0 is fixed to 0 38 | // 39 | // Some UEFI implementations do not fullfil those requirements for CR0 and 40 | // need adjustments. The requirements for CR4 are always satisfied as far 41 | // as the author has experimented (although not guaranteed). 42 | // 43 | // See: A.7 VMX-FIXED BITS IN CR0 44 | // See: A.8 VMX-FIXED BITS IN CR4 45 | let fixed0cr0 = rdmsr(x86::msr::IA32_VMX_CR0_FIXED0); 46 | let fixed1cr0 = rdmsr(x86::msr::IA32_VMX_CR0_FIXED1); 47 | let mut new_cr0 = cr0().bits() as u64; 48 | new_cr0 &= fixed1cr0; 49 | new_cr0 |= fixed0cr0; 50 | let new_cr0 = Cr0::from_bits_truncate(new_cr0 as usize); 51 | cr0_write(new_cr0); 52 | } 53 | 54 | /// Updates the CR4 to satisfy the requirement for entering VMX operation. 55 | fn adjust_cr4() { 56 | let fixed0cr4 = rdmsr(x86::msr::IA32_VMX_CR4_FIXED0); 57 | let fixed1cr4 = rdmsr(x86::msr::IA32_VMX_CR4_FIXED1); 58 | let mut new_cr4 = cr4().bits() as u64; 59 | new_cr4 &= fixed1cr4; 60 | new_cr4 |= fixed0cr4; 61 | let new_cr4 = Cr4::from_bits_truncate(new_cr4 as usize); 62 | cr4_write(new_cr4); 63 | } 64 | 65 | /// Updates an MSR to satisfy the requirement for entering VMX operation. 66 | fn adjust_feature_control_msr() { 67 | const IA32_FEATURE_CONTROL_LOCK_BIT_FLAG: u64 = 1 << 0; 68 | const IA32_FEATURE_CONTROL_ENABLE_VMX_OUTSIDE_SMX_FLAG: u64 = 1 << 2; 69 | 70 | // If the lock bit is cleared, set it along with the VMXON-outside-SMX 71 | // operation bit. Without those two bits, the VMXON instruction fails. They 72 | // are normally set but not always, for example, Bochs with OVMF does not. 73 | // See: 23.7 ENABLING AND ENTERING VMX OPERATION 74 | let feature_control = rdmsr(x86::msr::IA32_FEATURE_CONTROL); 75 | if (feature_control & IA32_FEATURE_CONTROL_LOCK_BIT_FLAG) == 0 { 76 | wrmsr( 77 | x86::msr::IA32_FEATURE_CONTROL, 78 | feature_control 79 | | IA32_FEATURE_CONTROL_ENABLE_VMX_OUTSIDE_SMX_FLAG 80 | | IA32_FEATURE_CONTROL_LOCK_BIT_FLAG, 81 | ); 82 | } 83 | } 84 | } 85 | 86 | impl Drop for Vmx { 87 | fn drop(&mut self) { 88 | if self.vmx_enabled { 89 | vmxoff(); 90 | } 91 | } 92 | } 93 | 94 | /// The region of memory that the logical processor uses to support VMX 95 | /// operation. 96 | /// 97 | /// See: 25.11.5 VMXON Region 98 | #[derive(derivative::Derivative)] 99 | #[derivative(Debug)] 100 | #[repr(C, align(4096))] 101 | struct Vmxon { 102 | revision_id: u32, 103 | #[derivative(Debug = "ignore")] 104 | data: [u8; 4092], 105 | } 106 | impl Default for Vmxon { 107 | fn default() -> Self { 108 | Self { 109 | revision_id: rdmsr(x86::msr::IA32_VMX_BASIC) as u32, 110 | data: [0; 4092], 111 | } 112 | } 113 | } 114 | const _: () = assert!(core::mem::size_of::() == BASE_PAGE_SIZE); 115 | 116 | /// The wrapper of the VMXON instruction. 117 | fn vmxon(vmxon_region: &mut Vmxon) { 118 | // Safety: this project runs at CPL0. 119 | unsafe { x86::bits64::vmx::vmxon(vmxon_region as *mut _ as u64).unwrap() }; 120 | } 121 | 122 | /// The wrapper of the VMXOFF instruction. 123 | fn vmxoff() { 124 | // Safety: this project runs at CPL0. 125 | unsafe { x86::current::vmx::vmxoff().unwrap() }; 126 | } 127 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/epts.rs: -------------------------------------------------------------------------------- 1 | use bitfield::bitfield; 2 | use core::ptr::addr_of; 3 | use log::trace; 4 | use uefi::table::boot::PAGE_SIZE; 5 | use x86::current::paging::{BASE_PAGE_SHIFT, LARGE_PAGE_SIZE}; 6 | 7 | use super::mtrr::Mtrr; 8 | 9 | #[repr(C, align(4096))] 10 | pub(crate) struct Epts { 11 | pml4: Pml4, 12 | pdpt: Pdpt, 13 | pd: [Pd; 512], 14 | pt: Pt, 15 | } 16 | impl Epts { 17 | pub(crate) fn build_identify(&mut self) { 18 | let mtrr = Mtrr::new(); 19 | trace!("{mtrr:#x?}"); 20 | trace!("Initializing EPTs"); 21 | 22 | let mut pa = 0u64; 23 | 24 | self.pml4.0.entries[0].set_readable(true); 25 | self.pml4.0.entries[0].set_writable(true); 26 | self.pml4.0.entries[0].set_executable(true); 27 | self.pml4.0.entries[0].set_pfn(addr_of!(self.pdpt) as u64 >> BASE_PAGE_SHIFT); 28 | for (i, pdpte) in self.pdpt.0.entries.iter_mut().enumerate() { 29 | pdpte.set_readable(true); 30 | pdpte.set_writable(true); 31 | pdpte.set_executable(true); 32 | pdpte.set_pfn(addr_of!(self.pd[i]) as u64 >> BASE_PAGE_SHIFT); 33 | for pde in &mut self.pd[i].0.entries { 34 | if pa == 0 { 35 | // First 2MB is managed by 4KB EPT PTs so MTRR memory types 36 | // are properly reflected into the EPT memory memory types. 37 | pde.set_readable(true); 38 | pde.set_writable(true); 39 | pde.set_executable(true); 40 | pde.set_pfn(addr_of!(self.pt) as u64 >> BASE_PAGE_SHIFT); 41 | for pte in &mut self.pt.0.entries { 42 | let memory_type = 43 | mtrr.find(pa..pa + PAGE_SIZE as u64).unwrap_or_else(|| { 44 | panic!("Memory type could not be resolved for {pa:#x?}") 45 | }); 46 | pte.set_readable(true); 47 | pte.set_writable(true); 48 | pte.set_executable(true); 49 | pte.set_memory_type(memory_type as u64); 50 | pte.set_pfn(pa >> BASE_PAGE_SHIFT); 51 | pa += PAGE_SIZE as u64; 52 | } 53 | } else { 54 | // For the rest of GPAes, manage them with 2MB large page EPTs. 55 | // We assume MTRR memory types are configured for 2MB or greater 56 | // granularity. 57 | let memory_type = 58 | mtrr.find(pa..pa + LARGE_PAGE_SIZE as u64) 59 | .unwrap_or_else(|| { 60 | panic!("Memory type could not be resolved for {pa:#x?}") 61 | }); 62 | pde.set_readable(true); 63 | pde.set_writable(true); 64 | pde.set_executable(true); 65 | pde.set_memory_type(memory_type as u64); 66 | pde.set_large(true); 67 | pde.set_pfn(pa >> BASE_PAGE_SHIFT); 68 | pa += LARGE_PAGE_SIZE as u64; 69 | } 70 | } 71 | } 72 | } 73 | 74 | pub(crate) fn make_2mb_ro(&mut self, gpa: u64) { 75 | self.pde_mut(gpa).set_writable(false); 76 | } 77 | 78 | pub(crate) fn make_2mb_pwa(&mut self, gpa: u64) { 79 | self.pde_mut(gpa).set_paging_write_access(true); 80 | } 81 | 82 | pub(crate) fn make_2mb_gpv(&mut self, gpa: u64) { 83 | self.pde_mut(gpa).set_verify_guest_paging(true); 84 | } 85 | 86 | fn pde_mut(&mut self, gpa: u64) -> &mut Entry { 87 | let gpa = gpa as usize; 88 | let i4 = gpa >> 39 & 0b1_1111_1111; 89 | let i3 = gpa >> 30 & 0b1_1111_1111; 90 | let i2 = gpa >> 21 & 0b1_1111_1111; 91 | 92 | assert!((gpa % LARGE_PAGE_SIZE) == 0); 93 | assert!(i4 == 0); 94 | 95 | let entry = &mut self.pd[i3].0.entries[i2]; 96 | assert!(entry.large()); 97 | entry 98 | } 99 | } 100 | 101 | #[derive(Debug, Clone, Copy)] 102 | struct Pml4(Table); 103 | 104 | #[derive(Debug, Clone, Copy)] 105 | struct Pdpt(Table); 106 | 107 | #[derive(Debug, Clone, Copy)] 108 | struct Pd(Table); 109 | 110 | #[derive(Debug, Clone, Copy)] 111 | struct Pt(Table); 112 | 113 | #[derive(Debug, Clone, Copy)] 114 | #[repr(C, align(4096))] 115 | struct Table { 116 | entries: [Entry; 512], 117 | } 118 | 119 | bitfield! { 120 | /// Figure 29-1. Formats of EPTP and EPT Paging-Structure Entries 121 | #[derive(Clone, Copy)] 122 | struct Entry(u64); 123 | impl Debug; 124 | readable, set_readable: 0; 125 | writable, set_writable: 1; 126 | executable, set_executable: 2; 127 | memory_type, set_memory_type: 5, 3; 128 | large, set_large: 7; 129 | pfn, set_pfn: 51, 12; 130 | verify_guest_paging, set_verify_guest_paging: 57; 131 | paging_write_access, set_paging_write_access: 58; 132 | } 133 | -------------------------------------------------------------------------------- /hypervisor/src/hypervisor.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, info, trace}; 2 | use num_derive::FromPrimitive; 3 | use num_traits::FromPrimitive; 4 | use x86::{ 5 | controlregs::{Cr4, Xcr0}, 6 | cpuid::cpuid, 7 | vmx::vmcs, 8 | }; 9 | 10 | use crate::{ 11 | intel_vt::{ 12 | vm::{vmread, Vm, VmExitReason}, 13 | vmx::Vmx, 14 | }, 15 | x86_instructions::{cr4, cr4_write, rdmsr, wrmsr, xsetbv}, 16 | GuestRegisters, 17 | }; 18 | 19 | pub(crate) const CPUID_VENDOR_AND_MAX_FUNCTIONS: u32 = 0x4000_0000; 20 | pub(crate) const HLAT_VENDOR_NAME: u32 = 0x5441_4c48; // "HLAT" 21 | 22 | /// Installs the hypervisor on the current processor. 23 | pub(crate) fn start_hypervisor(regs: &GuestRegisters) -> ! { 24 | debug!("Enabling virtualization extension"); 25 | let mut vmx = Vmx::new(); 26 | vmx.enable(); 27 | 28 | // Create a new (empty) VM instance and tell the processor to operate on it. 29 | let vm = &mut Vm::new(); 30 | vm.activate(); 31 | vm.initialize(regs); 32 | 33 | debug!("Starting the VM"); 34 | loop { 35 | // Then, run the VM until events we (hypervisor) need to handle. 36 | match vm.run() { 37 | VmExitReason::Cpuid => handle_cpuid(vm), 38 | VmExitReason::Rdmsr => handle_rdmsr(vm), 39 | VmExitReason::Wrmsr => handle_wrmsr(vm), 40 | VmExitReason::XSetBv => handle_xsetbv(vm), 41 | VmExitReason::Vmcall => handle_vmcall(vm), 42 | } 43 | } 44 | } 45 | 46 | fn handle_cpuid(vm: &mut Vm) { 47 | let leaf = vm.regs.rax as u32; 48 | let sub_leaf = vm.regs.rcx as u32; 49 | trace!("CPUID {leaf:#x?} {sub_leaf:#x?}"); 50 | let mut regs = cpuid!(leaf, sub_leaf); 51 | 52 | // Indicate that the hypervisor is present relevant CPUID is asked. 53 | if leaf == CPUID_VENDOR_AND_MAX_FUNCTIONS { 54 | regs.ebx = HLAT_VENDOR_NAME; 55 | regs.ecx = HLAT_VENDOR_NAME; 56 | regs.edx = HLAT_VENDOR_NAME; 57 | } else if leaf == 1 { 58 | // CPUID.1.ECX[5] indicates if VT-x is supported. Clear this on this 59 | // processor to prevent other hypervisor tries to use it. 60 | // See: Table 3-10. Feature Information Returned in the ECX Register 61 | regs.ecx &= !(1 << 5); 62 | } 63 | 64 | vm.regs.rax = u64::from(regs.eax); 65 | vm.regs.rbx = u64::from(regs.ebx); 66 | vm.regs.rcx = u64::from(regs.ecx); 67 | vm.regs.rdx = u64::from(regs.edx); 68 | vm.regs.rip += vmread(vmcs::ro::VMEXIT_INSTRUCTION_LEN); 69 | } 70 | 71 | fn handle_rdmsr(vm: &mut Vm) { 72 | let msr = vm.regs.rcx as u32; 73 | trace!("RDMSR {msr:#x?}"); 74 | let value = rdmsr(msr); 75 | 76 | vm.regs.rax = value & 0xffff_ffff; 77 | vm.regs.rdx = value >> 32; 78 | vm.regs.rip += vmread(vmcs::ro::VMEXIT_INSTRUCTION_LEN); 79 | } 80 | 81 | fn handle_wrmsr(vm: &mut Vm) { 82 | let msr = vm.regs.rcx as u32; 83 | let value = (vm.regs.rax & 0xffff_ffff) | ((vm.regs.rdx & 0xffff_ffff) << 32); 84 | info!("WRMSR {msr:#x?} {value:#x?}"); 85 | wrmsr(msr, value); 86 | 87 | vm.regs.rip += vmread(vmcs::ro::VMEXIT_INSTRUCTION_LEN); 88 | } 89 | 90 | fn handle_xsetbv(vm: &mut Vm) { 91 | let xcr: u32 = vm.regs.rcx as u32; 92 | let value = (vm.regs.rax & 0xffff_ffff) | ((vm.regs.rdx & 0xffff_ffff) << 32); 93 | let value = Xcr0::from_bits(value).unwrap(); 94 | info!("XSETBV {xcr:#x?} {value:#x?}"); 95 | 96 | cr4_write(cr4() | Cr4::CR4_ENABLE_OS_XSAVE); 97 | xsetbv(xcr, value); 98 | 99 | vm.regs.rip += vmread(vmcs::ro::VMEXIT_INSTRUCTION_LEN); 100 | } 101 | 102 | fn handle_vmcall(vm: &mut Vm) { 103 | if cfg!(feature = "enable_vt_rp") { 104 | match FromPrimitive::from_u64(vm.regs.rcx) { 105 | Some(Hypercall::BlockRemappingLa) => { 106 | // Prevent remapping the specified LA by enabling HLAT paging. 107 | // Save GPA of the protected LA. 108 | vm.gpa = Some(vm.hlat.enable_hlat_for_4kb(vm.regs.rdx)); 109 | } 110 | Some(Hypercall::MakeHvManagedTablesReadOnly) => { 111 | // Make the hypervisor-managed paging structures read-only with 112 | // EPT. 113 | vm.epts.make_2mb_ro(vm.hlat.as_ref() as *const _ as u64); 114 | } 115 | Some(Hypercall::EnablePwForHvManagedPagingStructures) => { 116 | // Enable PW for the hypervisor-managed paging structures so that 117 | // even if they are marked as read-only, the processor can set 118 | // "dirty" and "accessed" bits during page walk. 119 | vm.epts.make_2mb_pwa(vm.hlat.as_ref() as *const _ as u64); 120 | } 121 | Some(Hypercall::BlockAliasingGpa) => { 122 | // Prevent aliasing the HLAT protected GPA by enabling GPV. Then, 123 | // enable PW for the hypervisor-managed paging structures so that 124 | // the GPA can still be translated with them (but only with them). 125 | vm.epts.make_2mb_gpv(vm.gpa.expect("HLAT is enabled")); 126 | vm.epts.make_2mb_pwa(vm.hlat.as_ref() as *const _ as u64); 127 | } 128 | None => panic!("{} is not a supported hypercall number", vm.regs.rcx), 129 | } 130 | vm.regs.rax = 0; 131 | } else { 132 | vm.regs.rax = u64::MAX; 133 | } 134 | 135 | vm.regs.rip += vmread(vmcs::ro::VMEXIT_INSTRUCTION_LEN); 136 | } 137 | 138 | #[derive(FromPrimitive)] 139 | enum Hypercall { 140 | BlockRemappingLa, 141 | MakeHvManagedTablesReadOnly, 142 | EnablePwForHvManagedPagingStructures, 143 | BlockAliasingGpa, 144 | } 145 | -------------------------------------------------------------------------------- /xtask/src/vmtest/vmware.rs: -------------------------------------------------------------------------------- 1 | use crate::DynError; 2 | use std::{ 3 | env, 4 | fs::{self}, 5 | io::{BufRead, BufReader}, 6 | path::Path, 7 | process::{Command, Stdio}, 8 | sync::mpsc::channel, 9 | thread, 10 | time::{Duration, SystemTime}, 11 | }; 12 | 13 | use super::{copy_artifacts_to, TestVm, UnixCommand}; 14 | 15 | pub(crate) struct Vmware {} 16 | 17 | impl TestVm for Vmware { 18 | fn deploy(&self, release: bool) -> Result<(), DynError> { 19 | let output = UnixCommand::new("dd") 20 | .args([ 21 | "if=/dev/zero", 22 | "of=/tmp/vmware_cd.img", 23 | "bs=1k", 24 | "count=2880", 25 | ]) 26 | .output()?; 27 | if !output.status.success() { 28 | Err(format!("dd failed: {output:#?}"))?; 29 | } 30 | 31 | let output = UnixCommand::new("mformat") 32 | .args(["-i", "/tmp/vmware_cd.img", "-f", "2880", "::"]) 33 | .output()?; 34 | if !output.status.success() { 35 | Err(format!("mformat failed: {output:#?}"))?; 36 | } 37 | 38 | copy_artifacts_to("/tmp/vmware_cd.img", release)?; 39 | 40 | let output = UnixCommand::new("mkisofs") 41 | .args([ 42 | "-eltorito-boot", 43 | "vmware_cd.img", 44 | "-no-emul-boot", 45 | "-o", 46 | "/tmp/vmware_cd.iso", 47 | "/tmp/vmware_cd.img", 48 | ]) 49 | .output()?; 50 | if !output.status.success() { 51 | Err(format!("mkisofs failed: {output:#?}"))?; 52 | } 53 | Ok(()) 54 | } 55 | 56 | fn run(&self) -> Result<(), DynError> { 57 | let vmrun = if cfg!(target_os = "windows") { 58 | r"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" 59 | } else if wsl::is_wsl() { 60 | "/mnt/c/Program Files (x86)/VMware/VMware Workstation/vmrun.exe" 61 | } else { 62 | "vmrun" 63 | }; 64 | 65 | let vmx_path = if wsl::is_wsl() { 66 | windows_path("./tests/samples/vmware/NoOS_windows.vmx") 67 | } else { 68 | format!("./tests/samples/vmware/NoOS_{}.vmx", env::consts::OS) 69 | }; 70 | 71 | // Stop the VM if requested. This is best effort and failures are ignored. 72 | let _unused = Command::new(vmrun) 73 | .args(["stop", vmx_path.as_str(), "nogui"]) 74 | .output()?; 75 | 76 | // If the serial output file exists, delete it to avoid a popup 77 | let log_file = if cfg!(target_os = "windows") { 78 | r"\\wsl$\Ubuntu\tmp\serial.log" 79 | } else { 80 | "/tmp/serial.log" 81 | }; 82 | if Path::new(log_file).exists() { 83 | fs::remove_file(log_file)?; 84 | } 85 | 86 | // Start the VM 87 | println!("🕒 Starting a VMware VM"); 88 | let product_type = if cfg!(target_os = "macos") { 89 | "fusion" 90 | } else { 91 | "ws" 92 | }; 93 | let output = Command::new(vmrun) 94 | .args(["-T", product_type, "start", vmx_path.as_str()]) 95 | .spawn()? 96 | .wait()?; 97 | if !output.success() { 98 | Err(format!("vmrun failed: {output:#?}"))?; 99 | } 100 | 101 | // Wait until the serial output file is created. Then, enter loop to read it. 102 | while !Path::new(log_file).exists() { 103 | thread::sleep(Duration::from_secs(1)); 104 | } 105 | 106 | let _unused = thread::spawn(|| { 107 | let output = UnixCommand::new("tail") 108 | .args(["-f", "/tmp/serial.log"]) 109 | .stdin(Stdio::piped()) 110 | .stdout(Stdio::piped()) 111 | .spawn() 112 | .unwrap(); 113 | 114 | let now = SystemTime::now(); 115 | 116 | // Read and print stdout as they come in. This does not return. 117 | let reader = BufReader::new(output.stdout.unwrap()); 118 | reader 119 | .lines() 120 | .map_while(std::result::Result::ok) 121 | .for_each(|line| { 122 | println!( 123 | "{:>4}: {line}\r", 124 | now.elapsed().unwrap_or_default().as_secs() 125 | ); 126 | }); 127 | }); 128 | 129 | println!("🕒 Please select 'EFI Internal Shell (Unsupported option)' on VMware..."); 130 | let (tx, rx) = channel(); 131 | ctrlc::set_handler(move || tx.send(()).unwrap())?; 132 | rx.recv()?; 133 | 134 | // Stop the VM if requested. This is best effort and failures are ignored. 135 | println!("🕒 Shutting down the VM"); 136 | let _unused = Command::new(vmrun) 137 | .args(["stop", vmx_path.as_str(), "nogui"]) 138 | .output()?; 139 | 140 | Ok(()) 141 | } 142 | } 143 | 144 | fn windows_path(path: &str) -> String { 145 | if wsl::is_wsl() { 146 | let output = UnixCommand::new("wslpath") 147 | .args(["-a", "-w", path]) 148 | .output() 149 | .unwrap(); 150 | assert!(output.status.success()); 151 | std::str::from_utf8(&output.stdout) 152 | .unwrap() 153 | .trim() 154 | .to_string() 155 | } else { 156 | path.to_string() 157 | } 158 | } 159 | 160 | #[cfg(test)] 161 | mod tests { 162 | use crate::vmtest::vmware::windows_path; 163 | 164 | #[test] 165 | fn test_windows_path() { 166 | if cfg!(target_os = "windows") { 167 | assert_eq!(windows_path(r"C:\"), r"C:\"); 168 | assert_eq!(windows_path("/mnt/c/tmp"), "/mnt/c/tmp"); 169 | } else { 170 | assert_eq!(windows_path("/tmp"), r"\\wsl.localhost\Ubuntu\tmp"); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/run_vmx_vm.S: -------------------------------------------------------------------------------- 1 | ;// The module containing the `run_vmx_vm` function. 2 | 3 | ;// Offsets to each field in the GuestRegisters struct. 4 | .set registers_rax, 0x0 5 | .set registers_rbx, 0x8 6 | .set registers_rcx, 0x10 7 | .set registers_rdx, 0x18 8 | .set registers_rdi, 0x20 9 | .set registers_rsi, 0x28 10 | .set registers_rbp, 0x30 11 | .set registers_r8, 0x38 12 | .set registers_r9, 0x40 13 | .set registers_r10, 0x48 14 | .set registers_r11, 0x50 15 | .set registers_r12, 0x58 16 | .set registers_r13, 0x60 17 | .set registers_r14, 0x68 18 | .set registers_r15, 0x70 19 | 20 | ;// Runs the guest until VM-exit occurs. 21 | ;// 22 | ;// This function works as follows: 23 | ;// 1. saves host general purpose register values to stack. 24 | ;// 2. loads guest general purpose register values from `GuestRegisters`. 25 | ;// 3. executes the VMLAUNCH or VMRESUME instruction that 26 | ;// 1. saves host register values to the VMCS. 27 | ;// 2. loads guest register values from the VMCS. 28 | ;// 3. starts running code in VMX non-root operation until VM-exit. 29 | ;// 4. on VM-exit, the processor 30 | ;// 1. saves guest register values to the VMCS. 31 | ;// 2. loads host register values from the VMCS. Some registers are reset to 32 | ;// hard-coded values. For example, interrupts are always disabled. 33 | ;// 3. updates VM-exit information fields in VMCS to record causes of VM-exit. 34 | ;// 4. starts running code in the VMX root operation. 35 | ;// 5. saves guest general purpose register values to `GuestRegisters`. 36 | ;// 6. loads host general purpose register values from stack. 37 | ;// 38 | ;// On VM-exit, the processor comes back to this function (at "VmExit") because 39 | ;// the host RIP is configured so. 40 | ;// 41 | ;// Note that state switch implemented here is not complete, and some register 42 | ;// values are "leaked" to the other side, for example, XMM registers. 43 | ;// 44 | ;// extern "efiapi" fn run_vmx_vm(registers: &mut GuestRegisters) -> u64; 45 | .global run_vmx_vm 46 | run_vmx_vm: 47 | xchg bx, bx 48 | 49 | ;// Save current (host) general purpose registers onto stack. 50 | push rax 51 | push rcx 52 | push rdx 53 | push rbx 54 | push rbp 55 | push rsi 56 | push rdi 57 | push r8 58 | push r9 59 | push r10 60 | push r11 61 | push r12 62 | push r13 63 | push r14 64 | push r15 65 | 66 | ;// Copy `registers` for use. Then, save it at the top of stack so that after 67 | ;// VM-exit, we can find it. 68 | mov r15, rcx ;// r15 <= `registers` 69 | push rcx ;// [rsp] <= `registers` 70 | 71 | ;// Restore guest general purpose registers from `registers` and try VMRESUME. 72 | mov rax, [r15 + registers_rax] 73 | mov rbx, [r15 + registers_rbx] 74 | mov rcx, [r15 + registers_rcx] 75 | mov rdx, [r15 + registers_rdx] 76 | mov rdi, [r15 + registers_rdi] 77 | mov rsi, [r15 + registers_rsi] 78 | mov rbp, [r15 + registers_rbp] 79 | mov r8, [r15 + registers_r8] 80 | mov r9, [r15 + registers_r9] 81 | mov r10, [r15 + registers_r10] 82 | mov r11, [r15 + registers_r11] 83 | mov r12, [r15 + registers_r12] 84 | mov r13, [r15 + registers_r13] 85 | mov r14, [r15 + registers_r14] 86 | mov r15, [r15 + registers_r15] 87 | vmresume 88 | pushf ;// Save flags on stack as next VMREAD will overwrite it 89 | 90 | ;// If the failure is due to "VMRESUME with non-launched VMCS" (5), continue 91 | ;// and perform VMLAUNCH instead. It occurs on the first time. 92 | mov r15, 0x4400 ;// VM-instruction error 93 | vmread r15, r15 94 | cmp r15, 5 95 | jz .Launch 96 | popf ;// Restore flags from stack 97 | jmp .VmEntryFailure 98 | 99 | .Launch: 100 | pop r15 ;// Discard flags from stack 101 | ;// The VM has never launched with the current VMCS. Configure the host RSP 102 | ;// and RIP first. Then, restore the rest of guest general purpose registers 103 | ;// and run the guest until VM-exit occurs. 104 | xchg bx, bx 105 | mov r15, 0x6C14 ;// Host RSP 106 | vmwrite r15, rsp 107 | lea r14, [rip + .VmExit] 108 | mov r15, 0x6C16 ;// Host RIP 109 | vmwrite r15, r14 110 | mov r15, [rsp] ;// r15 <= `registers` 111 | mov r14, [r15 + registers_r14] 112 | mov r15, [r15 + registers_r15] 113 | vmlaunch 114 | 115 | .VmEntryFailure: 116 | ;// VMLAUNCH or VMRESUME failed. If it were successful, VM-exit should have 117 | ;// led to "VmExit", not here. 118 | jmp .Exit 119 | 120 | .VmExit: 121 | ;// VM-exit occurred. Save current (guest) general purpose registers. 122 | xchg bx, bx 123 | xchg r15, [rsp] ;// r15 <= `registers` / [rsp] <= guest r15 124 | mov [r15 + registers_rax], rax 125 | mov [r15 + registers_rbx], rbx 126 | mov [r15 + registers_rcx], rcx 127 | mov [r15 + registers_rdx], rdx 128 | mov [r15 + registers_rsi], rsi 129 | mov [r15 + registers_rdi], rdi 130 | mov [r15 + registers_rbp], rbp 131 | mov [r15 + registers_r8], r8 132 | mov [r15 + registers_r9], r9 133 | mov [r15 + registers_r10], r10 134 | mov [r15 + registers_r11], r11 135 | mov [r15 + registers_r12], r12 136 | mov [r15 + registers_r13], r13 137 | mov [r15 + registers_r14], r14 138 | mov rax, [rsp] ;// rax <= guest R15 139 | mov [r15 + registers_r15], rax 140 | 141 | .Exit: 142 | ;// Adjust the stack pointer. 143 | pop rax 144 | 145 | ;// Restore host general purpose registers from stack. 146 | pop r15 147 | pop r14 148 | pop r13 149 | pop r12 150 | pop r11 151 | pop r10 152 | pop r9 153 | pop r8 154 | pop rdi 155 | pop rsi 156 | pop rbp 157 | pop rbx 158 | pop rdx 159 | pop rcx 160 | pop rax 161 | 162 | ;// Return the rflags value. 163 | pushfq 164 | pop rax 165 | ret 166 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/mtrr.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::{arch::asm, ops::Range}; 3 | use log::trace; 4 | use num_derive::FromPrimitive; 5 | use num_traits::FromPrimitive; 6 | use x86::current::paging::BASE_PAGE_SHIFT; 7 | 8 | use crate::x86_instructions::rdmsr; 9 | 10 | #[derive(Copy, Clone, Debug, PartialEq, FromPrimitive)] 11 | pub(crate) enum MemoryType { 12 | Uncachable = 0, 13 | WriteCombining = 1, 14 | WriteThrough = 4, 15 | WriteProtected = 5, 16 | WriteBack = 6, 17 | UncachableMinus = 7, 18 | } 19 | 20 | #[derive(Debug)] 21 | pub(crate) struct Mtrr { 22 | default_memory_type: MemoryType, 23 | fixed: Vec, 24 | variable: Vec, 25 | } 26 | 27 | impl Mtrr { 28 | pub(crate) fn new() -> Self { 29 | let raw_mtrrs = RawMtrrs::new(); 30 | trace!("{raw_mtrrs:#x?}"); 31 | Self { 32 | default_memory_type: raw_mtrrs.default_memory_type, 33 | fixed: Self::convert_from_raw_fixed(&raw_mtrrs.fixed), 34 | variable: Self::convert_from_raw_variable(&raw_mtrrs.variable), 35 | } 36 | } 37 | 38 | pub(crate) fn find(&self, range: Range) -> Option { 39 | // Look up the fixed range MTRRs if the range start within 1MB (which is managed 40 | // by the fixed range MTRRs), since the fixed range MTRRs are priority over 41 | // the variable range MTRRs. 42 | if range.start < 0x10_0000 { 43 | // If the range crosses the 1MB boundary, report error. For simplicity, 44 | // we do not attempt to resolve the memory type of the range that spans both 45 | // fixed and variable range MTRRs. The caller should query a memory type for 46 | // a shorter range instead. That is almost always required anyway since 47 | // lowest 1MB has variety of memory types through fixed range MTRRs. 48 | if range.end > 0x10_0000 { 49 | return None; 50 | } 51 | return self.find_from_fixed(range); 52 | } 53 | // Otherwise, look up the variable range MTRRs. 54 | self.find_from_variable(range) 55 | } 56 | 57 | fn find_from_fixed(&self, range: Range) -> Option { 58 | // Return the memory type of the first range that contains the given range. 59 | // Within `self.fixed` there is no overlap of ranges, so the first match 60 | // is the only match. 61 | self.fixed 62 | .iter() 63 | .find(|mtrr| mtrr.range.contains(&range.start) && mtrr.range.contains(&(range.end - 1))) 64 | .map(|found| found.memory_type) 65 | } 66 | 67 | fn find_from_variable(&self, range: Range) -> Option { 68 | let mut return_memory_type = None::; 69 | for mtrr in &self.variable { 70 | if mtrr.range.contains(&range.start) { 71 | // If the entire range is not managed by this single entry, bail out. 72 | // This means the given range is managed by multiple conflicting MTRR 73 | // settings. The caller needs to call this function with smaller range. 74 | if !mtrr.range.contains(&(range.end - 1)) { 75 | return None; 76 | } 77 | 78 | // Use if the current matching entry is for UC, as the UC memory type 79 | // takes precedence. 80 | if mtrr.memory_type == MemoryType::Uncachable { 81 | return Some(mtrr.memory_type); 82 | } 83 | 84 | // The WT memory type takes precedence over the WB memory type. 85 | if let Some(memory_type) = return_memory_type { 86 | if memory_type == MemoryType::WriteBack 87 | && mtrr.memory_type == MemoryType::WriteThrough 88 | { 89 | return_memory_type = Some(mtrr.memory_type); 90 | } 91 | } else { 92 | // Overwise, use the last matching MTRR's memory type. 93 | return_memory_type = Some(mtrr.memory_type); 94 | } 95 | } 96 | } 97 | 98 | // Use the default type if none of MTRRs controls any page in this range. 99 | if return_memory_type.is_none() { 100 | return_memory_type = Some(self.default_memory_type); 101 | } 102 | return_memory_type 103 | } 104 | 105 | fn convert_from_raw_fixed(raw_fixed_mtrrs: &[RawFixedMtrr]) -> Vec { 106 | const FIXED_MTRR_RANGES: [FixedMtrrRangeInfo; 11] = [ 107 | FixedMtrrRangeInfo::new(0x0, 0x10000), 108 | FixedMtrrRangeInfo::new(0x80000, 0x4000), 109 | FixedMtrrRangeInfo::new(0xA0000, 0x4000), 110 | FixedMtrrRangeInfo::new(0xC0000, 0x1000), 111 | FixedMtrrRangeInfo::new(0xC8000, 0x1000), 112 | FixedMtrrRangeInfo::new(0xD0000, 0x1000), 113 | FixedMtrrRangeInfo::new(0xD8000, 0x1000), 114 | FixedMtrrRangeInfo::new(0xE0000, 0x1000), 115 | FixedMtrrRangeInfo::new(0xE8000, 0x1000), 116 | FixedMtrrRangeInfo::new(0xF0000, 0x1000), 117 | FixedMtrrRangeInfo::new(0xF8000, 0x1000), 118 | ]; 119 | 120 | assert!(raw_fixed_mtrrs.len() == FIXED_MTRR_RANGES.len()); 121 | 122 | let mut combined_ranges = Vec::::new(); 123 | for (i, fixed_raw) in raw_fixed_mtrrs.iter().enumerate() { 124 | let range = &FIXED_MTRR_RANGES[i]; 125 | for (j, byte) in fixed_raw.value.to_be_bytes().iter().enumerate() { 126 | let memory_type = ::from_u8(*byte).unwrap(); 127 | let base = range.base + (range.size * (j as u64)); 128 | let range = MemoryTypeRange { 129 | memory_type, 130 | range: (base..base + range.size), 131 | }; 132 | Self::update_combined_ranges(&mut combined_ranges, range); 133 | } 134 | } 135 | combined_ranges 136 | } 137 | 138 | fn convert_from_raw_variable(raw_variable_mtrrs: &[RawVariableMtrr]) -> Vec { 139 | let mut combined_ranges = Vec::::new(); 140 | 141 | for raw_variable in raw_variable_mtrrs { 142 | const IA32_MTRR_PHYSMASK_VALID_FLAG: u64 = 1 << 11; 143 | 144 | if raw_variable.mask & IA32_MTRR_PHYSMASK_VALID_FLAG != 0 { 145 | let pfn = raw_variable.mask >> BASE_PAGE_SHIFT; 146 | let length = Self::bit_scan_forward(pfn); 147 | let size_in_pages = 1u64 << length; 148 | let size_in_bytes = size_in_pages << BASE_PAGE_SHIFT; 149 | 150 | let memory_type = 151 | ::from_u64(raw_variable.base & 0xff).unwrap(); 152 | let base = raw_variable.base & !0xfff; 153 | let range = MemoryTypeRange { 154 | memory_type, 155 | range: (base..base + size_in_bytes), 156 | }; 157 | Self::update_combined_ranges(&mut combined_ranges, range); 158 | } 159 | } 160 | 161 | combined_ranges 162 | } 163 | 164 | fn bit_scan_forward(value: u64) -> u64 { 165 | let result: u64; 166 | unsafe { asm!("bsf {}, {}", out(reg) result, in(reg) value) }; 167 | result 168 | } 169 | 170 | fn update_combined_ranges(combined_ranges: &mut Vec, range: MemoryTypeRange) { 171 | if let Some(last_range) = combined_ranges.last_mut() { 172 | // Combine this entry if it is contiguous from the previous entry 173 | // with the same memory type. 174 | if last_range.memory_type == range.memory_type { 175 | if last_range.range.end == range.range.start { 176 | last_range.range.end = range.range.end; 177 | return; 178 | } else if last_range.range.start == range.range.end { 179 | last_range.range.start = range.range.start; 180 | return; 181 | } 182 | } 183 | } 184 | 185 | combined_ranges.push(range); 186 | } 187 | } 188 | 189 | #[derive(Debug)] 190 | struct MemoryTypeRange { 191 | memory_type: MemoryType, 192 | range: Range, 193 | } 194 | 195 | struct FixedMtrrRangeInfo { 196 | base: u64, 197 | size: u64, 198 | } 199 | 200 | impl FixedMtrrRangeInfo { 201 | const fn new(base: u64, size: u64) -> Self { 202 | Self { base, size } 203 | } 204 | } 205 | 206 | #[derive(Debug)] 207 | struct RawMtrrs { 208 | default_memory_type: MemoryType, 209 | fixed: Vec, 210 | variable: Vec, 211 | } 212 | 213 | impl RawMtrrs { 214 | fn new() -> Self { 215 | const IA32_MTRR_DEF_TYPE_FIXED_RANGE_MTRR_ENABLE_FLAG: u64 = 1 << 10; 216 | const IA32_MTRR_DEF_TYPE_MTRR_ENABLE_FLAG: u64 = 1 << 11; 217 | 218 | const FIXED_MTRRS: [u32; 11] = [ 219 | x86::msr::IA32_MTRR_FIX64K_00000, 220 | x86::msr::IA32_MTRR_FIX16K_80000, 221 | x86::msr::IA32_MTRR_FIX16K_A0000, 222 | x86::msr::IA32_MTRR_FIX4K_C0000, 223 | x86::msr::IA32_MTRR_FIX4K_C8000, 224 | x86::msr::IA32_MTRR_FIX4K_D0000, 225 | x86::msr::IA32_MTRR_FIX4K_D8000, 226 | x86::msr::IA32_MTRR_FIX4K_E0000, 227 | x86::msr::IA32_MTRR_FIX4K_E8000, 228 | x86::msr::IA32_MTRR_FIX4K_F0000, 229 | x86::msr::IA32_MTRR_FIX4K_F8000, 230 | ]; 231 | 232 | const VARIABLE_MTRRS: [u32; 20] = [ 233 | x86::msr::IA32_MTRR_PHYSBASE0, 234 | x86::msr::IA32_MTRR_PHYSMASK0, 235 | x86::msr::IA32_MTRR_PHYSBASE1, 236 | x86::msr::IA32_MTRR_PHYSMASK1, 237 | x86::msr::IA32_MTRR_PHYSBASE2, 238 | x86::msr::IA32_MTRR_PHYSMASK2, 239 | x86::msr::IA32_MTRR_PHYSBASE3, 240 | x86::msr::IA32_MTRR_PHYSMASK3, 241 | x86::msr::IA32_MTRR_PHYSBASE4, 242 | x86::msr::IA32_MTRR_PHYSMASK4, 243 | x86::msr::IA32_MTRR_PHYSBASE5, 244 | x86::msr::IA32_MTRR_PHYSMASK5, 245 | x86::msr::IA32_MTRR_PHYSBASE6, 246 | x86::msr::IA32_MTRR_PHYSMASK6, 247 | x86::msr::IA32_MTRR_PHYSBASE7, 248 | x86::msr::IA32_MTRR_PHYSMASK7, 249 | x86::msr::IA32_MTRR_PHYSBASE8, 250 | x86::msr::IA32_MTRR_PHYSMASK8, 251 | x86::msr::IA32_MTRR_PHYSBASE9, 252 | x86::msr::IA32_MTRR_PHYSMASK9, 253 | ]; 254 | 255 | // For simplicity, panic when the system does not support MTRRs or enable 256 | // fixed range MTRRs. 257 | let default_type = rdmsr(x86::msr::IA32_MTRR_DEF_TYPE); 258 | assert!( 259 | (default_type & IA32_MTRR_DEF_TYPE_MTRR_ENABLE_FLAG) != 0, 260 | "MTRRs not enabled" 261 | ); 262 | assert!( 263 | (default_type & IA32_MTRR_DEF_TYPE_FIXED_RANGE_MTRR_ENABLE_FLAG) != 0, 264 | "Fixed range MTRRs not enabled" 265 | ); 266 | let default_memory_type = 267 | ::from_u64(default_type & 0b111).unwrap(); 268 | 269 | // Read all fixed range MTRRs. 270 | let mut fixed = Vec::::new(); 271 | for msr in FIXED_MTRRS { 272 | fixed.push(RawFixedMtrr { value: rdmsr(msr) }); 273 | } 274 | 275 | // Get how many variable range MTRRs is supported on this system and read 276 | // them. 277 | let capabilities = rdmsr(x86::msr::IA32_MTRRCAP); 278 | let variable_mtrr_count = (capabilities & 0b1111_1111) as usize; 279 | 280 | let mut variable = Vec::::new(); 281 | for i in (0..VARIABLE_MTRRS.len()).step_by(2) { 282 | if i < variable_mtrr_count * 2 { 283 | variable.push(RawVariableMtrr { 284 | base: rdmsr(VARIABLE_MTRRS[i]), 285 | mask: rdmsr(VARIABLE_MTRRS[i + 1]), 286 | }); 287 | } 288 | } 289 | 290 | Self { 291 | default_memory_type, 292 | fixed, 293 | variable, 294 | } 295 | } 296 | } 297 | 298 | #[derive(Debug)] 299 | struct RawFixedMtrr { 300 | value: u64, 301 | } 302 | 303 | #[derive(Debug)] 304 | struct RawVariableMtrr { 305 | base: u64, 306 | mask: u64, 307 | } 308 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "anstream" 7 | version = "0.6.11" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" 10 | dependencies = [ 11 | "anstyle", 12 | "anstyle-parse", 13 | "anstyle-query", 14 | "anstyle-wincon", 15 | "colorchoice", 16 | "utf8parse", 17 | ] 18 | 19 | [[package]] 20 | name = "anstyle" 21 | version = "1.0.6" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" 24 | 25 | [[package]] 26 | name = "anstyle-parse" 27 | version = "0.2.3" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" 30 | dependencies = [ 31 | "utf8parse", 32 | ] 33 | 34 | [[package]] 35 | name = "anstyle-query" 36 | version = "1.0.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" 39 | dependencies = [ 40 | "windows-sys", 41 | ] 42 | 43 | [[package]] 44 | name = "anstyle-wincon" 45 | version = "3.0.2" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" 48 | dependencies = [ 49 | "anstyle", 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "autocfg" 55 | version = "1.1.0" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 58 | 59 | [[package]] 60 | name = "bit_field" 61 | version = "0.10.2" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" 64 | 65 | [[package]] 66 | name = "bitfield" 67 | version = "0.14.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" 70 | 71 | [[package]] 72 | name = "bitflags" 73 | version = "1.3.2" 74 | source = "registry+https://github.com/rust-lang/crates.io-index" 75 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 76 | 77 | [[package]] 78 | name = "bitflags" 79 | version = "2.4.2" 80 | source = "registry+https://github.com/rust-lang/crates.io-index" 81 | checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" 82 | 83 | [[package]] 84 | name = "cfg-if" 85 | version = "1.0.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 88 | 89 | [[package]] 90 | name = "clap" 91 | version = "4.5.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" 94 | dependencies = [ 95 | "clap_builder", 96 | "clap_derive", 97 | ] 98 | 99 | [[package]] 100 | name = "clap_builder" 101 | version = "4.5.0" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" 104 | dependencies = [ 105 | "anstream", 106 | "anstyle", 107 | "clap_lex", 108 | "strsim", 109 | ] 110 | 111 | [[package]] 112 | name = "clap_derive" 113 | version = "4.5.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" 116 | dependencies = [ 117 | "heck", 118 | "proc-macro2", 119 | "quote", 120 | "syn 2.0.48", 121 | ] 122 | 123 | [[package]] 124 | name = "clap_lex" 125 | version = "0.7.0" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" 128 | 129 | [[package]] 130 | name = "colorchoice" 131 | version = "1.0.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" 134 | 135 | [[package]] 136 | name = "ctrlc" 137 | version = "3.4.2" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" 140 | dependencies = [ 141 | "nix", 142 | "windows-sys", 143 | ] 144 | 145 | [[package]] 146 | name = "derivative" 147 | version = "2.2.0" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" 150 | dependencies = [ 151 | "proc-macro2", 152 | "quote", 153 | "syn 1.0.109", 154 | ] 155 | 156 | [[package]] 157 | name = "heck" 158 | version = "0.4.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 161 | 162 | [[package]] 163 | name = "libc" 164 | version = "0.2.153" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" 167 | 168 | [[package]] 169 | name = "lock_api" 170 | version = "0.4.11" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" 173 | dependencies = [ 174 | "autocfg", 175 | "scopeguard", 176 | ] 177 | 178 | [[package]] 179 | name = "log" 180 | version = "0.4.20" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" 183 | 184 | [[package]] 185 | name = "nix" 186 | version = "0.27.1" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" 189 | dependencies = [ 190 | "bitflags 2.4.2", 191 | "cfg-if", 192 | "libc", 193 | ] 194 | 195 | [[package]] 196 | name = "num-derive" 197 | version = "0.4.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" 200 | dependencies = [ 201 | "proc-macro2", 202 | "quote", 203 | "syn 2.0.48", 204 | ] 205 | 206 | [[package]] 207 | name = "num-traits" 208 | version = "0.2.18" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" 211 | dependencies = [ 212 | "autocfg", 213 | ] 214 | 215 | [[package]] 216 | name = "os_client" 217 | version = "0.1.0" 218 | 219 | [[package]] 220 | name = "proc-macro2" 221 | version = "1.0.78" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 224 | dependencies = [ 225 | "unicode-ident", 226 | ] 227 | 228 | [[package]] 229 | name = "ptr_meta" 230 | version = "0.2.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | checksum = "bcada80daa06c42ed5f48c9a043865edea5dc44cbf9ac009fda3b89526e28607" 233 | dependencies = [ 234 | "ptr_meta_derive", 235 | ] 236 | 237 | [[package]] 238 | name = "ptr_meta_derive" 239 | version = "0.2.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2" 242 | dependencies = [ 243 | "proc-macro2", 244 | "quote", 245 | "syn 1.0.109", 246 | ] 247 | 248 | [[package]] 249 | name = "quote" 250 | version = "1.0.35" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 253 | dependencies = [ 254 | "proc-macro2", 255 | ] 256 | 257 | [[package]] 258 | name = "raw-cpuid" 259 | version = "10.7.0" 260 | source = "registry+https://github.com/rust-lang/crates.io-index" 261 | checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" 262 | dependencies = [ 263 | "bitflags 1.3.2", 264 | ] 265 | 266 | [[package]] 267 | name = "scopeguard" 268 | version = "1.2.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 271 | 272 | [[package]] 273 | name = "spin" 274 | version = "0.9.8" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 277 | dependencies = [ 278 | "lock_api", 279 | ] 280 | 281 | [[package]] 282 | name = "strsim" 283 | version = "0.11.0" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" 286 | 287 | [[package]] 288 | name = "syn" 289 | version = "1.0.109" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 292 | dependencies = [ 293 | "proc-macro2", 294 | "quote", 295 | "unicode-ident", 296 | ] 297 | 298 | [[package]] 299 | name = "syn" 300 | version = "2.0.48" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" 303 | dependencies = [ 304 | "proc-macro2", 305 | "quote", 306 | "unicode-ident", 307 | ] 308 | 309 | [[package]] 310 | name = "ucs2" 311 | version = "0.3.2" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "bad643914094137d475641b6bab89462505316ec2ce70907ad20102d28a79ab8" 314 | dependencies = [ 315 | "bit_field", 316 | ] 317 | 318 | [[package]] 319 | name = "uefi" 320 | version = "0.26.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "07ead9f748a4646479b850add36b527113a80e80a7e0f44d7b0334291850dcc5" 323 | dependencies = [ 324 | "bitflags 2.4.2", 325 | "log", 326 | "ptr_meta", 327 | "ucs2", 328 | "uefi-macros", 329 | "uefi-raw", 330 | "uguid", 331 | ] 332 | 333 | [[package]] 334 | name = "uefi-macros" 335 | version = "0.13.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "26a7b1c2c808c3db854a54d5215e3f7e7aaf5dcfbce095598cba6af29895695d" 338 | dependencies = [ 339 | "proc-macro2", 340 | "quote", 341 | "syn 2.0.48", 342 | ] 343 | 344 | [[package]] 345 | name = "uefi-raw" 346 | version = "0.5.0" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "864ac69eadd877bfb34e7814be1928122ed0057d9f975169a56ee496aa7bdfd7" 349 | dependencies = [ 350 | "bitflags 2.4.2", 351 | "ptr_meta", 352 | "uguid", 353 | ] 354 | 355 | [[package]] 356 | name = "uefi-services" 357 | version = "0.23.0" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "a79fcb420624743c895bad0f9480fbc2f64e7c8d8611fb1ada6bdd799942feb4" 360 | dependencies = [ 361 | "cfg-if", 362 | "log", 363 | "uefi", 364 | ] 365 | 366 | [[package]] 367 | name = "uefi_client" 368 | version = "0.1.0" 369 | dependencies = [ 370 | "bitfield", 371 | "uefi", 372 | "uefi-services", 373 | "x86", 374 | ] 375 | 376 | [[package]] 377 | name = "uguid" 378 | version = "2.2.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "ab14ea9660d240e7865ce9d54ecdbd1cd9fa5802ae6f4512f093c7907e921533" 381 | 382 | [[package]] 383 | name = "unicode-ident" 384 | version = "1.0.12" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 387 | 388 | [[package]] 389 | name = "utf8parse" 390 | version = "0.2.1" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 393 | 394 | [[package]] 395 | name = "vt-rp" 396 | version = "0.1.0" 397 | dependencies = [ 398 | "bitfield", 399 | "derivative", 400 | "log", 401 | "num-derive", 402 | "num-traits", 403 | "spin", 404 | "uefi", 405 | "x86", 406 | ] 407 | 408 | [[package]] 409 | name = "windows-sys" 410 | version = "0.52.0" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 413 | dependencies = [ 414 | "windows-targets", 415 | ] 416 | 417 | [[package]] 418 | name = "windows-targets" 419 | version = "0.52.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" 422 | dependencies = [ 423 | "windows_aarch64_gnullvm", 424 | "windows_aarch64_msvc", 425 | "windows_i686_gnu", 426 | "windows_i686_msvc", 427 | "windows_x86_64_gnu", 428 | "windows_x86_64_gnullvm", 429 | "windows_x86_64_msvc", 430 | ] 431 | 432 | [[package]] 433 | name = "windows_aarch64_gnullvm" 434 | version = "0.52.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" 437 | 438 | [[package]] 439 | name = "windows_aarch64_msvc" 440 | version = "0.52.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" 443 | 444 | [[package]] 445 | name = "windows_i686_gnu" 446 | version = "0.52.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" 449 | 450 | [[package]] 451 | name = "windows_i686_msvc" 452 | version = "0.52.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" 455 | 456 | [[package]] 457 | name = "windows_x86_64_gnu" 458 | version = "0.52.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" 461 | 462 | [[package]] 463 | name = "windows_x86_64_gnullvm" 464 | version = "0.52.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" 467 | 468 | [[package]] 469 | name = "windows_x86_64_msvc" 470 | version = "0.52.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 473 | 474 | [[package]] 475 | name = "wsl" 476 | version = "0.1.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "f8dab7ac864710bdea6594becbea5b5050333cf34fefb0dc319567eb347950d4" 479 | 480 | [[package]] 481 | name = "x86" 482 | version = "0.52.0" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" 485 | dependencies = [ 486 | "bit_field", 487 | "bitflags 1.3.2", 488 | "raw-cpuid", 489 | ] 490 | 491 | [[package]] 492 | name = "xtask" 493 | version = "0.1.2" 494 | dependencies = [ 495 | "cfg-if", 496 | "clap", 497 | "ctrlc", 498 | "wsl", 499 | ] 500 | -------------------------------------------------------------------------------- /hypervisor/src/intel_vt/vm.rs: -------------------------------------------------------------------------------- 1 | use alloc::{ 2 | alloc::handle_alloc_error, 3 | boxed::Box, 4 | format, 5 | string::{String, ToString}, 6 | }; 7 | use core::{alloc::Layout, arch::global_asm}; 8 | use log::trace; 9 | use x86::{ 10 | current::{paging::BASE_PAGE_SIZE, rflags::RFlags}, 11 | segmentation::{cs, ds, es, fs, gs, ss}, 12 | vmx::vmcs, 13 | }; 14 | 15 | use super::{descriptors::Descriptors, epts::Epts, hlat}; 16 | use crate::{ 17 | paging_structures::PagingStructures, 18 | x86_instructions::{cr0, cr3, cr4, lar, lsl, rdmsr, sidt}, 19 | GuestRegisters, Page, 20 | }; 21 | 22 | pub(crate) enum VmExitReason { 23 | Cpuid, 24 | Rdmsr, 25 | Wrmsr, 26 | XSetBv, 27 | Vmcall, 28 | } 29 | 30 | pub(crate) struct Vm { 31 | pub(crate) regs: GuestRegisters, 32 | pub(crate) epts: Box, 33 | pub(crate) hlat: Box, 34 | pub(crate) gpa: Option, 35 | host_paging_structures: Box, 36 | host_descriptors: Descriptors, 37 | descriptors: Descriptors, 38 | vmcs: Box, 39 | msr_bitmaps: Box, 40 | } 41 | 42 | impl Vm { 43 | pub(crate) fn new() -> Self { 44 | let mut vmcs = Box::::default(); 45 | vmcs.revision_id = rdmsr(x86::msr::IA32_VMX_BASIC) as u32; 46 | trace!("{vmcs:#x?}"); 47 | 48 | let mut host_paging_structures = unsafe { box_zeroed::() }; 49 | host_paging_structures.build_identity(); 50 | 51 | let mut epts = unsafe { box_zeroed::() }; 52 | epts.build_identify(); 53 | 54 | Self { 55 | regs: GuestRegisters::default(), 56 | epts, 57 | hlat: unsafe { box_zeroed::() }, 58 | gpa: None, 59 | host_paging_structures, 60 | host_descriptors: Descriptors::new_for_host(), 61 | descriptors: Descriptors::new_from_current(), 62 | vmcs, 63 | msr_bitmaps: unsafe { box_zeroed::() }, 64 | } 65 | } 66 | 67 | pub(crate) fn activate(&mut self) { 68 | vmclear(&mut self.vmcs); 69 | vmptrld(&mut self.vmcs); 70 | } 71 | 72 | // Set the initial VM state from the current system state. 73 | #[allow(clippy::too_many_lines)] 74 | pub(crate) fn initialize(&mut self, regs: &GuestRegisters) { 75 | let idtr = sidt(); 76 | self.regs = *regs; 77 | 78 | vmwrite(vmcs::guest::ES_SELECTOR, es().bits()); 79 | vmwrite(vmcs::guest::CS_SELECTOR, cs().bits()); 80 | vmwrite(vmcs::guest::SS_SELECTOR, ss().bits()); 81 | vmwrite(vmcs::guest::DS_SELECTOR, ds().bits()); 82 | vmwrite(vmcs::guest::FS_SELECTOR, fs().bits()); 83 | vmwrite(vmcs::guest::GS_SELECTOR, gs().bits()); 84 | vmwrite(vmcs::guest::TR_SELECTOR, self.descriptors.tr.bits()); 85 | vmwrite(vmcs::guest::LDTR_SELECTOR, 0u16); 86 | 87 | vmwrite( 88 | vmcs::guest::ES_ACCESS_RIGHTS, 89 | Self::access_rights_from_native(lar(es())), 90 | ); 91 | vmwrite( 92 | vmcs::guest::CS_ACCESS_RIGHTS, 93 | Self::access_rights_from_native(lar(cs())), 94 | ); 95 | vmwrite( 96 | vmcs::guest::SS_ACCESS_RIGHTS, 97 | Self::access_rights_from_native(lar(ss())), 98 | ); 99 | vmwrite( 100 | vmcs::guest::DS_ACCESS_RIGHTS, 101 | Self::access_rights_from_native(lar(ds())), 102 | ); 103 | vmwrite( 104 | vmcs::guest::FS_ACCESS_RIGHTS, 105 | Self::access_rights_from_native(lar(fs())), 106 | ); 107 | vmwrite( 108 | vmcs::guest::GS_ACCESS_RIGHTS, 109 | Self::access_rights_from_native(lar(gs())), 110 | ); 111 | vmwrite( 112 | vmcs::guest::TR_ACCESS_RIGHTS, 113 | Self::access_rights_from_native(self.descriptors.tss.ar), 114 | ); 115 | vmwrite( 116 | vmcs::guest::LDTR_ACCESS_RIGHTS, 117 | Self::access_rights_from_native(0u32), 118 | ); 119 | 120 | vmwrite(vmcs::guest::ES_LIMIT, lsl(es())); 121 | vmwrite(vmcs::guest::CS_LIMIT, lsl(cs())); 122 | vmwrite(vmcs::guest::SS_LIMIT, lsl(ss())); 123 | vmwrite(vmcs::guest::DS_LIMIT, lsl(ds())); 124 | vmwrite(vmcs::guest::FS_LIMIT, lsl(fs())); 125 | vmwrite(vmcs::guest::GS_LIMIT, lsl(gs())); 126 | vmwrite(vmcs::guest::TR_LIMIT, self.descriptors.tss.limit); 127 | vmwrite(vmcs::guest::LDTR_LIMIT, 0u32); 128 | 129 | // All segment base registers are assumed to be zero, except that of TR. 130 | vmwrite(vmcs::guest::TR_BASE, self.descriptors.tss.base); 131 | 132 | vmwrite(vmcs::guest::GDTR_BASE, self.descriptors.gdtr.base as u64); 133 | vmwrite(vmcs::guest::GDTR_LIMIT, self.descriptors.gdtr.limit); 134 | vmwrite(vmcs::guest::IDTR_BASE, idtr.base as u64); 135 | vmwrite(vmcs::guest::IDTR_LIMIT, idtr.limit); 136 | 137 | vmwrite(vmcs::guest::IA32_EFER_FULL, rdmsr(x86::msr::IA32_EFER)); 138 | vmwrite(vmcs::guest::CR0, cr0().bits() as u64); 139 | vmwrite(vmcs::guest::CR3, cr3()); 140 | vmwrite(vmcs::guest::CR4, cr4().bits() as u64); 141 | 142 | vmwrite(vmcs::guest::LINK_PTR_FULL, u64::MAX); 143 | 144 | // Initialize the host part. 145 | vmwrite(vmcs::host::CS_SELECTOR, self.host_descriptors.cs.bits()); 146 | vmwrite(vmcs::host::TR_SELECTOR, self.host_descriptors.tr.bits()); 147 | vmwrite(vmcs::host::CR0, cr0().bits() as u64); 148 | vmwrite( 149 | vmcs::host::CR3, 150 | self.host_paging_structures.as_ref() as *const _ as u64, 151 | ); 152 | vmwrite(vmcs::host::CR4, cr4().bits() as u64); 153 | vmwrite(vmcs::host::TR_BASE, self.host_descriptors.tss.base); 154 | vmwrite( 155 | vmcs::host::GDTR_BASE, 156 | self.host_descriptors.gdtr.base as u64, 157 | ); 158 | vmwrite(vmcs::host::IDTR_BASE, u64::MAX); // Bogus. No proper exception handling. 159 | vmwrite( 160 | vmcs::control::VMEXIT_CONTROLS, 161 | Self::adjust_vmx_control( 162 | VmxControl::VmExit, 163 | IA32_VMX_EXIT_CTLS_HOST_ADDRESS_SPACE_SIZE_FLAG, 164 | ), 165 | ); 166 | vmwrite( 167 | vmcs::control::VMENTRY_CONTROLS, 168 | Self::adjust_vmx_control( 169 | VmxControl::VmEntry, 170 | IA32_VMX_ENTRY_CTLS_IA32E_MODE_GUEST_FLAG, 171 | ), 172 | ); 173 | vmwrite( 174 | vmcs::control::PINBASED_EXEC_CONTROLS, 175 | Self::adjust_vmx_control(VmxControl::PinBased, 0), 176 | ); 177 | vmwrite( 178 | vmcs::control::PRIMARY_PROCBASED_EXEC_CONTROLS, 179 | Self::adjust_vmx_control( 180 | VmxControl::ProcessorBased, 181 | IA32_VMX_PROCBASED_CTLS_USE_MSR_BITMAPS_FLAG 182 | | IA32_VMX_PROCBASED_CTLS_ACTIVATE_SECONDARY_CONTROLS_FLAG, 183 | ), 184 | ); 185 | vmwrite( 186 | vmcs::control::SECONDARY_PROCBASED_EXEC_CONTROLS, 187 | Self::adjust_vmx_control( 188 | VmxControl::ProcessorBased2, 189 | IA32_VMX_PROCBASED_CTLS2_ENABLE_EPT_FLAG 190 | | IA32_VMX_PROCBASED_CTLS2_ENABLE_RDTSCP_FLAG 191 | | IA32_VMX_PROCBASED_CTLS2_ENABLE_INVPCID_FLAG 192 | | IA32_VMX_PROCBASED_CTLS2_ENABLE_XSAVES_FLAG, 193 | ), 194 | ); 195 | vmwrite( 196 | vmcs::control::MSR_BITMAPS_ADDR_FULL, 197 | self.msr_bitmaps.as_ref() as *const _ as u64, 198 | ); 199 | vmwrite( 200 | vmcs::control::EPTP_FULL, 201 | Self::eptp_from_nested_cr3(self.epts.as_ref() as *const _ as u64), 202 | ); 203 | 204 | if cfg!(feature = "enable_vt_rp") { 205 | vmwrite( 206 | vmcs::control::PRIMARY_PROCBASED_EXEC_CONTROLS, 207 | Self::adjust_vmx_control( 208 | VmxControl::ProcessorBased, 209 | IA32_VMX_PROCBASED_CTLS_ACTIVATE_TERTIARY_CONTROLS_FLAG 210 | | vmread(vmcs::control::PRIMARY_PROCBASED_EXEC_CONTROLS), 211 | ), 212 | ); 213 | vmwrite( 214 | VMCS_CTRL_TERTIARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, 215 | Self::adjust_vmx_control( 216 | VmxControl::ProcessorBased3, 217 | IA32_VMX_PROCBASED_CTLS3_ENABLE_HLAT_FLAG 218 | | IA32_VMX_PROCBASED_CTLS3_EPT_PAGING_WRITE_CONTROL_FLAG 219 | | IA32_VMX_PROCBASED_CTLS3_GUEST_PAGING_VERIFICATION_FLAG, 220 | ), 221 | ); 222 | vmwrite( 223 | VMCS_CTRL_HLAT_POINTER, 224 | self.hlat.as_ref() as *const _ as u64, 225 | ); 226 | self.hlat.deactivate(); 227 | } 228 | } 229 | 230 | pub(crate) fn run(&mut self) -> VmExitReason { 231 | const VMX_EXIT_REASON_CPUID: u16 = 10; 232 | const VMX_EXIT_REASON_VMCALL: u16 = 18; 233 | const VMX_EXIT_REASON_RDMSR: u16 = 31; 234 | const VMX_EXIT_REASON_WRMSR: u16 = 32; 235 | const VMX_EXIT_REASON_XSETBV: u16 = 55; 236 | 237 | vmwrite(vmcs::guest::RIP, self.regs.rip); 238 | vmwrite(vmcs::guest::RSP, self.regs.rsp); 239 | vmwrite(vmcs::guest::RFLAGS, self.regs.rflags); 240 | 241 | // Execute the VM until VM-exit occurs. 242 | trace!("Entering the VM"); 243 | let flags = unsafe { run_vmx_vm(&mut self.regs) }; 244 | trace!("Exited the VM"); 245 | if let Err(err) = vm_succeed(RFlags::from_raw(flags)) { 246 | panic!("{err}"); 247 | } 248 | self.regs.rip = vmread(vmcs::guest::RIP); 249 | self.regs.rsp = vmread(vmcs::guest::RSP); 250 | self.regs.rflags = vmread(vmcs::guest::RFLAGS); 251 | 252 | // Return VM-exit reason. 253 | match vmread(vmcs::ro::EXIT_REASON) as u16 { 254 | VMX_EXIT_REASON_CPUID => VmExitReason::Cpuid, 255 | VMX_EXIT_REASON_VMCALL => VmExitReason::Vmcall, 256 | VMX_EXIT_REASON_RDMSR => VmExitReason::Rdmsr, 257 | VMX_EXIT_REASON_WRMSR => VmExitReason::Wrmsr, 258 | VMX_EXIT_REASON_XSETBV => VmExitReason::XSetBv, 259 | _ => panic!( 260 | "Unhandled VM-exit reason: {:?}", 261 | vmread(vmcs::ro::EXIT_REASON) 262 | ), 263 | } 264 | } 265 | 266 | /// Returns an adjust value for the control field according to the 267 | /// capability MSR. 268 | fn adjust_vmx_control(control: VmxControl, requested_value: u64) -> u64 { 269 | const IA32_VMX_BASIC_VMX_CONTROLS_FLAG: u64 = 1 << 55; 270 | 271 | // This determines the right VMX capability MSR based on the value of 272 | // IA32_VMX_BASIC. This is required to fullfil the following requirements: 273 | // 274 | // "It is necessary for software to consult only one of the capability MSRs 275 | // to determine the allowed settings of the pin based VM-execution controls:" 276 | // See: A.3.1 Pin-Based VM-Execution Controls 277 | let vmx_basic = rdmsr(x86::msr::IA32_VMX_BASIC); 278 | let true_cap_msr_supported = (vmx_basic & IA32_VMX_BASIC_VMX_CONTROLS_FLAG) != 0; 279 | 280 | let cap_msr = match (control, true_cap_msr_supported) { 281 | (VmxControl::PinBased, true) => x86::msr::IA32_VMX_TRUE_PINBASED_CTLS, 282 | (VmxControl::PinBased, false) => x86::msr::IA32_VMX_PINBASED_CTLS, 283 | (VmxControl::ProcessorBased, true) => x86::msr::IA32_VMX_TRUE_PROCBASED_CTLS, 284 | (VmxControl::ProcessorBased, false) => x86::msr::IA32_VMX_PROCBASED_CTLS, 285 | (VmxControl::VmExit, true) => x86::msr::IA32_VMX_TRUE_EXIT_CTLS, 286 | (VmxControl::VmExit, false) => x86::msr::IA32_VMX_EXIT_CTLS, 287 | (VmxControl::VmEntry, true) => x86::msr::IA32_VMX_TRUE_ENTRY_CTLS, 288 | (VmxControl::VmEntry, false) => x86::msr::IA32_VMX_ENTRY_CTLS, 289 | // There is no TRUE MSR for IA32_VMX_PROCBASED_CTLS2. Just use 290 | // IA32_VMX_PROCBASED_CTLS2 unconditionally. 291 | (VmxControl::ProcessorBased2, _) => x86::msr::IA32_VMX_PROCBASED_CTLS2, 292 | (VmxControl::ProcessorBased3, _) => { 293 | const IA32_VMX_PROCBASED_CTLS3: u32 = 0x492; 294 | 295 | let allowed1 = rdmsr(IA32_VMX_PROCBASED_CTLS3); 296 | let effective_value = requested_value & allowed1; 297 | assert!( 298 | effective_value | requested_value == effective_value, 299 | "One or more requested features are not supported: {effective_value:#x?} : {requested_value:#x?} " 300 | ); 301 | return effective_value; 302 | } 303 | }; 304 | 305 | // Each bit of the following VMCS values might have to be set or cleared 306 | // according to the value indicated by the VMX capability MSRs. 307 | // - pin-based VM-execution controls, 308 | // - primary processor-based VM-execution controls, 309 | // - secondary processor-based VM-execution controls. 310 | // 311 | // The VMX capability MSR is composed of two 32bit values, the lower 32bits 312 | // indicate bits can be 0, and the higher 32bits indicates bits can be 1. 313 | // In other words, if those bits are "cleared", corresponding bits MUST BE 1 314 | // and MUST BE 0 respectively. The below summarizes the interpretation: 315 | // 316 | // Lower bits (allowed 0) Higher bits (allowed 1) Meaning 317 | // Bit X 1 1 The bit X is flexible 318 | // Bit X 1 0 The bit X is fixed to 0 319 | // Bit X 0 1 The bit X is fixed to 1 320 | // 321 | // The following code enforces this logic by setting bits that must be 1, 322 | // and clearing bits that must be 0. 323 | // 324 | // See: A.3.1 Pin-Based VM-Execution Controls 325 | // See: A.3.2 Primary Processor-Based VM-Execution Controls 326 | // See: A.3.3 Secondary Processor-Based VM-Execution Controls 327 | let capabilities = rdmsr(cap_msr); 328 | let allowed0 = capabilities as u32; 329 | let allowed1 = (capabilities >> 32) as u32; 330 | let requested_value = u32::try_from(requested_value).unwrap(); 331 | let mut effective_value = requested_value; 332 | effective_value |= allowed0; 333 | effective_value &= allowed1; 334 | assert!( 335 | effective_value | requested_value == effective_value, 336 | "One or more requested features are not supported for {control:?}: {effective_value:#x?} vs {requested_value:#x?}" 337 | ); 338 | u64::from(effective_value) 339 | } 340 | 341 | fn access_rights_from_native(access_rights: u32) -> u32 { 342 | const VMX_SEGMENT_ACCESS_RIGHTS_UNUSABLE_FLAG: u32 = 1 << 16; 343 | 344 | if access_rights == 0 { 345 | return VMX_SEGMENT_ACCESS_RIGHTS_UNUSABLE_FLAG; 346 | } 347 | 348 | (access_rights >> 8) & 0b1111_0000_1111_1111 349 | } 350 | 351 | fn eptp_from_nested_cr3(value: u64) -> u64 { 352 | const EPT_POINTER_MEMORY_TYPE_WRITE_BACK: u64 = 6 /* << 0 */; 353 | const EPT_POINTER_PAGE_WALK_LENGTH_4: u64 = 3 << 3; 354 | 355 | assert!(value.trailing_zeros() >= 12); 356 | value | EPT_POINTER_PAGE_WALK_LENGTH_4 | EPT_POINTER_MEMORY_TYPE_WRITE_BACK 357 | } 358 | } 359 | 360 | unsafe fn box_zeroed() -> Box { 361 | let layout = Layout::new::(); 362 | let ptr = unsafe { alloc::alloc::alloc_zeroed(layout) }.cast::(); 363 | if ptr.is_null() { 364 | handle_alloc_error(layout); 365 | } 366 | unsafe { Box::from_raw(ptr) } 367 | } 368 | 369 | #[derive(Clone, Copy, Debug)] 370 | enum VmxControl { 371 | PinBased, 372 | ProcessorBased, 373 | ProcessorBased2, 374 | ProcessorBased3, 375 | VmExit, 376 | VmEntry, 377 | } 378 | 379 | extern "efiapi" { 380 | /// Runs the VM until VM-exit occurs. 381 | fn run_vmx_vm(registers: &mut GuestRegisters) -> u64; 382 | } 383 | global_asm!(include_str!("run_vmx_vm.S")); 384 | 385 | /// The region of memory that the logical processor uses to represent a virtual 386 | /// CPU. Called virtual-machine control data structure (VMCS). 387 | /// 388 | /// See: 25.2 FORMAT OF THE VMCS REGION 389 | #[derive(derivative::Derivative)] 390 | #[derivative(Default, Debug)] 391 | #[repr(C, align(4096))] 392 | struct Vmcs { 393 | revision_id: u32, 394 | abort_indicator: u32, 395 | #[derivative(Default(value = "[0; 4088]"), Debug = "ignore")] 396 | data: [u8; 4088], 397 | } 398 | const _: () = assert!(core::mem::size_of::() == BASE_PAGE_SIZE); 399 | 400 | /// The wrapper of the VMCLEAR instruction. 401 | fn vmclear(vmcs_region: &mut Vmcs) { 402 | // Safety: this project runs at CPL0. 403 | unsafe { x86::bits64::vmx::vmclear(vmcs_region as *mut _ as u64).unwrap() }; 404 | } 405 | 406 | /// The wrapper of the VMPTRLD instruction. 407 | fn vmptrld(vmcs_region: &mut Vmcs) { 408 | // Safety: this project runs at CPL0. 409 | unsafe { x86::bits64::vmx::vmptrld(vmcs_region as *mut _ as u64).unwrap() } 410 | } 411 | 412 | /// The wrapper of the VMREAD instruction. Returns zero on error. 413 | pub(crate) fn vmread(encoding: u32) -> u64 { 414 | // Safety: this project runs at CPL0. 415 | unsafe { x86::bits64::vmx::vmread(encoding).unwrap() } 416 | } 417 | 418 | /// The wrapper of the VMWRITE instruction. 419 | pub(crate) fn vmwrite>(encoding: u32, value: T) 420 | where 421 | u64: From, 422 | { 423 | let val = u64::from(value); 424 | // Safety: this project runs at CPL0. 425 | unsafe { x86::bits64::vmx::vmwrite(encoding, val) } 426 | .unwrap_or_else(|_| panic!("Could not write {val:x?} to {encoding:x?}")); 427 | } 428 | 429 | /// Checks that the latest VMX instruction succeeded. 430 | /// 431 | /// See: 31.2 CONVENTIONS 432 | fn vm_succeed(flags: RFlags) -> Result<(), String> { 433 | if flags.contains(RFlags::FLAGS_ZF) { 434 | // See: 31.4 VM INSTRUCTION ERROR NUMBERS 435 | Err(format!( 436 | "VmFailValid with {}", 437 | vmread(vmcs::ro::VM_INSTRUCTION_ERROR) 438 | )) 439 | } else if flags.contains(RFlags::FLAGS_CF) { 440 | Err("VmFailInvalid".to_string()) 441 | } else { 442 | Ok(()) 443 | } 444 | } 445 | 446 | // See: Table 25-6. Definitions of Primary Processor-Based VM-Execution Controls 447 | const IA32_VMX_PROCBASED_CTLS_ACTIVATE_TERTIARY_CONTROLS_FLAG: u64 = 1 << 17; 448 | const IA32_VMX_PROCBASED_CTLS_USE_MSR_BITMAPS_FLAG: u64 = 1 << 28; 449 | const IA32_VMX_PROCBASED_CTLS_ACTIVATE_SECONDARY_CONTROLS_FLAG: u64 = 1 << 31; 450 | 451 | // See: Table 25-7. Definitions of Secondary Processor-Based VM-Execution 452 | // Controls 453 | const IA32_VMX_PROCBASED_CTLS2_ENABLE_EPT_FLAG: u64 = 1 << 1; 454 | const IA32_VMX_PROCBASED_CTLS2_ENABLE_RDTSCP_FLAG: u64 = 1 << 3; 455 | const IA32_VMX_PROCBASED_CTLS2_ENABLE_INVPCID_FLAG: u64 = 1 << 12; 456 | const IA32_VMX_PROCBASED_CTLS2_ENABLE_XSAVES_FLAG: u64 = 1 << 20; 457 | 458 | // See: Table 25-8. Definitions of Tertiary Processor-Based VM-Execution 459 | // Controls 460 | const IA32_VMX_PROCBASED_CTLS3_ENABLE_HLAT_FLAG: u64 = 1 << 1; 461 | const IA32_VMX_PROCBASED_CTLS3_EPT_PAGING_WRITE_CONTROL_FLAG: u64 = 1 << 2; 462 | const IA32_VMX_PROCBASED_CTLS3_GUEST_PAGING_VERIFICATION_FLAG: u64 = 1 << 3; 463 | 464 | // See: Table 25-13. Definitions of Primary VM-Exit Controls 465 | const IA32_VMX_EXIT_CTLS_HOST_ADDRESS_SPACE_SIZE_FLAG: u64 = 1 << 9; 466 | 467 | // See: Table 25-15. Definitions of VM-Entry Controls 468 | const IA32_VMX_ENTRY_CTLS_IA32E_MODE_GUEST_FLAG: u64 = 1 << 9; 469 | 470 | // See: APPENDIX B FIELD ENCODING IN VMCS 471 | const VMCS_CTRL_TERTIARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS: u32 = 0x2034; 472 | const VMCS_CTRL_HLAT_POINTER: u32 = 0x2040; 473 | --------------------------------------------------------------------------------