-
Notifications
You must be signed in to change notification settings - Fork 143
gdt: Add remaining GDT flags #181
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -184,55 +184,114 @@ pub enum Descriptor { | |
bitflags! { | ||
/// Flags for a GDT descriptor. Not all flags are valid for all descriptor types. | ||
pub struct DescriptorFlags: u64 { | ||
/// For data segments, this flag sets the segment as writable. Ignored for code segments. | ||
/// Set by the processor if this segment has been accessed. Only cleared by software. | ||
/// _Setting_ this bit in software prevents GDT writes on first use. | ||
const ACCESSED = 1 << 40; | ||
/// For 32-bit data segments, sets the segment as writable. For 32-bit code segments, | ||
/// sets the segment as _readable_. In 64-bit mode, ignored for all segments. | ||
const WRITABLE = 1 << 41; | ||
/// Marks a code segment as “conforming”. This influences the privilege checks that | ||
/// occur on control transfers. | ||
/// For code segments, sets the segment as “conforming”, influencing the | ||
/// privilege checks that occur on control transfers. For 32-bit data segments, | ||
/// sets the segment as "expand down". In 64-bit mode, ignored for data segments. | ||
const CONFORMING = 1 << 42; | ||
/// This flag must be set for code segments. | ||
/// This flag must be set for code segments and unset for data segments. | ||
const EXECUTABLE = 1 << 43; | ||
/// This flag must be set for user segments (in contrast to system segments). | ||
const USER_SEGMENT = 1 << 44; | ||
/// The DPL for this descriptor is Ring 3. In 64-bit mode, ignored for data segments. | ||
const DPL_RING_3 = 3 << 45; | ||
/// Must be set for any segment, causes a segment not present exception if not set. | ||
const PRESENT = 1 << 47; | ||
/// Must be set for long mode code segments. | ||
/// Available for use by the Operating System | ||
const AVAILABLE = 1 << 52; | ||
/// Must be set for 64-bit code segments, unset otherwise. | ||
const LONG_MODE = 1 << 53; | ||
/// Use 32-bit (as opposed to 16-bit) operands. If [`LONG_MODE`][Self::LONG_MODE] is set, | ||
/// this must be unset. In 64-bit mode, ignored for data segments. | ||
const DEFAULT_SIZE = 1 << 54; | ||
/// Limit field is scaled by 4096 bytes. In 64-bit mode, ignored for all segments. | ||
const GRANULARITY = 1 << 55; | ||
|
||
/// The DPL for this descriptor is Ring 3 | ||
const DPL_RING_3 = 3 << 45; | ||
/// Bits `0..=15` of the limit field (ignored in 64-bit mode) | ||
const LIMIT_0_15 = 0xFFFF; | ||
/// Bits `16..=19` of the limit field (ignored in 64-bit mode) | ||
const LIMIT_16_19 = 0xF << 48; | ||
/// Bits `0..=23` of the base field (ignored in 64-bit mode, except for fs and gs) | ||
const BASE_0_23 = 0xFF_FFFF << 16; | ||
/// Bits `24..=31` of the base field (ignored in 64-bit mode, except for fs and gs) | ||
const BASE_24_31 = 0xFF << 56; | ||
} | ||
} | ||
|
||
/// The following constants define default values for common GDT entries. They | ||
/// are all "flat" segments, meaning they can access the entire address space. | ||
/// These values all set [`WRITABLE`][DescriptorFlags::WRITABLE] and | ||
/// [`ACCESSED`][DescriptorFlags::ACCESSED]. They also match the values loaded | ||
/// by the `syscall`/`sysret` and `sysenter`/`sysexit` instructions. | ||
/// | ||
/// In short, these values disable segmentation, permission checks, and access | ||
/// tracking at the GDT level. Kernels using these values should use paging to | ||
/// implement this functionality. | ||
impl DescriptorFlags { | ||
// Flags that we set for all our default segments | ||
const COMMON: Self = Self::from_bits_truncate( | ||
Self::USER_SEGMENT.bits() | ||
| Self::PRESENT.bits() | ||
| Self::WRITABLE.bits() | ||
| Self::ACCESSED.bits() | ||
| Self::LIMIT_0_15.bits() | ||
| Self::LIMIT_16_19.bits() | ||
| Self::GRANULARITY.bits(), | ||
); | ||
/// A kernel data segment (64-bit or flat 32-bit) | ||
pub const KERNEL_DATA: Self = | ||
Self::from_bits_truncate(Self::COMMON.bits() | Self::DEFAULT_SIZE.bits()); | ||
/// A flat 32-bit kernel code segment | ||
pub const KERNEL_CODE32: Self = Self::from_bits_truncate( | ||
Self::COMMON.bits() | Self::EXECUTABLE.bits() | Self::DEFAULT_SIZE.bits(), | ||
); | ||
/// A 64-bit kernel code segment | ||
pub const KERNEL_CODE64: Self = Self::from_bits_truncate( | ||
Self::COMMON.bits() | Self::EXECUTABLE.bits() | Self::LONG_MODE.bits(), | ||
); | ||
/// A user data segment (64-bit or flat 32-bit) | ||
pub const USER_DATA: Self = | ||
Self::from_bits_truncate(Self::KERNEL_DATA.bits() | Self::DPL_RING_3.bits()); | ||
/// A flat 32-bit user code segment | ||
pub const USER_CODE32: Self = | ||
Self::from_bits_truncate(Self::KERNEL_CODE32.bits() | Self::DPL_RING_3.bits()); | ||
/// A 64-bit user code segment | ||
pub const USER_CODE64: Self = | ||
Self::from_bits_truncate(Self::KERNEL_CODE64.bits() | Self::DPL_RING_3.bits()); | ||
} | ||
|
||
impl Descriptor { | ||
/// Creates a segment descriptor for a long mode kernel code segment. | ||
/// Creates a segment descriptor for a 64-bit kernel code segment. Suitable | ||
/// for use with `syscall` or 64-bit `sysenter`. | ||
#[inline] | ||
pub fn kernel_code_segment() -> Descriptor { | ||
use self::DescriptorFlags as Flags; | ||
|
||
let flags = Flags::USER_SEGMENT | Flags::PRESENT | Flags::EXECUTABLE | Flags::LONG_MODE; | ||
Descriptor::UserSegment(flags.bits()) | ||
pub const fn kernel_code_segment() -> Descriptor { | ||
Descriptor::UserSegment(DescriptorFlags::KERNEL_CODE64.bits()) | ||
} | ||
Comment on lines
+272
to
274
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably also add a Also, a general There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch I forgot about syscall/sysenter/sysret/sysexit, this might actually impact what we do for defaults here. I'll do some more reading There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So it looks like we should set all of these "ignored" flags. Per the Intel manual, syscall/sysenter/sysret/sysexit all load a specific selector from the GDT, but they don't actually read the GDT entry, they instead load fixed data in the descriptor caches. The manual then states:
So we should just have our defaults match those fixed values the processor loads automatically. These are the values I had initially (i.e. exactly the values Linux uses). It now also makes sense why Linux was setting ignored bits, accessed bits, etc... Specific details of what flags are set by the processorRing 3 -> Ring 0CSSYSCALL: SEL = IA32_START[47:32]
SSSYSCALL: SEL = IA32_START[47:32] + 8
Ring 0 -> Ring 3CSSYSRET: SEL = IA32_STAR[63:48] + 16
SSSYSRET: SEL = IA32_STAR[63:48]+8
Interestingly, due to the constraints of these commands, if you wanted to support both, you would need a GDT that had the following segments ordered like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
A user can already do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was mostly thinking about improving the discoverability of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for investigating this! I agree that it makes sense to use the sames values at Linux then.
Unrelated to this PR, but maybe it makes sense to add a new constructor function to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Agreed here, but to make this |
||
|
||
/// Creates a segment descriptor for a long mode ring 3 data segment. | ||
/// Creates a segment descriptor for a kernel data segment (32-bit or | ||
/// 64-bit). Suitable for use with `syscall` or `sysenter`. | ||
#[inline] | ||
pub fn user_data_segment() -> Descriptor { | ||
use self::DescriptorFlags as Flags; | ||
|
||
let flags = Flags::USER_SEGMENT | Flags::PRESENT | Flags::WRITABLE | Flags::DPL_RING_3; | ||
Descriptor::UserSegment(flags.bits()) | ||
pub const fn kernel_data_segment() -> Descriptor { | ||
Descriptor::UserSegment(DescriptorFlags::KERNEL_DATA.bits()) | ||
} | ||
|
||
/// Creates a segment descriptor for a long mode ring 3 code segment. | ||
/// Creates a segment descriptor for a ring 3 data segment (32-bit or | ||
/// 64-bit). Suitable for use with `sysret` or `sysexit`. | ||
#[inline] | ||
pub fn user_code_segment() -> Descriptor { | ||
use self::DescriptorFlags as Flags; | ||
pub const fn user_data_segment() -> Descriptor { | ||
Descriptor::UserSegment(DescriptorFlags::USER_DATA.bits()) | ||
} | ||
|
||
let flags = Flags::USER_SEGMENT | ||
| Flags::PRESENT | ||
| Flags::EXECUTABLE | ||
| Flags::LONG_MODE | ||
| Flags::DPL_RING_3; | ||
Descriptor::UserSegment(flags.bits()) | ||
/// Creates a segment descriptor for a 64-bit ring 3 code segment. Suitable | ||
/// for use with `sysret` or `sysexit`. | ||
#[inline] | ||
pub const fn user_code_segment() -> Descriptor { | ||
Descriptor::UserSegment(DescriptorFlags::USER_CODE64.bits()) | ||
} | ||
|
||
/// Creates a TSS system descriptor for the given TSS. | ||
|
@@ -258,3 +317,21 @@ impl Descriptor { | |
Descriptor::SystemSegment(low, high) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::DescriptorFlags as Flags; | ||
|
||
#[test] | ||
#[rustfmt::skip] | ||
pub fn linux_kernel_defaults() { | ||
// Make sure our defaults match the ones used by the Linux kernel. | ||
// Constants pulled from an old version of arch/x86/kernel/cpu/common.c | ||
assert_eq!(Flags::KERNEL_CODE64.bits(), 0x00af9b000000ffff); | ||
assert_eq!(Flags::KERNEL_CODE32.bits(), 0x00cf9b000000ffff); | ||
assert_eq!(Flags::KERNEL_DATA.bits(), 0x00cf93000000ffff); | ||
assert_eq!(Flags::USER_CODE64.bits(), 0x00affb000000ffff); | ||
assert_eq!(Flags::USER_CODE32.bits(), 0x00cffb000000ffff); | ||
assert_eq!(Flags::USER_DATA.bits(), 0x00cff3000000ffff); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.