diff --git a/docs/seg_desc.png b/docs/seg_desc.png new file mode 100644 index 0000000..a9ee633 Binary files /dev/null and b/docs/seg_desc.png differ diff --git a/docs/vmm-reference-gdt.md b/docs/vmm-reference-gdt.md new file mode 100644 index 0000000..e756e23 --- /dev/null +++ b/docs/vmm-reference-gdt.md @@ -0,0 +1,179 @@ +# [GDT](https://github.com/codenet/vmm-reference/blob/main/src/vm-vcpu-ref/src/x86_64/gdt.rs) + +This is breif description of file [gdt.rs](https://github.com/codenet/vmm-reference/blob/main/src/vm-vcpu-ref/src/x86_64/gdt.rs) + +This module is used for building Global Descriptors Table (GDT) and writing it to Guest Memory. + +The module uses following crates- +- [`kvm-bindings`](https://github.com/rust-vmm/kvm-bindings): To use `kvm_segment` struct which is the datatype of all sregs registers (cs, ds, es, fs etc.) in `KvmVcpu` (see [file](https://github.com/codenet/vmm-reference/blob/main/src/vm-vcpu/src/vcpu/mod.rs)) +- [`vm-memory`](https://github.com/rust-vmm/vm-memory): used when writing GDT to Guest Memory + +

+Let's now start understanding the different parts of the file- +## `SegmentDescriptor` +```rs +pub struct SegmentDescriptor(pub u64); +``` +SegmentDescriptor is the class to create GDT entries and export them as `kvm_segment`. + +The segment descriptor has following structure (from Section "3.4.5 Segment Descriptors" of the [Intel Manual](https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.pdf)) +![Segment Descriptor](seg_desc.png) +

+ +Now let's look at the methods of the class- +### `from` +```rs +pub fn from(flags: u16, base: u32, limit: u32) -> Self +``` +This method creates segment descriptor from the arguments `base`, `limit` and `flags`. The descriptions of these and strcutrure of the 64 bits are the same as given in the above figure. +

+ +### Segment Descriptor Fields Extraction Methods +```rs +fn base(&self) -> u64 +fn limit(&self) -> u32 +fn g(&self) -> u8 +fn db(&self) -> u8 +fn l(&self) -> u8 +fn avl(&self) -> u8 +fn p(&self) -> u8 +fn dpl(&self) -> u8 +fn s(&self) -> u8 +fn segment_type(&self) -> u8 +``` +All the fields are explianed in the figure above. These methods extract the values of these fields from the 64 bits stored in the class. +

+ +### `create_kvm_segment` +```rs +fn create_kvm_segment(&self, table_index: usize) -> kvm_segment { + kvm_segment { + base: self.base(), + limit: self.limit(), + // The multiplication is safe because the table_index can be maximum + // `MAX_GDT_SIZE`. The conversion is safe because the result fits in u16. + selector: (table_index * 8) as u16, + type_: self.segment_type(), + present: self.p(), + dpl: self.dpl(), + db: self.db(), + s: self.s(), + l: self.l(), + g: self.g(), + avl: self.avl(), + padding: 0, + unusable: match self.p() { + 0 => 1, + _ => 0, + }, + } +} +``` +This method creates a `kvm_segment` from `SegmentDescriptor` object. + +All the fields are the same as the segment descriptor except for `selector` and `unusable`. + +- `selector` stores the 16 bit segment selector value. First 13 bits are index of the entry in GDT table and last 3 bits are flags (TI and RPL) which are set to 0 here. + - TI specifies which descriptor table to use. It's set if LDT (Local Descriptor Table) is used. + - RPL is requested Privilege Level of the selector. +- If the entry is not present then it is `unusable`. +

+ +## `Gdt` +```rs +pub struct Gdt(Vec); +``` +`Gdt` is a wrapper for creating and managing operations on the Global Descriptor Table (GDT). + +This class has methods `new`, `try_push`, `create_kvm_segment_for` and `write_to_mem`. +

+ +### `new` +```rs +pub fn new() -> Gdt +``` +This just creates empty GDT. +

+ +### `try_push` +```rs +pub fn try_push(&mut self, entry: SegmentDescriptor) -> Result<()> { + if self.0.len() >= MAX_GDT_SIZE { + return Err(Error::TooManyEntries); + } + self.0.push(entry); + Ok(()) +} +``` +This method tries to push an entry (`SegmentDescriptor` object) to the GDT and is successful if the size of GDT is less than `MAX_GDT_SIZE` (set to 213). +

+ +### `create_kvm_segment_for` +```rs +pub fn create_kvm_segment_for(&self, index: usize) -> Option +``` +This method creates and returns `kvm_segmet` for the GDT entry at given index. +

+ +### `write_to_mem` +```rs +pub fn write_to_mem(&self, mem: &Memory) -> Result<()> { + let boot_gdt_addr = GuestAddress(BOOT_GDT_OFFSET); + for (index, entry) in self.0.iter().enumerate() { + // The multiplication below cannot fail because we can have maximum 8192 entries in + // the gdt table, and 8192 * 4 (size_of::) fits in usize + let addr = mem + .checked_offset(boot_gdt_addr, index * mem::size_of::()) + .ok_or(GuestMemoryError::InvalidGuestAddress(boot_gdt_addr))?; + mem.write_obj(*entry, addr)?; + } + Ok(()) +} +``` +This method writes the GDT into the Guest Memory with GDT base at address `BOOT_GDT_OFFSET` (equals `0x500`). +

+ +### Default GDT +```rs +fn default() -> Self { + Gdt(vec![ + SegmentDescriptor::from(0, 0, 0), // NULL + SegmentDescriptor::from(0xa09b, 0, 0xfffff), // CODE + SegmentDescriptor::from(0xc093, 0, 0xfffff), // DATA + SegmentDescriptor::from(0x808b, 0, 0xfffff), // TSS + ]) +} +``` +- `Gdt` provides a default implementation ("Flat Memory Model") that can be used for setting up a vCPU for booting. +- The default implementation contains all 4 segment descriptors corresponding to null, code, data, and TSS. +- The default can beextended by using `try_push`. +

+ +## IDT +This file also provides a method `write_idt_value` to write val in the IDT Offset (equals `0x520`). +

+ +

+ +### Uses in the codebase +This file has been used in [this file](https://github.com/codenet/vmm-reference/blob/main/src/vm-vcpu/src/vcpu/mod.rs) in `configure_sregs` method of class `KvmCpu`. The modules are used for the following (in the specified order). +```rs +let gdt_table = Gdt::default(); +``` +- Create Default GDT +```rs +let code_seg = gdt_table.create_kvm_segment_for(1).unwrap(); +let data_seg = gdt_table.create_kvm_segment_for(2).unwrap(); +let tss_seg = gdt_table.create_kvm_segment_for(3).unwrap(); +``` +- Convert default segment descriptors for code, data and tss to `kvm_segment` (to be stored in `sregs`) +```rs +gdt_table.write_to_mem(guest_memory).map_err(Error::Gdt)?; +``` +- Write GDT Table into Guest Memory +```rs +write_idt_value(0, guest_memory).map_err(Error::Gdt)?; +``` +- Write IDT Offset value (default 0) + +