From 6797d6303f78bb05508345ffef421679d3107bf1 Mon Sep 17 00:00:00 2001 From: Michael Jenny Date: Fri, 8 Apr 2022 18:08:39 +0200 Subject: [PATCH 01/13] Provide a method to retrieve the null segment descriptor. This may be useful when used to load into a data register in ring 0. e.g. ``` DS::set_reg(gdt.null_segment_descriptor()) ``` --- src/structures/gdt.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 5942030d0..92093a8cc 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -93,6 +93,14 @@ impl GlobalDescriptorTable { &self.table[..self.len] } + /// Get the GDT's null segment descriptor. + /// + /// Can be used as a selector into a non-existent segment and assigned to segment registers. + #[inline] + pub fn null_segment_descriptor(&self) -> SegmentSelector { + SegmentSelector::new(0u16, PrivilegeLevel::from_u16(0u16)) + } + /// Adds the given segment descriptor to the GDT, returning the segment selector. /// /// Panics if the GDT doesn't have enough free entries to hold the Descriptor. From 0d3abb92a0d186ce6b88717467cac244e97b26f3 Mon Sep 17 00:00:00 2001 From: Michael Jenny Date: Fri, 8 Apr 2022 18:28:50 +0200 Subject: [PATCH 02/13] Rename function to null_segment_selctor --- 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 92093a8cc..6d7fdb52c 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -93,11 +93,11 @@ impl GlobalDescriptorTable { &self.table[..self.len] } - /// Get the GDT's null segment descriptor. + /// Get the GDT's null segment descriptor as selector. /// /// Can be used as a selector into a non-existent segment and assigned to segment registers. #[inline] - pub fn null_segment_descriptor(&self) -> SegmentSelector { + pub fn null_segment_selector(&self) -> SegmentSelector { SegmentSelector::new(0u16, PrivilegeLevel::from_u16(0u16)) } From 3959a0bafbda0e6f0b80c79e82a7a46b0527c1cf Mon Sep 17 00:00:00 2001 From: Michael Jenny Date: Sat, 9 Apr 2022 11:57:55 +0200 Subject: [PATCH 03/13] Provide null segment selector as associated constant on SegmentSelector --- src/registers/segmentation.rs | 4 ++++ src/structures/gdt.rs | 8 -------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/registers/segmentation.rs b/src/registers/segmentation.rs index be497d075..b5d0878ad 100644 --- a/src/registers/segmentation.rs +++ b/src/registers/segmentation.rs @@ -77,6 +77,10 @@ impl SegmentSelector { SegmentSelector(index << 3 | (rpl as u16)) } + /// Can be used as a selector into a non-existent segment and assigned to segment registers, + /// e.g. data segment register in ring 0 + pub const NULL: Self = Self::new(0, PrivilegeLevel::Ring0); + /// Returns the GDT index. #[inline] pub fn index(self) -> u16 { diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 6d7fdb52c..5942030d0 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -93,14 +93,6 @@ impl GlobalDescriptorTable { &self.table[..self.len] } - /// Get the GDT's null segment descriptor as selector. - /// - /// Can be used as a selector into a non-existent segment and assigned to segment registers. - #[inline] - pub fn null_segment_selector(&self) -> SegmentSelector { - SegmentSelector::new(0u16, PrivilegeLevel::from_u16(0u16)) - } - /// Adds the given segment descriptor to the GDT, returning the segment selector. /// /// Panics if the GDT doesn't have enough free entries to hold the Descriptor. From e33cabc4e1009052ebf44e8617e20655c01a4746 Mon Sep 17 00:00:00 2001 From: Michael Jenny Date: Sat, 9 Apr 2022 12:55:11 +0200 Subject: [PATCH 04/13] Correct wrong comment. Unlike the comment suggests, more descriptors fields than mentioned in the comment are recognized by the CPU --- src/registers/segmentation.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/registers/segmentation.rs b/src/registers/segmentation.rs index be497d075..ccc6b4690 100644 --- a/src/registers/segmentation.rs +++ b/src/registers/segmentation.rs @@ -107,10 +107,12 @@ impl fmt::Debug for SegmentSelector { /// Code Segment /// -/// The segment base and limit are unused in 64-bit mode. Only the L (long), D -/// (default operation size), and DPL (descriptor privilege-level) fields of the -/// descriptor are recognized. So changing the segment register can be used to -/// change privilege level or enable/disable long mode. +/// Most fields like the segment base and limit are unused in 64-bit mode. +/// Only the LONG_MODE, PRESENT, USER_SEGMENT, EXECUTABLE, DEFAULT_SIZE (default operand size) +/// and DPL_RING_3 (descriptor privilege-level) fields of the +/// descriptor are recognized by the CPU. Some of them (e.g. DEFAULT_SIZE) are required to hold +/// a specific value and can't be changed. Others like the DPL_RING_3 and LONG_MODE +/// can be used to change privilege level or enable/disable long mode. #[derive(Debug)] pub struct CS; From e58e5e15263d3a27f1a86101a9cb49e1b69bd69c Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 13 Apr 2022 11:37:36 +0200 Subject: [PATCH 05/13] fix `VirtAddr::align_up` & `VirtAddr::align_down` --- src/addr.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index e6179268a..a13fa6a18 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -158,12 +158,17 @@ impl VirtAddr { /// Aligns the virtual address upwards to the given alignment. /// /// See the `align_up` function for more information. + /// + /// # Panics + /// + /// This function panics if the resulting address is higher than + /// `0xffff_ffff_ffff_ffff`. #[inline] pub fn align_up(self, align: U) -> Self where U: Into, { - VirtAddr(align_up(self.0, align.into())) + VirtAddr::new_truncate(align_up(self.0, align.into())) } /// Aligns the virtual address downwards to the given alignment. @@ -174,7 +179,7 @@ impl VirtAddr { where U: Into, { - VirtAddr(align_down(self.0, align.into())) + VirtAddr::new_truncate(align_down(self.0, align.into())) } /// Checks whether the virtual address has the demanded alignment. @@ -791,4 +796,22 @@ mod tests { assert_eq!(align_up(0, 2), 0); assert_eq!(align_up(0, 0x8000_0000_0000_0000), 0); } + + #[test] + fn test_virt_addr_align_up() { + // Make sure the 47th bit is extended. + assert_eq!( + VirtAddr::new(0x7fff_ffff_ffff).align_up(2u64), + VirtAddr::new(0xffff_8000_0000_0000) + ); + } + + #[test] + fn test_virt_addr_align_down() { + // Make sure the 47th bit is extended. + assert_eq!( + VirtAddr::new(0xffff_8000_0000_0000).align_down(1u64 << 48), + VirtAddr::new(0) + ); + } } From e0c56797a04ed7c9769d6cafec40bde4c7137186 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Wed, 13 Apr 2022 11:42:07 +0200 Subject: [PATCH 06/13] fix `PhysAddr::align_up` --- src/addr.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/addr.rs b/src/addr.rs index a13fa6a18..d1549770d 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -483,12 +483,17 @@ impl PhysAddr { /// Aligns the physical address upwards to the given alignment. /// /// See the `align_up` function for more information. + /// + /// # Panics + /// + /// This function panics if the resulting address has a bit in the range 52 + /// to 64 set. #[inline] pub fn align_up(self, align: U) -> Self where U: Into, { - PhysAddr(align_up(self.0, align.into())) + PhysAddr::new(align_up(self.0, align.into())) } /// Aligns the physical address downwards to the given alignment. From 77a152f221d748e3708440abb04bfd7c534a0fb9 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 14 Apr 2022 00:34:08 -0700 Subject: [PATCH 07/13] Update comment and cleanup doc links Signed-off-by: Joe Richey --- src/registers/segmentation.rs | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/registers/segmentation.rs b/src/registers/segmentation.rs index ccc6b4690..0ef55d3ae 100644 --- a/src/registers/segmentation.rs +++ b/src/registers/segmentation.rs @@ -6,13 +6,15 @@ use bit_field::BitField; use core::fmt; // imports for intra doc links #[cfg(doc)] -use crate::registers::control::Cr4Flags; +use crate::{ + registers::control::Cr4Flags, + structures::gdt::{Descriptor, DescriptorFlags, GlobalDescriptorTable}, +}; /// An x86 segment /// /// Segment registers on x86 are 16-bit [`SegmentSelector`]s, which index into -/// the [`GlobalDescriptorTable`](crate::structures::gdt::GlobalDescriptorTable). The -/// corresponding GDT entry is used to +/// 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 { @@ -107,12 +109,19 @@ impl fmt::Debug for SegmentSelector { /// Code Segment /// -/// Most fields like the segment base and limit are unused in 64-bit mode. -/// Only the LONG_MODE, PRESENT, USER_SEGMENT, EXECUTABLE, DEFAULT_SIZE (default operand size) -/// and DPL_RING_3 (descriptor privilege-level) fields of the -/// descriptor are recognized by the CPU. Some of them (e.g. DEFAULT_SIZE) are required to hold -/// a specific value and can't be changed. Others like the DPL_RING_3 and LONG_MODE -/// can be used to change privilege level or enable/disable long mode. +/// While most fields in the Code-Segment [`Descriptor`] are unused in 64-bit +/// long mode, some of them must be set to a specific value. The +/// [`EXECUTABLE`](DescriptorFlags::EXECUTABLE), +/// [`USER_SEGMENT`](DescriptorFlags::USER_SEGMENT), and +/// [`LONG_MODE`](DescriptorFlags::LONG_MODE) bits must be set, while the +/// [`DEFAULT_SIZE`](DescriptorFlags::DEFAULT_SIZE) bit must be unset. +/// +/// The [`DPL_RING_3`](DescriptorFlags::DPL_RING_3) field can be used to change +/// privilege level. The [`PRESENT`](DescriptorFlags::PRESENT) bit can be used +/// to make a segment present or not present. +/// +/// All other fields (like the segment base and limit) are ignored by the +/// processor and setting them has no effect. #[derive(Debug)] pub struct CS; @@ -120,7 +129,7 @@ pub struct CS; /// /// Entirely unused in 64-bit mode; setting the segment register does nothing. /// However, in ring 3, the SS register still has to point to a valid -/// [`Descriptor`](crate::structures::gdt::Descriptor) (it cannot be zero). This +/// [`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 From 9e377c406af228c363fca2927ca8a59f5594d53d Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 14 Apr 2022 09:54:50 +0200 Subject: [PATCH 08/13] explicitly check for overflow --- src/addr.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/addr.rs b/src/addr.rs index d1549770d..1b2087284 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -647,7 +647,7 @@ pub const fn align_down(addr: u64, align: u64) -> u64 { /// /// Returns the smallest `x` with alignment `align` so that `x >= addr`. /// -/// Panics if the alignment is not a power of two. +/// Panics if the alignment is not a power of two or if an overflow occurs. #[inline] pub const fn align_up(addr: u64, align: u64) -> u64 { assert!(align.is_power_of_two(), "`align` must be a power of two"); @@ -655,7 +655,12 @@ pub const fn align_up(addr: u64, align: u64) -> u64 { if addr & align_mask == 0 { addr // already aligned } else { - (addr | align_mask) + 1 + // FIXME: Replace with .expect, once `Option::expect` is const. + if let Some(aligned) = (addr | align_mask).checked_add(1) { + aligned + } else { + panic!("attempt to add with overflow") + } } } From 49b920493c423b6a2cd7a226fe1dc22c8b472e08 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 14 Apr 2022 09:55:11 +0200 Subject: [PATCH 09/13] add overflow tests --- src/addr.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/addr.rs b/src/addr.rs index 1b2087284..d3bec6a8b 100644 --- a/src/addr.rs +++ b/src/addr.rs @@ -824,4 +824,16 @@ mod tests { VirtAddr::new(0) ); } + + #[test] + #[should_panic] + fn test_virt_addr_align_up_overflow() { + VirtAddr::new(0xffff_ffff_ffff_ffff).align_up(2u64); + } + + #[test] + #[should_panic] + fn test_phys_addr_align_up_overflow() { + PhysAddr::new(0x000f_ffff_ffff_ffff).align_up(2u64); + } } From ef05ded22bc37c8ac580cc7c72a4b661ce60afad Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Thu, 14 Apr 2022 01:13:36 -0700 Subject: [PATCH 10/13] Cleanup Segment macros We no longer use the symbols for the external assembly, so we can simplify the macros. Signed-off-by: Joe Richey --- src/instructions/segmentation.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index 334c42237..adb29fc8a 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -9,7 +9,7 @@ use crate::{ use core::arch::asm; macro_rules! get_reg_impl { - ($name:literal, $asm_get:ident) => { + ($name:literal) => { fn get_reg() -> SegmentSelector { let segment: u16; unsafe { @@ -21,9 +21,9 @@ macro_rules! get_reg_impl { } macro_rules! segment_impl { - ($type:ty, $name:literal, $asm_get:ident, $asm_load:ident) => { + ($type:ty, $name:literal) => { impl Segment for $type { - get_reg_impl!($name, $asm_get); + get_reg_impl!($name); unsafe fn set_reg(sel: SegmentSelector) { unsafe { @@ -35,7 +35,7 @@ macro_rules! segment_impl { } macro_rules! segment64_impl { - ($type:ty, $name:literal, $base:ty, $asm_rd:ident, $asm_wr:ident) => { + ($type:ty, $name:literal, $base:ty) => { impl Segment64 for $type { const BASE: Msr = <$base>::MSR; fn read_base() -> VirtAddr { @@ -56,7 +56,7 @@ macro_rules! segment64_impl { } impl Segment for CS { - get_reg_impl!("cs", x86_64_asm_get_cs); + get_reg_impl!("cs"); /// 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 @@ -82,13 +82,13 @@ impl Segment for CS { } } -segment_impl!(SS, "ss", x86_64_asm_get_ss, x86_64_asm_load_ss); -segment_impl!(DS, "ds", x86_64_asm_get_ds, x86_64_asm_load_ds); -segment_impl!(ES, "es", x86_64_asm_get_es, x86_64_asm_load_es); -segment_impl!(FS, "fs", x86_64_asm_get_fs, x86_64_asm_load_fs); -segment64_impl!(FS, "fs", FsBase, x86_64_asm_rdfsbase, x86_64_asm_wrfsbase); -segment_impl!(GS, "gs", x86_64_asm_get_gs, x86_64_asm_load_gs); -segment64_impl!(GS, "gs", GsBase, x86_64_asm_rdgsbase, x86_64_asm_wrgsbase); +segment_impl!(SS, "ss"); +segment_impl!(DS, "ds"); +segment_impl!(ES, "es"); +segment_impl!(FS, "fs"); +segment64_impl!(FS, "fs", FsBase); +segment_impl!(GS, "gs"); +segment64_impl!(GS, "gs", GsBase); impl GS { /// Swap `KernelGsBase` MSR and `GsBase` MSR. From 1d40fb279647835afa9f26e7c268d22b0b908fa9 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 20 Apr 2022 00:09:00 -0700 Subject: [PATCH 11/13] Add Safety comment to FrameAllocator This fixes a clippy warning Signed-off-by: Joe Richey --- src/structures/paging/frame_alloc.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/structures/paging/frame_alloc.rs b/src/structures/paging/frame_alloc.rs index d713720a1..102c80cb4 100644 --- a/src/structures/paging/frame_alloc.rs +++ b/src/structures/paging/frame_alloc.rs @@ -4,8 +4,11 @@ use crate::structures::paging::{PageSize, PhysFrame}; /// A trait for types that can allocate a frame of memory. /// -/// This trait is unsafe to implement because the implementer must guarantee that -/// the `allocate_frame` method returns only unique unused frames. +/// # Safety +/// +/// The implementer of this trait must guarantee that the `allocate_frame` +/// method returns only unique unused frames. Otherwise, undefined behavior +/// may result from two caller modifying or deallocating the same frame. pub unsafe trait FrameAllocator { /// Allocate a frame of the appropriate size and return it if possible. fn allocate_frame(&mut self) -> Option>; From 43afc598bd3b289e8b9ef1c6361ffd7b881d285e Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Wed, 20 Apr 2022 00:09:22 -0700 Subject: [PATCH 12/13] Add links to other useful OS crates Fixes #372 Signed-off-by: Joe Richey --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index 5670179a3..32c819886 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,24 @@ If no features are enabled (`--no-default-features`), Rust 1.57.0 is required. If only the `instructions` feature is enabled (`--no-default-features --features instructions`), Rust 1.59.0 is required. If the `nightly` feature or any of its sub-features is enabled, a recent nightly is required. + +## Other OS development crates + +This crate does not attempt to handle every facet of OS development. Other +useful crates in this space include: + - [`raw-cpuid`](https://crates.io/crates/raw-cpuid): safe wrappers around the + [`cpuid` instruction](https://en.wikipedia.org/wiki/CPUID) + - Provides parsed versions of the CPUID data, rather than just raw binary values. + - Support for AMD and Intel specific values. + - Works on x86 and x86_64 systems, in both user and kernel mode. + - [`uefi`](https://crates.io/crates/uefi): abstractions for + [UEFI](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface) + (the successor to BIOS) + - Provides UEFI tables, functions, and types. + - Useful for writing UEFI applications, or calling UEFI functions from your OS. + - Works on a variety of modern platforms, not just x86_64. + - [`volatile`](https://crates.io/crates/volatile): interface to + [`read_volatile`](https://doc.rust-lang.org/std/ptr/fn.read_volatile.html) and + [`write_volatile`](https://doc.rust-lang.org/std/ptr/fn.write_volatile.html) + - Makes it easier to program [MMIO](https://en.wikipedia.org/wiki/Memory-mapped_I/O) interfaces and devices. + - Works on any Rust target. \ No newline at end of file From 81a3cec31ef96b8d91418aa613c778a3b9243edb Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Wed, 20 Apr 2022 00:38:01 -0700 Subject: [PATCH 13/13] Fix typo Co-authored-by: Tom Dohrmann --- src/structures/paging/frame_alloc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/structures/paging/frame_alloc.rs b/src/structures/paging/frame_alloc.rs index 102c80cb4..106308dcd 100644 --- a/src/structures/paging/frame_alloc.rs +++ b/src/structures/paging/frame_alloc.rs @@ -8,7 +8,7 @@ use crate::structures::paging::{PageSize, PhysFrame}; /// /// The implementer of this trait must guarantee that the `allocate_frame` /// method returns only unique unused frames. Otherwise, undefined behavior -/// may result from two caller modifying or deallocating the same frame. +/// may result from two callers modifying or deallocating the same frame. pub unsafe trait FrameAllocator { /// Allocate a frame of the appropriate size and return it if possible. fn allocate_frame(&mut self) -> Option>;