Compare commits

..

16 Commits

Author SHA1 Message Date
mii443
cb006a8df8 fix VMCB reserved fields
Some checks failed
Check / Build ISO (nightly-2025-04-27) (push) Failing after 1m19s
2025-08-31 00:07:06 +09:00
mii443
ce079a1067 VMCB 0D0h 2025-08-29 08:26:06 +09:00
mii443
0bfb86a2b4 wip
Some checks failed
Check / Build ISO (nightly-2025-04-27) (push) Failing after 44s
2025-08-28 19:22:30 +09:00
mii443
baeed3320f VMCB 014h ~ 068h
Some checks failed
Check / Build ISO (nightly-2025-04-27) (push) Failing after 41s
2025-08-28 16:29:47 +09:00
mii443
525b6cbf0d AMD VMCB 000h ~ 014h
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 41s
2025-08-28 15:53:30 +09:00
mii443
33c511dcb0 add VMCB
Some checks failed
Check / Build ISO (nightly-2025-04-27) (push) Failing after 1m29s
2025-08-28 13:57:21 +09:00
Masato Imai
53a31749b4 serial wip
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 1m10s
2025-08-26 14:55:38 +00:00
Masato Imai
a355e91c6e 0x3F9 #2
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 1m14s
2025-08-25 12:02:53 +00:00
Masato Imai
294e4f719d fix QualIO #2
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 41s
2025-08-24 14:07:52 +00:00
Masato Imai
1606184551 Serial I/O buffer #2
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 42s
2025-08-24 12:24:53 +00:00
Masato Imai
a0188056f0 Serial I/O virtualization #2 2025-08-24 12:14:02 +00:00
Masato Imai
db650f470f clippy
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 1m18s
2025-08-24 05:23:18 +00:00
Masato Imai
5df8c77ea7 fix XCR0 emulation
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 46s
Release ISO / release (push) Successful in 57s
2025-08-23 07:15:22 +00:00
Masato Imai
2d0db85574 add XSETBV, CLAC, STAC, initrd
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 43s
2025-08-22 14:32:21 +00:00
Masato Imai
df56e251e4 add external interrupt
All checks were successful
Check / Build ISO (nightly-2025-04-27) (push) Successful in 46s
2025-08-22 13:32:40 +00:00
Masato Imai
b215f0010f support low ram machine
Some checks failed
Check / Build ISO (nightly-2025-04-27) (push) Failing after 43s
2025-08-22 12:59:05 +00:00
29 changed files with 969 additions and 230 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ nel_os_bootloader/fat.img
nel_os_bootloader/myOSimage.img
nel_os_bootloader/iso/
nel_os_bootloader/nel_os.iso
nel_os_bootloader/vmlinux

View File

@@ -1,9 +1,16 @@
#!/bin/sh -ex
#!/bin/bash -ex
EFI_BINARY="$1"
cd ../nel_os_kernel
cargo build --release -q
if [[ "$EFI_BINARY" == "target/x86_64-unknown-uefi/release/"* ]]; then
cargo build --release -q
elif [[ "$EFI_BINARY" == "target/x86_64-unknown-uefi/debug/"* ]]; then
cargo build -q
else
echo "Error: EFI binary path must contain either '/target/x86_64-unknown-uefi/release/' or '/target/x86_64-unknown-uefi/debug/'"
exit 1
fi
cd ../nel_os_bootloader
dd if=/dev/zero of=fat.img bs=1k count=32768

View File

@@ -1,56 +0,0 @@
CPU Reset (CPU 0)
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00000000
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
EIP=00000000 EFL=00000000 [-------] CPL=0 II=0 A20=0 SMM=0 HLT=0
ES =0000 00000000 00000000 00000000
CS =0000 00000000 00000000 00000000
SS =0000 00000000 00000000 00000000
DS =0000 00000000 00000000 00000000
FS =0000 00000000 00000000 00000000
GS =0000 00000000 00000000 00000000
LDT=0000 00000000 00000000 00000000
TR =0000 00000000 00000000 00000000
GDT= 00000000 00000000
IDT= 00000000 00000000
CR0=00000000 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=0000000000000000 DR7=0000000000000000
CCS=00000000 CCD=00000000 CCO=DYNAMIC
EFER=0000000000000000
FCW=0000 FSW=0000 [ST=0] FTW=ff MXCSR=00000000
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000
CPU Reset (CPU 0)
EAX=00000000 EBX=00000000 ECX=00000000 EDX=00060fb1
ESI=00000000 EDI=00000000 EBP=00000000 ESP=00000000
EIP=0000fff0 EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 0000ffff 00009300
CS =f000 ffff0000 0000ffff 00009b00
SS =0000 00000000 0000ffff 00009300
DS =0000 00000000 0000ffff 00009300
FS =0000 00000000 0000ffff 00009300
GS =0000 00000000 0000ffff 00009300
LDT=0000 00000000 0000ffff 00008200
TR =0000 00000000 0000ffff 00008b00
GDT= 00000000 0000ffff
IDT= 00000000 0000ffff
CR0=60000010 CR2=00000000 CR3=00000000 CR4=00000000
DR0=0000000000000000 DR1=0000000000000000 DR2=0000000000000000 DR3=0000000000000000
DR6=00000000ffff0ff0 DR7=0000000000000400
CCS=00000000 CCD=00000000 CCO=DYNAMIC
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=0000000000000000 0000000000000000 XMM01=0000000000000000 0000000000000000
XMM02=0000000000000000 0000000000000000 XMM03=0000000000000000 0000000000000000
XMM04=0000000000000000 0000000000000000 XMM05=0000000000000000 0000000000000000
XMM06=0000000000000000 0000000000000000 XMM07=0000000000000000 0000000000000000

View File

@@ -6,10 +6,9 @@ EFI_BINARY="$1"
./create-iso.sh "$EFI_BINARY"
qemu-system-x86_64 -enable-kvm \
-m 4G \
-m 512M \
-serial mon:stdio \
-nographic \
-no-reboot \
-drive if=pflash,format=raw,readonly=on,file=OVMF_CODE.fd \
-drive if=pflash,format=raw,readonly=on,file=OVMF_VARS.fd \
-cdrom nel_os.iso \

View File

@@ -13,7 +13,4 @@ pub const HEAP_START: usize = 0x4444_4444_0000;
pub const HEAP_SIZE: usize = 128 * 1024;
pub const PAGE_SIZE: usize = 4096;
pub const MAX_MEMORY: usize = 256 * 1024 * 1024 * 1024;
pub const FRAME_COUNT: usize = MAX_MEMORY / PAGE_SIZE;
pub const BITS_PER_ENTRY: usize = 8 * core::mem::size_of::<usize>();
pub const ENTRY_COUNT: usize = FRAME_COUNT / BITS_PER_ENTRY;

View File

@@ -60,9 +60,7 @@ pub fn unsubscribe(callback: SubscriberCallback) -> Result<(), &'static str> {
pub fn dispatch_to_subscribers(context: &InterruptContext) {
let subscribers = SUBSCRIBERS.lock();
for subscriber in subscribers.iter() {
if let Some(subscriber) = subscriber {
(subscriber.callback)(subscriber.context, context);
}
for subscriber in subscribers.iter().flatten() {
(subscriber.callback)(subscriber.context, context);
}
}

View File

@@ -1,14 +1,14 @@
#[macro_export]
macro_rules! info {
($($arg:tt)*) => ($crate::print!("[{:>12.5} I] {}\n", crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
($($arg:tt)*) => ($crate::print!("[{:>12.5} I] {}\n", $crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => ($crate::print!("[{:>12.5} E] {}\n", crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
($($arg:tt)*) => ($crate::print!("[{:>12.5} E] {}\n", $crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => ($crate::print!("[{:>12.5} W] {}\n", crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
($($arg:tt)*) => ($crate::print!("[{:>12.5} W] {}\n", $crate::time::get_ticks() as f64 / 1000., format_args!($($arg)*)));
}

View File

@@ -30,7 +30,7 @@ use crate::{
constant::{KERNEL_STACK_SIZE, PKG_VERSION},
graphics::{FrameBuffer, FRAME_BUFFER},
interrupt::apic,
memory::{allocator, memory::BitmapMemoryTable, paging},
memory::{allocator, bitmap::BitmapMemoryTable, paging},
};
pub static BZIMAGE_ADDR: Once<u64> = Once::new();
@@ -98,10 +98,13 @@ pub extern "sysv64" fn main(boot_info: &nel_os_common::BootInfo) {
let ranges = boot_info.usable_memory.ranges();
let mut count = 0;
let mut max_range = 0;
for range in ranges {
count += range.end - range.start;
max_range = max_range.max(range.end);
}
info!("Usable memory: {}MiB", count / 1024 / 1024);
memory::bitmap::MAX_MEMORY.call_once(|| max_range as usize * 2);
let mut bitmap_table = BitmapMemoryTable::init(&boot_info.usable_memory);
info!(
@@ -130,7 +133,7 @@ pub extern "sysv64" fn main(boot_info: &nel_os_common::BootInfo) {
if boot_info.frame_buffer.is_some() {
let frame_buffer =
FrameBuffer::from_raw_buffer(&boot_info.frame_buffer.as_ref().unwrap(), (64, 64, 64));
FrameBuffer::from_raw_buffer(boot_info.frame_buffer.as_ref().unwrap(), (64, 64, 64));
frame_buffer.clear();
FRAME_BUFFER.lock().replace(frame_buffer);

View File

@@ -1,12 +1,19 @@
use core::slice;
use nel_os_common::memory::{self, UsableMemory};
use spin::Once;
use x86_64::{
structures::paging::{FrameAllocator, PhysFrame, Size4KiB},
PhysAddr,
};
use crate::constant::{BITS_PER_ENTRY, ENTRY_COUNT, PAGE_SIZE};
use crate::constant::{BITS_PER_ENTRY, PAGE_SIZE};
pub static MAX_MEMORY: Once<usize> = Once::new();
pub fn get_entry_count() -> usize {
MAX_MEMORY.get().unwrap_or(&0) / PAGE_SIZE / BITS_PER_ENTRY
}
pub struct BitmapMemoryTable {
pub used_map: &'static mut [usize],
@@ -21,16 +28,17 @@ impl BitmapMemoryTable {
max_addr = max_addr.max(range.end);
}
let bitmap_size = ENTRY_COUNT * core::mem::size_of::<usize>();
let entry_count = get_entry_count();
let bitmap_size = entry_count * core::mem::size_of::<usize>();
let bitmap_addr = ((max_addr as usize).saturating_sub(bitmap_size)) & !(PAGE_SIZE - 1);
let used_map = unsafe {
let ptr = bitmap_addr as *mut usize;
slice::from_raw_parts_mut(ptr, ENTRY_COUNT)
slice::from_raw_parts_mut(ptr, entry_count)
};
(0..ENTRY_COUNT).for_each(|i| {
(0..entry_count).for_each(|i| {
used_map[i] = 0;
});
@@ -50,8 +58,8 @@ impl BitmapMemoryTable {
table.set_frame(bitmap_start_frame + i, false);
}
for i in 0..ENTRY_COUNT {
let index = ENTRY_COUNT - i - 1;
for i in 0..entry_count {
let index = entry_count - i - 1;
if table.used_map[index] != 0 {
let offset = 63 - table.used_map[index].leading_zeros();
table.end = index * BITS_PER_ENTRY + (BITS_PER_ENTRY - offset as usize);

View File

@@ -1,3 +1,3 @@
pub mod allocator;
pub mod memory;
pub mod bitmap;
pub mod paging;

View File

@@ -47,6 +47,27 @@ pub fn _print(args: ::core::fmt::Arguments) {
});
}
#[inline(always)]
pub fn write_byte(byte: u8) {
use x86_64::instructions::interrupts;
interrupts::without_interrupts(|| {
SERIAL1.lock().send(byte);
});
}
#[inline(always)]
pub fn write_bytes(bytes: &[u8]) {
use x86_64::instructions::interrupts;
interrupts::without_interrupts(|| {
let mut serial = SERIAL1.lock();
for &b in bytes {
serial.send(b);
}
});
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {

View File

@@ -1 +1,2 @@
pub mod vcpu;
pub mod vmcb;

View File

@@ -3,18 +3,32 @@ use x86_64::structures::paging::{FrameAllocator, Size4KiB};
use crate::{
error, info,
vmm::{x86_64::common, VCpu},
vmm::{
x86_64::{amd::vmcb::Vmcb, common},
VCpu,
},
};
pub struct AMDVCpu;
pub struct AMDVCpu {
vmcb: Vmcb,
}
impl AMDVCpu {
pub fn setup(&mut self) -> Result<(), &'static str> {
info!("Setting up AMD VCPU");
let raw_vmcb = self.vmcb.get_raw_vmcb();
raw_vmcb.set_intercept_hlt(true);
Ok(())
}
}
impl VCpu for AMDVCpu {
fn run(
&mut self,
_frame_allocator: &mut dyn FrameAllocator<Size4KiB>,
) -> Result<(), &'static str> {
info!("VCpu on AMD");
Ok(())
}
@@ -39,7 +53,7 @@ impl VCpu for AMDVCpu {
unimplemented!("AMDVCpu::get_guest_memory_size is not implemented yet")
}
fn new(_frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str>
fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str>
where
Self: Sized,
{
@@ -47,7 +61,9 @@ impl VCpu for AMDVCpu {
efer |= 1 << 12;
common::write_msr(0xc000_0080, efer);
Ok(AMDVCpu)
Ok(AMDVCpu {
vmcb: Vmcb::new(frame_allocator)?,
})
}
fn is_supported() -> bool

View File

@@ -0,0 +1,241 @@
#![allow(non_snake_case)]
use modular_bitfield::{bitfield, prelude::*};
use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};
pub struct Vmcb {
pub frame: PhysFrame,
}
impl Vmcb {
pub fn new(frame_allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str> {
let frame = frame_allocator
.allocate_frame()
.ok_or("Failed to allocate VMCB frame")?;
Ok(Vmcb { frame })
}
pub fn get_raw_vmcb(&self) -> &mut RawVmcb {
let ptr = self.frame.start_address().as_u64() as *mut RawVmcb;
unsafe { &mut *ptr }
}
}
#[bitfield]
pub struct RawVmcb {
// 000h
pub intercept_cr_read: B16,
pub intercept_cr_write: B16,
// 004h
pub intercept_dr_read: B16,
pub intercept_dr_write: B16,
// 008h
pub intercept_exceptions: B32,
// 00Ch
pub intercept_intr: bool,
pub intercept_nmi: bool,
pub intercept_smi: bool,
pub intercept_init: bool,
pub intercept_vintr: bool,
pub intercept_cr0_write_ts_or_mp: bool,
pub intercept_read_idtr: bool,
pub intercept_read_gdtr: bool,
pub intercept_read_ldtr: bool,
pub intercept_read_tr: bool,
pub intercept_write_idtr: bool,
pub intercept_write_gdtr: bool,
pub intercept_write_ldtr: bool,
pub intercept_write_tr: bool,
pub intercept_rdtsc: bool,
pub intercept_rdpmc: bool,
pub intercept_pushf: bool,
pub intercept_popf: bool,
pub intercept_cpuid: bool,
pub intercept_rsm: bool,
pub intercept_iret: bool,
pub intercept_int_n: bool,
pub intercept_invd: bool,
pub intercept_pause: bool,
pub intercept_hlt: bool,
pub intercept_invlpg: bool,
pub intercept_invlpga: bool,
pub intercept_ioio_prot: bool,
pub intercept_msr_prot: bool,
pub intercept_task_switch: bool,
pub intercept_ferr_freeze: bool,
pub intercept_shutdown: bool,
// 010h
pub intercept_vmrun: bool,
pub intercept_vmcall: bool,
pub intercept_vmload: bool,
pub intercept_vmsave: bool,
pub intercept_stgi: bool,
pub intercept_clgi: bool,
pub intercept_skinit: bool,
pub intercept_rdtscp: bool,
pub intercept_icebp: bool,
pub intercept_wbinvd_and_wbnoinvd: bool,
pub intercept_monitor_and_monitorx: bool,
pub intercept_mwait_and_mwaitx_unconditionally: bool,
pub intercept_mwait_and_mwaitx: bool,
pub intercept_xsetbv: bool,
pub intercept_rdpru: bool,
pub intercept_write_efer_after_guest_inst_finish: bool,
pub intercept_write_cr0_after_guest_inst_finish: B16,
// 014h
pub intercept_all_invlpgb: bool,
pub intercept_illegally_specified_invlpgb: bool,
pub intercept_invpcid: bool,
pub intercept_mcommit: bool,
pub intercept_tlbsync: bool,
pub intercept_bus_lock: bool,
pub intercept_idle_hlt: bool,
#[skip]
__: B25,
// 018h-03Bh
#[skip]
__: B128,
#[skip]
__: B128,
#[skip]
__: B32,
// 03Ch
pub pause_filter_threshold: B16,
// 03Eh
pub pause_filter_count: B16,
// 040h
pub iopm_base_physical_address: B64,
// 048h
pub msrpm_base_physical_address: B64,
// 050h
pub tsc_offset: B64,
// 058h
pub guest_asid: B32,
pub tlb_control: TlbControl,
pub allow_larger_rap: bool,
pub clear_rap_on_vmrun: bool,
#[skip]
__: B22,
// 060h
pub v_tpr: B8,
pub v_irq: bool,
pub vgif: bool,
pub v_nmi: bool,
pub v_nmi_mask: bool,
#[skip]
__: B3,
pub v_intr_prio: B4,
pub v_ign_tpr: bool,
#[skip]
__: B3,
pub v_intr_masking: bool,
pub amd_virtual_gif: bool,
pub v_nmi_enable: bool,
#[skip]
__: B3,
pub x2avic_enable: bool,
pub avic_enable: bool,
pub v_intr_vector: B8,
#[skip]
__: B24,
// 068h
pub interrupt_shadow: bool,
pub guest_interrupt_mask: bool,
#[skip]
__: B62,
// 070h
pub exit_code: B64,
// 078h
pub exit_info1: B64,
// 080h
pub exit_info2: B64,
// 088h
pub exit_int_info: B64,
// 090h
pub np_enable: bool,
pub enable_sev: bool,
pub enable_encrypted_state: bool,
pub guest_mode_execution_trap: bool,
pub sss_check_enable: bool,
pub virtual_transparent_encryption: bool,
pub enable_read_only_guest_page_table: bool,
pub enable_invlpgb_and_tlbsync: bool,
#[skip]
__: B56,
// 098h
pub avic_apic_bar: B52,
#[skip]
__: B12,
// 0A0h
pub ghcb_gpa: B64,
// 0A8h
pub event_injection: B64,
// 0B0h
pub n_cr3: B64,
// 0D8h
pub lbr_virtualization_enable: bool,
pub vmload_vmsave_virtualization_enable: bool,
pub ibs_virtualization_enable: bool,
pub pmc_virtualization_enable: bool,
#[skip]
__: B60,
// 0C0h
pub vmcb_clean_bits: B32,
#[skip]
__: B32,
// 0C8h
pub next_rip: B64,
// 0D0h
pub fetched_bytes: B8,
pub gutest_instruction_bytes: B120,
// 0E0h
pub avic_apic_backing_page_pointer: B52,
#[skip]
__: B12,
// 0E8-0EFh Reserved
#[skip]
__: B64,
#[skip]
__: B64,
#[skip]
__: B64,
#[skip]
__: B64,
// 0F0h
#[skip]
__: B12,
pub avic_logical_table_pointer: B40,
#[skip]
__: B12,
// 0F8h
pub avic_physical_max_index: B12,
pub avic_physical_table_pointer: B40,
#[skip]
__: B12,
// 100h-107h Reserved
#[skip]
__: B64,
// 108h
#[skip]
__: B12,
pub vmsa_pointer: B40,
#[skip]
__: B12,
// 110h
pub vmgexit_rax: B64,
// 118h
pub vmgexit_cpl: B8,
}
#[derive(Specifier, Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum TlbControl {
DoNothing = 0,
FlushAll = 1,
_RESERVED1 = 2,
FlushGuest = 3,
_RESERVED2 = 4,
FlushHost = 5,
_RESERVED3 = 6,
FlushGuestNonGlobal = 7,
}

View File

@@ -10,6 +10,12 @@ pub fn load_kernel(vcpu: &mut dyn VCpu) -> Result<(), &'static str> {
let kernel =
unsafe { core::slice::from_raw_parts(*kernel_addr as *const u8, *kernel_size as usize) };
let initrd_addr = crate::ROOTFS_ADDR.get().unwrap();
let initrd_size = crate::ROOTFS_SIZE.get().unwrap();
let initrd =
unsafe { core::slice::from_raw_parts(*initrd_addr as *const u8, *initrd_size as usize) };
info!("Creating boot parameters");
let guest_mem_size = vcpu.get_guest_memory_size();
let mut bp = BootParams::from_bytes(kernel)?;
@@ -23,6 +29,8 @@ pub fn load_kernel(vcpu: &mut dyn VCpu) -> Result<(), &'static str> {
bp.hdr.loadflags.set_keep_segments(true);
bp.hdr.cmd_line_ptr = LAYOUT_CMDLINE as u32;
bp.hdr.vid_mode = 0xFFFF;
bp.hdr.ramdisk_image = LAYOUT_INITRD as u32;
bp.hdr.ramdisk_size = initrd.len() as u32;
bp.add_e820_entry(0, LAYOUT_KERNEL_BASE, E820Type::Ram);
bp.add_e820_entry(
@@ -38,7 +46,7 @@ pub fn load_kernel(vcpu: &mut dyn VCpu) -> Result<(), &'static str> {
256
};
let cmdline_start = LAYOUT_CMDLINE as u64;
let cmdline_start = LAYOUT_CMDLINE;
let cmdline_end = cmdline_start + cmdline_max_size as u64;
vcpu.write_memory_ranged(cmdline_start, cmdline_end, 0)?;
let cmdline_val = "console=ttyS0 earlyprintk=serial nokaslr";
@@ -65,6 +73,9 @@ pub fn load_kernel(vcpu: &mut dyn VCpu) -> Result<(), &'static str> {
LAYOUT_KERNEL_BASE as usize,
)?;
info!("Loading initrd image into guest memory");
load_image(vcpu, initrd, LAYOUT_INITRD as usize)?;
Ok(())
}
@@ -117,11 +128,17 @@ pub struct BootParams {
pub _unimplemented: [u8; 0x330],
}
impl Default for BootParams {
fn default() -> Self {
Self::new()
}
}
impl BootParams {
pub const E820MAX: usize = 128;
pub fn new() -> Self {
let params = Self {
Self {
_screen_info: [0; 0x40],
_apm_bios_info: [0; 0x14],
_pad2: [0; 4],
@@ -151,9 +168,7 @@ impl BootParams {
type_: E820Type::Ram as u32,
}; Self::E820MAX],
_unimplemented: [0; 0x330],
};
params
}
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {

View File

@@ -44,20 +44,20 @@ pub fn check_vmcs_control_fields() -> Result<(), &'static str> {
fn is_valid_ept_ptr(ept_ptr: u64) -> Result<(), &'static str> {
let memory_type = ept_ptr & 0b111;
if memory_type != 0 && memory_type != 6 {
return Err("VMCS EPT pointer memory type is not valid (must be 0 or 6)");
return Err("VMCS Ept pointer memory type is not valid (must be 0 or 6)");
}
let walk_length = (ept_ptr >> 3) & 0b111;
if walk_length != 3 {
return Err("VMCS EPT pointer walk length is not valid (must be 3)");
return Err("VMCS Ept pointer walk length is not valid (must be 3)");
}
if ept_ptr & 0xf00 != 0 {
return Err("VMCS EPT pointer reserved bits are not zero");
return Err("VMCS Ept pointer reserved bits are not zero");
}
if !is_valid_phys_addr(ept_ptr) {
return Err("VMCS EPT pointer is not a valid physical address");
return Err("VMCS Ept pointer is not a valid physical address");
}
Ok(())
@@ -72,12 +72,12 @@ fn check_ept() -> Result<(), &'static str> {
} else {
if secondary_exec_ctrl.unrestricted_guest() {
return Err(
"VMCS Secondary processor-based execution controls field: EPT is not set while unrestricted guest is set",
"VMCS Secondary processor-based execution controls field: Ept is not set while unrestricted guest is set",
);
}
if secondary_exec_ctrl.mode_based_control_ept() {
return Err(
"VMCS Secondary processor-based execution controls field: EPT is not set while mode-based control for EPT is set",
"VMCS Secondary processor-based execution controls field: Ept is not set while mode-based control for Ept is set",
);
}
}
@@ -107,15 +107,13 @@ fn check_interrupt() -> Result<(), &'static str> {
} else {
// TODO
}
} else {
if secondary_exec_ctrl.virtualize_x2apic_mode()
|| secondary_exec_ctrl.apic_register_virtualization()
|| secondary_exec_ctrl.virtual_interrupt_delivery()
{
return Err(
"VMCS Primary processor-based execution controls field: Use TPR shadow is not set while virtualize x2APIC mode, APIC register virtualization, or virtual interrupt delivery is set",
);
}
} else if secondary_exec_ctrl.virtualize_x2apic_mode()
|| secondary_exec_ctrl.apic_register_virtualization()
|| secondary_exec_ctrl.virtual_interrupt_delivery()
{
return Err(
"VMCS Primary processor-based execution controls field: Use TPR shadow is not set while virtualize x2APIC mode, APIC register virtualization, or virtual interrupt delivery is set",
);
}
// TODO
@@ -130,7 +128,7 @@ fn check_ept_violation_exception_info() -> Result<(), &'static str> {
let exception_info = vmread(vmcs::control::VIRT_EXCEPTION_INFO_ADDR_FULL)?;
if is_valid_page_aligned_phys_addr(exception_info) {
return Err("VMCS EPT violation exception info address is not a valid page-aligned physical address");
return Err("VMCS Ept violation exception info address is not a valid page-aligned physical address");
}
}

View File

@@ -16,7 +16,7 @@ pub fn setup_exec_controls() -> Result<(), &'static str> {
raw_pin_exec_ctrl &= (reserved_bits >> 32) as u32;
let mut pin_exec_ctrl = vmcs::controls::PinBasedVmExecutionControls::from(raw_pin_exec_ctrl);
pin_exec_ctrl.set_external_interrupt_exiting(false);
pin_exec_ctrl.set_external_interrupt_exiting(true);
pin_exec_ctrl.write()?;
@@ -109,13 +109,7 @@ pub fn setup_exit_controls() -> Result<(), &'static str> {
exit_ctrl.write()?;
/*vmwrite(
0x4004,
1u64 << x86::irq::DOUBLE_FAULT_VECTOR
| 1u64 << x86::irq::GENERAL_PROTECTION_FAULT_VECTOR
| 1u64 << x86::irq::PAGE_FAULT_VECTOR
| 1u64 << x86::irq::X87_FPU_VECTOR,
)?;*/
vmwrite(0x4004, 1u64 << x86::irq::INVALID_OPCODE_VECTOR)?;
Ok(())
}

View File

@@ -258,6 +258,7 @@ pub struct ExtFeatureEbx0 {
pub avx512vl: bool,
}
#[allow(clippy::enum_clike_unportable_variant)]
pub enum VmxLeaf {
MAXIMUM_INPUT = 0x0,
VERSION_AND_FEATURE_INFO = 0x1,
@@ -268,7 +269,7 @@ pub enum VmxLeaf {
EXTENDED_FEATURE_2 = 0x80000002,
EXTENDED_FEATURE_3 = 0x80000003,
EXTENDED_FEATURE_4 = 0x80000004,
UNKNOWN = 0xFFFFFFFF,
Unknown = 0xFFFFFFFF,
}
impl VmxLeaf {
@@ -283,7 +284,7 @@ impl VmxLeaf {
0x80000002 => VmxLeaf::EXTENDED_FEATURE_2,
0x80000003 => VmxLeaf::EXTENDED_FEATURE_3,
0x80000004 => VmxLeaf::EXTENDED_FEATURE_4,
_ => VmxLeaf::UNKNOWN,
_ => VmxLeaf::Unknown,
}
}
}

View File

@@ -9,15 +9,15 @@ use x86_64::{
PhysAddr,
};
pub struct EPT {
pub struct Ept {
pub root_table: PhysFrame,
}
impl EPT {
impl Ept {
pub fn new(allocator: &mut impl FrameAllocator<Size4KiB>) -> Result<Self, &'static str> {
let root_table_frame = allocator
.allocate_frame()
.ok_or("Failed to allocate EPT root table frame")?;
.ok_or("Failed to allocate Ept root table frame")?;
Self::init_table(&root_table_frame);
@@ -289,7 +289,7 @@ impl EPT {
#[bitfield]
#[repr(u64)]
#[derive(Debug)]
pub struct EPTP {
pub struct Eptp {
pub typ: B3,
pub level: B3,
pub dirty_accessed: bool,
@@ -298,9 +298,9 @@ pub struct EPTP {
pub phys: B52,
}
impl EPTP {
impl Eptp {
pub fn init(lv4_table: &PhysFrame) -> Self {
EPTP::new()
Eptp::new()
.with_typ(6)
.with_level(3)
.with_dirty_accessed(true)
@@ -308,7 +308,7 @@ impl EPTP {
.with_phys(lv4_table.start_address().as_u64() >> 12)
}
pub fn get_lv4_table(&self) -> &mut [EntryBase; 512] {
pub fn get_lv4_table(&mut self) -> &mut [EntryBase; 512] {
let table_ptr = self.phys() << 12;
unsafe { &mut *(table_ptr as *mut [EntryBase; 512]) }

View File

@@ -0,0 +1,69 @@
#![allow(non_snake_case)]
use modular_bitfield::{bitfield, prelude::B44};
use crate::vmm::x86_64::intel::vcpu::IntelVCpu;
#[bitfield]
#[repr(u64)]
#[derive(Debug, Clone, Copy)]
pub struct XCR0 {
pub x87: bool,
pub sse: bool,
pub avx: bool,
pub bndreg: bool,
pub bndcsr: bool,
pub opmask: bool,
pub zmm_hi256: bool,
pub hi16_zmm: bool,
pub pt: bool,
pub pkru: bool,
pub pasid: bool,
pub cet_u: bool,
pub cet_s: bool,
pub hdc: bool,
pub intr: bool,
pub lbr: bool,
pub hwp: bool,
pub xtilecfg: bool,
pub xtiledata: bool,
pub apx: bool,
#[skip]
__: B44,
}
pub fn set_xcr(vcpu: &mut IntelVCpu, index: u32, xcr: u64) -> Result<(), &'static str> {
if index != 0 {
return Err("Invalid XCR index");
}
if xcr & 0b1 == 0 {
return Err("X87 is not enabled");
}
if (xcr & 0b100 != 0) && (xcr & 0b10 == 0) {
return Err("SSE is not enabled");
}
if !(xcr & 0b1000) != (!(xcr & 0b10000)) {
return Err("BNDREGS and BNDCSR are not both enabled");
}
if xcr & 0b11100000 != 0 {
if xcr & 0b100 == 0 {
return Err("YMM bits are not enabled");
}
if (xcr & 0b11100000) != 0b11100000 {
return Err("Invalid bits set in XCR0");
}
}
if (xcr & 0b1000000000000 != 0) && (xcr & 0b1000000000000 != 0b1000000000000) {
return Err("xtile bits are not both enabled");
}
vcpu.guest_xcr0 = XCR0::from(xcr);
Ok(())
}

View File

@@ -1,12 +1,32 @@
use x86::vmx::vmcs;
use x86::{
io::inb,
vmx::{self, vmcs},
};
use x86_64::structures::paging::{FrameAllocator, PhysFrame, Size4KiB};
use super::qual::QualIo;
use crate::{
info,
vmm::x86_64::intel::{register::GuestRegisters, vmwrite},
interrupt::subscriber::InterruptContext,
serial,
vmm::x86_64::intel::{
register::GuestRegisters, vmcs::controls::EntryIntrInfo, vmread, vmwrite,
},
};
pub fn vmm_interrupt_subscriber(vcpu_ptr: *mut core::ffi::c_void, context: &InterruptContext) {
if vcpu_ptr.is_null() {
return;
}
let vcpu = unsafe { &mut *(vcpu_ptr as *mut super::vcpu::IntelVCpu) };
if 0x20 <= context.vector && context.vector <= 0x20 + 16 {
let irq = context.vector - 0x20;
vcpu.pic.pending_irq |= 1 << irq;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InitPhase {
Uninitialized,
@@ -17,11 +37,17 @@ pub enum InitPhase {
}
pub enum ReadSel {
IRR,
ISR,
Irr,
Isr,
}
pub struct PIC {
#[derive(Debug, Clone, Copy, Default)]
pub struct Serial {
pub ier: u8,
pub mcr: u8,
}
pub struct Pic {
pub primary_mask: u8,
pub secondary_mask: u8,
pub primary_phase: InitPhase,
@@ -34,9 +60,11 @@ pub struct PIC {
pub secondary_isr: u8,
pub primary_read_sel: ReadSel,
pub secondary_read_sel: ReadSel,
pub serial: Serial,
pub pending_irq: u16,
}
impl PIC {
impl Pic {
pub fn new() -> Self {
Self {
primary_mask: 0xFF,
@@ -49,8 +77,10 @@ impl PIC {
primary_isr: 0,
secondary_irr: 0,
secondary_isr: 0,
primary_read_sel: ReadSel::IRR,
secondary_read_sel: ReadSel::IRR,
primary_read_sel: ReadSel::Irr,
secondary_read_sel: ReadSel::Irr,
serial: Serial::default(),
pending_irq: 0,
}
}
@@ -66,41 +96,183 @@ impl PIC {
}
}
pub fn handle_io_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
pub fn inject_external_interrupt(&mut self) -> Result<bool, &'static str> {
let pending = self.pending_irq;
if pending == 0 {
return Ok(false);
}
if self.primary_phase != InitPhase::Initialized {
return Ok(false);
}
let eflags = vmread(vmx::vmcs::guest::RFLAGS)?;
if eflags >> 9 & 1 == 0 {
return Ok(false);
}
let interruptibility = vmread(vmx::vmcs::guest::INTERRUPTIBILITY_STATE)?;
if interruptibility & 0x3 != 0 {
return Ok(false);
}
let is_secondary_masked = (self.primary_mask >> 2) & 1 != 0;
for i in 0..16 {
if is_secondary_masked && i >= 8 {
continue;
}
let irq_bit = 1 << i;
if pending & irq_bit == 0 {
continue;
}
let delta = if i < 8 { i } else { i - 8 };
let is_masked = if i < 8 {
(self.primary_mask >> delta) & 1 != 0
} else {
let is_irq_masked = (self.secondary_mask >> delta) & 1 != 0;
is_secondary_masked || is_irq_masked
};
if is_masked {
continue;
}
let interrupt_info = EntryIntrInfo::new()
.with_vector(
delta as u8
+ if i < 8 {
self.primary_base
} else {
self.secondary_base
},
)
.with_typ(0)
.with_ec_available(false)
.with_valid(true);
vmwrite(
vmx::vmcs::control::VMENTRY_INTERRUPTION_INFO_FIELD,
u32::from(interrupt_info) as u64,
)?;
self.pending_irq &= !irq_bit;
return Ok(true);
}
Ok(false)
}
pub fn inject_exception(
&mut self,
vector: u32,
error_code: Option<u32>,
) -> Result<(), &'static str> {
let has_error_code = matches!(vector, 8 | 10..=14 | 17 | 21);
let interrupt_info = EntryIntrInfo::new()
.with_vector(vector as u8)
.with_typ(3)
.with_ec_available(has_error_code)
.with_valid(true);
vmwrite(
vmx::vmcs::control::VMENTRY_INTERRUPTION_INFO_FIELD,
u32::from(interrupt_info) as u64,
)?;
if has_error_code {
let ec = error_code.unwrap_or(0);
vmwrite(vmx::vmcs::control::VMENTRY_EXCEPTION_ERR_CODE, ec as u64)?;
}
Ok(())
}
fn handle_io_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
match qual.port() {
0x0CF8..=0x0CFF => regs.rax = 0,
0xC000..=0xCFFF => {} //ignore
0x20..=0x21 => self.handle_pic_in(regs, qual),
0xA0..=0xA1 => self.handle_pic_in(regs, qual),
0x0070..=0x0071 => regs.rax = 0,
0x03F8..=0x03FF => self.handle_serial_in(regs, qual),
_ => regs.rax = 0,
}
}
pub fn handle_io_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
fn handle_io_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
match qual.port() {
0x0CF8..=0x0CFF => {} //ignore
0xC000..=0xCFFF => {} //ignore
0x20..=0x21 => self.handle_pic_out(regs, qual),
0xA0..=0xA1 => self.handle_pic_out(regs, qual),
0x03F8..=0x03FF => self.handle_serial_out(regs, qual),
0x0070..=0x0071 => {} //ignore
_ => {}
}
}
pub fn handle_pic_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
fn handle_serial_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
match qual.port() {
0x3F8 => regs.rax = unsafe { inb(qual.port()).into() },
0x3F9 => regs.rax = self.serial.ier as u64,
0x3FA => {} //regs.rax = 0, // unsafe { inb(qual.port()).into() },
0x3FB => {} //regs.rax = 0,
0x3FC => {} //regs.rax = 0, //self.serial.mcr as u64,
0x3FD => {
if qual.size() == 1 {
regs.rax = 0x60
}
}
0x3FE => {
if qual.size() == 1 {
regs.rax = 0xb0
}
}
0x3FF => {} //regs.rax = 0,
_ => {
panic!("Serial in: invalid port: {:#x}", qual.port());
}
}
}
fn handle_serial_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
match qual.port() {
0x3F8 => serial::write_byte(regs.rax as u8),
0x3F9 => {
self.serial.ier = regs.rax as u8;
if regs.rax & 0b10 != 0 {
self.pending_irq |= 1 << 4;
}
}
0x3FA => {}
0x3FB => {}
0x3FC => self.serial.mcr = regs.rax as u8,
0x3FD => {}
0x3FF => {}
_ => {
panic!("Serial out: invalid port: {:#x}", qual.port());
}
}
}
fn handle_pic_in(&self, regs: &mut GuestRegisters, qual: QualIo) {
match qual.port() {
0x20 => {
let v = match self.primary_read_sel {
ReadSel::IRR => self.primary_irr,
ReadSel::ISR => self.primary_isr,
ReadSel::Irr => self.primary_irr,
ReadSel::Isr => self.primary_isr,
};
regs.rax = v as u64;
}
0xA0 => {
let v = match self.secondary_read_sel {
ReadSel::IRR => self.secondary_irr,
ReadSel::ISR => self.secondary_isr,
ReadSel::Irr => self.secondary_irr,
ReadSel::Isr => self.secondary_isr,
};
regs.rax = v as u64;
}
@@ -120,14 +292,14 @@ impl PIC {
}
}
pub fn handle_pic_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
fn handle_pic_out(&mut self, regs: &mut GuestRegisters, qual: QualIo) {
let pic = self;
let dx = regs.rax as u8;
match qual.port() {
0x20 => match dx {
0x11 => pic.primary_phase = InitPhase::Phase1,
0x0A => pic.primary_read_sel = ReadSel::ISR,
0x0B => pic.primary_read_sel = ReadSel::IRR,
0x0A => pic.primary_read_sel = ReadSel::Isr,
0x0B => pic.primary_read_sel = ReadSel::Irr,
0x20 => {
pic.primary_isr = 0;
}
@@ -135,7 +307,7 @@ impl PIC {
let irq = dx & 0x7;
pic.primary_isr &= !(1 << irq);
}
_ => panic!("Primary PIC command: {:#x}", dx),
_ => panic!("Primary Pic command: {:#x}", dx),
},
0x21 => match pic.primary_phase {
InitPhase::Uninitialized | InitPhase::Initialized => pic.primary_mask = dx,
@@ -147,14 +319,14 @@ impl PIC {
pic.primary_phase = InitPhase::Phase3;
}
InitPhase::Phase3 => {
info!("Primary PIC Initialized");
info!("Primary Pic Initialized");
pic.primary_phase = InitPhase::Initialized
}
},
0xA0 => match dx {
0x11 => pic.secondary_phase = InitPhase::Phase1,
0x0A => pic.secondary_read_sel = ReadSel::ISR,
0x0B => pic.secondary_read_sel = ReadSel::IRR,
0x0A => pic.secondary_read_sel = ReadSel::Isr,
0x0B => pic.secondary_read_sel = ReadSel::Irr,
0x20 => {
pic.secondary_isr = 0;
}
@@ -162,7 +334,7 @@ impl PIC {
let irq = dx & 0x7;
pic.secondary_isr &= !(1 << irq);
}
_ => panic!("Secondary PIC command: {:#x}", dx),
_ => panic!("Secondary Pic command: {:#x}", dx),
},
0xA1 => match pic.secondary_phase {
InitPhase::Uninitialized | InitPhase::Initialized => pic.secondary_mask = dx,
@@ -174,7 +346,7 @@ impl PIC {
pic.secondary_phase = InitPhase::Phase3;
}
InitPhase::Phase3 => {
info!("Secondary PIC Initialized");
info!("Secondary Pic Initialized");
pic.secondary_phase = InitPhase::Initialized
}
},
@@ -209,8 +381,8 @@ impl IOBitmap {
core::ptr::write_bytes(bitmap_b_addr as *mut u8, u8::MAX, 4096);
}
self.set_io_ports(0x02F8..=0x03FF);
self.set_io_ports(0x0040..=0x0047);
//self.set_io_ports(0x02F8..=0x03EF);
vmwrite(vmcs::control::IO_BITMAP_A_ADDR_FULL, bitmap_a_addr as u64)?;
vmwrite(vmcs::control::IO_BITMAP_B_ADDR_FULL, bitmap_b_addr as u64)?;
@@ -235,13 +407,13 @@ impl IOBitmap {
}
}
fn get_bitmap_a(&self) -> &mut [u8] {
fn get_bitmap_a(&mut self) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(self.bitmap_a.start_address().as_u64() as *mut u8, 4096)
}
}
fn get_bitmap_b(&self) -> &mut [u8] {
fn get_bitmap_b(&mut self) -> &mut [u8] {
unsafe {
core::slice::from_raw_parts_mut(self.bitmap_b.start_address().as_u64() as *mut u8, 4096)
}

View File

@@ -4,6 +4,7 @@ mod controls;
mod cpuid;
mod cr;
mod ept;
mod fpu;
mod io;
mod msr;
mod qual;

View File

@@ -12,7 +12,7 @@ type MsrIndex = u32;
const MAX_NUM_ENTS: usize = 512;
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Default)]
#[repr(C, packed)]
pub struct SavedMsr {
pub index: MsrIndex,
@@ -20,16 +20,6 @@ pub struct SavedMsr {
pub data: u64,
}
impl Default for SavedMsr {
fn default() -> Self {
Self {
index: 0,
reserved: 0,
data: 0,
}
}
}
#[derive(Debug)]
pub struct ShadowMsr {
ents: Vec<SavedMsr>,
@@ -126,6 +116,12 @@ pub fn _update_msrs(vcpu: &mut IntelVCpu) -> Result<(), MsrError> {
Ok(())
}
impl Default for ShadowMsr {
fn default() -> Self {
Self::new()
}
}
impl ShadowMsr {
pub fn new() -> Self {
let ents = vec![];

View File

@@ -3,7 +3,7 @@
use core::convert::TryFrom;
use core::fmt::Debug;
use modular_bitfield::prelude::{B1, B16, B32, B4, B8};
use modular_bitfield::prelude::{B1, B16, B3, B32, B4, B9};
use modular_bitfield::{bitfield, Specifier};
#[repr(u8)]
@@ -113,12 +113,12 @@ pub struct QualCr {
#[repr(u64)]
#[derive(Debug, Clone, Copy)]
pub struct QualIo {
pub size: B4,
pub size: B3,
pub direction: B1,
pub string: B1,
pub rep: B1,
pub operand_encoding: B1,
_reserved1: B8,
_reserved1: B9,
pub port: B16,
_reserved2: B32,
}

View File

@@ -1,18 +1,25 @@
use core::arch::{
asm,
x86_64::{_xgetbv, _xsetbv},
};
use raw_cpuid::cpuid;
use x86::controlregs::cr4;
use x86_64::{
registers::control::Cr4Flags,
registers::control::{Cr4, Cr4Flags},
structures::paging::{FrameAllocator, Size4KiB},
VirtAddr,
};
use crate::{
info,
info, interrupt,
vmm::{
x86_64::{
common::{self, read_msr},
intel::{
auditor, controls, cpuid, ept,
io::IOBitmap,
fpu::{self, XCR0},
io::{vmm_interrupt_subscriber, IOBitmap},
msr::{self, ShadowMsr},
qual::{QualCr, QualIo},
register::GuestRegisters,
@@ -39,14 +46,16 @@ pub struct IntelVCpu {
activated: bool,
vmxon: vmxon::Vmxon,
vmcs: vmcs::Vmcs,
ept: ept::EPT,
eptp: ept::EPTP,
ept: ept::Ept,
eptp: ept::Eptp,
guest_memory_size: u64,
pub host_msr: ShadowMsr,
pub guest_msr: ShadowMsr,
pub ia32e_enabled: bool,
pic: super::io::PIC,
pub pic: super::io::Pic,
io_bitmap: IOBitmap,
pub host_xcr0: u64,
pub guest_xcr0: XCR0,
}
impl IntelVCpu {
@@ -77,7 +86,19 @@ impl IntelVCpu {
match exit_reason {
VmxExitReason::HLT => {
info!("VM hlt");
let injected = self.pic.inject_external_interrupt().unwrap_or(false);
if !injected {
unsafe {
asm!("sti");
asm!("nop");
asm!("cli");
}
}
vmwrite(vmcs::guest::ACTIVITY_STATE, 0)?;
vmwrite(vmcs::guest::INTERRUPTIBILITY_STATE, 0)?;
self.step_next_inst()?;
}
VmxExitReason::CPUID => {
@@ -100,6 +121,15 @@ impl IntelVCpu {
self.step_next_inst()?;
}
VmxExitReason::XSETBV => {
fpu::set_xcr(
self,
self.guest_registers.rcx as u32,
self.guest_registers.rax,
)?;
self.step_next_inst()?;
}
VmxExitReason::IO_INSTRUCTION => {
let qual = vmread(vmcs::ro::EXIT_QUALIFICATION)?;
let qual_io = QualIo::from(qual);
@@ -108,39 +138,102 @@ impl IntelVCpu {
self.step_next_inst()?;
}
VmxExitReason::EXTERNAL_INTERRUPT => {
vmwrite(vmcs::ro::VMEXIT_INTERRUPTION_INFO, 0)?;
unsafe {
asm!("sti");
asm!("nop");
asm!("cli");
}
self.pic.inject_external_interrupt()?;
}
VmxExitReason::EPT_VIOLATION => {
let guest_address = vmread(vmcs::ro::GUEST_PHYSICAL_ADDR_FULL)?;
info!("EPT Violation at guest address: {:#x}", guest_address);
return Err("EPT Violation");
info!("Ept Violation at guest address: {:#x}", guest_address);
return Err("Ept Violation");
}
VmxExitReason::TRIPLE_FAULT => {
info!("Triple fault detected");
return Err("Triple fault");
}
VmxExitReason::EXCEPTION => {
let vmexit_intr_info = vmread(vmcs::ro::VMEXIT_INTERRUPTION_INFO)?;
let vector = (vmexit_intr_info & 0xFF) as u8;
let error_code = (vmexit_intr_info >> 8) & 0b111;
let error_code_valid = (vmexit_intr_info >> 11) & 0b1 != 0;
let vmexit_intr_info = vmread(vmcs::ro::VMEXIT_INTERRUPTION_INFO).unwrap();
let vector = (vmexit_intr_info & 0xFF) as u32;
let has_error_code = (vmexit_intr_info & (1 << 11)) != 0;
let idt_vectoring_info = vmread(vmcs::ro::IDT_VECTORING_INFO)?;
info!("idt valid: {}", idt_vectoring_info >> 31 & 0b1 != 0);
let rip = vmread(vmcs::guest::RIP)?;
let hpa = self.ept.get_phys_addr(rip).unwrap();
if error_code_valid {
info!(
"VM exit due to exception: vector {}, error code {}, at RIP {:#x} (hpa: {:#x})",
vector, error_code, rip, hpa
);
let error_code = if has_error_code {
Some(vmread(vmcs::ro::VMEXIT_INTERRUPTION_ERR_CODE).unwrap() as u32)
} else {
info!("VM exit due to exception: vector {}", vector);
None
};
let rip = vmread(vmcs::guest::RIP).unwrap();
let mut instruction_bytes = [0u8; 16];
let mut valid_bytes = 0;
match self.translate_guest_address(rip) {
Ok(guest_phys_addr) => {
for i in 0..16 {
match self.ept.get(guest_phys_addr + i) {
Ok(byte) => {
instruction_bytes[i as usize] = byte;
valid_bytes = i + 1;
}
Err(_) => break,
}
}
}
Err(e) => {
info!(
"Failed to get physical address for RIP: {:#x}, {:?}",
rip, e
);
return Err("Failed to get physical address for RIP");
}
}
if valid_bytes > 0 {
match instruction_bytes[0] {
0x0F => {
if valid_bytes > 1 {
match instruction_bytes[1] {
0x01 => match instruction_bytes[2] {
0xCA => {
let rflags = vmread(vmcs::guest::RFLAGS).unwrap();
vmwrite(vmcs::guest::RFLAGS, rflags & !(1 << 18))
.unwrap();
self.step_next_inst().unwrap();
}
0xCB => {
let rflags = vmread(vmcs::guest::RFLAGS).unwrap();
vmwrite(vmcs::guest::RFLAGS, rflags | (1 << 18))
.unwrap();
self.step_next_inst().unwrap();
}
_ => {
self.pic
.inject_exception(vector, error_code)
.unwrap();
}
},
_ => {
self.pic.inject_exception(vector, error_code).unwrap();
}
}
}
}
_ => {
self.pic.inject_exception(vector, error_code).unwrap();
}
}
} else {
self.pic.inject_exception(vector, error_code).unwrap();
}
return Err("VM exit due to exception");
}
_ => {
info!("VM exit reason: {:?}", exit_reason);
return Err("Unhandled VM exit reason");
}
}
@@ -149,6 +242,50 @@ impl IntelVCpu {
Ok(())
}
fn load_guest_xcr0(&mut self) -> Result<(), &'static str> {
let host_cr4 = unsafe { cr4() };
if (host_cr4.bits() & Cr4Flags::OSXSAVE.bits() as usize) == 0 {
return Ok(());
}
if self.host_xcr0 == 0 {
self.host_xcr0 = unsafe { _xgetbv(0) };
}
let guest_cr4 = vmread(x86::vmx::vmcs::guest::CR4)?;
if guest_cr4 & Cr4Flags::OSXSAVE.bits() != 0
&& u64::from(self.guest_xcr0) != self.host_xcr0
&& u64::from(self.guest_xcr0) != 0
{
unsafe {
_xsetbv(0, u64::from(self.guest_xcr0));
}
}
Ok(())
}
fn load_host_xcr0(&mut self) -> Result<(), &'static str> {
let host_cr4 = unsafe { cr4() };
if (host_cr4.bits() & Cr4Flags::OSXSAVE.bits() as usize) == 0 {
return Ok(());
}
let guest_cr4 = vmread(x86::vmx::vmcs::guest::CR4)?;
if guest_cr4 & Cr4Flags::OSXSAVE.bits() != 0 {
let current_xcr0 = unsafe { _xgetbv(0) };
if current_xcr0 != self.host_xcr0 {
unsafe {
_xsetbv(0, self.host_xcr0);
}
}
}
Ok(())
}
fn step_next_inst(&mut self) -> Result<(), &'static str> {
use x86::vmx::vmcs;
let rip = vmread(vmcs::guest::RIP)?;
@@ -164,9 +301,13 @@ impl IntelVCpu {
let success = {
let result: u16;
self.load_guest_xcr0().unwrap();
unsafe {
result = crate::vmm::x86_64::intel::asm::asm_vm_entry(self as *mut _);
};
self.load_host_xcr0().unwrap();
result == 0
};
@@ -198,12 +339,22 @@ impl IntelVCpu {
self.setup_guest_state()?;
self.io_bitmap.setup()?;
interrupt::subscriber::subscribe(
vmm_interrupt_subscriber,
self as &mut IntelVCpu as *mut IntelVCpu as *mut core::ffi::c_void,
)?;
self.init_guest_memory(frame_allocator)?;
common::linux::load_kernel(self)?;
msr::register_msrs(self).map_err(|_| "MSR error")?;
let cr4 = Cr4::read() | Cr4Flags::OSFXSR;
unsafe {
Cr4::write(cr4);
}
Ok(())
}
@@ -223,7 +374,7 @@ impl IntelVCpu {
pages -= 1;
}
let eptp = ept::EPTP::init(&self.ept.root_table);
let eptp = ept::Eptp::init(&self.ept.root_table);
vmwrite(x86::vmx::vmcs::control::EPTP_FULL, u64::from(eptp))?;
Ok(())
@@ -237,12 +388,12 @@ impl IntelVCpu {
vmwrite(vmcs::host::CR3, unsafe { cr3() })?;
vmwrite(
vmcs::host::CR4,
unsafe { cr4() }.bits() as u64, /* | Cr4Flags::OSXSAVE.bits()*/
unsafe { cr4() }.bits() as u64 | Cr4Flags::OSXSAVE.bits(),
)?;
vmwrite(
vmcs::host::RIP,
crate::vmm::x86_64::intel::asm::asm_vmexit_handler as u64,
crate::vmm::x86_64::intel::asm::asm_vmexit_handler as usize as u64,
)?;
vmwrite(
vmcs::host::RSP,
@@ -384,12 +535,83 @@ impl IntelVCpu {
vmwrite(vmcs::guest::RIP, common::linux::LAYOUT_KERNEL_BASE)?;
self.guest_registers.rsi = common::linux::LAYOUT_BOOTPARAM;
//vmwrite(vmcs::control::CR0_READ_SHADOW, vmread(vmcs::guest::CR0)?)?;
//vmwrite(vmcs::control::CR4_READ_SHADOW, vmread(vmcs::guest::CR4)?)?;
vmwrite(vmcs::control::CR0_READ_SHADOW, vmread(vmcs::guest::CR0)?)?;
vmwrite(vmcs::control::CR4_READ_SHADOW, vmread(vmcs::guest::CR4)?)?;
Ok(())
}
fn translate_guest_address(&mut self, vaddr: u64) -> Result<u64, &'static str> {
let cr3 = vmread(x86::vmx::vmcs::guest::CR3).map_err(|_| "Failed to read guest CR3")?;
let pml4_base = cr3 & !0xFFF; // Clear lower 12 bits to get page table base
let efer = vmread(x86::vmx::vmcs::guest::IA32_EFER_FULL).unwrap_or(0);
let is_long_mode = (efer & (1 << 10)) != 0; // LMA bit
if !is_long_mode {
return Ok(vaddr & 0xFFFFFFFF);
}
let pml4_idx = (vaddr >> 39) & 0x1FF;
let pdpt_idx = (vaddr >> 30) & 0x1FF;
let pd_idx = (vaddr >> 21) & 0x1FF;
let pt_idx = (vaddr >> 12) & 0x1FF;
let page_offset = vaddr & 0xFFF;
let pml4_entry_addr = pml4_base + (pml4_idx * 8);
let pml4_entry = self.read_guest_phys_u64(pml4_entry_addr)?;
if (pml4_entry & 1) == 0 {
return Err("PML4 entry not present");
}
let pdpt_base = pml4_entry & 0x000FFFFFFFFFF000;
let pdpt_entry_addr = pdpt_base + (pdpt_idx * 8);
let pdpt_entry = self.read_guest_phys_u64(pdpt_entry_addr)?;
if (pdpt_entry & 1) == 0 {
return Err("PDPT entry not present");
}
if (pdpt_entry & (1 << 7)) != 0 {
let page_base = pdpt_entry & 0x000FFFFFC0000000;
return Ok(page_base | (vaddr & 0x3FFFFFFF));
}
let pd_base = pdpt_entry & 0x000FFFFFFFFFF000;
let pd_entry_addr = pd_base + (pd_idx * 8);
let pd_entry = self.read_guest_phys_u64(pd_entry_addr)?;
if (pd_entry & 1) == 0 {
return Err("PD entry not present");
}
if (pd_entry & (1 << 7)) != 0 {
let page_base = pd_entry & 0x000FFFFFFFE00000;
return Ok(page_base | (vaddr & 0x1FFFFF));
}
let pt_base = pd_entry & 0x000FFFFFFFFFF000;
let pt_entry_addr = pt_base + (pt_idx * 8);
let pt_entry = self.read_guest_phys_u64(pt_entry_addr)?;
if (pt_entry & 1) == 0 {
return Err("PT entry not present");
}
let page_base = pt_entry & 0x000FFFFFFFFFF000;
Ok(page_base | page_offset)
}
fn read_guest_phys_u64(&mut self, gpa: u64) -> Result<u64, &'static str> {
let mut result_bytes = [0u8; 8];
for i in 0..8 {
match self.ept.get(gpa + i) {
Ok(byte) => result_bytes[i as usize] = byte,
Err(_) => return Err("Failed to read from Ept"),
}
}
Ok(u64::from_le_bytes(result_bytes))
}
fn dump_vmcs_settings(&self) -> Result<(), &'static str> {
info!("=== VMCS Control Fields ===");
@@ -419,9 +641,9 @@ impl IntelVCpu {
let exit_ctrl = vmread(x86::vmx::vmcs::control::VMEXIT_CONTROLS)?;
info!("VM-exit controls: {:#x}", exit_ctrl);
// EPT pointer
// Ept pointer
let eptp = vmread(x86::vmx::vmcs::control::EPTP_FULL)?;
info!("EPT pointer: {:#x}", eptp);
info!("Ept pointer: {:#x}", eptp);
info!("=== Guest State ===");
@@ -673,8 +895,8 @@ impl VCpu for IntelVCpu {
let vmcs = vmcs::Vmcs::new(frame_allocator)?;
let ept = ept::EPT::new(frame_allocator)?;
let eptp = ept::EPTP::init(&ept.root_table);
let ept = ept::Ept::new(frame_allocator)?;
let eptp = ept::Eptp::init(&ept.root_table);
Ok(IntelVCpu {
launch_done: false,
@@ -688,8 +910,10 @@ impl VCpu for IntelVCpu {
host_msr: ShadowMsr::new(),
guest_msr: ShadowMsr::new(),
ia32e_enabled: false,
pic: super::io::PIC::new(),
pic: super::io::Pic::new(),
io_bitmap: IOBitmap::new(frame_allocator),
host_xcr0: 0,
guest_xcr0: XCR0::new(),
})
}

View File

@@ -9,21 +9,24 @@ use crate::vmm::x86_64::intel::vmcs;
#[derive(Debug, Clone, Copy)]
pub struct PinBasedVmExecutionControls {
pub external_interrupt_exiting: bool,
_reserved1: B1,
#[skip]
__: B1,
pub interrupt_window_exiting: bool,
pub nmi_exiting: bool,
_reserved2: B1,
#[skip]
__: B1,
pub virtual_nmi: bool,
pub activate_vmx_preemption_timer: bool,
pub process_posted_interrupts: bool,
_reserved3: B24,
#[skip]
__: B24,
}
impl PinBasedVmExecutionControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::PIN_BASED_VM_EXECUTION_CONTROLS
.read()
.map(|value| PinBasedVmExecutionControls::from(value))
.map(PinBasedVmExecutionControls::from)
.map_err(|_| "Failed to read Pin-Based VM Execution Controls")
}
@@ -36,21 +39,26 @@ impl PinBasedVmExecutionControls {
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub struct PrimaryProcessorBasedVmExecutionControls {
_reserved1: B2,
#[skip]
__: B2,
pub interrupt_window: bool,
pub tsc_offsetting: bool,
_reserved2: B3,
#[skip]
__: B3,
pub hlt: bool,
_reserved3: B1,
#[skip]
__: B1,
pub invlpg: bool,
pub mwait: bool,
pub rdpmc: bool,
pub rdtsc: bool,
_reserved4: B2,
#[skip]
__: B2,
pub cr3load: bool,
pub cr3store: bool,
pub activate_teritary_controls: bool,
_reserved5: B1,
#[skip]
__: B1,
pub cr8load: bool,
pub cr8store: bool,
pub use_tpr_shadow: bool,
@@ -58,7 +66,8 @@ pub struct PrimaryProcessorBasedVmExecutionControls {
pub mov_dr: bool,
pub unconditional_io: bool,
pub use_io_bitmap: bool,
_reserved6: B1,
#[skip]
__: B1,
pub monitor_trap: bool,
pub use_msr_bitmap: bool,
pub monitor: bool,
@@ -70,7 +79,7 @@ impl PrimaryProcessorBasedVmExecutionControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::PRIMARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS
.read()
.map(|value| PrimaryProcessorBasedVmExecutionControls::from(value))
.map(PrimaryProcessorBasedVmExecutionControls::from)
.map_err(|_| "Failed to read Primary Processor-Based VM Execution Controls")
}
@@ -114,14 +123,15 @@ pub struct SecondaryProcessorBasedVmExecutionControls {
pub enable_enclv: bool,
pub vmm_buslock_detect: bool,
pub instruction_timeout: bool,
_reserved: B1,
#[skip]
__: B1,
}
impl SecondaryProcessorBasedVmExecutionControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS
.read()
.map(|value| SecondaryProcessorBasedVmExecutionControls::from(value))
.map(SecondaryProcessorBasedVmExecutionControls::from)
.map_err(|_| "Failed to read Secondary Processor-Based VM Execution Controls")
}
@@ -134,13 +144,16 @@ impl SecondaryProcessorBasedVmExecutionControls {
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub struct EntryControls {
_reserved1: B2,
#[skip]
__: B2,
pub load_debug_controls: bool,
_reserved2: B6,
#[skip]
__: B6,
pub ia32e_mode_guest: bool,
pub entry_smm: bool,
pub deactivate_dualmonitor: bool,
_reserved3: B1,
#[skip]
__: B1,
pub load_perf_global_ctrl: bool,
pub load_ia32_pat: bool,
pub load_ia32_efer: bool,
@@ -151,14 +164,15 @@ pub struct EntryControls {
pub load_cet_state: bool,
pub load_guest_lbr_ctl: bool,
pub load_pkrs: bool,
_reserved4: B9,
#[skip]
__: B9,
}
impl EntryControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::VM_ENTRY_CONTROLS
.read()
.map(|value| EntryControls::from(value))
.map(EntryControls::from)
.map_err(|_| "Failed to read VM Entry Controls")
}
@@ -171,15 +185,20 @@ impl EntryControls {
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub struct PrimaryExitControls {
_reserved1: B2,
#[skip]
__: B2,
pub save_debug: bool,
_reserved2: B6,
#[skip]
__: B6,
pub host_addr_space_size: bool,
_reserved3: B3,
#[skip]
__: B3,
pub load_perf_global_ctrl: bool,
_reserved4: B1,
#[skip]
__: B1,
pub ack_interrupt_onexit: bool,
_reserved5: B2,
#[skip]
__: B2,
pub save_ia32_pat: bool,
pub load_ia32_pat: bool,
pub save_ia32_efer: bool,
@@ -200,7 +219,7 @@ impl PrimaryExitControls {
pub fn read() -> Result<Self, &'static str> {
vmcs::VmcsControl32::PRIMARY_VM_EXIT_CONTROLS
.read()
.map(|value| PrimaryExitControls::from(value))
.map(PrimaryExitControls::from)
.map_err(|_| "Failed to read Primary VM Exit Controls")
}
@@ -208,3 +227,15 @@ impl PrimaryExitControls {
vmcs::VmcsControl32::PRIMARY_VM_EXIT_CONTROLS.write(u32::from(*self))
}
}
#[bitfield]
#[repr(u32)]
#[derive(Debug, Clone, Copy)]
pub struct EntryIntrInfo {
pub vector: B8,
pub typ: B3,
pub ec_available: bool,
#[skip]
__: B19,
pub valid: bool,
}

View File

@@ -42,7 +42,7 @@ impl InstructionError {
InstructionError::try_from(err).map_err(|_| "Unknown instruction error")
}
pub fn to_str(&self) -> &'static str {
pub fn to_str(self) -> &'static str {
match self {
InstructionError::NOT_AVAILABLE => "Instruction not available",
InstructionError::VMCALL_IN_VMXROOT => "VMCALL in VMX root operation",

View File

@@ -139,8 +139,8 @@ impl VmxExitReason {
VIRTUALIZED_EOI => "Virtualized EOI",
ACCESS_TO_GDTR_OR_IDTR => "Access to GDTR or IDTR",
ACCESS_TO_LDTR_OR_TR => "Access to LDTR or TR",
EPT_VIOLATION => "EPT violation",
EPT_MISCONFIGURATION => "EPT misconfiguration",
EPT_VIOLATION => "Ept violation",
EPT_MISCONFIGURATION => "Ept misconfiguration",
INVEPT => "INVEPT instruction execution",
RDTSCP => "RDTSCP instruction execution",
VMX_PREEMPTION_TIMER_EXPIRED => "VMX-preemption timer expired",

View File

@@ -26,14 +26,16 @@ pub struct SegmentRights {
pub desc_type: DescriptorType,
pub dpl: B2,
pub present: bool,
_reserved: B4,
#[skip]
__: B4,
pub avl: bool,
pub long: bool,
pub db: bool,
#[bits = 1]
pub granularity: Granularity,
pub unusable: bool,
_reserved2: B15,
#[skip]
__: B15,
}
impl Default for SegmentRights {