x86_64/instructions/
segmentation.rs

1//! Provides functions to read and write segment registers.
2
3pub use crate::registers::segmentation::{Segment, Segment64, CS, DS, ES, FS, GS, SS};
4use crate::{
5    registers::model_specific::{FsBase, GsBase, Msr},
6    structures::gdt::SegmentSelector,
7    VirtAddr,
8};
9use core::arch::asm;
10
11macro_rules! get_reg_impl {
12    ($name:literal) => {
13        #[inline]
14        fn get_reg() -> SegmentSelector {
15            let segment: u16;
16            unsafe {
17                asm!(concat!("mov {0:x}, ", $name), out(reg) segment, options(nomem, nostack, preserves_flags));
18            }
19            SegmentSelector(segment)
20        }
21    };
22}
23
24macro_rules! segment_impl {
25    ($type:ty, $name:literal) => {
26        impl Segment for $type {
27            get_reg_impl!($name);
28
29            #[inline]
30            unsafe fn set_reg(sel: SegmentSelector) {
31                unsafe {
32                    asm!(concat!("mov ", $name, ", {0:x}"), in(reg) sel.0, options(nostack, preserves_flags));
33                }
34            }
35        }
36    };
37}
38
39macro_rules! segment64_impl {
40    ($type:ty, $name:literal, $base:ty) => {
41        impl Segment64 for $type {
42            const BASE: Msr = <$base>::MSR;
43            #[inline]
44            fn read_base() -> VirtAddr {
45                unsafe {
46                    let val: u64;
47                    asm!(concat!("rd", $name, "base {}"), out(reg) val, options(nomem, nostack, preserves_flags));
48                    VirtAddr::new_unsafe(val)
49                }
50            }
51
52            #[inline]
53            unsafe fn write_base(base: VirtAddr) {
54                unsafe{
55                    asm!(concat!("wr", $name, "base {}"), in(reg) base.as_u64(), options(nostack, preserves_flags));
56                }
57            }
58        }
59    };
60}
61
62impl Segment for CS {
63    get_reg_impl!("cs");
64
65    /// Note this is special since we cannot directly move to [`CS`]; x86 requires the instruction
66    /// pointer and [`CS`] to be set at the same time. To do this, we push the new segment selector
67    /// and return value onto the stack and use a "far return" (`retfq`) to reload [`CS`] and
68    /// continue at the end of our function.
69    ///
70    /// Note we cannot use a "far call" (`lcall`) or "far jmp" (`ljmp`) to do this because then we
71    /// would only be able to jump to 32-bit instruction pointers. Only Intel implements support
72    /// for 64-bit far calls/jumps in long-mode, AMD does not.
73    #[inline]
74    unsafe fn set_reg(sel: SegmentSelector) {
75        unsafe {
76            asm!(
77                "push {sel}",
78                "lea {tmp}, [55f + rip]",
79                "push {tmp}",
80                "retfq",
81                "55:",
82                sel = in(reg) u64::from(sel.0),
83                tmp = lateout(reg) _,
84                options(preserves_flags),
85            );
86        }
87    }
88}
89
90segment_impl!(SS, "ss");
91segment_impl!(DS, "ds");
92segment_impl!(ES, "es");
93segment_impl!(FS, "fs");
94segment64_impl!(FS, "fs", FsBase);
95segment_impl!(GS, "gs");
96segment64_impl!(GS, "gs", GsBase);
97
98impl GS {
99    /// Swap `KernelGsBase` MSR and `GsBase` MSR.
100    ///
101    /// ## Safety
102    ///
103    /// This function is unsafe because the caller must ensure that the
104    /// swap operation cannot lead to undefined behavior.
105    #[inline]
106    pub unsafe fn swap() {
107        unsafe {
108            asm!("swapgs", options(nostack, preserves_flags));
109        }
110    }
111}