diff --git a/src/asm/asm.s b/src/asm/asm.s index 16f11c6c1..d679108bf 100644 --- a/src/asm/asm.s +++ b/src/asm/asm.s @@ -173,6 +173,36 @@ _x86_64_asm_load_gs: mov %di, %gs retq +.global _x86_64_asm_get_ss +.p2align 4 +_x86_64_asm_get_ss: + mov %ss, %ax + retq + +.global _x86_64_asm_get_ds +.p2align 4 +_x86_64_asm_get_ds: + mov %ds, %ax + retq + +.global _x86_64_asm_get_es +.p2align 4 +_x86_64_asm_get_es: + mov %es, %ax + retq + +.global _x86_64_asm_get_fs +.p2align 4 +_x86_64_asm_get_fs: + mov %fs, %ax + retq + +.global _x86_64_asm_get_gs +.p2align 4 +_x86_64_asm_get_gs: + mov %gs, %ax + retq + .global _x86_64_asm_swapgs .p2align 4 _x86_64_asm_swapgs: diff --git a/src/asm/mod.rs b/src/asm/mod.rs index 57ec996b8..57dc54799 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -114,6 +114,36 @@ extern "C" { )] pub(crate) fn x86_64_asm_load_gs(sel: u16); + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_get_ss" + )] + pub(crate) fn x86_64_asm_get_ss() -> u16; + + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_get_ds" + )] + pub(crate) fn x86_64_asm_get_ds() -> u16; + + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_get_es" + )] + pub(crate) fn x86_64_asm_get_es() -> u16; + + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_get_fs" + )] + pub(crate) fn x86_64_asm_get_fs() -> u16; + + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_get_gs" + )] + pub(crate) fn x86_64_asm_get_gs() -> u16; + #[cfg_attr( any(target_env = "gnu", target_env = "musl"), link_name = "_x86_64_asm_swapgs" diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 7de0fbb59..2ca05ac83 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -1,210 +1,305 @@ //! Provides functions to read and write segment registers. -use crate::structures::gdt::SegmentSelector; +#[cfg(docsrs)] +use crate::{ + registers::control::Cr4Flags, + structures::gdt::{Descriptor, GlobalDescriptorTable}, +}; +use crate::{ + registers::model_specific::{FsBase, GsBase, Msr}, + structures::gdt::SegmentSelector, + VirtAddr, +}; -/// Reload code segment register. +/// An x86 segment /// -/// Note this is special since we can not directly move -/// to cs. Instead we push the new segment selector -/// and return value on the stack and use retf -/// to reload cs and continue at 1:. -/// -/// ## Safety -/// -/// This function is unsafe because the caller must ensure that `sel` -/// is a valid code segment descriptor. -#[inline] -pub unsafe fn set_cs(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!( - "push {sel}", - "lea {tmp}, [1f + rip]", - "push {tmp}", - "retfq", - "1:", - sel = in(reg) u64::from(sel.0), - tmp = lateout(reg) _, - options(preserves_flags), - ); - - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_set_cs(u64::from(sel.0)); -} - -/// Reload stack segment register. -/// -/// ## Safety +/// 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 /// -/// This function is unsafe because the caller must ensure that `sel` -/// is a valid stack segment descriptor. -#[inline] -pub unsafe fn load_ss(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!("mov ss, {0:x}", in(reg) sel.0, options(nostack, preserves_flags)); +/// 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); +} - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_ss(sel.0); +macro_rules! get_reg_impl { + ($name:literal, $asm_get:ident) => { + fn get_reg() -> SegmentSelector { + let segment: u16; + #[cfg(feature = "inline_asm")] + unsafe { + asm!(concat!("mov {0:x}, ", $name), out(reg) segment, options(nomem, nostack, preserves_flags)); + } + #[cfg(not(feature = "inline_asm"))] + unsafe { + segment = crate::asm::$asm_get(); + } + SegmentSelector(segment) + } + }; } -/// Reload data segment register. -/// -/// ## Safety -/// -/// This function is unsafe because the caller must ensure that `sel` -/// is a valid data segment descriptor. -#[inline] -pub unsafe fn load_ds(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!("mov ds, {0:x}", in(reg) sel.0, options(nostack, preserves_flags)); +macro_rules! segment_impl { + ($type:ty, $name:literal, $asm_get:ident, $asm_load:ident) => { + impl Segment for $type { + get_reg_impl!($name, $asm_get); + + unsafe fn set_reg(sel: SegmentSelector) { + #[cfg(feature = "inline_asm")] + asm!(concat!("mov ", $name, ", {0:x}"), in(reg) sel.0, options(nostack, preserves_flags)); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_ds(sel.0); + #[cfg(not(feature = "inline_asm"))] + crate::asm::$asm_load(sel.0); + } + } + }; } -/// Reload es segment register. -/// -/// ## Safety -/// -/// This function is unsafe because the caller must ensure that `sel` -/// is a valid extra segment descriptor. -#[inline] -pub unsafe fn load_es(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!("mov es, {0:x}", in(reg) sel.0, options(nostack, preserves_flags)); +macro_rules! segment64_impl { + ($type:ty, $name:literal, $base:ty, $asm_rd:ident, $asm_wr:ident) => { + impl Segment64 for $type { + const BASE: Msr = <$base>::MSR; + fn read_base() -> VirtAddr { + #[cfg(feature = "inline_asm")] + unsafe { + let val: u64; + asm!(concat!("rd", $name, "base {}"), out(reg) val, options(nomem, nostack, preserves_flags)); + VirtAddr::new_unsafe(val) + } + #[cfg(not(feature = "inline_asm"))] + unsafe { + VirtAddr::new_unsafe(crate::asm::$asm_rd()) + } + } + + unsafe fn write_base(base: VirtAddr) { + #[cfg(feature = "inline_asm")] + asm!(concat!("wr", $name, "base {}"), in(reg) base.as_u64(), options(nostack, preserves_flags)); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_es(sel.0); + #[cfg(not(feature = "inline_asm"))] + crate::asm::$asm_wr(base.as_u64()); + } + } + }; } -/// Reload fs segment register. +/// Code Segment /// -/// ## Safety -/// -/// This function is unsafe because the caller must ensure that `sel` -/// is a valid fs segment descriptor. -#[inline] -pub unsafe fn load_fs(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!("mov fs, {0:x}", in(reg) sel.0, options(nostack, preserves_flags)); +/// 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); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_fs(sel.0); + /// Note this is special since we cannot directly move to [`CS`]. Instead we + /// push the new segment selector and return value on the stack and use + /// `retfq` to reload [`CS`] and continue at the end of our function. + unsafe fn set_reg(sel: SegmentSelector) { + #[cfg(feature = "inline_asm")] + asm!( + "push {sel}", + "lea {tmp}, [1f + rip]", + "push {tmp}", + "retfq", + "1:", + sel = in(reg) u64::from(sel.0), + tmp = lateout(reg) _, + options(preserves_flags), + ); + + #[cfg(not(feature = "inline_asm"))] + crate::asm::x86_64_asm_set_cs(u64::from(sel.0)); + } } -/// Reload gs segment register. +/// Stack Segment /// -/// ## Safety +/// 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 function is unsafe because the caller must ensure that `sel` -/// is a valid gs segment descriptor. -#[inline] -pub unsafe fn load_gs(sel: SegmentSelector) { - #[cfg(feature = "inline_asm")] - asm!("mov gs, {0:x}", in(reg) sel.0, options(nostack, preserves_flags)); +/// 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); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_gs(sel.0); -} +/// 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); -/// Swap `KernelGsBase` MSR and `GsBase` MSR. +/// ES Segment /// -/// ## Safety +/// 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 /// -/// This function is unsafe because the caller must ensure that the -/// swap operation cannot lead to undefined behavior. -#[inline] -pub unsafe fn swap_gs() { - #[cfg(feature = "inline_asm")] - asm!("swapgs", options(nostack, preserves_flags)); +/// 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); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_swapgs(); -} +/// 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); -/// Returns the current value of the code segment register. -#[inline] -pub fn cs() -> SegmentSelector { - let segment: u16; +impl GS { + /// Swap `KernelGsBase` MSR and `GsBase` MSR. + /// + /// ## Safety + /// + /// This function is unsafe because the caller must ensure that the + /// swap operation cannot lead to undefined behavior. + pub unsafe fn swap() { + #[cfg(feature = "inline_asm")] + asm!("swapgs", options(nostack, preserves_flags)); - #[cfg(feature = "inline_asm")] - unsafe { - asm!("mov {0:x}, cs", out(reg) segment, options(nomem, nostack, preserves_flags)); - } - #[cfg(not(feature = "inline_asm"))] - unsafe { - segment = crate::asm::x86_64_asm_get_cs(); + #[cfg(not(feature = "inline_asm"))] + crate::asm::x86_64_asm_swapgs(); } - - SegmentSelector(segment) } -/// Writes the FS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +/// Alias for [`CS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `CS::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn set_cs(sel: SegmentSelector) { + CS::set_reg(sel) +} +/// Alias for [`SS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `SS::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn load_ss(sel: SegmentSelector) { + SS::set_reg(sel) +} +/// Alias for [`DS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `DS::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn load_ds(sel: SegmentSelector) { + DS::set_reg(sel) +} +/// Alias for [`ES::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `ES::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn load_es(sel: SegmentSelector) { + ES::set_reg(sel) +} +/// Alias for [`FS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `FS::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn load_fs(sel: SegmentSelector) { + FS::set_reg(sel) +} +/// Alias for [`GS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `GS::set_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn load_gs(sel: SegmentSelector) { + GS::set_reg(sel) +} +/// Alias for [`GS::swap()`] +#[deprecated(since = "0.14.4", note = "use `GS::swap()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub unsafe fn swap_gs() { + GS::swap() +} +/// Alias for [`CS::get_reg()`] +#[deprecated(since = "0.14.4", note = "use `CS::get_reg()` instead")] +#[allow(clippy::missing_safety_doc)] +#[inline] +pub fn cs() -> SegmentSelector { + CS::get_reg() +} +/// Alias for [`FS::write_base()`]. /// -/// The caller must ensure that this write operation has no unsafe side -/// effects, as the FS segment base address is often used for thread -/// local storage. +/// Panics if the provided address is non-canonical. +#[deprecated(since = "0.14.4", note = "use `FS::write_base()` instead")] +#[allow(clippy::missing_safety_doc)] #[inline] pub unsafe fn wrfsbase(val: u64) { - #[cfg(feature = "inline_asm")] - asm!("wrfsbase {}", in(reg) val, options(nostack, preserves_flags)); - - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_wrfsbase(val); + FS::write_base(VirtAddr::new(val)) } - -/// Reads the FS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +/// Alias for [`FS::read_base()`] +#[deprecated(since = "0.14.4", note = "use `FS::read_base()` instead")] +#[allow(clippy::missing_safety_doc)] #[inline] pub unsafe fn rdfsbase() -> u64 { - #[cfg(feature = "inline_asm")] - { - let val: u64; - asm!("rdfsbase {}", out(reg) val, options(nomem, nostack, preserves_flags)); - val - } - - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_rdfsbase() + FS::read_base().as_u64() } - -/// Writes the GS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +/// Alias for [`GS::write_base()`]. /// -/// The caller must ensure that this write operation has no unsafe side -/// effects, as the GS segment base address might be in use. +/// Panics if the provided address is non-canonical. +#[deprecated(since = "0.14.4", note = "use `GS::write_base()` instead")] +#[allow(clippy::missing_safety_doc)] #[inline] pub unsafe fn wrgsbase(val: u64) { - #[cfg(feature = "inline_asm")] - asm!("wrgsbase {}", in(reg) val, options(nostack, preserves_flags)); - - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_wrgsbase(val); + GS::write_base(VirtAddr::new(val)) } - -/// Reads the GS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +/// Alias for [`GS::read_base()`] +#[deprecated(since = "0.14.4", note = "use `GS::read_base()` instead")] +#[allow(clippy::missing_safety_doc)] #[inline] pub unsafe fn rdgsbase() -> u64 { - #[cfg(feature = "inline_asm")] - { - let val: u64; - asm!("rdgsbase {}", out(reg) val, options(nomem, nostack, preserves_flags)); - val - } - - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_rdgsbase() + GS::read_base().as_u64() } diff --git a/src/registers/mod.rs b/src/registers/mod.rs index 81b668417..27762f9dc 100644 --- a/src/registers/mod.rs +++ b/src/registers/mod.rs @@ -6,6 +6,7 @@ pub mod rflags; pub mod xcontrol; #[cfg(feature = "instructions")] +#[allow(deprecated)] pub use crate::instructions::segmentation::{rdfsbase, rdgsbase, wrfsbase, wrgsbase}; #[cfg(all(feature = "instructions", feature = "inline_asm"))] diff --git a/src/registers/model_specific.rs b/src/registers/model_specific.rs index acebb1a5a..073a08c4f 100644 --- a/src/registers/model_specific.rs +++ b/src/registers/model_specific.rs @@ -1,5 +1,11 @@ //! Functions to read and write model specific registers. +#[cfg(docsrs)] +use crate::{ + instructions::segmentation::{Segment64, FS, GS}, + registers::control::Cr4Flags, +}; + use bitflags::bitflags; /// A model specific register. @@ -18,15 +24,19 @@ impl Msr { #[derive(Debug)] pub struct Efer; -/// FS.Base Model Specific Register. +/// [FS].Base Model Specific Register. #[derive(Debug)] pub struct FsBase; -/// GS.Base Model Specific Register. +/// [GS].Base Model Specific Register. +/// +/// [`GS::swap`] swaps this register with [`KernelGsBase`]. #[derive(Debug)] pub struct GsBase; /// KernelGsBase Model Specific Register. +/// +/// [`GS::swap`] swaps this register with [`GsBase`]. #[derive(Debug)] pub struct KernelGsBase; @@ -223,12 +233,18 @@ mod x86_64 { impl FsBase { /// Read the current FsBase register. + /// + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is set, the more efficient + /// [`FS::read_base`] can be used instead. #[inline] pub fn read() -> VirtAddr { VirtAddr::new(unsafe { Self::MSR.read() }) } /// Write a given virtual address to the FS.Base register. + /// + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is set, the more efficient + /// [`FS::write_base`] can be used instead. #[inline] pub fn write(address: VirtAddr) { let mut msr = Self::MSR; @@ -238,12 +254,18 @@ mod x86_64 { impl GsBase { /// Read the current GsBase register. + /// + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is set, the more efficient + /// [`GS::read_base`] can be used instead. #[inline] pub fn read() -> VirtAddr { VirtAddr::new(unsafe { Self::MSR.read() }) } /// Write a given virtual address to the GS.Base register. + /// + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is set, the more efficient + /// [`GS::write_base`] can be used instead. #[inline] pub fn write(address: VirtAddr) { let mut msr = Self::MSR; diff --git a/src/structures/idt.rs b/src/structures/idt.rs index e24aa3623..df76238a2 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -630,13 +630,13 @@ impl Entry { #[cfg(feature = "instructions")] #[inline] fn set_handler_addr(&mut self, addr: u64) -> &mut EntryOptions { - use crate::instructions::segmentation; + use crate::instructions::segmentation::{Segment, CS}; self.pointer_low = addr as u16; self.pointer_middle = (addr >> 16) as u16; self.pointer_high = (addr >> 32) as u32; - self.gdt_selector = segmentation::cs().0; + self.gdt_selector = CS::get_reg().0; self.options.set_present(true); &mut self.options diff --git a/testing/src/gdt.rs b/testing/src/gdt.rs index 3cfa6cc84..70f4df5cf 100644 --- a/testing/src/gdt.rs +++ b/testing/src/gdt.rs @@ -38,12 +38,12 @@ struct Selectors { } pub fn init() { - use x86_64::instructions::segmentation::set_cs; + use x86_64::instructions::segmentation::{CS, Segment}; use x86_64::instructions::tables::load_tss; GDT.0.load(); unsafe { - set_cs(GDT.1.code_selector); + CS::set_reg(GDT.1.code_selector); load_tss(GDT.1.tss_selector); } }