diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7e4b50d04..a03b51729 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,23 +62,26 @@ jobs: - name: "Run cargo build" run: cargo build + - name: "Run cargo build for stable without instructions" + run: cargo build --no-default-features + - name: "Run cargo build for stable" - run: cargo build --no-default-features --features stable + run: cargo build --no-default-features --features external_asm,instructions if: runner.os != 'Windows' - name: "Run cargo build for stable on musl" - run: cargo build --target x86_64-unknown-linux-musl --no-default-features --features stable + run: cargo build --target x86_64-unknown-linux-musl --no-default-features --features external_asm,instructions if: runner.os == 'Linux' - name: "Run cargo test" run: cargo test - name: "Run cargo test for stable" - run: cargo test --no-default-features --features stable + run: cargo test --no-default-features --features external_asm,instructions if: runner.os != 'Windows' - name: "Run cargo test for stable on musl" - run: cargo test --target x86_64-unknown-linux-musl --no-default-features --features stable + run: cargo test --target x86_64-unknown-linux-musl --no-default-features --features external_asm,instructions if: runner.os == 'Linux' - name: 'Deny Warnings' @@ -90,8 +93,8 @@ jobs: rustup target add thumbv7em-none-eabihf - name: 'Build on non x86_64 platforms' run: | - cargo build --target i686-unknown-linux-gnu - cargo build --target thumbv7em-none-eabihf + cargo build --target i686-unknown-linux-gnu --no-default-features --features nightly + cargo build --target thumbv7em-none-eabihf --no-default-features --features nightly - name: "Install Rustup Components" run: rustup component add rust-src llvm-tools-preview diff --git a/Cargo.toml b/Cargo.toml index b266fbd35..406b962e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,15 +28,15 @@ edition = "2018" [dependencies] bit_field = "0.9.0" bitflags = "1.0.4" -array-init = { version = "0.1.1", optional = true } [build-dependencies] cc = { version = "1.0.37", optional = true } [features] -default = [ "nightly" ] +default = [ "nightly", "instructions" ] +instructions = [] deny-warnings = [] -stable = [ "cc", "array-init" ] +external_asm = [ "cc" ] nightly = [ "inline_asm", "const_fn", "abi_x86_interrupt" ] inline_asm = [] abi_x86_interrupt = [] diff --git a/README.md b/README.md index f94d8ca93..26eadd3bd 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Support for x86_64 specific instructions (e.g. TLB flush), registers (e.g. contr ## Crate Feature Flags -* `nightly`: This is the default. -* `stable`: Use this to build with non-nightly rust. Needs `default-features = false`. +* `nightly`: Enables features only available on nightly Rust; enabled by default. +* `instructions`: Enabled by default, turns on x86\_64 specific instructions, and dependent features. Only available for x86\_64 targets. +* `external_asm`: Use this to build with non-nightly rust. Needs `default-features = false, features = ["instructions"]`. Is unsupported on Windows. ## Building with stable rust diff --git a/build.rs b/build.rs index dd2865889..21c783bad 100644 --- a/build.rs +++ b/build.rs @@ -1,40 +1,54 @@ -#[cfg(feature = "inline_asm")] -fn main() {} - -#[cfg(all(not(feature = "inline_asm"), not(feature = "stable")))] fn main() { - compile_error!("Neither feature \"stable\" nor \"nightly\" was set!"); -} + println!("cargo:rerun-if-changed=build.rs"); -#[cfg(all(not(feature = "inline_asm"), feature = "stable"))] -fn main() { - use std::ffi::OsString; - use std::fs; + #[cfg(all(feature = "external_asm", windows))] + compile_error!("\"external_asm\" feature is not available on windows toolchain!"); - println!("cargo:rerun-if-changed=build.rs"); + #[cfg(feature = "instructions")] + if std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() != "x86_64" { + panic!("\"instructions\" feature is only available for x86_64 targets!"); + } - let entries = fs::read_dir("src/asm") - .unwrap() - .filter_map(|f| { - f.ok().and_then(|e| { - let path = e.path(); - match path.extension() { - Some(ext) if ext.eq(&OsString::from("s")) => Some(path), - _ => None, - } + #[cfg(all( + feature = "instructions", + not(feature = "inline_asm"), + not(feature = "external_asm") + ))] + compile_error!("\"instructions\" feature is enabled, but neither feature \"external_asm\" nor \"inline_asm\" was set!"); + + #[cfg(all(feature = "inline_asm", feature = "external_asm"))] + compile_error!( + "\"inline_asm\" and \"external_asm\" features can not be enabled at the same time!" + ); + + #[cfg(all(feature = "instructions", feature = "external_asm"))] + { + use std::ffi::OsString; + use std::fs; + + let entries = fs::read_dir("src/asm") + .unwrap() + .filter_map(|f| { + f.ok().and_then(|e| { + let path = e.path(); + match path.extension() { + Some(ext) if ext.eq(&OsString::from("s")) => Some(path), + _ => None, + } + }) }) - }) - .collect::>(); - - cc::Build::new() - .no_default_flags(true) - .files(&entries) - .pic(true) - .static_flag(true) - .shared_flag(false) - .compile("x86_64_asm"); - - for e in entries { - println!("cargo:rerun-if-changed={}", e.to_str().unwrap()); + .collect::>(); + + cc::Build::new() + .no_default_flags(true) + .files(&entries) + .pic(true) + .static_flag(true) + .shared_flag(false) + .compile("x86_64_asm"); + + for e in entries { + println!("cargo:rerun-if-changed={}", e.to_str().unwrap()); + } } } diff --git a/src/instructions/mod.rs b/src/instructions/mod.rs index 18c7c1125..d1160330f 100644 --- a/src/instructions/mod.rs +++ b/src/instructions/mod.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "x86_64")] +#![cfg(feature = "instructions")] //! Special x86_64 instructions. @@ -51,3 +51,18 @@ pub fn bochs_breakpoint() { llvm_asm!("xchgw %bx, %bx" :::: "volatile"); } } + +/// Gets the current instruction pointer. Note that this is only approximate as it requires a few +/// instructions to execute. +#[cfg(feature = "inline_asm")] +#[inline(always)] +pub fn read_rip() -> u64 { + let rip: u64; + unsafe { + llvm_asm!( + "lea (%rip), $0" + : "=r"(rip) ::: "volatile" + ); + } + rip +} diff --git a/src/instructions/random.rs b/src/instructions/random.rs index 34a06d0d3..e1599e089 100644 --- a/src/instructions/random.rs +++ b/src/instructions/random.rs @@ -4,7 +4,6 @@ /// Used to obtain random numbers using x86_64's RDRAND opcode pub struct RdRand(()); -#[cfg(target_arch = "x86_64")] impl RdRand { /// Creates Some(RdRand) if RDRAND is supported, None otherwise #[inline] @@ -66,7 +65,7 @@ impl RdRand { } } -#[cfg(all(test, target_arch = "x86_64"))] +#[cfg(all(test))] mod tests { use super::*; diff --git a/src/instructions/segmentation.rs b/src/instructions/segmentation.rs index d9ddfc1d4..8dc898197 100644 --- a/src/instructions/segmentation.rs +++ b/src/instructions/segmentation.rs @@ -140,3 +140,102 @@ pub fn cs() -> SegmentSelector { SegmentSelector(segment) } } + +/// Writes the FS segment base address +/// +/// ## Safety +/// +/// 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. +#[inline] +pub unsafe fn wrfsbase(val: u64) { + #[cfg(feature = "inline_asm")] + #[inline(always)] + unsafe fn inner(val: u64) { + llvm_asm!("wrfsbase $0" :: "r"(val) :: "volatile") + } + + #[cfg(not(feature = "inline_asm"))] + #[inline(always)] + unsafe fn inner(val: u64) { + crate::asm::x86_64_asm_wrfsbase(val) + } + + inner(val) +} + +/// Reads the FS segment base address +/// +/// ## Safety +/// +/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +#[inline] +pub unsafe fn rdfsbase() -> u64 { + #[cfg(feature = "inline_asm")] + #[inline(always)] + unsafe fn inner() -> u64 { + let val: u64; + llvm_asm!("rdfsbase $0" : "=r" (val) ::: "volatile"); + val + } + + #[cfg(not(feature = "inline_asm"))] + #[inline(always)] + unsafe fn inner() -> u64 { + crate::asm::x86_64_asm_rdfsbase() + } + + inner() +} + +/// Writes the GS segment base address +/// +/// ## Safety +/// +/// 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 GS segment base address might be in use. +#[inline] +pub unsafe fn wrgsbase(val: u64) { + #[cfg(feature = "inline_asm")] + #[inline(always)] + unsafe fn inner(val: u64) { + llvm_asm!("wrgsbase $0" :: "r"(val) :: "volatile") + } + + #[cfg(not(feature = "inline_asm"))] + #[inline(always)] + unsafe fn inner(val: u64) { + crate::asm::x86_64_asm_wrgsbase(val) + } + + inner(val) +} + +/// Reads the GS segment base address +/// +/// ## Safety +/// +/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. +#[inline] +pub unsafe fn rdgsbase() -> u64 { + #[cfg(feature = "inline_asm")] + #[inline(always)] + unsafe fn inner() -> u64 { + let val: u64; + llvm_asm!("rdgsbase $0" : "=r" (val) ::: "volatile"); + val + } + + #[cfg(not(feature = "inline_asm"))] + #[inline(always)] + unsafe fn inner() -> u64 { + crate::asm::x86_64_asm_rdgsbase() + } + + inner() +} diff --git a/src/lib.rs b/src/lib.rs index 43fe485ec..528439b43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ macro_rules! const_fn { } } -#[cfg(not(feature = "inline_asm"))] +#[cfg(all(feature = "instructions", feature = "external_asm"))] pub(crate) mod asm; pub mod addr; diff --git a/src/registers/control.rs b/src/registers/control.rs index 3a559c31c..9885ea3d9 100644 --- a/src/registers/control.rs +++ b/src/registers/control.rs @@ -125,7 +125,7 @@ bitflags! { } } -#[cfg(target_arch = "x86_64")] +#[cfg(feature = "instructions")] mod x86_64 { use super::*; use crate::structures::paging::PhysFrame; diff --git a/src/registers/mod.rs b/src/registers/mod.rs index d63f9ce88..40f7b0282 100644 --- a/src/registers/mod.rs +++ b/src/registers/mod.rs @@ -4,116 +4,8 @@ pub mod control; pub mod model_specific; pub mod rflags; -/// Gets the current instruction pointer. Note that this is only approximate as it requires a few -/// instructions to execute. -#[cfg(feature = "inline_asm")] -#[inline(always)] -pub fn read_rip() -> u64 { - let rip: u64; - unsafe { - llvm_asm!( - "lea (%rip), $0" - : "=r"(rip) ::: "volatile" - ); - } - rip -} +#[cfg(feature = "instructions")] +pub use crate::instructions::segmentation::{rdfsbase, rdgsbase, wrfsbase, wrgsbase}; -/// Writes the FS segment base address -/// -/// ## Safety -/// -/// 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. -#[inline] -pub unsafe fn wrfsbase(val: u64) { - #[cfg(feature = "inline_asm")] - #[inline(always)] - unsafe fn inner(val: u64) { - llvm_asm!("wrfsbase $0" :: "r"(val) :: "volatile") - } - - #[cfg(not(feature = "inline_asm"))] - #[inline(always)] - unsafe fn inner(val: u64) { - crate::asm::x86_64_asm_wrfsbase(val) - } - - inner(val) -} - -/// Reads the FS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. -#[inline] -pub unsafe fn rdfsbase() -> u64 { - #[cfg(feature = "inline_asm")] - #[inline(always)] - unsafe fn inner() -> u64 { - let val: u64; - llvm_asm!("rdfsbase $0" : "=r" (val) ::: "volatile"); - val - } - - #[cfg(not(feature = "inline_asm"))] - #[inline(always)] - unsafe fn inner() -> u64 { - crate::asm::x86_64_asm_rdfsbase() - } - - inner() -} - -/// Writes the GS segment base address -/// -/// ## Safety -/// -/// 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 GS segment base address might be in use. -#[inline] -pub unsafe fn wrgsbase(val: u64) { - #[cfg(feature = "inline_asm")] - #[inline(always)] - unsafe fn inner(val: u64) { - llvm_asm!("wrgsbase $0" :: "r"(val) :: "volatile") - } - - #[cfg(not(feature = "inline_asm"))] - #[inline(always)] - unsafe fn inner(val: u64) { - crate::asm::x86_64_asm_wrgsbase(val) - } - - inner(val) -} - -/// Reads the GS segment base address -/// -/// ## Safety -/// -/// If `CR4.FSGSBASE` is not set, this instruction will throw an `#UD`. -#[inline] -pub unsafe fn rdgsbase() -> u64 { - #[cfg(feature = "inline_asm")] - #[inline(always)] - unsafe fn inner() -> u64 { - let val: u64; - llvm_asm!("rdgsbase $0" : "=r" (val) ::: "volatile"); - val - } - - #[cfg(not(feature = "inline_asm"))] - #[inline(always)] - unsafe fn inner() -> u64 { - crate::asm::x86_64_asm_rdgsbase() - } - - inner() -} +#[cfg(all(feature = "instructions", feature = "inline_asm"))] +pub use crate::instructions::read_rip; diff --git a/src/registers/model_specific.rs b/src/registers/model_specific.rs index 9d0e798c5..d9f4ac2c4 100644 --- a/src/registers/model_specific.rs +++ b/src/registers/model_specific.rs @@ -1,11 +1,6 @@ //! Functions to read and write model specific registers. -use crate::registers::rflags::RFlags; -use crate::structures::gdt::SegmentSelector; -use crate::PrivilegeLevel; -use bit_field::BitField; use bitflags::bitflags; -use core::convert::TryInto; /// A model specific register. #[derive(Debug)] @@ -104,10 +99,15 @@ bitflags! { } } -#[cfg(target_arch = "x86_64")] +#[cfg(feature = "instructions")] mod x86_64 { use super::*; use crate::addr::VirtAddr; + use crate::registers::rflags::RFlags; + use crate::structures::gdt::SegmentSelector; + use crate::PrivilegeLevel; + use bit_field::BitField; + use core::convert::TryInto; impl Msr { /// Read 64 bits msr register. diff --git a/src/registers/rflags.rs b/src/registers/rflags.rs index 2227ad5f1..f71fc3e10 100644 --- a/src/registers/rflags.rs +++ b/src/registers/rflags.rs @@ -1,6 +1,6 @@ //! Processor state stored in the RFLAGS register. -#[cfg(target_arch = "x86_64")] +#[cfg(feature = "instructions")] pub use self::x86_64::*; use bitflags::bitflags; @@ -62,7 +62,7 @@ bitflags! { } } -#[cfg(target_arch = "x86_64")] +#[cfg(feature = "instructions")] mod x86_64 { use super::*; diff --git a/src/structures/gdt.rs b/src/structures/gdt.rs index 3217bda04..68a07d2cb 100644 --- a/src/structures/gdt.rs +++ b/src/structures/gdt.rs @@ -139,7 +139,7 @@ impl GlobalDescriptorTable { /// functions](crate::instructions::segmentation): /// [load_ss](crate::instructions::segmentation::load_ss), /// [set_cs](crate::instructions::segmentation::set_cs). - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] pub fn load(&'static self) { use crate::instructions::tables::{lgdt, DescriptorTablePointer}; diff --git a/src/structures/idt.rs b/src/structures/idt.rs index f5a4874c8..d20264a50 100644 --- a/src/structures/idt.rs +++ b/src/structures/idt.rs @@ -411,7 +411,7 @@ impl InterruptDescriptorTable { } /// Loads the IDT in the CPU using the `lidt` command. - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] pub fn load(&'static self) { unsafe { self.load_unsafe() } @@ -427,7 +427,7 @@ impl InterruptDescriptorTable { /// - `self` always stays at the same memory location. It is recommended to wrap it in /// a `Box`. /// - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] pub unsafe fn load_unsafe(&self) { use crate::instructions::tables::{lidt, DescriptorTablePointer}; @@ -603,7 +603,7 @@ impl Entry { /// /// The function returns a mutable reference to the entry's options that allows /// further customization. - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] fn set_handler_addr(&mut self, addr: u64) -> &mut EntryOptions { use crate::instructions::segmentation; @@ -621,7 +621,7 @@ impl Entry { macro_rules! impl_set_handler_fn { ($h:ty) => { - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] impl Entry<$h> { /// Set the handler function for the IDT entry and sets the present bit. /// diff --git a/src/structures/paging/mapper/mod.rs b/src/structures/paging/mapper/mod.rs index 44dc0d4b7..d1c73898c 100644 --- a/src/structures/paging/mapper/mod.rs +++ b/src/structures/paging/mapper/mod.rs @@ -1,8 +1,10 @@ //! Abstractions for reading and modifying the mapping of pages. pub use self::mapped_page_table::{MappedPageTable, PhysToVirt}; -#[cfg(target_arch = "x86_64")] -pub use self::{offset_page_table::OffsetPageTable, recursive_page_table::RecursivePageTable}; +#[cfg(target_pointer_width = "64")] +pub use self::offset_page_table::OffsetPageTable; +#[cfg(feature = "instructions")] +pub use self::recursive_page_table::RecursivePageTable; use crate::structures::paging::{ frame_alloc::FrameAllocator, page_table::PageTableFlags, Page, PageSize, PhysFrame, Size1GiB, @@ -12,6 +14,7 @@ use crate::{PhysAddr, VirtAddr}; mod mapped_page_table; mod offset_page_table; +#[cfg(feature = "instructions")] mod recursive_page_table; /// This trait defines page table operations that work for all page sizes of the x86_64 @@ -124,10 +127,12 @@ pub trait Mapper { /// Create a USER_ACCESSIBLE mapping: /// /// ``` + /// # #[cfg(feature = "instructions")] /// # use x86_64::structures::paging::{ /// # Mapper, Page, PhysFrame, FrameAllocator, /// # Size4KiB, OffsetPageTable, page_table::PageTableFlags /// # }; + /// # #[cfg(feature = "instructions")] /// # unsafe fn test(mapper: &mut OffsetPageTable, frame_allocator: &mut impl FrameAllocator, /// # page: Page, frame: PhysFrame) { /// mapper @@ -209,10 +214,12 @@ pub trait Mapper { /// the top hierarchy only with USER_ACCESSIBLE: /// /// ``` + /// # #[cfg(feature = "instructions")] /// # use x86_64::structures::paging::{ /// # Mapper, PhysFrame, Page, FrameAllocator, /// # Size4KiB, OffsetPageTable, page_table::PageTableFlags /// # }; + /// # #[cfg(feature = "instructions")] /// # unsafe fn test(mapper: &mut OffsetPageTable, frame_allocator: &mut impl FrameAllocator, /// # page: Page, frame: PhysFrame) { /// mapper @@ -355,7 +362,7 @@ impl MapperFlush { } /// Flush the page from the TLB to ensure that the newest mapping is used. - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] pub fn flush(self) { crate::instructions::tlb::flush(self.0.start_address()); @@ -383,7 +390,7 @@ impl MapperFlushAll { } /// Flush all pages from the TLB to ensure that the newest mapping is used. - #[cfg(target_arch = "x86_64")] + #[cfg(feature = "instructions")] #[inline] pub fn flush_all(self) { crate::instructions::tlb::flush_all() diff --git a/src/structures/paging/mapper/offset_page_table.rs b/src/structures/paging/mapper/offset_page_table.rs index 76287e672..dc8da2cd5 100644 --- a/src/structures/paging/mapper/offset_page_table.rs +++ b/src/structures/paging/mapper/offset_page_table.rs @@ -1,4 +1,4 @@ -#![cfg(target_arch = "x86_64")] +#![cfg(target_pointer_width = "64")] use crate::structures::paging::{ frame::PhysFrame, mapper::*, page_table::PageTable, Page, PageTableFlags, diff --git a/src/structures/paging/mapper/recursive_page_table.rs b/src/structures/paging/mapper/recursive_page_table.rs index ffd211a7b..b45567272 100644 --- a/src/structures/paging/mapper/recursive_page_table.rs +++ b/src/structures/paging/mapper/recursive_page_table.rs @@ -1,5 +1,3 @@ -#![cfg(target_arch = "x86_64")] - //! Access the page tables through a recursively mapped level 4 table. use super::*; diff --git a/src/structures/paging/mod.rs b/src/structures/paging/mod.rs index 5204fcff7..a2dd1322d 100644 --- a/src/structures/paging/mod.rs +++ b/src/structures/paging/mod.rs @@ -8,10 +8,13 @@ pub use self::frame_alloc::UnusedPhysFrame; pub use self::frame_alloc::{FrameAllocator, FrameDeallocator}; #[doc(no_inline)] pub use self::mapper::MappedPageTable; -pub use self::mapper::{Mapper, MapperAllSizes}; -#[cfg(target_arch = "x86_64")] +#[cfg(target_pointer_width = "64")] +#[doc(no_inline)] +pub use self::mapper::OffsetPageTable; +#[cfg(feature = "instructions")] #[doc(no_inline)] -pub use self::mapper::{OffsetPageTable, RecursivePageTable}; +pub use self::mapper::RecursivePageTable; +pub use self::mapper::{Mapper, MapperAllSizes}; pub use self::page::{Page, PageSize, Size1GiB, Size2MiB, Size4KiB}; pub use self::page_table::{PageOffset, PageTable, PageTableFlags, PageTableIndex}; diff --git a/src/structures/paging/page_table.rs b/src/structures/paging/page_table.rs index fb7b785ed..39631ae94 100644 --- a/src/structures/paging/page_table.rs +++ b/src/structures/paging/page_table.rs @@ -194,12 +194,12 @@ impl PageTable { } } - /// Creates an empty page table. #[cfg(not(feature = "const_fn"))] #[inline] pub fn new() -> Self { + const EMPTY: PageTableEntry = PageTableEntry::new(); PageTable { - entries: array_init::array_init(|_| PageTableEntry::new()), + entries: [EMPTY; ENTRY_COUNT], } } @@ -256,6 +256,12 @@ impl IndexMut for PageTable { } } +impl Default for PageTable { + fn default() -> Self { + Self::new() + } +} + impl fmt::Debug for PageTable { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {