Skip to content

Commit

Permalink
Merge pull request #309 from rust-osdev/fix-307
Browse files Browse the repository at this point in the history
Move segment types into a new `registers::segmentation` module
josephlr authored Sep 20, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
2 parents 05b5f4f + a280bbe commit 022598a
Showing 7 changed files with 184 additions and 162 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -77,6 +77,12 @@ jobs:
args: --no-default-features --features external_asm,instructions
if: runner.os != 'Windows'

- name: "Run cargo doc without default features"
uses: actions-rs/cargo@v1
with:
command: doc
args: --no-default-features

- name: "Run cargo build for stable without instructions"
uses: actions-rs/cargo@v1
with:
101 changes: 1 addition & 100 deletions src/instructions/segmentation.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,12 @@
//! Provides functions to read and write segment registers.
#[cfg(doc)]
use crate::{
registers::control::Cr4Flags,
structures::gdt::{Descriptor, GlobalDescriptorTable},
};
pub use crate::registers::segmentation::{Segment, Segment64, CS, DS, ES, FS, GS, SS};
use crate::{
registers::model_specific::{FsBase, GsBase, Msr},
structures::gdt::SegmentSelector,
VirtAddr,
};

/// An x86 segment
///
/// Segment registers on x86 are 16-bit [`SegmentSelector`]s, which index into
/// the [`GlobalDescriptorTable`]. The corresponding GDT entry is used to
/// configure the segment itself. Note that most segmentation functionality is
/// disabled in 64-bit mode. See the individual segments for more information.
pub trait Segment {
/// Returns the current value of the segment register.
fn get_reg() -> SegmentSelector;
/// Reload the segment register. Depending on the segment, this may also
/// reconfigure the corresponding segment.
///
/// ## Safety
///
/// This function is unsafe because the caller must ensure that `sel`
/// is a valid segment descriptor, and that reconfiguring the segment will
/// not cause undefined behavior.
unsafe fn set_reg(sel: SegmentSelector);
}

/// An x86 segment which is actually used in 64-bit mode
///
/// While most segments are unused in 64-bit mode, the FS and GS segments are
/// still partially used. Only the 64-bit segment base address is used, and this
/// address can be set via the GDT, or by using the `FSGSBASE` instructions.
pub trait Segment64: Segment {
/// MSR containing the segment base. This MSR can be used to set the base
/// when [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is **not** set.
const BASE: Msr;
/// Reads the segment base address
///
/// ## Exceptions
///
/// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`.
fn read_base() -> VirtAddr;
/// Writes the segment base address
///
/// ## Exceptions
///
/// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`.
///
/// ## Safety
///
/// The caller must ensure that this write operation has no unsafe side
/// effects, as the segment base address might be in use.
unsafe fn write_base(base: VirtAddr);
}

macro_rules! get_reg_impl {
($name:literal, $asm_get:ident) => {
fn get_reg() -> SegmentSelector {
@@ -120,14 +68,6 @@ macro_rules! segment64_impl {
};
}

/// Code Segment
///
/// The segment base and limit are unused in 64-bit mode. Only the L (long), D
/// (default operation size), and DPL (descriptor privilege-level) fields of the
/// descriptor are recognized. So changing the segment register can be used to
/// change privilege level or enable/disable long mode.
#[derive(Debug)]
pub struct CS;
impl Segment for CS {
get_reg_impl!("cs", x86_64_asm_get_cs);

@@ -157,50 +97,11 @@ impl Segment for CS {
}
}

/// Stack Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
/// However, in ring 3, the SS register still has to point to a valid
/// [`Descriptor`] (it cannot be zero). This means a user-mode read/write
/// segment descriptor must be present in the GDT.
///
/// This register is also set by the `syscall`/`sysret` and
/// `sysenter`/`sysexit` instructions (even on 64-bit transitions). This is to
/// maintain symmetry with 32-bit transitions where setting SS actually will
/// actually have an effect.
#[derive(Debug)]
pub struct SS;
segment_impl!(SS, "ss", x86_64_asm_get_ss, x86_64_asm_load_ss);

/// Data Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
#[derive(Debug)]
pub struct DS;
segment_impl!(DS, "ds", x86_64_asm_get_ds, x86_64_asm_load_ds);

/// ES Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
#[derive(Debug)]
pub struct ES;
segment_impl!(ES, "es", x86_64_asm_get_es, x86_64_asm_load_es);

/// FS Segment
///
/// Only base is used in 64-bit mode, see [`Segment64`]. This is often used in
/// user-mode for Thread-Local Storage (TLS).
#[derive(Debug)]
pub struct FS;
segment_impl!(FS, "fs", x86_64_asm_get_fs, x86_64_asm_load_fs);
segment64_impl!(FS, "fs", FsBase, x86_64_asm_rdfsbase, x86_64_asm_wrfsbase);

/// GS Segment
///
/// Only base is used in 64-bit mode, see [`Segment64`]. In kernel-mode, the GS
/// base often points to a per-cpu kernel data structure.
#[derive(Debug)]
pub struct GS;
segment_impl!(GS, "gs", x86_64_asm_get_gs, x86_64_asm_load_gs);
segment64_impl!(GS, "gs", GsBase, x86_64_asm_rdgsbase, x86_64_asm_wrgsbase);

3 changes: 0 additions & 3 deletions src/registers/control.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
//! Functions to read and write control registers.
pub use super::model_specific::{Efer, EferFlags};
#[cfg(doc)]
use crate::{registers::rflags::RFlags, structures::paging::PageTableFlags};

use bitflags::bitflags;

/// Various control flags modifying the basic operation of the CPU.
1 change: 1 addition & 0 deletions src/registers/mod.rs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
pub mod control;
pub mod model_specific;
pub mod rflags;
pub mod segmentation;
pub mod xcontrol;

#[cfg(feature = "instructions")]
25 changes: 17 additions & 8 deletions src/registers/model_specific.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
//! Functions to read and write model specific registers.
#[cfg(doc)]
use crate::{
instructions::segmentation::{Segment64, FS, GS},
registers::control::Cr4Flags,
};

use bitflags::bitflags;
// imports for intra doc links
#[cfg(doc)]
use crate::registers::segmentation::{FS, GS};

/// A model specific register.
#[derive(Debug)]
@@ -30,13 +27,19 @@ pub struct FsBase;

/// [GS].Base Model Specific Register.
///
/// [`GS::swap`] swaps this register with [`KernelGsBase`].
#[cfg_attr(
feature = "instructions",
doc = "[`GS::swap`] swaps this register with [`KernelGsBase`]."
)]
#[derive(Debug)]
pub struct GsBase;

/// KernelGsBase Model Specific Register.
///
/// [`GS::swap`] swaps this register with [`GsBase`].
#[cfg_attr(
feature = "instructions",
doc = "[`GS::swap`] swaps this register with [`GsBase`]."
)]
#[derive(Debug)]
pub struct KernelGsBase;

@@ -118,6 +121,12 @@ mod x86_64 {
use crate::PrivilegeLevel;
use bit_field::BitField;
use core::convert::TryInto;
// imports for intra doc links
#[cfg(doc)]
use crate::registers::{
control::Cr4Flags,
segmentation::{Segment, Segment64, CS, SS},
};

impl Msr {
/// Read 64 bits msr register.
155 changes: 155 additions & 0 deletions src/registers/segmentation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
//! Abstractions for segment registers.
use super::model_specific::Msr;
use crate::{PrivilegeLevel, VirtAddr};
use bit_field::BitField;
use core::fmt;
// imports for intra doc links
#[cfg(doc)]
use crate::registers::control::Cr4Flags;

/// An x86 segment
///
/// Segment registers on x86 are 16-bit [`SegmentSelector`]s, which index into
/// the [`GlobalDescriptorTable`](crate::structures::gdt::GlobalDescriptorTable). The
/// corresponding GDT entry is used to
/// configure the segment itself. Note that most segmentation functionality is
/// disabled in 64-bit mode. See the individual segments for more information.
pub trait Segment {
/// Returns the current value of the segment register.
fn get_reg() -> SegmentSelector;
/// Reload the segment register. Depending on the segment, this may also
/// reconfigure the corresponding segment.
///
/// ## Safety
///
/// This function is unsafe because the caller must ensure that `sel`
/// is a valid segment descriptor, and that reconfiguring the segment will
/// not cause undefined behavior.
unsafe fn set_reg(sel: SegmentSelector);
}

/// An x86 segment which is actually used in 64-bit mode
///
/// While most segments are unused in 64-bit mode, the FS and GS segments are
/// still partially used. Only the 64-bit segment base address is used, and this
/// address can be set via the GDT, or by using the `FSGSBASE` instructions.
pub trait Segment64: Segment {
/// MSR containing the segment base. This MSR can be used to set the base
/// when [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is **not** set.
const BASE: Msr;
/// Reads the segment base address
///
/// ## Exceptions
///
/// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`.
fn read_base() -> VirtAddr;
/// Writes the segment base address
///
/// ## Exceptions
///
/// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`.
///
/// ## Safety
///
/// The caller must ensure that this write operation has no unsafe side
/// effects, as the segment base address might be in use.
unsafe fn write_base(base: VirtAddr);
}

/// Specifies which element to load into a segment from
/// descriptor tables (i.e., is a index to LDT or GDT table
/// with some additional flags).
///
/// See Intel 3a, Section 3.4.2 "Segment Selectors"
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct SegmentSelector(pub u16);

impl SegmentSelector {
/// Creates a new SegmentSelector
///
/// # Arguments
/// * `index`: index in GDT or LDT array (not the offset)
/// * `rpl`: the requested privilege level
#[inline]
pub const fn new(index: u16, rpl: PrivilegeLevel) -> SegmentSelector {
SegmentSelector(index << 3 | (rpl as u16))
}

/// Returns the GDT index.
#[inline]
pub fn index(self) -> u16 {
self.0 >> 3
}

/// Returns the requested privilege level.
#[inline]
pub fn rpl(self) -> PrivilegeLevel {
PrivilegeLevel::from_u16(self.0.get_bits(0..2))
}

/// Set the privilege level for this Segment selector.
#[inline]
pub fn set_rpl(&mut self, rpl: PrivilegeLevel) {
self.0.set_bits(0..2, rpl as u16);
}
}

impl fmt::Debug for SegmentSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("SegmentSelector");
s.field("index", &self.index());
s.field("rpl", &self.rpl());
s.finish()
}
}

/// Code Segment
///
/// The segment base and limit are unused in 64-bit mode. Only the L (long), D
/// (default operation size), and DPL (descriptor privilege-level) fields of the
/// descriptor are recognized. So changing the segment register can be used to
/// change privilege level or enable/disable long mode.
#[derive(Debug)]
pub struct CS;

/// Stack Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
/// However, in ring 3, the SS register still has to point to a valid
/// [`Descriptor`](crate::structures::gdt::Descriptor) (it cannot be zero). This
/// means a user-mode read/write segment descriptor must be present in the GDT.
///
/// This register is also set by the `syscall`/`sysret` and
/// `sysenter`/`sysexit` instructions (even on 64-bit transitions). This is to
/// maintain symmetry with 32-bit transitions where setting SS actually will
/// actually have an effect.
#[derive(Debug)]
pub struct SS;

/// Data Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
#[derive(Debug)]
pub struct DS;

/// ES Segment
///
/// Entirely unused in 64-bit mode; setting the segment register does nothing.
#[derive(Debug)]
pub struct ES;

/// FS Segment
///
/// Only base is used in 64-bit mode, see [`Segment64`]. This is often used in
/// user-mode for Thread-Local Storage (TLS).
#[derive(Debug)]
pub struct FS;

/// GS Segment
///
/// Only base is used in 64-bit mode, see [`Segment64`]. In kernel-mode, the GS
/// base often points to a per-cpu kernel data structure.
#[derive(Debug)]
pub struct GS;
55 changes: 4 additions & 51 deletions src/structures/gdt.rs
Original file line number Diff line number Diff line change
@@ -1,60 +1,13 @@
//! Types for the Global Descriptor Table and segment selectors.
#[cfg(doc)]
use crate::instructions::segmentation::{Segment, CS, SS};
pub use crate::registers::segmentation::SegmentSelector;
use crate::structures::tss::TaskStateSegment;
use crate::PrivilegeLevel;
use bit_field::BitField;
use bitflags::bitflags;
use core::fmt;

/// Specifies which element to load into a segment from
/// descriptor tables (i.e., is a index to LDT or GDT table
/// with some additional flags).
///
/// See Intel 3a, Section 3.4.2 "Segment Selectors"
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct SegmentSelector(pub u16);

impl SegmentSelector {
/// Creates a new SegmentSelector
///
/// # Arguments
/// * `index`: index in GDT or LDT array (not the offset)
/// * `rpl`: the requested privilege level
#[inline]
pub const fn new(index: u16, rpl: PrivilegeLevel) -> SegmentSelector {
SegmentSelector(index << 3 | (rpl as u16))
}

/// Returns the GDT index.
#[inline]
pub fn index(self) -> u16 {
self.0 >> 3
}

/// Returns the requested privilege level.
#[inline]
pub fn rpl(self) -> PrivilegeLevel {
PrivilegeLevel::from_u16(self.0.get_bits(0..2))
}

/// Set the privilege level for this Segment selector.
#[inline]
pub fn set_rpl(&mut self, rpl: PrivilegeLevel) {
self.0.set_bits(0..2, rpl as u16);
}
}

impl fmt::Debug for SegmentSelector {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("SegmentSelector");
s.field("index", &self.index());
s.field("rpl", &self.rpl());
s.finish()
}
}
// imports for intra-doc links
#[cfg(doc)]
use crate::registers::segmentation::{Segment, CS, SS};

/// A 64-bit mode global descriptor table (GDT).
///

0 comments on commit 022598a

Please sign in to comment.