diff --git a/src/lib.rs b/src/lib.rs index 6c32eebd6..2319dacfa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ //! and access to various system registers. #![cfg_attr(not(test), no_std)] -#![cfg_attr(feature = "const_fn", feature(const_mut_refs))] // GDT add_entry() +#![cfg_attr(feature = "const_fn", feature(const_mut_refs))] // GDT::append() #![cfg_attr(feature = "asm_const", feature(asm_const))] #![cfg_attr(feature = "abi_x86_interrupt", feature(abi_x86_interrupt))] #![cfg_attr(feature = "step_trait", feature(step_trait))] diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index bcf65d388..c81d58137 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -5,22 +5,54 @@ use crate::structures::tss::TaskStateSegment; use crate::PrivilegeLevel; use bit_field::BitField; use bitflags::bitflags; +use core::fmt; // imports for intra-doc links #[cfg(doc)] use crate::registers::segmentation::{Segment, CS, SS}; +/// 8-byte entry in a descriptor table. +/// +/// A [`GlobalDescriptorTable`] (or LDT) is an array of these entries, and +/// [`SegmentSelector`]s index into this array. Each [`Descriptor`] in the table +/// uses either 1 Entry (if it is a [`UserSegment`](Descriptor::UserSegment)) or +/// 2 Entries (if it is a [`SystemSegment`](Descriptor::SystemSegment)). This +/// type exists to give users access to the raw entry bits in a GDT. +#[derive(Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct Entry(u64); + +impl Entry { + // Create a new Entry from a raw value. + const fn new(raw: u64) -> Self { + Self(raw) + } + + /// The raw bits for this entry. Depending on the [`Descriptor`] type, these + /// bits may correspond to those in [`DescriptorFlags`]. + pub fn raw(&self) -> u64 { + self.0 + } +} + +impl fmt::Debug for Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Display inner value as hex + write!(f, "Entry({:#018x})", self.raw()) + } +} + /// A 64-bit mode global descriptor table (GDT). /// /// In 64-bit mode, segmentation is not supported. The GDT is used nonetheless, for example for /// switching between user and kernel mode or for loading a TSS. /// /// The GDT has a fixed maximum size given by the `MAX` const generic parameter. -/// Trying to add more entries than this maximum via [`GlobalDescriptorTable::add_entry`] -/// will panic. +/// Overflowing this limit by adding too many [`Descriptor`]s via +/// [`GlobalDescriptorTable::append`] will panic. /// /// You do **not** need to add a null segment descriptor yourself - this is already done -/// internally. This means you can add up to `MAX - 1` additional [`Descriptor`]s to -/// this table. +/// internally. This means you can add up to `MAX - 1` additional [`Entry`]s to +/// this table. Note that some [`Descriptor`]s may take up 2 [`Entry`]s. /// /// Data segment registers in ring 0 can be loaded with the null segment selector. When running in /// ring 3, the `ss` register must point to a valid data segment which can be obtained through the @@ -40,16 +72,16 @@ use crate::registers::segmentation::{Segment, CS, SS}; /// use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor}; /// /// let mut gdt = GlobalDescriptorTable::new(); -/// gdt.add_entry(Descriptor::kernel_code_segment()); -/// gdt.add_entry(Descriptor::user_code_segment()); -/// gdt.add_entry(Descriptor::user_data_segment()); +/// gdt.append(Descriptor::kernel_code_segment()); +/// gdt.append(Descriptor::user_code_segment()); +/// gdt.append(Descriptor::user_data_segment()); /// /// // Add entry for TSS, call gdt.load() then update segment registers /// ``` #[derive(Debug, Clone)] pub struct GlobalDescriptorTable { - table: [u64; MAX], + table: [Entry; MAX], len: usize, } @@ -61,57 +93,70 @@ impl GlobalDescriptorTable { } impl GlobalDescriptorTable { - /// Creates an empty GDT which can hold `MAX` number of [`Descriptor`]s. + /// Creates an empty GDT which can hold `MAX` number of [`Entry`]s. #[inline] pub const fn empty() -> Self { // TODO: Replace with compiler error when feature(generic_const_exprs) is stable. assert!(MAX > 0, "A GDT cannot have 0 entries"); assert!(MAX <= (1 << 13), "A GDT can only have at most 2^13 entries"); + const NULL: Entry = Entry::new(0); Self { - table: [0; MAX], + table: [NULL; MAX], len: 1, } } /// Forms a GDT from a slice of `u64`. /// - /// # Safety + /// This method allows for creation of a GDT with malformed or invalid + /// entries. However, it is safe because loading a GDT with invalid + /// entires doesn't do anything until those entries are used. For example, + /// [`CS::set_reg`] and [`load_tss`](crate::instructions::tables::load_tss) + /// are both unsafe for this reason. /// - /// * The user must make sure that the entries are well formed - /// * Panics if the provided slice has more than `MAX` entries + /// Panics if: + /// * the provided slice has more than `MAX` entries + /// * the provided slice is empty + /// * the first entry is not zero + #[cfg_attr(not(feature = "instructions"), allow(rustdoc::broken_intra_doc_links))] #[inline] - pub const unsafe fn from_raw_slice(slice: &[u64]) -> Self { + pub const fn from_raw_entries(slice: &[u64]) -> Self { let len = slice.len(); - let mut table = [0; MAX]; + let mut table = Self::empty().table; let mut idx = 0; + assert!(len > 0, "cannot initialize GDT with empty slice"); + assert!(slice[0] == 0, "first GDT entry must be zero"); assert!( len <= MAX, "cannot initialize GDT with slice exceeding the maximum length" ); while idx < len { - table[idx] = slice[idx]; + table[idx] = Entry::new(slice[idx]); idx += 1; } Self { table, len } } - /// Get a reference to the internal table. + /// Get a reference to the internal [`Entry`] table. /// - /// The resulting slice may contain system descriptors, which span two `u64`s. + /// The resulting slice may contain system descriptors, which span two [`Entry`]s. #[inline] - pub fn as_raw_slice(&self) -> &[u64] { + pub fn entries(&self) -> &[Entry] { &self.table[..self.len] } - /// Adds the given segment descriptor to the GDT, returning the segment selector. + /// Appends the given segment descriptor to the GDT, returning the segment selector. + /// + /// Note that depending on the type of the [`Descriptor`] this may append + /// either one or two new [`Entry`]s to the table. /// - /// Panics if the GDT doesn't have enough free entries to hold the Descriptor. + /// Panics if the GDT doesn't have enough free entries. #[inline] #[cfg_attr(feature = "const_fn", rustversion::attr(all(), const))] - pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector { + pub fn append(&mut self, entry: Descriptor) -> SegmentSelector { let index = match entry { Descriptor::UserSegment(value) => { if self.len > self.table.len().saturating_sub(1) { @@ -179,7 +224,7 @@ impl GlobalDescriptorTable { #[cfg_attr(feature = "const_fn", rustversion::attr(all(), const))] fn push(&mut self, value: u64) -> usize { let index = self.len; - self.table[index] = value; + self.table[index] = Entry::new(value); self.len += 1; index } @@ -378,11 +423,11 @@ mod tests { // Makes a GDT that has two free slots fn make_six_entry_gdt() -> GlobalDescriptorTable { let mut gdt = GlobalDescriptorTable::new(); - gdt.add_entry(Descriptor::kernel_code_segment()); - gdt.add_entry(Descriptor::kernel_data_segment()); - gdt.add_entry(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits())); - gdt.add_entry(Descriptor::user_data_segment()); - gdt.add_entry(Descriptor::user_code_segment()); + gdt.append(Descriptor::kernel_code_segment()); + gdt.append(Descriptor::kernel_data_segment()); + gdt.append(Descriptor::UserSegment(DescriptorFlags::USER_CODE32.bits())); + gdt.append(Descriptor::user_data_segment()); + gdt.append(Descriptor::user_code_segment()); assert_eq!(gdt.len, 6); gdt } @@ -391,7 +436,7 @@ mod tests { fn make_full_gdt() -> GlobalDescriptorTable { let mut gdt = make_six_entry_gdt(); - gdt.add_entry(Descriptor::tss_segment(&TSS)); + gdt.append(Descriptor::tss_segment(&TSS)); assert_eq!(gdt.len, 8); gdt } @@ -400,9 +445,9 @@ mod tests { pub fn push_max_segments() { // Make sure we don't panic with user segments let mut gdt = make_six_entry_gdt(); - gdt.add_entry(Descriptor::user_data_segment()); + gdt.append(Descriptor::user_data_segment()); assert_eq!(gdt.len, 7); - gdt.add_entry(Descriptor::user_data_segment()); + gdt.append(Descriptor::user_data_segment()); assert_eq!(gdt.len, 8); // Make sure we don't panic with system segments let _ = make_full_gdt(); @@ -412,15 +457,23 @@ mod tests { #[should_panic] pub fn panic_user_segment() { let mut gdt = make_full_gdt(); - gdt.add_entry(Descriptor::user_data_segment()); + gdt.append(Descriptor::user_data_segment()); } #[test] #[should_panic] pub fn panic_system_segment() { let mut gdt = make_six_entry_gdt(); - gdt.add_entry(Descriptor::user_data_segment()); + gdt.append(Descriptor::user_data_segment()); // We have one free slot, but the GDT requires two - gdt.add_entry(Descriptor::tss_segment(&TSS)); + gdt.append(Descriptor::tss_segment(&TSS)); + } + + #[test] + pub fn from_entries() { + let raw = [0, Flags::KERNEL_CODE64.bits(), Flags::KERNEL_DATA.bits()]; + let gdt = GlobalDescriptorTable::<3>::from_raw_entries(&raw); + assert_eq!(gdt.table.len(), 3); + assert_eq!(gdt.entries().len(), 3); } } diff --git a/testing/src/gdt.rs b/testing/src/gdt.rs index 14fc74023..f4d2643c7 100644 --- a/testing/src/gdt.rs +++ b/testing/src/gdt.rs @@ -20,8 +20,8 @@ lazy_static! { }; static ref GDT: (SingleUseCell, Selectors) = { let mut gdt = GlobalDescriptorTable::new(); - let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); - let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); + let code_selector = gdt.append(Descriptor::kernel_code_segment()); + let tss_selector = gdt.append(Descriptor::tss_segment(&TSS)); ( SingleUseCell::new(gdt), Selectors {