From 14eba7efab9c99bbf9911ff088b1a700fc90b97d Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 28 May 2021 17:39:09 -0700 Subject: [PATCH 01/41] Add common abstrations for segmentation Signed-off-by: Joe Richey --- src/asm/asm.s | 30 +++ src/asm/mod.rs | 30 +++ src/instructions/segmentation.rs | 380 +++++++++++++++++-------------- src/structures/idt.rs | 4 +- testing/src/gdt.rs | 4 +- 5 files changed, 278 insertions(+), 170 deletions(-) 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..59c306680 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -1,210 +1,258 @@ //! Provides functions to read and write segment registers. -use crate::structures::gdt::SegmentSelector; +#[cfg(docsrs)] +use crate::structures::gdt::GlobalDescriptorTable; +use crate::{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 { + /// Reads the segment base address + /// + /// ## Safety + /// + /// If `CR4.FSGSBASE` is not set, this instruction will throw a `#UD`. + unsafe fn read_base() -> VirtAddr; + /// Writes the segment base address + /// + /// ## Safety + /// + /// If `CR4.FSGSBASE` is not set, this instruction will throw a `#UD`. + /// + /// 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, $asm_rd:ident, $asm_wr:ident) => { + impl Segment64 for $type { + unsafe fn read_base() -> VirtAddr { + #[cfg(feature = "inline_asm")] + { + 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"))] + 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 chaning 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); + unsafe fn set_reg(sel: SegmentSelector) { + // 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 1:. + #[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. +#[derive(Debug)] +pub struct SS; +segment_impl!(SS, "ss", x86_64_asm_get_ss, x86_64_asm_load_ss); + +/// Data Segment /// -/// 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)); +/// 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); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_load_gs(sel.0); -} +/// 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); -/// Swap `KernelGsBase` MSR and `GsBase` MSR. +/// FS Segment /// -/// ## Safety +/// 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", x86_64_asm_rdfsbase, x86_64_asm_wrfsbase); + +/// GS 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`]. 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", x86_64_asm_rdgsbase, x86_64_asm_wrgsbase); - #[cfg(not(feature = "inline_asm"))] - crate::asm::x86_64_asm_swapgs(); +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(not(feature = "inline_asm"))] + crate::asm::x86_64_asm_swapgs(); + } } -/// Returns the current value of the code segment register. +/// Alias for [`CS::set_reg()`] +#[inline] +pub unsafe fn set_cs(sel: SegmentSelector) { + CS::set_reg(sel) +} +/// Alias for [`SS::set_reg()`] +#[inline] +pub unsafe fn load_ss(sel: SegmentSelector) { + SS::set_reg(sel) +} +/// Alias for [`DS::set_reg()`] +#[inline] +pub unsafe fn load_ds(sel: SegmentSelector) { + DS::set_reg(sel) +} +/// Alias for [`ES::set_reg()`] +#[inline] +pub unsafe fn load_es(sel: SegmentSelector) { + ES::set_reg(sel) +} +/// Alias for [`FS::set_reg()`] +#[inline] +pub unsafe fn load_fs(sel: SegmentSelector) { + FS::set_reg(sel) +} +/// Alias for [`GS::set_reg()`] +#[inline] +pub unsafe fn load_gs(sel: SegmentSelector) { + GS::set_reg(sel) +} +/// Alias for [`GS::swap()`] +#[inline] +pub unsafe fn swap_gs() { + GS::swap() +} +/// Alias for [`CS::get_reg()`] #[inline] pub fn cs() -> SegmentSelector { - let segment: u16; - - #[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(); - } - - SegmentSelector(segment) + CS::get_reg() } - -/// Writes the FS segment base address -/// -/// ## Safety +/// Alias for [`FS::write_base()`]. /// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. -/// -/// 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. +/// Raises #GP if the provided address is non-canonical. #[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()`] #[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. +/// Raises #GP if the provided address is non-canonical. #[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()`] #[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/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); } } From 40d20e47fd2efc499088c0f957d33dad938b3287 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 29 May 2021 03:41:21 -0700 Subject: [PATCH 02/41] Deprecate old standalone functions Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 12 ++++++++++++ src/registers/mod.rs | 1 + 2 files changed, 13 insertions(+) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 59c306680..0c7e023ac 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -193,41 +193,49 @@ impl GS { } /// Alias for [`CS::set_reg()`] +#[deprecated(since = "0.14.4", note = "use `CS::set_reg()` instead")] #[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")] #[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")] #[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")] #[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")] #[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")] #[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")] #[inline] pub unsafe fn swap_gs() { GS::swap() } /// Alias for [`CS::get_reg()`] +#[deprecated(since = "0.14.4", note = "use `CS::get_reg()` instead")] #[inline] pub fn cs() -> SegmentSelector { CS::get_reg() @@ -235,11 +243,13 @@ pub fn cs() -> SegmentSelector { /// Alias for [`FS::write_base()`]. /// /// Raises #GP if the provided address is non-canonical. +#[deprecated(since = "0.14.4", note = "use `FS::write_base()` instead")] #[inline] pub unsafe fn wrfsbase(val: u64) { FS::write_base(VirtAddr::new(val)) } /// Alias for [`FS::read_base()`] +#[deprecated(since = "0.14.4", note = "use `FS::read_base()` instead")] #[inline] pub unsafe fn rdfsbase() -> u64 { FS::read_base().as_u64() @@ -247,11 +257,13 @@ pub unsafe fn rdfsbase() -> u64 { /// Alias for [`GS::write_base()`]. /// /// Raises #GP if the provided address is non-canonical. +#[deprecated(since = "0.14.4", note = "use `GS::write_base()` instead")] #[inline] pub unsafe fn wrgsbase(val: u64) { GS::write_base(VirtAddr::new(val)) } /// Alias for [`GS::read_base()`] +#[deprecated(since = "0.14.4", note = "use `GS::read_base()` instead")] #[inline] pub unsafe fn rdgsbase() -> u64 { 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"))] From a7de595e1dba0f59c55751c9f91202e0754ee33d Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 31 May 2021 03:32:08 -0700 Subject: [PATCH 03/41] Improve Cross-Doc links Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 22 +++++++++++++++------- src/registers/model_specific.rs | 26 ++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 0c7e023ac..ad8235dab 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -1,8 +1,12 @@ //! Provides functions to read and write segment registers. #[cfg(docsrs)] -use crate::structures::gdt::GlobalDescriptorTable; -use crate::{structures::gdt::SegmentSelector, VirtAddr}; +use crate::{registers::control::Cr4Flags, structures::gdt::GlobalDescriptorTable}; +use crate::{ + registers::model_specific::{FsBase, GsBase, Msr}, + structures::gdt::SegmentSelector, + VirtAddr, +}; /// An x86 segment /// @@ -30,17 +34,20 @@ pub trait Segment { /// 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 /// /// ## Safety /// - /// If `CR4.FSGSBASE` is not set, this instruction will throw a `#UD`. + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`. unsafe fn read_base() -> VirtAddr; /// Writes the segment base address /// /// ## Safety /// - /// If `CR4.FSGSBASE` is not set, this instruction will throw a `#UD`. + /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`. /// /// The caller must ensure that this write operation has no unsafe side /// effects, as the segment base address might be in use. @@ -81,8 +88,9 @@ macro_rules! segment_impl { } macro_rules! segment64_impl { - ($type:ty, $name:literal, $asm_rd:ident, $asm_wr:ident) => { + ($type:ty, $name:literal, $base:ty, $asm_rd:ident, $asm_wr:ident) => { impl Segment64 for $type { + const BASE: Msr = <$base>::MSR; unsafe fn read_base() -> VirtAddr { #[cfg(feature = "inline_asm")] { @@ -165,7 +173,7 @@ segment_impl!(ES, "es", x86_64_asm_get_es, x86_64_asm_load_es); #[derive(Debug)] pub struct FS; segment_impl!(FS, "fs", x86_64_asm_get_fs, x86_64_asm_load_fs); -segment64_impl!(FS, "fs", x86_64_asm_rdfsbase, x86_64_asm_wrfsbase); +segment64_impl!(FS, "fs", FsBase, x86_64_asm_rdfsbase, x86_64_asm_wrfsbase); /// GS Segment /// @@ -174,7 +182,7 @@ segment64_impl!(FS, "fs", x86_64_asm_rdfsbase, x86_64_asm_wrfsbase); #[derive(Debug)] pub struct GS; segment_impl!(GS, "gs", x86_64_asm_get_gs, x86_64_asm_load_gs); -segment64_impl!(GS, "gs", x86_64_asm_rdgsbase, x86_64_asm_wrgsbase); +segment64_impl!(GS, "gs", GsBase, x86_64_asm_rdgsbase, x86_64_asm_wrgsbase); impl GS { /// Swap `KernelGsBase` MSR and `GsBase` MSR. 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; From 6164bd0232a3b0ff438d8c0a33c83b6ce56513bb Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 12 Jun 2021 01:09:40 -0700 Subject: [PATCH 04/41] Allow missing_safety_doc in deprecated segment functions Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index ad8235dab..a33ffb8db 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -202,48 +202,56 @@ impl GS { /// 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() @@ -252,12 +260,14 @@ pub fn cs() -> SegmentSelector { /// /// Raises #GP 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) { FS::write_base(VirtAddr::new(val)) } /// 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 { FS::read_base().as_u64() @@ -266,12 +276,14 @@ pub unsafe fn rdfsbase() -> u64 { /// /// Raises #GP 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) { GS::write_base(VirtAddr::new(val)) } /// 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 { GS::read_base().as_u64() From 22aeec3edc250ee6c75bddd594f52350f2ef5280 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 12 Jun 2021 01:17:34 -0700 Subject: [PATCH 05/41] Make Segment64::read_base safe This function only throws a #UD, which we generally consider to be safe. Also, add an `Exceptions` section to the `Segment64` docs (this is similar to the `Panic` section in normal Rust docs). Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index a33ffb8db..e771f365f 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -35,20 +35,22 @@ pub trait Segment { /// 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. + /// when [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is **not** set. const BASE: Msr; /// Reads the segment base address /// - /// ## Safety + /// ## Exceptions /// /// If [`CR4.FSGSBASE`][Cr4Flags::FSGSBASE] is not set, this instruction will throw a `#UD`. - unsafe fn read_base() -> VirtAddr; + fn read_base() -> VirtAddr; /// Writes the segment base address /// - /// ## Safety + /// ## 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); @@ -91,15 +93,17 @@ 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; - unsafe fn read_base() -> VirtAddr { + 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"))] - VirtAddr::new_unsafe(crate::asm::$asm_rd()) + unsafe { + VirtAddr::new_unsafe(crate::asm::$asm_rd()) + } } unsafe fn write_base(base: VirtAddr) { From 9c4a2c901a0787ef4368d4f21c13cf3c970f066d Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 12 Jun 2021 01:24:38 -0700 Subject: [PATCH 06/41] Update SS docs to indicate why it is still sometimes set Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index e771f365f..2ef000859 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -151,21 +151,25 @@ impl Segment for CS { /// Stack Segment /// -/// Entirely unused in 64-bit mode, setting the segment register does nothing. +/// Entirely unused in 64-bit mode; setting the segment register does nothing. +/// However, this register is often 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. +/// 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. +/// 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); From c47d482df86ffe43e7ac9fccc6716f2b74538849 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 12 Jun 2021 01:39:12 -0700 Subject: [PATCH 07/41] Add CS::set_reg doc comment explaining its weirdness Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 2ef000859..3d089ba4c 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -128,10 +128,10 @@ pub struct CS; impl Segment for CS { get_reg_impl!("cs", x86_64_asm_get_cs); + /// 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) { - // 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 1:. #[cfg(feature = "inline_asm")] asm!( "push {sel}", From a81714bc90184a5d60d5ec56aefe0c08ef57b83c Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Sat, 12 Jun 2021 01:49:15 -0700 Subject: [PATCH 08/41] Fix Typo Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 3d089ba4c..957644a7c 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -121,7 +121,7 @@ macro_rules! segment64_impl { /// /// 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 chaning the segment register can be used to +/// 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; From b0ab474d627410c2788c39a0d398b481b327c39f Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 14 Jun 2021 04:22:01 -0700 Subject: [PATCH 09/41] Note that non-canonical addresses give a panic Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 957644a7c..b08cc9310 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -266,7 +266,7 @@ pub fn cs() -> SegmentSelector { } /// Alias for [`FS::write_base()`]. /// -/// Raises #GP if the provided address is non-canonical. +/// 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] @@ -282,7 +282,7 @@ pub unsafe fn rdfsbase() -> u64 { } /// Alias for [`GS::write_base()`]. /// -/// Raises #GP if the provided address is non-canonical. +/// 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] From a9c037c63638e5551e804bb39df4851027457751 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 14 Jun 2021 05:41:26 -0700 Subject: [PATCH 10/41] Explain that SS must be set in Ring 3 Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index b08cc9310..2ca05ac83 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -1,7 +1,10 @@ //! Provides functions to read and write segment registers. #[cfg(docsrs)] -use crate::{registers::control::Cr4Flags, structures::gdt::GlobalDescriptorTable}; +use crate::{ + registers::control::Cr4Flags, + structures::gdt::{Descriptor, GlobalDescriptorTable}, +}; use crate::{ registers::model_specific::{FsBase, GsBase, Msr}, structures::gdt::SegmentSelector, @@ -152,7 +155,11 @@ impl Segment for CS { /// Stack Segment /// /// Entirely unused in 64-bit mode; setting the segment register does nothing. -/// However, this register is often set by the `syscall`/`sysret` and +/// 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. From e5c202c98641793f88658301c4b56defb3775a83 Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Mon, 14 Jun 2021 21:53:02 +0900 Subject: [PATCH 11/41] chore: specify `sysv64` for the calling convention (#267) This ensures that the arguments are passed by RDI, RSI, RDX, RCX, etc. --- src/asm/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/asm/mod.rs b/src/asm/mod.rs index 57dc54799..9b9ddbdea 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -1,5 +1,5 @@ #[link(name = "x86_64_asm", kind = "static")] -extern "C" { +extern "sysv64" { #[cfg_attr( any(target_env = "gnu", target_env = "musl"), link_name = "_x86_64_asm_interrupt_enable" From 66b11ebd9c0ce793c385037a95b6c8bc54104350 Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Wed, 7 Jul 2021 22:19:56 +0900 Subject: [PATCH 12/41] feat(idt): make it available in the stable Rust (#271) This PR makes the `idt` module available for use in the stable Rust with the `instructions` feature. Especially, this PR - exposes `Entry::set_handler_addr` as an unsafe method. This is the only way for the users of this crate who build it with the stable Rust to set the addresses of the interrupt or exception handlers. - defines the handler function types as `()` if the `abi_x86_interrupt` feature is not enabled. These types are only used with the `abi_x86_interrupt` feature. Co-authored-by: Philipp Oppermann --- src/structures/idt.rs | 67 +++++++++++++++++++++++++++++++++++++++++-- src/structures/mod.rs | 2 -- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/src/structures/idt.rs b/src/structures/idt.rs index df76238a2..0e318ead3 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -8,6 +8,17 @@ // except according to those terms. //! Provides types for the Interrupt Descriptor Table and its entries. +//! +//! # For the builds without the `abi_x86_interrupt` feature +//! The following types are opaque and non-constructable instead of function pointers. +//! +//! - [`DivergingHandlerFunc`] +//! - [`DivergingHandlerFuncWithErrCode`] +//! - [`HandlerFunc`] +//! - [`HandlerFuncWithErrCode`] +//! - [`PageFaultHandlerFunc`] +//! +//! These types are defined for the compatibility with the Nightly Rust build. use crate::{PrivilegeLevel, VirtAddr}; use bit_field::BitField; @@ -593,17 +604,56 @@ impl PartialEq for Entry { } /// A handler function for an interrupt or an exception without error code. +/// +/// This type alias is only usable with the `abi_x86_interrupt` feature enabled. +#[cfg(feature = "abi_x86_interrupt")] pub type HandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame); +/// This type is not usable without the `abi_x86_interrupt` feature. +#[cfg(not(feature = "abi_x86_interrupt"))] +#[derive(Copy, Clone, Debug)] +pub struct HandlerFunc(()); + /// A handler function for an exception that pushes an error code. +/// +/// This type alias is only usable with the `abi_x86_interrupt` feature enabled. +#[cfg(feature = "abi_x86_interrupt")] pub type HandlerFuncWithErrCode = extern "x86-interrupt" fn(InterruptStackFrame, error_code: u64); +/// This type is not usable without the `abi_x86_interrupt` feature. +#[cfg(not(feature = "abi_x86_interrupt"))] +#[derive(Copy, Clone, Debug)] +pub struct HandlerFuncWithErrCode(()); + /// A page fault handler function that pushes a page fault error code. +/// +/// This type alias is only usable with the `abi_x86_interrupt` feature enabled. +#[cfg(feature = "abi_x86_interrupt")] pub type PageFaultHandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame, error_code: PageFaultErrorCode); +/// This type is not usable without the `abi_x86_interrupt` feature. +#[cfg(not(feature = "abi_x86_interrupt"))] +#[derive(Copy, Clone, Debug)] +pub struct PageFaultHandlerFunc(()); + /// A handler function that must not return, e.g. for a machine check exception. +/// +/// This type alias is only usable with the `abi_x86_interrupt` feature enabled. +#[cfg(feature = "abi_x86_interrupt")] pub type DivergingHandlerFunc = extern "x86-interrupt" fn(InterruptStackFrame) -> !; +/// This type is not usable without the `abi_x86_interrupt` feature. +#[cfg(not(feature = "abi_x86_interrupt"))] +#[derive(Copy, Clone, Debug)] +pub struct DivergingHandlerFunc(()); + /// A handler function with an error code that must not return, e.g. for a double fault exception. +/// +/// This type alias is only usable with the `abi_x86_interrupt` feature enabled. +#[cfg(feature = "abi_x86_interrupt")] pub type DivergingHandlerFuncWithErrCode = extern "x86-interrupt" fn(InterruptStackFrame, error_code: u64) -> !; +/// This type is not usable without the `abi_x86_interrupt` feature. +#[cfg(not(feature = "abi_x86_interrupt"))] +#[derive(Copy, Clone, Debug)] +pub struct DivergingHandlerFuncWithErrCode(()); impl Entry { /// Creates a non-present IDT entry (but sets the must-be-one bits). @@ -627,11 +677,18 @@ impl Entry { /// /// The function returns a mutable reference to the entry's options that allows /// further customization. + /// + /// # Safety + /// + /// The caller must ensure that `addr` is the address of a valid interrupt handler function, + /// and the signature of such a function is correct for the entry type. #[cfg(feature = "instructions")] #[inline] - fn set_handler_addr(&mut self, addr: u64) -> &mut EntryOptions { + pub unsafe fn set_handler_addr(&mut self, addr: VirtAddr) -> &mut EntryOptions { use crate::instructions::segmentation::{Segment, CS}; + let addr = addr.as_u64(); + self.pointer_low = addr as u16; self.pointer_middle = (addr >> 16) as u16; self.pointer_high = (addr >> 32) as u32; @@ -652,7 +709,7 @@ impl Entry { macro_rules! impl_set_handler_fn { ($h:ty) => { - #[cfg(feature = "instructions")] + #[cfg(all(feature = "instructions", feature = "abi_x86_interrupt"))] impl Entry<$h> { /// Set the handler function for the IDT entry and sets the present bit. /// @@ -661,9 +718,13 @@ macro_rules! impl_set_handler_fn { /// /// The function returns a mutable reference to the entry's options that allows /// further customization. + /// + /// This method is only usable with the `abi_x86_interrupt` feature enabled. Without it, the + /// unsafe [`Entry::set_handler_addr`] method has to be used instead. #[inline] pub fn set_handler_fn(&mut self, handler: $h) -> &mut EntryOptions { - self.set_handler_addr(handler as u64) + let handler = VirtAddr::new(handler as u64); + unsafe { self.set_handler_addr(handler) } } } }; diff --git a/src/structures/mod.rs b/src/structures/mod.rs index 79801c245..8fc481cf0 100644 --- a/src/structures/mod.rs +++ b/src/structures/mod.rs @@ -4,8 +4,6 @@ use crate::VirtAddr; pub mod gdt; -// idt needs `feature(abi_x86_interrupt)`, which is not available on stable rust -#[cfg(feature = "abi_x86_interrupt")] pub mod idt; pub mod paging; From 134cacd03ffe6d8ba6e79aff8766a92e06b0fe0b Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Wed, 7 Jul 2021 09:21:04 -0700 Subject: [PATCH 13/41] gdt: Fix off-by-one error in from_raw_slice() (#269) We use our poor-man's assert instead of trying to do math. Signed-off-by: Joe Richey --- src/structures/gdt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 02007fb71..f71a8c11c 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -123,7 +123,7 @@ impl GlobalDescriptorTable { "initializing a GDT from a slice requires it to be **at most** 8 elements." ); #[cfg(not(feature = "const_fn"))] - table[next_free]; // Will fail if slice.len() > 8 + [(); 1][!(next_free <= 8) as usize]; while idx != next_free { table[idx] = slice[idx]; From 99742005448dfee624989f1c71cecdad23ce69bf Mon Sep 17 00:00:00 2001 From: Ethin Probst Date: Sun, 27 Jun 2021 22:04:41 -0500 Subject: [PATCH 14/41] registers: add extra flags for CR0, CR4, and XCR0 Alos, add extra checks when writing to XCR0. Signed-off-by: Ethin Probst Signed-off-by: Joe Richey --- src/registers/control.rs | 10 +++++++++- src/registers/xcontrol.rs | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/registers/control.rs b/src/registers/control.rs index c09dd0d7e..58b2a47a2 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -24,6 +24,8 @@ bitflags! { /// /// This flags allows lazily saving x87/MMX/SSE instructions on hardware context switches. const TASK_SWITCHED = 1 << 3; + /// Indicates support of 387DX math coprocessor instructions when set + const EXTENSION_TYPE = 1 << 4; /// Enables the native error reporting mechanism for x87 FPU errors. const NUMERIC_ERROR = 1 << 5; /// Controls whether supervisor-level writes to read-only pages are inhibited. @@ -114,14 +116,20 @@ bitflags! { const PCID = 1 << 17; /// Enables extendet processor state management instructions, including XGETBV and XSAVE. const OSXSAVE = 1 << 18; + /// When set, the `LOADIWKEY` instruction is available; additionally, if system firmware has activated the AES key locker instructions, register EBX of CPUID leaf 0x19, bit 0 (AESKLE) is set and the AES key locker instructions are enabled. See the [Intel Key Locker Specification](https://software.intel.com/content/www/us/en/develop/download/intel-key-locker-specification.html) for information on this feature. + const KEY_LOCKER = 1 << 19; /// Prevents the execution of instructions that reside in pages accessible by user-mode /// software when the processor is in supervisor-mode. const SUPERVISOR_MODE_EXECUTION_PROTECTION = 1 << 20; /// Enables restrictions for supervisor-mode software when reading data from user-mode /// pages. const SUPERVISOR_MODE_ACCESS_PREVENTION = 1 << 21; - /// Enables 4-level paging to associate each linear address with a protection key. + /// Enables 4-level and 5-level paging to associate each linear address with a protection key in user mode. const PROTECTION_KEY = 1 << 22; + /// When set, enables intel control-flow enforcement technology. See chapter 18 of the Intel software developer manuals, volume 1, for more information. + const CONTROL_FLOW_ENFORCEMENT = 1 << 23; + /// When set, allows 4-level and 5-level paging implementations to use the `IA32_PKRS` MSR to specify, for each protection key, whether supervisor-mode linear addresses with a particular protection key can be read or written. + const PROTECTION_KEY_SUPERVISOR = 1 << 24; } } diff --git a/src/registers/xcontrol.rs b/src/registers/xcontrol.rs index 8d2dfb789..66501d914 100644 --- a/src/registers/xcontrol.rs +++ b/src/registers/xcontrol.rs @@ -16,8 +16,18 @@ bitflags! { /// Enables 256-bit SSE /// Must be set to enable AVX const YMM = 1<<2; + /// When set, MPX instructions are enabled and the bound registers BND0-BND3 can be managed by XSAVE. + const BNDREG = 1 << 3; + /// When set, MPX instructions can be executed and XSAVE can manage the BNDCFGU and BNDSTATUS registers. + const BNDCSR = 1 << 4; + /// If set, AVX-512 instructions can be executed and XSAVE can manage the K0-K7 mask registers. + const OPMASK = 1 << 5; + /// If set, AVX-512 instructions can be executed and XSAVE can be used to manage the upper halves of the lower ZMM registers. + const ZMM_HI256 = 1 << 6; + /// If set, AVX-512 instructions can be executed and XSAVE can manage the upper ZMM registers. + const HI16_ZMM = 1 << 7; /// When set, PKRU state management is supported by - /// ZSAVE/XRSTOR + /// XSAVE/XRSTOR const MPK = 1<<9; /// When set the Lightweight Profiling extensions are enabled const LWP = 1<<62; @@ -68,6 +78,13 @@ mod x86_64 { let old_value = Self::read_raw(); let reserved = old_value & !(XCr0Flags::all().bits()); let new_value = reserved | flags.bits(); + assert!(flags.contains(XCr0Flags::X87), "The X87 flag must be set"); + assert!((flags.contains(XCr0Flags::AVX) && flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)) || !(flags.contains(XCr0Flags::AVX) && flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)), "You must enable AVX to set or unset any of XCR0.opmask, XCR0.ZMM_Hi256, and XCR0.Hi16_ZMM"); + if !flags.contains(XCr0Flags::AVX) && (flags.contains(XCr0Flags::OPMASK) || flags.contains(XCr0Flags::ZMM_HI256) || flags.contains(XCr0Flags::HI16_ZMM)) { + panic!("You must have AVX enabled to set XCR0.opmask, XCR0.ZMM_Hi256, or XCR0.Hi16_ZMM"); + } + assert!((flags.contains(XCr0Flags::BNDREG) && flags.contains(XCr0Flags::BNDCSR)) || !(flags.contains(XCr0Flags::BNDREG) && flags.contains(XCr0Flags::BNDCSR)), "BNDREG and BNDCSR must be set and unset together"); + assert!((flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)) || !(flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)), "You must set or unset all of XCR0.opmask, XCR0.ZMM_Hi256, and XCR0.Hi16_ZMM"); Self::write_raw(new_value); } From 7dfb388f6610b4ebb2e6a37cad3fad2470eed748 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 7 Jul 2021 05:29:44 -0700 Subject: [PATCH 15/41] registers: Update documentation for control flags Signed-off-by: Joe Richey --- src/registers/control.rs | 100 +++++++++++++++++++++++--------------- src/registers/xcontrol.rs | 40 ++++++++++----- 2 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/registers/control.rs b/src/registers/control.rs index 58b2a47a2..a8f306e68 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -1,6 +1,8 @@ //! Functions to read and write control registers. pub use super::model_specific::{Efer, EferFlags}; +#[cfg(docsrs)] +use crate::{registers::rflags::RFlags, structures::paging::PageTableFlags}; use bitflags::bitflags; @@ -9,114 +11,127 @@ use bitflags::bitflags; pub struct Cr0; bitflags! { - /// Configuration flags of the Cr0 register. + /// Configuration flags of the [`Cr0`] register. pub struct Cr0Flags: u64 { /// Enables protected mode. const PROTECTED_MODE_ENABLE = 1; /// Enables monitoring of the coprocessor, typical for x87 instructions. /// - /// Controls together with the `TASK_SWITCHED` flag whether a `wait` or `fwait` - /// instruction should cause a device-not-available exception. + /// Controls (together with the [`TASK_SWITCHED`](Cr0Flags::TASK_SWITCHED) + /// flag) whether a `wait` or `fwait` instruction should cause an `#NE` exception. const MONITOR_COPROCESSOR = 1 << 1; - /// Force all x87 and MMX instructions to cause an exception. + /// Force all x87 and MMX instructions to cause an `#NE` exception. const EMULATE_COPROCESSOR = 1 << 2; /// Automatically set to 1 on _hardware_ task switch. /// /// This flags allows lazily saving x87/MMX/SSE instructions on hardware context switches. const TASK_SWITCHED = 1 << 3; - /// Indicates support of 387DX math coprocessor instructions when set + /// Indicates support of 387DX math coprocessor instructions. + /// + /// Always set on all recent x86 processors, cannot be cleared. const EXTENSION_TYPE = 1 << 4; - /// Enables the native error reporting mechanism for x87 FPU errors. + /// Enables the native (internal) error reporting mechanism for x87 FPU errors. const NUMERIC_ERROR = 1 << 5; /// Controls whether supervisor-level writes to read-only pages are inhibited. /// /// When set, it is not possible to write to read-only pages from ring 0. const WRITE_PROTECT = 1 << 16; - /// Enables automatic alignment checking. + /// Enables automatic usermode alignment checking if [`RFlags::ALIGNMENT_CHECK`] is also set. const ALIGNMENT_MASK = 1 << 18; - /// Ignored. Used to control write-back/write-through cache strategy on older CPUs. + /// Ignored, should always be unset. + /// + /// Must be unset if [`CACHE_DISABLE`](Cr0Flags::CACHE_DISABLE) is unset. + /// Older CPUs used this to control write-back/write-through cache strategy. const NOT_WRITE_THROUGH = 1 << 29; - /// Disables internal caches (only for some cases). + /// Disables some processor caches, specifics are model-dependent. const CACHE_DISABLE = 1 << 30; - /// Enables page translation. + /// Enables paging. + /// + /// If this bit is set, [`PROTECTED_MODE_ENABLE`](Cr0Flags::PROTECTED_MODE_ENABLE) must be set. const PAGING = 1 << 31; } } /// Contains the Page Fault Linear Address (PFLA). /// -/// When page fault occurs, the CPU sets this register to the accessed address. +/// When a page fault occurs, the CPU sets this register to the faulting virtual address. #[derive(Debug)] pub struct Cr2; -/// Contains the physical address of the level 4 page table. +/// Contains the physical address of the highest-level page table. #[derive(Debug)] pub struct Cr3; bitflags! { - /// Controls cache settings for the level 4 page table. + /// Controls cache settings for the highest-level page table. + /// + /// Unused if paging is disabled or if [`PCID`](Cr4Flags::PCID) is enabled. pub struct Cr3Flags: u64 { - /// Use a writethrough cache policy for the P4 table (else a writeback policy is used). + /// Use a writethrough cache policy for the table (otherwise a writeback policy is used). const PAGE_LEVEL_WRITETHROUGH = 1 << 3; - /// Disable caching for the P4 table. + /// Disable caching for the table. const PAGE_LEVEL_CACHE_DISABLE = 1 << 4; } } -/// Various control flags modifying the basic operation of the CPU while in protected mode. -/// -/// Note: The documention for the individual fields is taken from the AMD64 and Intel x86_64 -/// manuals. +/// Contains various control flags that enable architectural extensions, and +/// indicate support for specific processor capabilities. #[derive(Debug)] pub struct Cr4; bitflags! { - /// Controls cache settings for the level 4 page table. + /// Configuration flags of the [`Cr4`] register. pub struct Cr4Flags: u64 { /// Enables hardware-supported performance enhancements for software running in /// virtual-8086 mode. const VIRTUAL_8086_MODE_EXTENSIONS = 1; /// Enables support for protected-mode virtual interrupts. const PROTECTED_MODE_VIRTUAL_INTERRUPTS = 1 << 1; - /// When set, only privilege-level 0 can execute the RDTSC or RDTSCP instructions. + /// When set, only privilege-level 0 can execute the `RDTSC` or `RDTSCP` instructions. const TIMESTAMP_DISABLE = 1 << 2; - /// Enables I/O breakpoint capability and enforces treatment of DR4 and DR5 registers + /// Enables I/O breakpoint capability and enforces treatment of `DR4` and `DR5` registers /// as reserved. const DEBUGGING_EXTENSIONS = 1 << 3; - /// Enables the use of 4MB physical frames; ignored in long mode. + /// Enables the use of 4MB physical frames; ignored if + /// [`PHYSICAL_ADDRESS_EXTENSION`](Cr4Flags::PHYSICAL_ADDRESS_EXTENSION) + /// is set (so always ignored in long mode). const PAGE_SIZE_EXTENSION = 1 << 4; - /// Enables physical address extension and 2MB physical frames; required in long mode. + /// Enables physical address extensions and 2MB physical frames. Required in long mode. const PHYSICAL_ADDRESS_EXTENSION = 1 << 5; /// Enables the machine-check exception mechanism. const MACHINE_CHECK_EXCEPTION = 1 << 6; - /// Enables the global-page mechanism, which allows to make page translations global - /// to all processes. + /// Enables the global page feature, allowing some page translations to + /// be marked as global (see [`PageTableFlags::GLOBAL`]). const PAGE_GLOBAL = 1 << 7; - /// Allows software running at any privilege level to use the RDPMC instruction. + /// Allows software running at any privilege level to use the `RDPMC` instruction. const PERFORMANCE_MONITOR_COUNTER = 1 << 8; - /// Enable the use of legacy SSE instructions; allows using FXSAVE/FXRSTOR for saving + /// Enables the use of legacy SSE instructions; allows using `FXSAVE`/`FXRSTOR` for saving /// processor state of 128-bit media instructions. const OSFXSR = 1 << 9; - /// Enables the SIMD floating-point exception (#XF) for handling unmasked 256-bit and + /// Enables the SIMD floating-point exception (`#XF`) for handling unmasked 256-bit and /// 128-bit media floating-point errors. const OSXMMEXCPT_ENABLE = 1 << 10; - /// Prevents the execution of the SGDT, SIDT, SLDT, SMSW, and STR instructions by + /// Prevents the execution of the `SGDT`, `SIDT`, `SLDT`, `SMSW`, and `STR` instructions by /// user-mode software. const USER_MODE_INSTRUCTION_PREVENTION = 1 << 11; - /// Enables 5-level paging on supported CPUs. + /// Enables 5-level paging on supported CPUs (Intel Only). const L5_PAGING = 1 << 12; - /// Enables VMX insturctions. + /// Enables VMX instructions (Intel Only). const VIRTUAL_MACHINE_EXTENSIONS = 1 << 13; - /// Enables SMX instructions. + /// Enables SMX instructions (Intel Only). const SAFER_MODE_EXTENSIONS = 1 << 14; /// Enables software running in 64-bit mode at any privilege level to read and write /// the FS.base and GS.base hidden segment register state. const FSGSBASE = 1 << 16; /// Enables process-context identifiers (PCIDs). const PCID = 1 << 17; - /// Enables extendet processor state management instructions, including XGETBV and XSAVE. + /// Enables extended processor state management instructions, including `XGETBV` and `XSAVE`. const OSXSAVE = 1 << 18; - /// When set, the `LOADIWKEY` instruction is available; additionally, if system firmware has activated the AES key locker instructions, register EBX of CPUID leaf 0x19, bit 0 (AESKLE) is set and the AES key locker instructions are enabled. See the [Intel Key Locker Specification](https://software.intel.com/content/www/us/en/develop/download/intel-key-locker-specification.html) for information on this feature. + /// Enables the Key Locker feature (Intel Only). + /// + /// This enables creation and use of opaque AES key handles; see the + /// [Intel Key Locker Specification](https://software.intel.com/content/www/us/en/develop/download/intel-key-locker-specification.html) + /// for more information. const KEY_LOCKER = 1 << 19; /// Prevents the execution of instructions that reside in pages accessible by user-mode /// software when the processor is in supervisor-mode. @@ -124,11 +139,20 @@ bitflags! { /// Enables restrictions for supervisor-mode software when reading data from user-mode /// pages. const SUPERVISOR_MODE_ACCESS_PREVENTION = 1 << 21; - /// Enables 4-level and 5-level paging to associate each linear address with a protection key in user mode. + /// Enables protection keys for user-mode pages. + /// + /// Also enables access to the PKRU register (via the `RDPKRU`/`WRPKRU` + /// instructions) to set user-mode protection key access controls. const PROTECTION_KEY = 1 << 22; - /// When set, enables intel control-flow enforcement technology. See chapter 18 of the Intel software developer manuals, volume 1, for more information. + /// Enables Control-flow Enforcement Technology (CET) + /// + /// This enables the shadow stack feature, ensuring return addresses read + /// via `RET` and `IRET` have not been corrupted. const CONTROL_FLOW_ENFORCEMENT = 1 << 23; - /// When set, allows 4-level and 5-level paging implementations to use the `IA32_PKRS` MSR to specify, for each protection key, whether supervisor-mode linear addresses with a particular protection key can be read or written. + /// Enables protection keys for supervisor-mode pages (Intel Only). + /// + /// Also enables the `IA32_PKRS` MSR to set supervisor-mode protection + /// key access controls. const PROTECTION_KEY_SUPERVISOR = 1 << 24; } } diff --git a/src/registers/xcontrol.rs b/src/registers/xcontrol.rs index 66501d914..0fdc54705 100644 --- a/src/registers/xcontrol.rs +++ b/src/registers/xcontrol.rs @@ -7,29 +7,43 @@ pub struct XCr0; bitflags! { /// Configuration flags of the XCr0 register. + /// + /// For MPX, [`BNDREG`](XCr0Flags::BNDREG) and [`BNDCSR`](XCr0Flags::BNDCSR) must be set/unset simultaneously. + /// For AVX-512, [`OPMASK`](XCr0Flags::OPMASK), [`ZMM_HI256`](XCr0Flags::ZMM_HI256), and [`HI16_ZMM`](XCr0Flags::HI16_ZMM) must be set/unset simultaneously. pub struct XCr0Flags: u64 { - /// Enables x87 FPU + /// Enables using the x87 FPU state + /// with `XSAVE`/`XRSTOR`. + /// + /// Must be set. const X87 = 1; - /// Enables 128-bit (legacy) SSE - /// Must be set to enable AVX and YMM + /// Enables using MXCSR and the XMM registers + /// with `XSAVE`/`XRSTOR`. + /// + /// Must be set if [`YMM`](XCr0Flags::YMM) is set. const SSE = 1<<1; - /// Enables 256-bit SSE - /// Must be set to enable AVX + /// Enables AVX instructions and using the upper halves of the YMM registers + /// with `XSAVE`/`XRSTOR`. const YMM = 1<<2; - /// When set, MPX instructions are enabled and the bound registers BND0-BND3 can be managed by XSAVE. + /// Enables MPX instructions and using the BND0-BND3 bound registers + /// with `XSAVE`/`XRSTOR` (Intel Only). const BNDREG = 1 << 3; - /// When set, MPX instructions can be executed and XSAVE can manage the BNDCFGU and BNDSTATUS registers. + /// Enables MPX instructions and using the BNDCFGU and BNDSTATUS registers + /// with `XSAVE`/`XRSTOR` (Intel Only). const BNDCSR = 1 << 4; - /// If set, AVX-512 instructions can be executed and XSAVE can manage the K0-K7 mask registers. + /// Enables AVX-512 instructions and using the K0-K7 mask registers + /// with `XSAVE`/`XRSTOR` (Intel Only). const OPMASK = 1 << 5; - /// If set, AVX-512 instructions can be executed and XSAVE can be used to manage the upper halves of the lower ZMM registers. + /// Enables AVX-512 instructions and using the upper halves of the lower ZMM registers + /// with `XSAVE`/`XRSTOR` (Intel Only). const ZMM_HI256 = 1 << 6; - /// If set, AVX-512 instructions can be executed and XSAVE can manage the upper ZMM registers. + /// Enables AVX-512 instructions and using the upper ZMM registers + /// with `XSAVE`/`XRSTOR` (Intel Only). const HI16_ZMM = 1 << 7; - /// When set, PKRU state management is supported by - /// XSAVE/XRSTOR + /// Enables using the PKRU register + /// with `XSAVE`/`XRSTOR`. const MPK = 1<<9; - /// When set the Lightweight Profiling extensions are enabled + /// Enables Lightweight Profiling extensions and managing LWP state + /// with `XSAVE`/`XRSTOR` (AMD Only). const LWP = 1<<62; } } From d369377578543884a883aa1d2a08994cd413f617 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 7 Jul 2021 05:57:30 -0700 Subject: [PATCH 16/41] XCr0: Update the asserts to check all conditions This avoids getting a #GP when writing. Signed-off-by: Joe Richey --- src/registers/xcontrol.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/registers/xcontrol.rs b/src/registers/xcontrol.rs index 0fdc54705..8c4cb3cfa 100644 --- a/src/registers/xcontrol.rs +++ b/src/registers/xcontrol.rs @@ -82,6 +82,7 @@ mod x86_64 { /// Write XCR0 flags. /// /// Preserves the value of reserved fields. + /// Panics if invalid combinations of [`XCr0Flags`] are set. /// /// ## Safety /// @@ -92,13 +93,32 @@ mod x86_64 { let old_value = Self::read_raw(); let reserved = old_value & !(XCr0Flags::all().bits()); let new_value = reserved | flags.bits(); + assert!(flags.contains(XCr0Flags::X87), "The X87 flag must be set"); - assert!((flags.contains(XCr0Flags::AVX) && flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)) || !(flags.contains(XCr0Flags::AVX) && flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)), "You must enable AVX to set or unset any of XCR0.opmask, XCR0.ZMM_Hi256, and XCR0.Hi16_ZMM"); - if !flags.contains(XCr0Flags::AVX) && (flags.contains(XCr0Flags::OPMASK) || flags.contains(XCr0Flags::ZMM_HI256) || flags.contains(XCr0Flags::HI16_ZMM)) { - panic!("You must have AVX enabled to set XCR0.opmask, XCR0.ZMM_Hi256, or XCR0.Hi16_ZMM"); + if flags.contains(XCr0Flags::YMM) { + assert!( + flags.contains(XCr0Flags::SSE), + "AVX/YMM cannot be enabled without enabling SSE" + ); + } + let mpx = XCr0Flags::BNDREG | XCr0Flags::BNDCSR; + if flags.intersects(mpx) { + assert!( + flags.contains(mpx), + "MPX flags XCr0.BNDREG and XCr0.BNDCSR must be set and unset together" + ); + } + let avx512 = XCr0Flags::OPMASK | XCr0Flags::ZMM_HI256 | XCr0Flags::HI16_ZMM; + if flags.intersects(avx512) { + assert!( + flags.contains(XCr0Flags::YMM), + "AVX-512 cannot be enabled without enabling AVX/YMM" + ); + assert!( + flags.contains(avx512), + "AVX-512 flags XCR0.opmask, XCR0.ZMM_Hi256, and XCR0.Hi16_ZMM must be set and unset together" + ); } - assert!((flags.contains(XCr0Flags::BNDREG) && flags.contains(XCr0Flags::BNDCSR)) || !(flags.contains(XCr0Flags::BNDREG) && flags.contains(XCr0Flags::BNDCSR)), "BNDREG and BNDCSR must be set and unset together"); - assert!((flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)) || !(flags.contains(XCr0Flags::OPMASK) && flags.contains(XCr0Flags::ZMM_HI256) && flags.contains(XCr0Flags::HI16_ZMM)), "You must set or unset all of XCR0.opmask, XCR0.ZMM_Hi256, and XCR0.Hi16_ZMM"); Self::write_raw(new_value); } From 67be0e65a636d86c06e9f4d84926bf64b4b38caf Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 22 Jun 2021 04:42:14 -0700 Subject: [PATCH 17/41] Make align_up and align_down const We can't make VirtAddr and PhysAddr methods const as they wrap an into impl. --- src/addr.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index 5cac6c2db..2b064f718 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -534,8 +534,12 @@ impl Sub for PhysAddr { /// Returns the greatest x with alignment `align` so that x <= addr. The alignment must be /// a power of 2. #[inline] -pub fn align_down(addr: u64, align: u64) -> u64 { +pub const fn align_down(addr: u64, align: u64) -> u64 { + #[cfg(feature = "const_fn")] assert!(align.is_power_of_two(), "`align` must be a power of two"); + #[cfg(not(feature = "const_fn"))] + [(); 1][!align.is_power_of_two() as usize]; + addr & !(align - 1) } @@ -544,8 +548,12 @@ pub fn align_down(addr: u64, align: u64) -> u64 { /// Returns the smallest x with alignment `align` so that x >= addr. The alignment must be /// a power of 2. #[inline] -pub fn align_up(addr: u64, align: u64) -> u64 { +pub const fn align_up(addr: u64, align: u64) -> u64 { + #[cfg(feature = "const_fn")] assert!(align.is_power_of_two(), "`align` must be a power of two"); + #[cfg(not(feature = "const_fn"))] + [(); 1][!align.is_power_of_two() as usize]; + let align_mask = align - 1; if addr & align_mask == 0 { addr // already aligned From e501deaecb4f4c255a6a3746e039450e10d9b14e Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 16 Jul 2021 01:56:28 -0700 Subject: [PATCH 18/41] const_fn: Add const_assert helper macro Signed-off-by: Joe Richey --- src/lib.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 42be18368..cf146e31a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,19 @@ macro_rules! const_fn { }; } +// Helper method for assert! in const fn. Uses out of bounds indexing if an +// assertion fails and the "const_fn" feature is not enabled. +#[cfg(feature = "const_fn")] +macro_rules! const_assert { + ($cond:expr, $($arg:tt)+) => { assert!($cond, $($arg)*) }; +} +#[cfg(not(feature = "const_fn"))] +macro_rules! const_assert { + ($cond:expr, $($arg:tt)+) => { + [(); 1][!($cond as bool) as usize] + }; +} + #[cfg(all(feature = "instructions", feature = "external_asm"))] pub(crate) mod asm; From 2bbaebbfcf6295ecb051fbd611a870cf66737f51 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 16 Jul 2021 01:57:03 -0700 Subject: [PATCH 19/41] const_fn: Use const_assert helper macro Signed-off-by: Joe Richey --- src/addr.rs | 12 ++---------- src/structures/gdt.rs | 5 +---- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index 2b064f718..942376095 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -535,11 +535,7 @@ impl Sub for PhysAddr { /// a power of 2. #[inline] pub const fn align_down(addr: u64, align: u64) -> u64 { - #[cfg(feature = "const_fn")] - assert!(align.is_power_of_two(), "`align` must be a power of two"); - #[cfg(not(feature = "const_fn"))] - [(); 1][!align.is_power_of_two() as usize]; - + const_assert!(align.is_power_of_two(), "`align` must be a power of two"); addr & !(align - 1) } @@ -549,11 +545,7 @@ pub const fn align_down(addr: u64, align: u64) -> u64 { /// a power of 2. #[inline] pub const fn align_up(addr: u64, align: u64) -> u64 { - #[cfg(feature = "const_fn")] - assert!(align.is_power_of_two(), "`align` must be a power of two"); - #[cfg(not(feature = "const_fn"))] - [(); 1][!align.is_power_of_two() as usize]; - + const_assert!(align.is_power_of_two(), "`align` must be a power of two"); let align_mask = align - 1; if addr & align_mask == 0 { addr // already aligned diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index f71a8c11c..aa95ccf61 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -117,13 +117,10 @@ impl GlobalDescriptorTable { let mut table = [0; 8]; let mut idx = 0; - #[cfg(feature = "const_fn")] - assert!( + const_assert!( next_free <= 8, "initializing a GDT from a slice requires it to be **at most** 8 elements." ); - #[cfg(not(feature = "const_fn"))] - [(); 1][!(next_free <= 8) as usize]; while idx != next_free { table[idx] = slice[idx]; From 45d7c847bbf7cc1000078d7e6b3104498e0afc00 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 16 Jul 2021 01:57:22 -0700 Subject: [PATCH 20/41] Update documentation to note the weird panic message. Signed-off-by: Joe Richey --- src/addr.rs | 12 ++++++++---- src/structures/gdt.rs | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index 942376095..1c93f0219 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -531,8 +531,10 @@ impl Sub for PhysAddr { /// Align address downwards. /// -/// Returns the greatest x with alignment `align` so that x <= addr. The alignment must be -/// a power of 2. +/// Returns the greatest `x` with alignment `align` so that `x <= addr`. +/// +/// Panics if the alignment is not a power of two. Without the `const_fn` +/// feature, the panic message will be "index out of bounds". #[inline] pub const fn align_down(addr: u64, align: u64) -> u64 { const_assert!(align.is_power_of_two(), "`align` must be a power of two"); @@ -541,8 +543,10 @@ pub const fn align_down(addr: u64, align: u64) -> u64 { /// Align address upwards. /// -/// Returns the smallest x with alignment `align` so that x >= addr. The alignment must be -/// a power of 2. +/// Returns the smallest `x` with alignment `align` so that `x >= addr`. +/// +/// Panics if the alignment is not a power of two. Without the `const_fn` +/// feature, the panic message will be "index out of bounds". #[inline] pub const fn align_up(addr: u64, align: u64) -> u64 { const_assert!(align.is_power_of_two(), "`align` must be a power of two"); diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index aa95ccf61..c96d81161 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -141,7 +141,8 @@ impl GlobalDescriptorTable { const_fn! { /// Adds the given segment descriptor to the GDT, returning the segment selector. /// - /// Panics if the GDT has no free entries left. + /// Panics if the GDT has no free entries left. Without the `const_fn` + /// feature, the panic message will be "index out of bounds". #[inline] pub fn add_entry(&mut self, entry: Descriptor) -> SegmentSelector { let index = match entry { From 1a0b149c609491cd828d7d2b656a760bb7551a98 Mon Sep 17 00:00:00 2001 From: Ethan Budd Date: Fri, 16 Jul 2021 20:21:03 -0500 Subject: [PATCH 21/41] Add a SelectorErrorCode to segment_not_present in IDT (#274) * add a SelectorErrorCode * remove the breaking change * make SelectorErrorCode methods pub * Derive clone and copy for SelectorErrorCode * Make Descriptor table public say where it is used * update doc comment * added a new to SelectorErrorCode * Derive more * Update src/structures/idt.rs Co-authored-by: Joseph Richey --- src/structures/idt.rs | 75 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/structures/idt.rs b/src/structures/idt.rs index 0e318ead3..5931f738c 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -918,6 +918,81 @@ bitflags! { } } +/// Describes an error code referencing a segment selector. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct SelectorErrorCode { + flags: u64, +} + +impl SelectorErrorCode { + /// Create a SelectorErrorCode. Returns None is any of the reserved bits (16-64) are set. + pub const fn new(value: u64) -> Option { + if value > u16::MAX as u64 { + None + } else { + Some(Self { flags: value }) + } + } + + /// Create a new SelectorErrorCode dropping any reserved bits (16-64). + pub const fn new_truncate(value: u64) -> Self { + Self { + flags: (value as u16) as u64, + } + } + + /// If true, indicates that the exception occurred during delivery of an event + /// external to the program, such as an interrupt or an earlier exception. + pub fn external(&self) -> bool { + self.flags.get_bit(0) + } + + /// The descriptor table this error code refers to. + pub fn descriptor_table(&self) -> DescriptorTable { + match self.flags.get_bits(1..3) { + 0b00 => DescriptorTable::Gdt, + 0b01 => DescriptorTable::Idt, + 0b10 => DescriptorTable::Ldt, + 0b11 => DescriptorTable::Idt, + _ => unreachable!(), + } + } + + /// The index of the selector which caused the error. + pub fn index(&self) -> u64 { + self.flags.get_bits(3..16) + } + + /// If true, the #SS or #GP has returned zero as opposed to a SelectorErrorCode. + pub fn is_null(&self) -> bool { + self.flags == 0 + } +} + +impl fmt::Debug for SelectorErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut s = f.debug_struct("Selector Error"); + s.field("external", &self.external()); + s.field("descriptor table", &self.descriptor_table()); + s.field("index", &self.index()); + s.finish() + } +} + +/// The possible descriptor table values. +/// +/// Used by the [`SelectorErrorCode`] to indicate which table caused the error. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum DescriptorTable { + /// Global Descriptor Table. + Gdt, + /// Interrupt Descriptor Table. + Idt, + /// Logical Descriptor Table. + Ldt, +} + #[cfg(test)] mod test { use super::*; From 5afb69b491cc9e3af37ccabc9d3f917311c6dafb Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Sun, 18 Jul 2021 10:11:49 +0900 Subject: [PATCH 22/41] fix(Changelog.md): typo (#278) --- Changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index e03cdd157..2249590a9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,7 +6,7 @@ - `Port = PortGeneric` - `PortReadOnly = PortGeneric` - `PortWriteOnly = PortGeneric` -- The following methods no longer require the `nightly` feature to be `const fn`s` ([#255](https://github.com/rust-osdev/x86_64/pull/255)): +- The following methods no longer require the `nightly` feature to be `const fn`s ([#255](https://github.com/rust-osdev/x86_64/pull/255)): - `PageTable::new` - `GlobalDescriptorTable::from_raw_slice` - `MappedFrame::{start_address, size}` From cd9d7483fc369409dfc312b25751d8ed4eb3c878 Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Mon, 19 Jul 2021 03:38:56 +0900 Subject: [PATCH 23/41] feat(instructions): define `tables::sgdt` (#279) --- src/asm/asm.s | 6 ++++++ src/asm/mod.rs | 6 ++++++ src/instructions/tables.rs | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/src/asm/asm.s b/src/asm/asm.s index d679108bf..dfe0ff50c 100644 --- a/src/asm/asm.s +++ b/src/asm/asm.s @@ -123,6 +123,12 @@ _x86_64_asm_lidt: lidt (%rdi) retq +.global _x86_64_asm_sgdt +.p2align 4 +_x86_64_asm_sgdt: + sgdt (%rdi) + retq + .global _x86_64_asm_sidt .p2align 4 _x86_64_asm_sidt: diff --git a/src/asm/mod.rs b/src/asm/mod.rs index 9b9ddbdea..9ca4e0ca9 100644 --- a/src/asm/mod.rs +++ b/src/asm/mod.rs @@ -168,6 +168,12 @@ extern "sysv64" { )] pub(crate) fn x86_64_asm_lidt(idt: *const crate::instructions::tables::DescriptorTablePointer); + #[cfg_attr( + any(target_env = "gnu", target_env = "musl"), + link_name = "_x86_64_asm_sgdt" + )] + pub(crate) fn x86_64_asm_sgdt(gdt: *mut crate::instructions::tables::DescriptorTablePointer); + #[cfg_attr( any(target_env = "gnu", target_env = "musl"), link_name = "_x86_64_asm_sidt" diff --git a/src/instructions/tables.rs b/src/instructions/tables.rs index 27d79f920..667fb9b46 100644 --- a/src/instructions/tables.rs +++ b/src/instructions/tables.rs @@ -45,6 +45,23 @@ pub unsafe fn lidt(idt: &DescriptorTablePointer) { crate::asm::x86_64_asm_lidt(idt as *const _); } +/// Get the address of the current GDT. +#[inline] +pub fn sgdt() -> DescriptorTablePointer { + let mut gdt: DescriptorTablePointer = DescriptorTablePointer { + limit: 0, + base: VirtAddr::new(0), + }; + unsafe { + #[cfg(feature = "inline_asm")] + asm!("sgdt [{}]", in(reg) &mut gdt, options(nostack, preserves_flags)); + + #[cfg(not(feature = "inline_asm"))] + crate::asm::x86_64_asm_sgdt(&mut gdt as *mut _); + } + gdt +} + /// Get the address of the current IDT. #[inline] pub fn sidt() -> DescriptorTablePointer { From 769a15e546d210fd8314c894d1f2e8d5329ce35f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 19 Jul 2021 10:22:26 +0200 Subject: [PATCH 24/41] Create a CI script for automated releases (#281) * Create a basic release script * Install python requirements * Bump version for testing * Run cargo publish without --dry-run * Push the git tag to the repo * Set up crates.io token for releasing * Fix tagging * Authenticate `gh` tool * Run script only on master branch and set version back to 0.14.3 --- .github/workflows/release.yml | 27 +++++++++++++++++++++++++ scripts/ci-release.py | 37 +++++++++++++++++++++++++++++++++++ scripts/requirements.txt | 1 + 3 files changed, 65 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 scripts/ci-release.py create mode 100644 scripts/requirements.txt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..17583b0e1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,27 @@ +name: Release + +on: + push: + branches: + - 'master' + +jobs: + release: + name: "Release" + runs-on: ubuntu-latest + timeout-minutes: 15 + environment: crates_io_release + + steps: + - name: "Checkout Repository" + uses: actions/checkout@v1 + + - name: "Install Python Libraries" + run: python -m pip install --user -r requirements.txt + working-directory: "scripts" + + - name: "Run release script" + run: "python3 scripts/ci-release.py" + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/ci-release.py b/scripts/ci-release.py new file mode 100644 index 000000000..2386fe85c --- /dev/null +++ b/scripts/ci-release.py @@ -0,0 +1,37 @@ +import toml +import requests +import subprocess + +cargo_toml = toml.load("Cargo.toml") +crate_version = cargo_toml["package"]["version"] +print("Detected crate version " + crate_version) + +api_url = "https://crates.io/api/v1/crates/x86_64/versions" +crates_io_versions = requests.get(api_url).json() + +new_version = True +for version in crates_io_versions["versions"]: + assert (version["crate"] == "x86_64") + if version["num"] == crate_version: + new_version = False + break + +if new_version: + print("Could not find version " + crate_version + " on crates.io; creating a new release") + + print(" Running `cargo publish`") + subprocess.run(["cargo", "publish"], check=True) + + tag_name = "v" + crate_version + print(" Tagging commit as " + tag_name) + sha = subprocess.run(["git", "rev-parse", "HEAD"], check=True, stdout=subprocess.PIPE).stdout.decode("utf-8").strip() + subprocess.run([ + "gh", "api", "/repos/rust-osdev/x86_64/git/refs", + "-X", "POST", "-H", "Accept: application/vnd.github.v3+json", + "-F", "ref=refs/tags/" + tag_name, + "-F", "sha="+sha + ]) + + print(" Done") +else: + print("Version " + crate_version + " already exists on crates.io") diff --git a/scripts/requirements.txt b/scripts/requirements.txt new file mode 100644 index 000000000..bd79a658f --- /dev/null +++ b/scripts/requirements.txt @@ -0,0 +1 @@ +toml From 5d25dcaa80e37ac94ed11b60efe8078449f6627b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 19 Jul 2021 10:52:11 +0200 Subject: [PATCH 25/41] CI: Use more efficient crates.io API endpoint (#282) Instead of listing all versions, try to query the info about the respective version directly. --- scripts/ci-release.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/ci-release.py b/scripts/ci-release.py index 2386fe85c..ec0289e8f 100644 --- a/scripts/ci-release.py +++ b/scripts/ci-release.py @@ -6,25 +6,26 @@ crate_version = cargo_toml["package"]["version"] print("Detected crate version " + crate_version) -api_url = "https://crates.io/api/v1/crates/x86_64/versions" -crates_io_versions = requests.get(api_url).json() +api_url = "https://crates.io/api/v1/crates/x86_64/" + crate_version +released_version = requests.get(api_url).json() -new_version = True -for version in crates_io_versions["versions"]: +if "version" in released_version: + version = released_version["version"] assert (version["crate"] == "x86_64") - if version["num"] == crate_version: - new_version = False - break + assert (version["num"] == crate_version) + print("Version " + crate_version + " already exists on crates.io") -if new_version: - print("Could not find version " + crate_version + " on crates.io; creating a new release") +else: + print("Could not find version " + crate_version + + " on crates.io; creating a new release") print(" Running `cargo publish`") subprocess.run(["cargo", "publish"], check=True) tag_name = "v" + crate_version print(" Tagging commit as " + tag_name) - sha = subprocess.run(["git", "rev-parse", "HEAD"], check=True, stdout=subprocess.PIPE).stdout.decode("utf-8").strip() + sha = subprocess.run(["git", "rev-parse", "HEAD"], check=True, + stdout=subprocess.PIPE).stdout.decode("utf-8").strip() subprocess.run([ "gh", "api", "/repos/rust-osdev/x86_64/git/refs", "-X", "POST", "-H", "Accept: application/vnd.github.v3+json", @@ -33,5 +34,3 @@ ]) print(" Done") -else: - print("Version " + crate_version + " already exists on crates.io") From 2ab479b70676914a735e33c86c709da51277b8e8 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 19 Jul 2021 01:37:59 -0700 Subject: [PATCH 26/41] segmentation: update docs on how we set CS Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 2ca05ac83..b5423095a 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -131,9 +131,14 @@ pub struct CS; impl Segment for CS { get_reg_impl!("cs", x86_64_asm_get_cs); - /// 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. + /// Note this is special since we cannot directly move to [`CS`]; x86 requires the instruction + /// pointer and [`CS`] to be set at the same time. To do this, we push the new segment selector + /// and return value onto the stack and use a "far return" (`retfq`) to reload [`CS`] and + /// continue at the end of our function. + /// + /// Note we cannot use a "far call" (`lcall`) or "far jmp" (`ljmp`) to do this because then we + /// would only be able to jump to 32-bit instruction pointers. Only Intel implements support + /// for 64-bit far calls/jumps in long-mode, AMD does not. unsafe fn set_reg(sel: SegmentSelector) { #[cfg(feature = "inline_asm")] asm!( From c8928cc5bf2828dd8a88d717cd20e79e2a9492c5 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 19 Jul 2021 02:14:07 -0700 Subject: [PATCH 27/41] Changelog: update for next patch release Signed-off-by: Joe Richey --- Changelog.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Changelog.md b/Changelog.md index 2249590a9..84ee09b0b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,34 @@ # Unreleased +- Add `instructions::tables::sgdt` ([#279](https://github.com/rust-osdev/x86_64/pull/279)) +- Improve control register bits ([#273](https://github.com/rust-osdev/x86_64/pull/273)) + - Add `Cr0` bits: `EXTENSION_TYPE` (ET) + - Add `Cr4` bits: + - `KEY_LOCKER` (KL) + - `CONTROL_FLOW_ENFORCEMENT` (CET) + - `PROTECTION_KEY_SUPERVISOR` (PKS) + - Add `XCr0` bits: `BNDREG`, `BNDCSR`, `OPMASK`, `ZMM_HI256`, `HI16_ZMM` + - Add consistency checks for `XCr0` bits +- Add `SelectorErrorCode` for parsing interrupt error codes from `#TS`, `#NP`, `#SS`, and `#GP` ([#274](https://github.com/rust-osdev/x86_64/pull/274)) +- Make `addr::{align_up, align_down}` const ([#270](https://github.com/rust-osdev/x86_64/pull/270)) +- Make `structures::idt` available on stable Rust ([#271](https://github.com/rust-osdev/x86_64/pull/271)) + - Use dummy types for the `HandlerFunc`s if the `"abi_x86_interrupt"` feature is disabled + - Add unsafe `set_handler_addr` that just takes a `VirtAddr` +- Add common abstractions for x86 Segments ([#258](https://github.com/rust-osdev/x86_64/pull/258)) + - Add `SS`, `CS`, `DS`, `ES`, `FS`, `GS` marker types + - Add `Segment` trait for reading/writing the segment register + - Add `Segment64` trait for reading/writing the segment base + - Add `GS::swap()` + - Deprecate the corresponding free functions: + - `cs`, `set_cs` + - `swap_gs` + - `load_{ss,ds,es,fs,gs}` + - `{wr,rd}{fs,gs}base` +- Bug fixes: + - Corrected documentation typo ([#278](https://github.com/rust-osdev/x86_64/pull/278)) + - Avoided off-by-one error in `GlobalDescriptorTable::from_raw_slice` when `"const_fn"` is not enabled ([#269](https://github.com/rust-osdev/x86_64/pull/269)) + - Specify `sysv64` as the calling convention for the `"external_asm"` functions ([#267](https://github.com/rust-osdev/x86_64/pull/267)) + # 0.14.3 – 2021-05-14 - Make the following types aliases of the new `PortGeneric` type ([#248](https://github.com/rust-osdev/x86_64/pull/248)): From f8af5dcf3f516a692329be166701ad96a62f0127 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Mon, 19 Jul 2021 02:15:18 -0700 Subject: [PATCH 28/41] Bump version to 0.14.4 --- Cargo.toml | 2 +- Changelog.md | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 5859f2841..c0f64c9f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ license = "MIT/Apache-2.0" name = "x86_64" readme = "README.md" repository = "https://github.com/rust-osdev/x86_64" -version = "0.14.3" +version = "0.14.4" edition = "2018" [dependencies] diff --git a/Changelog.md b/Changelog.md index 84ee09b0b..ab5e428ad 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,7 @@ # Unreleased +# 0.14.4 – 2021-07-19 + - Add `instructions::tables::sgdt` ([#279](https://github.com/rust-osdev/x86_64/pull/279)) - Improve control register bits ([#273](https://github.com/rust-osdev/x86_64/pull/273)) - Add `Cr0` bits: `EXTENSION_TYPE` (ET) From 5af8aa8e92e11d494a9054de10a16fbebb869635 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 16 Jul 2021 00:44:03 -0700 Subject: [PATCH 29/41] XCr0: Add AVX as alias for YMM Deprecate YMM Signed-off-by: Joe Richey --- src/registers/xcontrol.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/registers/xcontrol.rs b/src/registers/xcontrol.rs index 8c4cb3cfa..cc94858c9 100644 --- a/src/registers/xcontrol.rs +++ b/src/registers/xcontrol.rs @@ -19,10 +19,13 @@ bitflags! { /// Enables using MXCSR and the XMM registers /// with `XSAVE`/`XRSTOR`. /// - /// Must be set if [`YMM`](XCr0Flags::YMM) is set. - const SSE = 1<<1; - /// Enables AVX instructions and using the upper halves of the YMM registers + /// Must be set if [`AVX`](XCr0Flags::AVX) is set. + const SSE = 1 << 1; + /// Enables AVX instructions and using the upper halves of the AVX registers /// with `XSAVE`/`XRSTOR`. + const AVX = 1 << 2; + /// Alias for [`AVX`](XCr0Flags::AVX) + #[deprecated(since = "0.14.5", note = "use `AVX` instead")] const YMM = 1<<2; /// Enables MPX instructions and using the BND0-BND3 bound registers /// with `XSAVE`/`XRSTOR` (Intel Only). @@ -95,10 +98,10 @@ mod x86_64 { let new_value = reserved | flags.bits(); assert!(flags.contains(XCr0Flags::X87), "The X87 flag must be set"); - if flags.contains(XCr0Flags::YMM) { + if flags.contains(XCr0Flags::AVX) { assert!( flags.contains(XCr0Flags::SSE), - "AVX/YMM cannot be enabled without enabling SSE" + "AVX cannot be enabled without enabling SSE" ); } let mpx = XCr0Flags::BNDREG | XCr0Flags::BNDCSR; @@ -111,8 +114,8 @@ mod x86_64 { let avx512 = XCr0Flags::OPMASK | XCr0Flags::ZMM_HI256 | XCr0Flags::HI16_ZMM; if flags.intersects(avx512) { assert!( - flags.contains(XCr0Flags::YMM), - "AVX-512 cannot be enabled without enabling AVX/YMM" + flags.contains(XCr0Flags::AVX), + "AVX-512 cannot be enabled without enabling AVX" ); assert!( flags.contains(avx512), From b8acbdaf5cc7646cd44e63af376c1c2195af65a7 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 16 Jul 2021 00:49:48 -0700 Subject: [PATCH 30/41] Cr4: Add PROTECTION_KEY_USER Deprecate PROTECTION_KEY Signed-off-by: Joe Richey --- src/registers/control.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registers/control.rs b/src/registers/control.rs index a8f306e68..c55b55871 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -143,6 +143,9 @@ bitflags! { /// /// Also enables access to the PKRU register (via the `RDPKRU`/`WRPKRU` /// instructions) to set user-mode protection key access controls. + const PROTECTION_KEY_USER = 1 << 22; + /// Alias for [`PROTECTION_KEY_USER`](Cr4Flags::PROTECTION_KEY_USER) + #[deprecated(since = "0.14.5", note = "use `PROTECTION_KEY_USER` instead")] const PROTECTION_KEY = 1 << 22; /// Enables Control-flow Enforcement Technology (CET) /// From 62b789585189b139e2fec61a9e802b95c677ad29 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 18 Aug 2021 11:07:00 +0200 Subject: [PATCH 31/41] Fix CI tests on Windows (#297) * ci: Use choco to install qemu * Version 6.0.0 corresponds to release on 2021.5.5 Co-authored-by: Joe Richey --- .github/workflows/build.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b60dba22..d99a2d820 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -133,14 +133,10 @@ jobs: HOMEBREW_NO_AUTO_UPDATE: 1 HOMEBREW_NO_BOTTLE_SOURCE_FALLBACK: 1 HOMEBREW_NO_INSTALL_CLEANUP: 1 - - name: Install Scoop (Windows) - run: | - Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') - echo "$HOME\scoop\shims" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - if: runner.os == 'Windows' - shell: pwsh - name: Install QEMU (Windows) - run: scoop install qemu + run: | + choco install qemu --version 2021.5.5 + echo "$Env:Programfiles\qemu" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append if: runner.os == 'Windows' shell: pwsh From a5a3a34a1a38be80b73bea2eb5202ed81a2b0bbe Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 1 Aug 2021 17:34:26 +0200 Subject: [PATCH 32/41] Run `cargo doc` in our CI job --- .github/workflows/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d99a2d820..cd7d224d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,6 +65,11 @@ jobs: with: command: build + - name: "Run cargo doc" + uses: actions-rs/cargo@v1 + with: + command: doc + - name: "Run cargo build for stable without instructions" uses: actions-rs/cargo@v1 with: From acfa59c6b7bf1ec6b6c4165cd536e3ff1f5bbfc6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 1 Aug 2021 17:47:09 +0200 Subject: [PATCH 33/41] Use `#[cfg(doc)]` instead of docs.rs-specific cfg flag The documentation can also be built locally using `cargo doc --open`. Doc links should be working there as well. --- Cargo.toml | 3 --- src/instructions/segmentation.rs | 2 +- src/registers/control.rs | 2 +- src/registers/model_specific.rs | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c0f64c9f1..1bc113a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,9 +42,6 @@ inline_asm = [] abi_x86_interrupt = [] const_fn = [] -[package.metadata.docs.rs] -rustdoc-args = ["--cfg", "docsrs"] - [package.metadata.release] no-dev-version = true pre-release-replacements = [ diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index b5423095a..548a556da 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -1,6 +1,6 @@ //! Provides functions to read and write segment registers. -#[cfg(docsrs)] +#[cfg(doc)] use crate::{ registers::control::Cr4Flags, structures::gdt::{Descriptor, GlobalDescriptorTable}, diff --git a/src/registers/control.rs b/src/registers/control.rs index c55b55871..904be4100 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -1,7 +1,7 @@ //! Functions to read and write control registers. pub use super::model_specific::{Efer, EferFlags}; -#[cfg(docsrs)] +#[cfg(doc)] use crate::{registers::rflags::RFlags, structures::paging::PageTableFlags}; use bitflags::bitflags; diff --git a/src/registers/model_specific.rs b/src/registers/model_specific.rs index 073a08c4f..191ace54a 100644 --- a/src/registers/model_specific.rs +++ b/src/registers/model_specific.rs @@ -1,6 +1,6 @@ //! Functions to read and write model specific registers. -#[cfg(docsrs)] +#[cfg(doc)] use crate::{ instructions::segmentation::{Segment64, FS, GS}, registers::control::Cr4Flags, From 17ca2b07cdee30daf9ea4c5c7d271f2cb998b00b Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 13 Aug 2021 17:47:08 -0700 Subject: [PATCH 34/41] Add doc_cfg for annotations on nightly Signed-off-by: Joe Richey --- Cargo.toml | 3 ++- src/instructions/interrupts.rs | 5 ++++- src/instructions/mod.rs | 5 ++++- src/lib.rs | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1bc113a86..e476b63d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,10 +37,11 @@ cc = { version = "1.0.37", optional = true } default = [ "nightly", "instructions" ] instructions = [] external_asm = [ "cc" ] -nightly = [ "inline_asm", "const_fn", "abi_x86_interrupt" ] +nightly = [ "inline_asm", "const_fn", "abi_x86_interrupt", "doc_cfg" ] inline_asm = [] abi_x86_interrupt = [] const_fn = [] +doc_cfg = [] [package.metadata.release] no-dev-version = true diff --git a/src/instructions/interrupts.rs b/src/instructions/interrupts.rs index f1d171cf8..eee606ee9 100644 --- a/src/instructions/interrupts.rs +++ b/src/instructions/interrupts.rs @@ -153,7 +153,10 @@ pub fn int3() { /// immediate. This macro will be replaced by a generic function when support for /// const generics is implemented in Rust. #[cfg(feature = "inline_asm")] -#[cfg_attr(docsrs, doc(cfg(any(feature = "nightly", feature = "inline_asm"))))] +#[cfg_attr( + feature = "doc_cfg", + doc(cfg(any(feature = "nightly", feature = "inline_asm"))) +)] #[macro_export] macro_rules! software_interrupt { ($x:expr) => {{ diff --git a/src/instructions/mod.rs b/src/instructions/mod.rs index de1c4c3fa..27c42f17a 100644 --- a/src/instructions/mod.rs +++ b/src/instructions/mod.rs @@ -54,7 +54,10 @@ pub fn bochs_breakpoint() { /// Gets the current instruction pointer. Note that this is only approximate as it requires a few /// instructions to execute. #[cfg(feature = "inline_asm")] -#[cfg_attr(docsrs, doc(cfg(any(feature = "nightly", feature = "inline_asm"))))] +#[cfg_attr( + feature = "doc_cfg", + doc(cfg(any(feature = "nightly", feature = "inline_asm"))) +)] #[inline(always)] pub fn read_rip() -> crate::VirtAddr { let rip: u64; diff --git a/src/lib.rs b/src/lib.rs index cf146e31a..73a6c5061 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ #![cfg_attr(feature = "const_fn", feature(const_fn_trait_bound))] // PageSize marker trait #![cfg_attr(feature = "inline_asm", feature(asm))] #![cfg_attr(feature = "abi_x86_interrupt", feature(abi_x86_interrupt))] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(feature = "doc_cfg", feature(doc_cfg))] #![warn(missing_docs)] #![deny(missing_debug_implementations)] From 5354b0243510a5f01138ad1c6b408d1d755f0c39 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Fri, 13 Aug 2021 17:52:23 -0700 Subject: [PATCH 35/41] CI: add cargo doc run using stable features Signed-off-by: Joe Richey --- .github/workflows/build.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd7d224d9..82e408a91 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,6 +70,13 @@ jobs: with: command: doc + - name: "Run cargo doc for stable" + uses: actions-rs/cargo@v1 + with: + command: doc + args: --no-default-features --features external_asm,instructions + if: runner.os != 'Windows' + - name: "Run cargo build for stable without instructions" uses: actions-rs/cargo@v1 with: From f6dbd9706ad88cdd1e91a3f1ecda4d28794e6cbd Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Sun, 29 Aug 2021 11:18:43 +0900 Subject: [PATCH 36/41] docs(idt): remove a panic info This method never panics. --- src/structures/idt.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/structures/idt.rs b/src/structures/idt.rs index 5931f738c..f65b91721 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -773,8 +773,6 @@ impl EntryOptions { /// Set the required privilege level (DPL) for invoking the handler. The DPL can be 0, 1, 2, /// or 3, the default is 0. If CPL < DPL, a general protection fault occurs. - /// - /// This function panics for a DPL > 3. #[inline] pub fn set_privilege_level(&mut self, dpl: PrivilegeLevel) -> &mut Self { self.0.set_bits(13..15, dpl as u16); From 278e1fee9d8a639076091efeed48b179bde7aa6b Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Sun, 15 Aug 2021 22:02:01 +0900 Subject: [PATCH 37/41] feat(mapper): expose `MapperFlush(All)?::new` It looks like there is no way to create an instance of these types. Fixes: #295 --- src/structures/paging/mapper/mod.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/structures/paging/mapper/mod.rs b/src/structures/paging/mapper/mod.rs index a73f2d8c4..4bfef61da 100644 --- a/src/structures/paging/mapper/mod.rs +++ b/src/structures/paging/mapper/mod.rs @@ -381,8 +381,11 @@ pub struct MapperFlush(Page); impl MapperFlush { /// Create a new flush promise + /// + /// Note that this method is intended for implementing the [`Mapper`] trait and no other uses + /// are expected. #[inline] - fn new(page: Page) -> Self { + pub fn new(page: Page) -> Self { MapperFlush(page) } @@ -403,14 +406,17 @@ impl MapperFlush { /// The old mapping might be still cached in the translation lookaside buffer (TLB), so it needs /// to be flushed from the TLB before it's accessed. This type is returned from a function that /// made the change to ensure that the TLB flush is not forgotten. -#[derive(Debug)] +#[derive(Debug, Default)] #[must_use = "Page Table changes must be flushed or ignored."] pub struct MapperFlushAll(()); impl MapperFlushAll { /// Create a new flush promise + /// + /// Note that this method is intended for implementing the [`Mapper`] trait and no other uses + /// are expected. #[inline] - fn new() -> Self { + pub fn new() -> Self { MapperFlushAll(()) } From 0290fa1aa1e6c31c9a6bf0193a61452e268a907d Mon Sep 17 00:00:00 2001 From: Nate Catelli Date: Sun, 29 Aug 2021 12:58:41 -0500 Subject: [PATCH 38/41] fix gdt load docs to cover deprecation --- src/structures/gdt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index c96d81161..bf2d33e94 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -173,8 +173,8 @@ impl GlobalDescriptorTable { /// Loads the GDT in the CPU using the `lgdt` instruction. This does **not** alter any of the /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): - /// [load_ss](crate::instructions::segmentation::load_ss), - /// [set_cs](crate::instructions::segmentation::set_cs). + /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), + /// [CSS::set_reg](crate::instructions::segmentation::CSS::set_reg). #[cfg(feature = "instructions")] #[inline] pub fn load(&'static self) { @@ -185,8 +185,8 @@ impl GlobalDescriptorTable { /// Loads the GDT in the CPU using the `lgdt` instruction. This does **not** alter any of the /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): - /// [load_ss](crate::instructions::segmentation::load_ss), - /// [set_cs](crate::instructions::segmentation::set_cs). + /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), + /// [CSS::set_reg](crate::instructions::segmentation::CSS::set_reg). /// /// # Safety /// From 62f99df3e0bfe70e5fadcba91d66bb3666bce56f Mon Sep 17 00:00:00 2001 From: Hiroki Tokunaga Date: Mon, 30 Aug 2021 10:35:35 +0900 Subject: [PATCH 39/41] fix(gdt): typo --- src/structures/gdt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index bf2d33e94..736dc7121 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -174,7 +174,7 @@ impl GlobalDescriptorTable { /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), - /// [CSS::set_reg](crate::instructions::segmentation::CSS::set_reg). + /// [CS::set_reg](crate::instructions::segmentation::CS::set_reg). #[cfg(feature = "instructions")] #[inline] pub fn load(&'static self) { @@ -186,7 +186,7 @@ impl GlobalDescriptorTable { /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), - /// [CSS::set_reg](crate::instructions::segmentation::CSS::set_reg). + /// [CS::set_reg](crate::instructions::segmentation::CS::set_reg). /// /// # Safety /// From 09dc4301a4d6c3bc3ba8e514210a3fa342050a10 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 31 Aug 2021 21:47:50 -0700 Subject: [PATCH 40/41] docs: Fix links by importing Segment trait We also fix the formating slightly for consistancy with the rest of the codebase. Signed-off-by: Joe Richey --- src/structures/gdt.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 736dc7121..38cc965e2 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -1,5 +1,7 @@ //! Types for the Global Descriptor Table and segment selectors. +#[cfg(doc)] +use crate::instructions::segmentation::{Segment, CS, SS}; use crate::structures::tss::TaskStateSegment; use crate::PrivilegeLevel; use bit_field::BitField; @@ -173,8 +175,7 @@ impl GlobalDescriptorTable { /// Loads the GDT in the CPU using the `lgdt` instruction. This does **not** alter any of the /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): - /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), - /// [CS::set_reg](crate::instructions::segmentation::CS::set_reg). + /// [`SS::set_reg()`] and [`CS::set_reg()`]. #[cfg(feature = "instructions")] #[inline] pub fn load(&'static self) { @@ -185,8 +186,7 @@ impl GlobalDescriptorTable { /// Loads the GDT in the CPU using the `lgdt` instruction. This does **not** alter any of the /// segment registers; you **must** (re)load them yourself using [the appropriate /// functions](crate::instructions::segmentation): - /// [SS::set_reg](crate::instructions::segmentation::SS::set_reg), - /// [CS::set_reg](crate::instructions::segmentation::CS::set_reg). + /// [`SS::set_reg()`] and [`CS::set_reg()`]. /// /// # Safety /// From cd4d3c2f895a0282ec920b2b3be051e22ed87fca Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 5 Sep 2021 13:27:56 +0200 Subject: [PATCH 41/41] Add `clean_up` and `clean_up_with_filter` (#264) --- src/addr.rs | 7 ++ src/instructions/tlb.rs | 1 + src/registers/control.rs | 4 + src/structures/idt.rs | 1 + .../paging/mapper/mapped_page_table.rs | 90 ++++++++++++++++- src/structures/paging/mapper/mod.rs | 48 ++++++++- .../paging/mapper/offset_page_table.rs | 24 ++++- .../paging/mapper/recursive_page_table.rs | 97 ++++++++++++++++++- src/structures/paging/page.rs | 13 +++ src/structures/paging/page_table.rs | 35 +++++++ 10 files changed, 311 insertions(+), 9 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index 1c93f0219..4c962e2b4 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -3,6 +3,7 @@ use core::fmt; use core::ops::{Add, AddAssign, Sub, SubAssign}; +use crate::structures::paging::page_table::PageTableLevel; use crate::structures::paging::{PageOffset, PageTableIndex}; use bit_field::BitField; @@ -198,6 +199,12 @@ impl VirtAddr { pub const fn p4_index(self) -> PageTableIndex { PageTableIndex::new_truncate((self.0 >> 12 >> 9 >> 9 >> 9) as u16) } + + /// Returns the 9-bit level page table index. + #[inline] + pub const fn page_table_index(self, level: PageTableLevel) -> PageTableIndex { + PageTableIndex::new_truncate((self.0 >> 12 >> ((level as u8 - 1) * 9)) as u16) + } } impl fmt::Debug for VirtAddr { diff --git a/src/instructions/tlb.rs b/src/instructions/tlb.rs index ea980863d..6e93f905d 100644 --- a/src/instructions/tlb.rs +++ b/src/instructions/tlb.rs @@ -72,6 +72,7 @@ impl Pcid { /// Invalidate the given address in the TLB using the `invpcid` instruction. /// /// ## Safety +/// /// This function is unsafe as it requires CPUID.(EAX=07H, ECX=0H):EBX.INVPCID to be 1. #[inline] pub unsafe fn flush_pcid(command: InvPicdCommand) { diff --git a/src/registers/control.rs b/src/registers/control.rs index 904be4100..0c8c81e12 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -301,6 +301,7 @@ mod x86_64 { /// Write a new P4 table address into the CR3 register. /// /// ## Safety + /// /// Changing the level 4 page table is unsafe, because it's possible to violate memory safety by /// changing the page mapping. #[inline] @@ -311,6 +312,7 @@ mod x86_64 { /// Write a new P4 table address into the CR3 register. /// /// ## Safety + /// /// Changing the level 4 page table is unsafe, because it's possible to violate memory safety by /// changing the page mapping. /// [`Cr4Flags::PCID`] must be set before calling this method. @@ -322,6 +324,7 @@ mod x86_64 { /// Write a new P4 table address into the CR3 register. /// /// ## Safety + /// /// Changing the level 4 page table is unsafe, because it's possible to violate memory safety by /// changing the page mapping. #[inline] @@ -400,6 +403,7 @@ mod x86_64 { /// Updates CR4 flags. /// /// Preserves the value of reserved fields. + /// /// ## Safety /// /// This function is unsafe because it's possible to violate memory diff --git a/src/structures/idt.rs b/src/structures/idt.rs index f65b91721..d427a76ed 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -789,6 +789,7 @@ impl EntryOptions { /// This function panics if the index is not in the range 0..7. /// /// ## Safety + /// /// This function is unsafe because the caller must ensure that the passed stack index is /// valid and not used by other interrupts. Otherwise, memory safety violations are possible. #[inline] diff --git a/src/structures/paging/mapper/mapped_page_table.rs b/src/structures/paging/mapper/mapped_page_table.rs index 7ae9e2c83..452e38cf7 100644 --- a/src/structures/paging/mapper/mapped_page_table.rs +++ b/src/structures/paging/mapper/mapped_page_table.rs @@ -1,9 +1,9 @@ use crate::structures::paging::{ frame::PhysFrame, - frame_alloc::FrameAllocator, + frame_alloc::{FrameAllocator, FrameDeallocator}, mapper::*, - page::{AddressNotAligned, Page, Size1GiB, Size2MiB, Size4KiB}, - page_table::{FrameError, PageTable, PageTableEntry, PageTableFlags}, + page::{AddressNotAligned, Page, PageRangeInclusive, Size1GiB, Size2MiB, Size4KiB}, + page_table::{FrameError, PageTable, PageTableEntry, PageTableFlags, PageTableLevel}, }; /// A Mapper implementation that relies on a PhysAddr to VirtAddr conversion function. @@ -584,6 +584,90 @@ impl<'a, P: PageTableFrameMapping> Translate for MappedPageTable<'a, P> { } } +impl<'a, P: PageTableFrameMapping> CleanUp for MappedPageTable<'a, P> { + #[inline] + unsafe fn clean_up(&mut self, frame_deallocator: &mut D) + where + D: FrameDeallocator, + { + self.clean_up_addr_range( + PageRangeInclusive { + start: Page::from_start_address(VirtAddr::new(0)).unwrap(), + end: Page::from_start_address(VirtAddr::new(0xffff_ffff_ffff_f000)).unwrap(), + }, + frame_deallocator, + ) + } + + unsafe fn clean_up_addr_range( + &mut self, + range: PageRangeInclusive, + frame_deallocator: &mut D, + ) where + D: FrameDeallocator, + { + unsafe fn clean_up( + page_table: &mut PageTable, + page_table_walker: &PageTableWalker

, + level: PageTableLevel, + range: PageRangeInclusive, + frame_deallocator: &mut impl FrameDeallocator, + ) -> bool { + if range.is_empty() { + return false; + } + + let table_addr = range + .start + .start_address() + .align_down(level.table_address_space_alignment()); + + let start = range.start.page_table_index(level); + let end = range.end.page_table_index(level); + + if let Some(next_level) = level.next_lower_level() { + let offset_per_entry = level.entry_address_space_alignment(); + for (i, entry) in page_table + .iter_mut() + .enumerate() + .take(usize::from(end) + 1) + .skip(usize::from(start)) + { + if let Ok(page_table) = page_table_walker.next_table_mut(entry) { + let start = table_addr + (offset_per_entry * (i as u64)); + let end = start + (offset_per_entry - 1); + let start = Page::::containing_address(start); + let start = start.max(range.start); + let end = Page::::containing_address(end); + let end = end.min(range.end); + if clean_up( + page_table, + page_table_walker, + next_level, + Page::range_inclusive(start, end), + frame_deallocator, + ) { + let frame = entry.frame().unwrap(); + entry.set_unused(); + frame_deallocator.deallocate_frame(frame); + } + } + } + } + + page_table.iter().all(PageTableEntry::is_unused) + } + + clean_up( + self.level_4_table, + &self.page_table_walker, + PageTableLevel::Four, + range, + frame_deallocator, + ); + } +} + #[derive(Debug)] struct PageTableWalker { page_table_frame_mapping: P, diff --git a/src/structures/paging/mapper/mod.rs b/src/structures/paging/mapper/mod.rs index 4bfef61da..5d205bdb2 100644 --- a/src/structures/paging/mapper/mod.rs +++ b/src/structures/paging/mapper/mod.rs @@ -7,8 +7,10 @@ pub use self::offset_page_table::OffsetPageTable; pub use self::recursive_page_table::{InvalidPageTable, RecursivePageTable}; use crate::structures::paging::{ - frame_alloc::FrameAllocator, page_table::PageTableFlags, Page, PageSize, PhysFrame, Size1GiB, - Size2MiB, Size4KiB, + frame_alloc::{FrameAllocator, FrameDeallocator}, + page::PageRangeInclusive, + page_table::PageTableFlags, + Page, PageSize, PhysFrame, Size1GiB, Size2MiB, Size4KiB, }; use crate::{PhysAddr, VirtAddr}; @@ -480,3 +482,45 @@ pub enum TranslateError { } static _ASSERT_OBJECT_SAFE: Option<&(dyn Translate + Sync)> = None; + +/// Provides methods for cleaning up unused entries. +pub trait CleanUp { + /// Remove all empty P1-P3 tables + /// + /// ## Safety + /// + /// The caller has to guarantee that it's safe to free page table frames: + /// All page table frames must only be used once and only in this page table + /// (e.g. no reference counted page tables or reusing the same page tables for different virtual addresses ranges in the same page table). + unsafe fn clean_up(&mut self, frame_deallocator: &mut D) + where + D: FrameDeallocator; + + /// Remove all empty P1-P3 tables in a certain range + /// ``` + /// # use core::ops::RangeInclusive; + /// # use x86_64::{VirtAddr, structures::paging::{ + /// # FrameDeallocator, Size4KiB, MappedPageTable, mapper::{RecursivePageTable, CleanUp}, page::{Page, PageRangeInclusive}, + /// # }}; + /// # unsafe fn test(page_table: &mut RecursivePageTable, frame_deallocator: &mut impl FrameDeallocator) { + /// // clean up all page tables in the lower half of the address space + /// let lower_half = Page::range_inclusive( + /// Page::containing_address(VirtAddr::new(0)), + /// Page::containing_address(VirtAddr::new(0x0000_7fff_ffff_ffff)), + /// ); + /// page_table.clean_up_addr_range(lower_half, frame_deallocator); + /// # } + /// ``` + /// + /// ## Safety + /// + /// The caller has to guarantee that it's safe to free page table frames: + /// All page table frames must only be used once and only in this page table + /// (e.g. no reference counted page tables or reusing the same page tables for different virtual addresses ranges in the same page table). + unsafe fn clean_up_addr_range( + &mut self, + range: PageRangeInclusive, + frame_deallocator: &mut D, + ) where + D: FrameDeallocator; +} diff --git a/src/structures/paging/mapper/offset_page_table.rs b/src/structures/paging/mapper/offset_page_table.rs index 6cfaf42f6..b7f04f6e4 100644 --- a/src/structures/paging/mapper/offset_page_table.rs +++ b/src/structures/paging/mapper/offset_page_table.rs @@ -1,7 +1,8 @@ #![cfg(target_pointer_width = "64")] use crate::structures::paging::{ - frame::PhysFrame, mapper::*, page_table::PageTable, Page, PageTableFlags, + frame::PhysFrame, mapper::*, page::PageRangeInclusive, page_table::PageTable, FrameDeallocator, + Page, PageTableFlags, }; /// A Mapper implementation that requires that the complete physically memory is mapped at some @@ -264,3 +265,24 @@ impl<'a> Translate for OffsetPageTable<'a> { self.inner.translate(addr) } } + +impl<'a> CleanUp for OffsetPageTable<'a> { + #[inline] + unsafe fn clean_up(&mut self, frame_deallocator: &mut D) + where + D: FrameDeallocator, + { + self.inner.clean_up(frame_deallocator) + } + + #[inline] + unsafe fn clean_up_addr_range( + &mut self, + range: PageRangeInclusive, + frame_deallocator: &mut D, + ) where + D: FrameDeallocator, + { + self.inner.clean_up_addr_range(range, frame_deallocator) + } +} diff --git a/src/structures/paging/mapper/recursive_page_table.rs b/src/structures/paging/mapper/recursive_page_table.rs index b66aec176..1ae8d2b26 100644 --- a/src/structures/paging/mapper/recursive_page_table.rs +++ b/src/structures/paging/mapper/recursive_page_table.rs @@ -4,12 +4,12 @@ use core::fmt; use super::*; use crate::registers::control::Cr3; -use crate::structures::paging::PageTableIndex; +use crate::structures::paging::page_table::PageTableLevel; use crate::structures::paging::{ frame_alloc::FrameAllocator, - page::{AddressNotAligned, NotGiantPageSize}, + page::{AddressNotAligned, NotGiantPageSize, PageRangeInclusive}, page_table::{FrameError, PageTable, PageTableEntry, PageTableFlags}, - Page, PageSize, PhysFrame, Size1GiB, Size2MiB, Size4KiB, + FrameDeallocator, Page, PageSize, PageTableIndex, PhysFrame, Size1GiB, Size2MiB, Size4KiB, }; use crate::VirtAddr; @@ -829,6 +829,97 @@ impl<'a> Translate for RecursivePageTable<'a> { } } +impl<'a> CleanUp for RecursivePageTable<'a> { + #[inline] + unsafe fn clean_up(&mut self, frame_deallocator: &mut D) + where + D: FrameDeallocator, + { + self.clean_up_addr_range( + PageRangeInclusive { + start: Page::from_start_address(VirtAddr::new(0)).unwrap(), + end: Page::from_start_address(VirtAddr::new(0xffff_ffff_ffff_f000)).unwrap(), + }, + frame_deallocator, + ) + } + + unsafe fn clean_up_addr_range( + &mut self, + range: PageRangeInclusive, + frame_deallocator: &mut D, + ) where + D: FrameDeallocator, + { + fn clean_up( + recursive_index: PageTableIndex, + page_table: &mut PageTable, + level: PageTableLevel, + range: PageRangeInclusive, + frame_deallocator: &mut impl FrameDeallocator, + ) -> bool { + if range.is_empty() { + return false; + } + + let table_addr = range + .start + .start_address() + .align_down(level.table_address_space_alignment()); + + let start = range.start.page_table_index(level); + let end = range.end.page_table_index(level); + + if let Some(next_level) = level.next_lower_level() { + let offset_per_entry = level.entry_address_space_alignment(); + for (i, entry) in page_table + .iter_mut() + .enumerate() + .take(usize::from(end) + 1) + .skip(usize::from(start)) + .filter(|(i, _)| { + !(level == PageTableLevel::Four && *i == recursive_index.into()) + }) + { + if let Ok(frame) = entry.frame() { + let start = table_addr + (offset_per_entry * (i as u64)); + let end = start + (offset_per_entry - 1); + let start = Page::::containing_address(start); + let start = start.max(range.start); + let end = Page::::containing_address(end); + let end = end.min(range.end); + let page_table = + [p1_ptr, p2_ptr, p3_ptr][level as usize - 2](start, recursive_index); + let page_table = unsafe { &mut *page_table }; + if clean_up( + recursive_index, + page_table, + next_level, + Page::range_inclusive(start, end), + frame_deallocator, + ) { + entry.set_unused(); + unsafe { + frame_deallocator.deallocate_frame(frame); + } + } + } + } + } + + page_table.iter().all(PageTableEntry::is_unused) + } + + clean_up( + self.recursive_index, + self.level_4_table(), + PageTableLevel::Four, + range, + frame_deallocator, + ); + } +} + /// The given page table was not suitable to create a `RecursivePageTable`. #[derive(Debug)] pub enum InvalidPageTable { diff --git a/src/structures/paging/page.rs b/src/structures/paging/page.rs index 1169946a5..0e9136dd0 100644 --- a/src/structures/paging/page.rs +++ b/src/structures/paging/page.rs @@ -1,5 +1,6 @@ //! Abstractions for default-sized and huge virtual memory pages. +use crate::structures::paging::page_table::PageTableLevel; use crate::structures::paging::PageTableIndex; use crate::VirtAddr; use core::fmt; @@ -130,6 +131,18 @@ impl Page { } } + const_fn! { + /// Returns the level 3 page table index of this page. + /// + /// ## Panics + /// + /// Panics if level is not between 1 and 4 + #[inline] + pub fn page_table_index(self, level: PageTableLevel) -> PageTableIndex { + self.start_address().page_table_index(level) + } + } + const_fn! { /// Returns a range of pages, exclusive `end`. #[inline] diff --git a/src/structures/paging/page_table.rs b/src/structures/paging/page_table.rs index ac83a2c52..3481b184a 100644 --- a/src/structures/paging/page_table.rs +++ b/src/structures/paging/page_table.rs @@ -364,3 +364,38 @@ impl From for usize { usize::from(offset.0) } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +/// A value between 1 and 4. +pub enum PageTableLevel { + /// Represents the level for a page table. + One = 1, + /// Represents the level for a page directory. + Two, + /// Represents the level for a page-directory pointer. + Three, + /// Represents the level for a page-map level-4. + Four, +} + +impl PageTableLevel { + /// Returns the next lower level or `None` for level 1 + pub const fn next_lower_level(self) -> Option { + match self { + PageTableLevel::Four => Some(PageTableLevel::Three), + PageTableLevel::Three => Some(PageTableLevel::Two), + PageTableLevel::Two => Some(PageTableLevel::One), + PageTableLevel::One => None, + } + } + + /// Returns the alignment for the address space described by a table of this level. + pub const fn table_address_space_alignment(self) -> u64 { + 1u64 << (self as u8 * 9 + 12) + } + + /// Returns the alignment for the address space described by an entry in a table of this level. + pub const fn entry_address_space_alignment(self) -> u64 { + 1u64 << (((self as u8 - 1) * 9) + 12) + } +}