From 48dbe8f254dd59f603af8382e0e09f971514d106 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 14 Apr 2022 22:37:01 -0700 Subject: [PATCH 1/4] Add structures::gdt::Entry type The current documentation for the GDT often confuses entries and `Descriptor`s. For example, adding a new `Descriptor` uses a method confusingly named `add_entry`. An entry is a raw 64-bit value that is indexed by a segment selector. The `MAX` length of the GDT is a number of _entries_, not `Descriptor`s. To fix this confusion, this PR makes the following changes: - Adds a transparent `u64` newtype called `Entry`. - Updates the `GlobalDescriptorTable` documentation to correctly use `Entry` or `Descriptor` where appropriate. - Renames the `add_entry` to `append`. - This better expresses that this method might add multiple entries. - Renames `from_raw_slice` to `from_raw_entries` - Renames `as_raw_slice` to `entries` - This method now returns a slice of `Entry`s instead of `u64`s This also fixes an issue where our `assert!`s in `empty()` wouldn't trigger if the GDT was constructed from raw values. Signed-off-by: Joe Richey --- src/lib.rs | 2 +- src/structures/gdt.rs | 98 +++++++++++++++++++++++++++++-------------- testing/src/gdt.rs | 4 +- 3 files changed, 70 insertions(+), 34 deletions(-) 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..3225e273b 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -9,18 +9,42 @@ use bitflags::bitflags; #[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, Debug)] +#[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 + } +} + /// 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 +64,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,28 +85,29 @@ 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`. + /// Forms a GDT from a slice of raw [`Entry`] values. /// /// # Safety /// /// * The user must make sure that the entries are well formed /// * Panics if the provided slice has more than `MAX` entries #[inline] - pub const unsafe fn from_raw_slice(slice: &[u64]) -> Self { + pub const unsafe 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!( @@ -91,27 +116,30 @@ impl GlobalDescriptorTable { ); 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. /// - /// Panics if the GDT doesn't have enough free entries to hold the Descriptor. + /// Note that depending on the type of the [`Descriptor`] this may add either + /// one or two new entries. + /// + /// 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 +207,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 +406,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 +419,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 +428,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 +440,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 = unsafe { 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 { From 255124a08352bde1433b59e97ad6c96df74d918f Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 15 Apr 2022 02:33:53 -0700 Subject: [PATCH 2/4] Add PartialEq, Eq, and Debug impls Signed-off-by: Joe Richey --- src/structures/gdt.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 3225e273b..6ffacc84e 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -5,6 +5,7 @@ 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}; @@ -16,7 +17,7 @@ use crate::registers::segmentation::{Segment, CS, SS}; /// 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, Debug)] +#[derive(Clone, PartialEq, Eq)] #[repr(transparent)] pub struct Entry(u64); @@ -33,6 +34,13 @@ impl Entry { } } +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 From 07f7ebf44fea0b27b1d9a9b29272fbc188fa5cbf Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 15 Apr 2022 02:49:38 -0700 Subject: [PATCH 3/4] Make from_raw_entries safe Also update the documentation Signed-off-by: Joe Richey --- src/structures/gdt.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 6ffacc84e..d3ef086e0 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -106,18 +106,26 @@ impl GlobalDescriptorTable { } } - /// Forms a GDT from a slice of raw [`Entry`] values. + /// 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 #[inline] - pub const unsafe fn from_raw_entries(slice: &[u64]) -> Self { + pub const fn from_raw_entries(slice: &[u64]) -> Self { let len = slice.len(); 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" @@ -463,7 +471,7 @@ mod tests { #[test] pub fn from_entries() { let raw = [0, Flags::KERNEL_CODE64.bits(), Flags::KERNEL_DATA.bits()]; - let gdt = unsafe { GlobalDescriptorTable::<3>::from_raw_entries(&raw) }; + let gdt = GlobalDescriptorTable::<3>::from_raw_entries(&raw); assert_eq!(gdt.table.len(), 3); assert_eq!(gdt.entries().len(), 3); } From 49b5295aacca3e9d039107ba0d76816f5cc0ce60 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 15 Apr 2022 11:27:52 -0700 Subject: [PATCH 4/4] Fix nits/warnings in docs Signed-off-by: Joe Richey --- src/structures/gdt.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index d3ef086e0..c81d58137 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -118,6 +118,7 @@ impl GlobalDescriptorTable { /// * 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 fn from_raw_entries(slice: &[u64]) -> Self { let len = slice.len(); @@ -147,10 +148,10 @@ impl GlobalDescriptorTable { &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 add either - /// one or two new entries. + /// 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. #[inline]