1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
//! Lazy loader and pager for guest virtual machine.
//!
//! ## Background
//! Lazy loading is a technique that delays resource allocations until the resource is required.
//! Lazy paging in project 3 implements the principle of lazy loading,
//! which involves delaying the reading from file and loading of guest OS's memory (such as text section of guest OS) into the host machine
//! until a specific physical page of the guest is required.
//!
//! Lazy paging in project 3 enhances the performance of the initial booting process since there is no need to read and load all the pages
//! guest OS at once. This approache also helps the reducing the memory usage of the guest OS by avoiding the allocation of memory which is not used
//!
//! In KeV projects, we will launch as KeOS as a guest operting system for the simplicity.
//! The `build.rs` automatically build the guest KeOS (gKeOS) from the `guests/project3/`
//! and build the file system from the `rootfs/` with the compiled gKeOS.
//!
//! KeOS's file system is rough. It only supports file without grow.
//! There is no more abstraction than file such as directory or symbolic link.
//!
//! ### Executable and Linkage Format
//! The ELF (Executable and Linkable Format) is a file format for executable programs in Unix-based operating systems.
//!
//! The ELF header contains information about the ELF metadata, type of file (executable, shared library, object file), the architecture (x86_64, ARM, ...),
//! the entry point (where execution should begin), and others.
//! In summary, ELF header contains the following sections:
//! * e_type: Type of tile
//! * e_machine: Set of the machine instrutions (SPARC, x86_64, ARM, MIPS...)
//! * e_version: Version of the elf (default is 1)
//! * e_entry: Start entry of the program
//! * e_phoff: Start offset of the program header table
//! * e_phnum: Entry size of the program header
//! ...
//!
//! The ELF Program header (PHDR) contains the information about the program's memory segments (sections of memory allocated for different parts of the program),
//! including their virtual address, physical address, file offsets, sizes, and access permissions.
//! In summary, the Program header contains the following sections:
//! * p_type: Type of the program header
//! * p_offset: Offset in file
//! * p_vaddr: Virtual address to be loaded
//! * p_paddr: Physical address to be loaded
//! * p_filesz: Size on file
//! * p_memsz: Size in memory
//! * p_flags: Flags for Read, Write, Execute
//!
//! The operating system (Hypervisor) loads and executes the program (Guest OS) by parsing the ELF format reading from a file.
//! The operating system should parse the ELF headers to locate and load the program's memory, setup the program's execution environment, and
//! begin executing the program from its entry point.
//!
//! ## Tasks
//! ### Translate kernel entry into Physical Address
//! The initial step to enable the lazy pager is to parse the kernel and populate the [`KernelVmPager`] struct.
//! Unlike the user level ELF program, kernel loading operates on physical address.
//! In this task, you must have to find the entry point of the kernel to be used for initial entry point for the guest kernel.
//! The physical address of the kernel entry point can be obtained by subtracting the virtual address of the [`Phdr`] from the kernel entry address [`ELF::entry()`].
//!
//! ### Load phdr to loader
//! The next step to have to implement is the [`load_phdr`] to enable the registeration of loaders that map physicall address in the [`Phdr`] to the pager.
//! The implementation requires reading from the kernel image file through `kernel.peeker().file` starting from the specified page offset
//! to page offset + size. Page offset and the size can be obtained from the [`Phdr`].
//! See the [`File`] for the apis to operate with the file system.
//!
//! ### Load page to extended page table
//! Lastly, you have to implement [`load_page`] that called on EPT violation.
//! [`load_page`] maps a page to the extended page table with permission set to READ, WRITE, and EXECUTABLE.
//! You MUST consider the case that multiple cores trigger EPT violations on the same physical page.
//!
//! [`File`]: keos::fs::File
//! [`ELF`]: project3::keos_vm::elf::ELF
//! [`Phdr`]: project3::keos_vm::elf::Phdr
//! [`ELF::entry()`]: project3::keos_vm::elf::ELF::entry
//! [`map_page`]: KernelVmPager::map_page
use crate::{
ept::{EptMappingError, EptPteFlags, ExtendedPageTable, Permission},
keos_vm::elf::{PType, Peeker, Phdr, ELF},
};
use alloc::{collections::BTreeMap, sync::Arc, vec::Vec};
use keos::{
addressing::{Pa, PAGE_MASK},
fs::{self, File},
mm::Page,
spin_lock::SpinLock,
};
use kev::{
vcpu::VmexitResult,
vm::{Gpa, Gva},
vmcs::{ActiveVmcs, ExitReason},
VmError,
};
struct FilePeeker {
file: File,
}
impl Peeker for FilePeeker {
type Error = fs::Error;
fn peek_bytes(&self, pos: usize, slice: &mut [u8]) -> Result<(), Self::Error> {
self.file.read(pos, slice).map(|_| ())
}
}
pub type PageLoader = Arc<dyn Fn(&mut Page) -> bool + Send + Sync>;
/// Vm Pager of the kernel.
pub struct KernelVmPager {
ept: ExtendedPageTable,
pub loaders: BTreeMap<Gpa, PageLoader>,
entry: usize,
}
impl KernelVmPager {
/// Create a new vm pager from the kernel image.
pub fn from_image(kernel: File, ram_in_kb: usize) -> Option<Self> {
let kernel = Arc::new(ELF::from_peeker(FilePeeker { file: kernel }).ok()?);
let mut pager = Self {
ept: ExtendedPageTable::new(),
loaders: BTreeMap::new(),
entry: 0,
};
for phdr in kernel.phdrs() {
if let Ok(p) = phdr {
if p.type_() == PType::Load {
pager.load_phdr(p, &kernel).then(|| ())?;
}
}
}
// Parse kernel entry from elf as a physical address.
pager.entry = todo!();
// Fill usable mems.
let empty_pager = Arc::new(|_: &mut Page| true);
let mut remainder = (ram_in_kb * 1024) / 4096;
unsafe {
let (kernel_start, kernel_end) = (
pager.loaders.keys().next().unwrap().into_usize(),
pager.loaders.keys().last().unwrap().into_usize() + 0x1000,
);
remainder -= (kernel_end - kernel_start) / 0x1000;
for gpa in (0..kernel_start).step_by(0x1000) {
if remainder == 0 {
break;
}
pager
.map_page(Gpa::new(gpa).unwrap(), empty_pager.clone())
.then(|| ())?;
remainder -= 1;
}
let mut gpa = kernel_end;
while remainder > 0 {
if gpa == 0xbffda000 {
// Hole for mmio.
gpa = 0x1_0000_0000;
continue;
}
pager
.map_page(Gpa::new(gpa).unwrap(), empty_pager.clone())
.then(|| ())?;
remainder -= 1;
gpa += 0x1000;
}
}
Some(pager)
}
/// Setup the page for mbinfo.
pub fn finalize_mem(&mut self) -> Option<usize> {
let mut section_start = self.loaders.keys().next().unwrap();
let mut section_end = section_start;
let mut sections = Vec::new();
for gpa in self.loaders.keys() {
if *gpa == *section_end + 0x1000 {
section_end = gpa;
} else {
if section_start != section_end {
sections.push(*section_start..*section_end + 0x1000);
}
section_start = gpa;
section_end = gpa;
}
}
if section_start != section_end {
sections.push(*section_start..*section_end + 0x1000);
}
assert!(self.loaders.remove(&Gpa::new(0).unwrap()).is_some());
pub struct MbiWriter {
page: Page,
pos: usize,
}
impl MbiWriter {
fn new() -> Option<Self> {
Some(Self {
page: Page::new()?,
pos: 4,
})
}
fn write_u32(&mut self, b: u32) -> &mut Self {
unsafe {
self.page.inner_mut()[self.pos..self.pos + 4].copy_from_slice(&b.to_le_bytes());
}
self.pos += 4;
self
}
fn write_u64(&mut self, b: u64) -> &mut Self {
unsafe {
self.page.inner_mut()[self.pos..self.pos + 8].copy_from_slice(&b.to_le_bytes());
}
self.pos += 8;
self
}
fn write_memory_info_head(&mut self, entry_cnt: u32) -> &mut Self {
self.write_u32(6) // Field.ty
.write_u32(16 + entry_cnt * 24) // Field.size
.write_u32(24) // stride
.write_u32(0) // version
}
fn write_memory_info(&mut self, base_addr: u64, length: u64, ty: u32) -> &mut Self {
self.write_u64(base_addr)
.write_u64(length)
.write_u32(ty)
.write_u32(0)
}
fn finalize(self) -> Page {
let Self { mut page, pos } = self;
unsafe {
page.inner_mut()[0..4].copy_from_slice(&(pos as u32).to_le_bytes());
}
page
}
}
let mut writer = MbiWriter::new()?;
writer
.write_u32(0) // MutiBootInfo2._rev
.write_memory_info_head(sections.len() as u32);
for s in sections.into_iter() {
unsafe {
writer.write_memory_info(
s.start.into_usize() as u64,
(s.end.into_usize() - s.start.into_usize()) as u64,
1, // Usable.
);
}
}
self.ept
.map(Gpa::new(0).unwrap(), writer.finalize(), Permission::all())
.expect("Failed to insert page for multiboot info");
Some(0)
}
// Register loaders of the PAs in the phdr to the pager.
//
// Return true if success. Otherwise, return false.
fn load_phdr(&mut self, phdr: Phdr, kernel: &Arc<ELF<FilePeeker>>) -> bool {
// Hint:
// - You can access to the file through [`kernel.peeker().file`].
todo!()
}
/// Get a entry point of the this kernel.
#[inline]
pub fn entry(&self) -> usize {
self.entry
}
/// Attach a mmio page at `gpa`.
#[inline]
pub fn map_mmio_page(&mut self, gpa: Gpa, page: Page) -> Result<(), EptMappingError> {
self.ept
.map(gpa, page, Permission::READ | Permission::EXECUTABLE)
}
/// Attach a page at `gpa`.
#[inline]
pub fn map_page(&mut self, gpa: Gpa, loader: PageLoader) -> bool {
assert_eq!(unsafe { gpa.into_usize() } & 0xfff, 0);
assert!(self.loaders.insert(gpa, loader).is_none());
true
}
/// Get ept ptr of the pager.
#[inline]
pub fn ept_ptr(&self) -> Pa {
self.ept.pa()
}
/// Map page to the ept with permission READ, WRITE, and EXECUTABLE.
fn load_page(&mut self, gpa: Gpa) -> bool {
assert_eq!(unsafe { gpa.into_usize() } & 0xfff, 0);
todo!()
}
/// Handle the ept violation and load the corresponding page.
pub fn try_lazy_paging(&mut self, reason: ExitReason) -> Result<VmexitResult, VmError> {
if let kev::vmcs::BasicExitReason::EptViolation { fault_addr, .. } =
reason.get_basic_reason()
{
if let Some(gpa) = fault_addr {
let gpa = Gpa::new(unsafe { gpa.into_usize() } & !PAGE_MASK).unwrap();
if self.load_page(gpa) {
return Ok(VmexitResult::Ok);
}
}
}
Err(VmError::HandleVmexitFailed(reason))
}
}
impl kev::Probe for KernelVmPager {
fn gpa2hpa(&self, vmcs: &ActiveVmcs, gpa: Gpa) -> Option<Pa> {
self.ept.gpa2hpa(vmcs, gpa)
}
fn gva2hpa(&self, vmcs: &ActiveVmcs, gva: Gva) -> Option<Pa> {
self.ept.gva2hpa(vmcs, gva)
}
}
pub struct Probe<'a> {
pub inner: &'a SpinLock<KernelVmPager>,
}
impl<'a> kev::Probe for Probe<'a> {
fn gpa2hpa(&self, vmcs: &ActiveVmcs, gpa: Gpa) -> Option<Pa> {
self.inner.lock().gpa2hpa(vmcs, gpa)
}
fn gva2hpa(&self, vmcs: &ActiveVmcs, gva: Gva) -> Option<Pa> {
self.inner.lock().gva2hpa(vmcs, gva)
}
}