Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add structures::gdt::Entry type #380

Merged
merged 4 commits into from
Apr 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))]
Expand Down
121 changes: 87 additions & 34 deletions src/structures/gdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<const MAX: usize = 8> {
table: [u64; MAX],
table: [Entry; MAX],
len: usize,
}

Expand All @@ -61,57 +93,70 @@ impl GlobalDescriptorTable {
}

impl<const MAX: usize> GlobalDescriptorTable<MAX> {
/// 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)
josephlr marked this conversation as resolved.
Show resolved Hide resolved
/// 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 {
phil-opp marked this conversation as resolved.
Show resolved Hide resolved
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 {
josephlr marked this conversation as resolved.
Show resolved Hide resolved
let index = match entry {
Descriptor::UserSegment(value) => {
if self.len > self.table.len().saturating_sub(1) {
Expand Down Expand Up @@ -179,7 +224,7 @@ impl<const MAX: usize> GlobalDescriptorTable<MAX> {
#[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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand All @@ -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();
Expand All @@ -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);
}
}
4 changes: 2 additions & 2 deletions testing/src/gdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ lazy_static! {
};
static ref GDT: (SingleUseCell<GlobalDescriptorTable>, 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 {
Expand Down