From 30391aa2a13ce8fc07fe894d76fc36fd529b12d3 Mon Sep 17 00:00:00 2001 From: janis Date: Thu, 23 Jul 2026 19:18:03 +0200 Subject: [PATCH] msrs, gdt, idt --- kernel/Cargo.toml | 4 + kernel/src/lib.rs | 2 +- kernel/src/x86_64/gdt.rs | 504 +++++++++++++++++------------- kernel/src/x86_64/idt.rs | 77 ++++- kernel/src/x86_64/instructions.rs | 66 +++- kernel/tests/stack_overflow.rs | 65 ++++ 6 files changed, 497 insertions(+), 221 deletions(-) create mode 100644 kernel/tests/stack_overflow.rs diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 7870692..9a5df6a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -17,6 +17,10 @@ bench = false name = "simple" harness = false +[[test]] +name = "stack_overflow" +harness = false + [dependencies] bit_field = "0.10.3" bitflags = "2.13.1" diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 481f87e..97e36e5 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -#![feature(const_trait_impl, const_default, const_range)] +#![feature(const_trait_impl, const_default, const_range, debug_closure_helpers)] #![cfg_attr(test, feature(custom_test_frameworks))] #![cfg_attr(test, test_runner(crate::tests::test_runner))] #![cfg_attr(test, no_main)] diff --git a/kernel/src/x86_64/gdt.rs b/kernel/src/x86_64/gdt.rs index dac3d6a..86d7b1c 100644 --- a/kernel/src/x86_64/gdt.rs +++ b/kernel/src/x86_64/gdt.rs @@ -1,8 +1,17 @@ -use core::ops::{Deref, DerefMut}; +use core::{ + arch::asm, + fmt::Debug, + mem::offset_of, + ops::{Deref, DerefMut}, +}; use bit_field::BitField; -use crate::{sync::LazyLock, x86_64::idt::ToAddress}; +use crate::{ + serial_println, + sync::LazyLock, + x86_64::{idt::ToAddress, instructions::*}, +}; #[repr(C)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -14,47 +23,92 @@ pub struct GlobalDescriptorTable { } impl GlobalDescriptorTable { - pub fn set_tss(&mut self, tss: &'static TaskStateSegment) { - const TSS_SIZE: u32 = core::mem::size_of::() as u32; - let tss_base = tss as *const TaskStateSegment as u64; - let tss_limit = TSS_SIZE - 1; - self.tss = TssEntry::new(tss_base, tss_limit); - } - pub fn new() -> Self { let tss = &*TSS; GlobalDescriptorTable { - null: RawGdtEntry::new(0, 0, GdtAccess(0), GdtFlags(0)), - kernel_code: RawGdtEntry::new( - 0, - 0, - GdtAccess(0) - .with_segment(true) - .with_privilege_level(RING0) - .with_present(true) - .with_segment_kind( - CodeDataSegmentKind::CodeSegment - | CodeDataSegmentKind::Executable - | CodeDataSegmentKind::ReadWrite, - ), - GdtFlags::new(0xff, false, true, DefaultOperationSize::Bit16, true), - ), - kernel_data: RawGdtEntry::new( - 0, - 0, - GdtAccess(0) - .with_segment(true) - .with_privilege_level(RING0) - .with_present(true) - .with_segment_kind(CodeDataSegmentKind::ReadWrite), - GdtFlags::new(0xff, false, false, DefaultOperationSize::Bit16, true), - ), + null: RawGdtEntry::new(0, 0, 0, 0), + kernel_code: RawGdtEntry::new(0xfffff, 0, 0, 0) + .with_segment(true) + .with_privilege_level(RING0) + .with_present(true) + .with_segment_kind( + CodeDataSegmentKind::CodeSegment + | CodeDataSegmentKind::Executable + | CodeDataSegmentKind::ReadWrite + | CodeDataSegmentKind::Accessed, + ) + .with_present(true) + .with_code_segment64(true) + .with_granularity(true), + kernel_data: RawGdtEntry::new(0xfffff, 0, 0, 0) + .with_segment(true) + .with_privilege_level(RING0) + .with_present(true) + .with_segment_kind(CodeDataSegmentKind::ReadWrite | CodeDataSegmentKind::Accessed) + .with_code_segment64(true) + .with_granularity(true), tss: TssEntry::new( - tss as *const TaskStateSegment as u64, core::mem::size_of::() as u32 - 1, + tss as *const TaskStateSegment as u64, ), } } + + pub fn load(&'static self) { + serial_println!("Loading GDT {:#?}", self); + unsafe { Self::load_unsafe(self) } + } + + /// # Safety + /// The caller must ensure that `this` is a valid pointer to a `GlobalDescriptorTable` that remains valid for the lifetime of the gdt. + pub unsafe fn load_unsafe(this: *const Self) { + let gdt_register = GdtRegister { + limit: (core::mem::size_of::() - 1) as u16, + base: this as u64, + }; + + unsafe { + core::arch::asm!( + "lgdt [{}]", + in(reg) &gdt_register, + options(readonly, nostack, preserves_flags) + ); + + let gs = msr::read_msr(msr::MSR_GS_BASE); + + asm!( + "push {code_seg}", + "lea rax, [rip + 2f]", + "push rax", + "retfq", + "2:", + "mov ax, {data_seg}", + "mov ds, ax", + "mov es, ax", + "mov fs, ax", + "mov gs, ax", + "mov ss, ax", + code_seg = const offset_of!(GlobalDescriptorTable, kernel_code), + data_seg = const offset_of!(GlobalDescriptorTable, kernel_data), + lateout("rax") _, + ); + + msr::write_msr(msr::MSR_GS_BASE, gs); + + asm!( + "mov ax, {tss_seg}", + "ltr ax", + tss_seg = const offset_of!(GlobalDescriptorTable, tss), + lateout("rax") _, + ); + } + } +} + +#[repr(C, packed)] +struct GdtRegister { + limit: u16, + base: u64, } impl Default for GlobalDescriptorTable { @@ -63,90 +117,6 @@ impl Default for GlobalDescriptorTable { } } -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct GdtAccess(u8); - -#[repr(transparent)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct GdtFlags(u8); - -impl GdtFlags { - pub fn new( - limit_high: u8, - available: bool, - code_segment64: bool, - default_operation_size: DefaultOperationSize, - granularity: bool, - ) -> Self { - let mut flags = GdtFlags(0); - flags.set_limit_high(limit_high); - flags.set_available(available); - flags.set_code_segment64(code_segment64); - flags.set_default_operation_size(default_operation_size); - flags.set_granularity(granularity); - flags - } - - pub fn with_available(mut self, available: bool) -> Self { - self.set_available(available); - self - } - pub fn with_code_segment64(mut self, code_segment64: bool) -> Self { - self.set_code_segment64(code_segment64); - self - } - pub fn with_default_operation_size(mut self, size: DefaultOperationSize) -> Self { - self.set_default_operation_size(size); - self - } - pub fn with_granularity(mut self, granularity: bool) -> Self { - self.set_granularity(granularity); - self - } - pub fn with_limit_high(mut self, limit_high: u8) -> Self { - self.set_limit_high(limit_high); - self - } - - pub fn limit_high(&self) -> u8 { - self.0.get_bits(0..4) - } - pub fn set_limit_high(&mut self, limit_high: u8) { - self.0.set_bits(0..4, limit_high); - } - pub fn available(&self) -> bool { - self.0.get_bit(4) - } - pub fn set_available(&mut self, available: bool) { - self.0.set_bit(4, available); - } - pub fn code_segment64(&self) -> bool { - self.0.get_bit(5) - } - pub fn set_code_segment64(&mut self, code_segment64: bool) { - self.0.set_bit(5, code_segment64); - } - - /// `default_operation_size` is the default size of operands for this segment. If `default_operation_size` is set, the default size is 32 bits, otherwise it is 16 bits. - /// On 64-bit mode, this should be 1. - pub fn default_operation_size(&self) -> DefaultOperationSize { - DefaultOperationSize::from_bool(self.0.get_bit(6)) - } - pub fn set_default_operation_size(&mut self, size: DefaultOperationSize) { - self.0.set_bit(6, size.into_u8() != 0); - } - - /// `granularity` is the unit of the limit field. If `granularity` is set, - /// the limit is in 4KiB blocks, otherwise it is in bytes. - pub fn granularity(&self) -> bool { - self.0.get_bit(7) - } - pub fn set_granularity(&mut self, granularity: bool) { - self.0.set_bit(7, granularity); - } -} - #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum SystemSegmentKind { @@ -165,9 +135,10 @@ pub const RING2: u8 = 2; pub const RING3: u8 = 3; bitflags::bitflags! { + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CodeDataSegmentKind: u8 { - const CodeSegment = 1 << 4; - const Executable = 1 << 3; + const CodeSegment = 1 << 3; + const Executable = 1 << 2; /// `ReadWrite` is set for code segments if they are readable, and for /// data segments if they are writable. const ReadWrite = 1 << 1; @@ -198,76 +169,65 @@ impl SystemSegmentKind { } } -impl GdtAccess { - pub fn with_segment_kind(mut self, kind: CodeDataSegmentKind) -> Self { - self.set_segment_kind(kind); - self - } - pub fn with_system_segment_kind(mut self, kind: SystemSegmentKind) -> Self { - self.set_system_segment_kind(kind); - self - } - pub fn with_segment(mut self, segment: bool) -> Self { - self.set_segment(segment); - self - } - pub fn with_privilege_level(mut self, level: u8) -> Self { - self.set_privilege_level(level); - self - } - pub fn with_present(mut self, present: bool) -> Self { - self.set_present(present); - self - } - - pub fn system_segment_kind(&self) -> SystemSegmentKind { - SystemSegmentKind::from_u8_or_reserved(self.0.get_bits(0..4)) - } - pub fn set_system_segment_kind(&mut self, kind: SystemSegmentKind) { - self.0.set_bits(0..4, kind.into_u8()); - } - /// `segment_kind` is valid if `segment` is set, otherwise - /// `system_segment_kind` is valid. `segment_kind` is the type of segment, - /// and is only valid for code and data segments. - pub fn segment_kind(&self) -> CodeDataSegmentKind { - CodeDataSegmentKind::from_bits_truncate(self.0.get_bits(0..4)) - } - pub fn set_segment_kind(&mut self, kind: CodeDataSegmentKind) { - self.0.set_bits(0..4, kind.bits()); - } - - /// `segment` is set if this is a code or data segment, and clear if it is a system segment. - pub fn segment(&self) -> bool { - self.0.get_bit(4) - } - pub fn set_segment(&mut self, system_segment: bool) { - self.0.set_bit(4, system_segment); - } - pub fn privilege_level(&self) -> u8 { - self.0.get_bits(5..7) - } - pub fn set_privilege_level(&mut self, level: u8) { - self.0.set_bits(5..7, level); - } - pub fn present(&self) -> bool { - self.0.get_bit(7) - } - pub fn set_present(&mut self, present: bool) { - self.0.set_bit(7, present); - } -} - #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct RawGdtEntry { pub limit_low: u16, pub base_low: u16, pub base_middle: u8, - pub access: GdtAccess, - pub flags: GdtFlags, + pub access: u8, + pub flags: u8, pub base_high: u8, } +fn debug_commont_gdt_fields<'a, 'b>( + entry: &RawGdtEntry, + dbg: &'a mut core::fmt::DebugStruct<'a, 'b>, +) -> &'a mut core::fmt::DebugStruct<'a, 'b> { + dbg.field_with("limit", |f| { + if entry.granularity() { + f.write_fmt(format_args!("0x{:x} (4KiB blocks)", entry.limit())) + } else { + f.write_fmt(format_args!("0x{:x} (bytes)", entry.limit())) + } + }) + .field("segment", &entry.segment()); + + if entry.segment() { + dbg.field("segment_kind", &entry.segment_kind()); + } else { + dbg.field("system_segment_kind", &entry.system_segment_kind()); + } + + dbg.field("privilege_level", &entry.privilege_level()) + .field("present", &entry.present()) + .field("available", &entry.available()) + .field("code_segment64", &entry.code_segment64()) + .field("default_operation_size", &entry.default_operation_size()) + .field_with("granularity", |f| { + if entry.granularity() { + f.write_str("4KiB") + } else { + f.write_str("bytes") + } + }); + + dbg +} + +impl Debug for RawGdtEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut dbg = f.debug_struct("RawGdtEntry"); + + dbg.field_with("base", |f| { + f.write_fmt(format_args!("0x{:x}", self.base32())) + }); + let dbg = debug_commont_gdt_fields(self, &mut dbg); + + dbg.finish() + } +} + #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum DefaultOperationSize { @@ -293,20 +253,9 @@ impl AsRef for TssEntry { &self.gdt } } -impl AsRef for RawGdtEntry { - fn as_ref(&self) -> &GdtAccess { - &self.access - } -} - -impl AsRef for RawGdtEntry { - fn as_ref(&self) -> &GdtFlags { - &self.flags - } -} impl RawGdtEntry { - pub fn new(limit: u32, base: u32, access: GdtAccess, flags: GdtFlags) -> Self { + pub fn new(limit: u32, base: u32, access: u8, flags: u8) -> Self { let limit_low = limit as u16; let limit_high = (limit >> 16) as u8; let base_low = base as u16; @@ -318,18 +267,139 @@ impl RawGdtEntry { base_low, base_middle, access, - flags: flags.with_limit_high(limit_high), + flags: (flags & 0xf0) | limit_high, base_high, } } + pub fn empty() -> Self { + RawGdtEntry { + limit_low: 0, + base_low: 0, + base_middle: 0, + access: 0, + flags: 0, + base_high: 0, + } + } - pub fn access(&self) -> GdtAccess { - self.access + pub fn with_segment_kind(mut self, kind: CodeDataSegmentKind) -> Self { + self.set_segment_kind(kind); + self + } + pub fn with_system_segment_kind(mut self, kind: SystemSegmentKind) -> Self { + self.set_system_segment_kind(kind); + self + } + pub fn with_segment(mut self, segment: bool) -> Self { + self.set_segment(segment); + self + } + pub fn with_privilege_level(mut self, level: u8) -> Self { + self.set_privilege_level(level); + self + } + pub fn with_present(mut self, present: bool) -> Self { + self.set_present(present); + self + } + + pub fn system_segment_kind(&self) -> SystemSegmentKind { + SystemSegmentKind::from_u8_or_reserved(self.access.get_bits(0..4)) + } + pub fn set_system_segment_kind(&mut self, kind: SystemSegmentKind) { + self.access.set_bits(0..4, kind.into_u8()); + } + /// `segment_kind` is valid if `segment` is set, otherwise + /// `system_segment_kind` is valid. `segment_kind` is the type of segment, + /// and is only valid for code and data segments. + pub fn segment_kind(&self) -> CodeDataSegmentKind { + CodeDataSegmentKind::from_bits_truncate(self.access.get_bits(0..4)) + } + pub fn set_segment_kind(&mut self, kind: CodeDataSegmentKind) { + self.access.set_bits(0..4, kind.bits()); + } + + /// `segment` is set if this is a code or data segment, and clear if it is a system segment. + pub fn segment(&self) -> bool { + self.access.get_bit(4) + } + pub fn set_segment(&mut self, system_segment: bool) { + self.access.set_bit(4, system_segment); + } + pub fn privilege_level(&self) -> u8 { + self.access.get_bits(5..7) + } + pub fn set_privilege_level(&mut self, level: u8) { + self.access.set_bits(5..7, level); + } + pub fn present(&self) -> bool { + self.access.get_bit(7) + } + pub fn set_present(&mut self, present: bool) { + self.access.set_bit(7, present); + } + + pub fn with_available(mut self, available: bool) -> Self { + self.set_available(available); + self + } + pub fn with_code_segment64(mut self, code_segment64: bool) -> Self { + self.set_code_segment64(code_segment64); + self + } + pub fn with_default_operation_size(mut self, size: DefaultOperationSize) -> Self { + self.set_default_operation_size(size); + self + } + pub fn with_granularity(mut self, granularity: bool) -> Self { + self.set_granularity(granularity); + self + } + pub fn with_limit_high(mut self, limit_high: u8) -> Self { + self.set_limit_high(limit_high); + self + } + + fn limit_high(&self) -> u8 { + self.flags.get_bits(0..4) + } + fn set_limit_high(&mut self, limit_high: u8) { + self.flags.set_bits(0..4, limit_high); + } + pub fn available(&self) -> bool { + self.flags.get_bit(4) + } + pub fn set_available(&mut self, available: bool) { + self.flags.set_bit(4, available); + } + pub fn code_segment64(&self) -> bool { + self.flags.get_bit(5) + } + pub fn set_code_segment64(&mut self, code_segment64: bool) { + self.flags.set_bit(5, code_segment64); + } + + /// `default_operation_size` is the default size of operands for this segment. If `default_operation_size` is set, the default size is 32 bits, otherwise it is 16 bits. + /// On 64-bit mode, this should be 1. + pub fn default_operation_size(&self) -> DefaultOperationSize { + DefaultOperationSize::from_bool(self.flags.get_bit(6)) + } + pub fn set_default_operation_size(&mut self, size: DefaultOperationSize) { + self.flags.set_bit(6, size.into_u8() != 0); + } + + /// `granularity` is the unit of the limit field. If `granularity` is set, + /// the limit is in 4KiB blocks, otherwise it is in bytes. + pub fn granularity(&self) -> bool { + self.flags.get_bit(7) + } + pub fn set_granularity(&mut self, granularity: bool) { + self.flags.set_bit(7, granularity); } pub fn limit(&self) -> u32 { let limit_low = self.limit_low as u32; - let limit_high = (self.flags.limit_high() as u32) << 16; + let limit_high = (self.limit_high() as u32) << 16; limit_low | limit_high } @@ -342,13 +412,26 @@ impl RawGdtEntry { } #[repr(C)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct TssEntry { gdt: RawGdtEntry, pub base_ext: u32, reserved: u32, } +impl Debug for TssEntry { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut dbg = f.debug_struct("TssEntry"); + + dbg.field_with("base", |f| { + f.write_fmt(format_args!("0x{:x}", self.base64())) + }); + let dbg = debug_commont_gdt_fields(&self.gdt, &mut dbg); + + dbg.finish() + } +} + impl TssEntry { pub fn base64(&self) -> u64 { let base_low = self.gdt.base32() as u64; @@ -356,22 +439,15 @@ impl TssEntry { (base_ext << 32) | base_low } - pub fn new(base: u64, limit: u32) -> Self { + pub fn new(limit: u32, base: u64) -> Self { let base_low = base as u32; let base_ext = (base >> 32) as u32; - let mut access = GdtAccess(0); - access.set_system_segment_kind(SystemSegmentKind::TssAvailable); - access.set_privilege_level(RING0); - access.set_present(true); - access.set_segment(true); - - let mut granularity = GdtFlags(0); - granularity.set_limit_high(limit.get_bits(16..20) as u8); - granularity.set_granularity(false); - TssEntry { - gdt: RawGdtEntry::new(limit, base_low, access, granularity), + gdt: RawGdtEntry::new(limit, base_low, 0, 0) + .with_present(true) + .with_privilege_level(RING0) + .with_system_segment_kind(SystemSegmentKind::TssAvailable), base_ext, reserved: 0, } @@ -425,7 +501,7 @@ pub const DF_STACK: u8 = 0; pub const NMI_STACK: u8 = 1; pub const MC_STACK: u8 = 2; -static TSS: LazyLock = LazyLock::new(|| { +pub static TSS: LazyLock = LazyLock::new(|| { let mut tss = TaskStateSegment::new(); const STACK_SIZE: usize = super::PAGE_SIZE * 5; static mut STACKS: [[u8; STACK_SIZE]; 3] = [[0; STACK_SIZE]; 3]; @@ -438,3 +514,5 @@ static TSS: LazyLock = LazyLock::new(|| { tss }); + +pub static GDT: LazyLock = LazyLock::new(GlobalDescriptorTable::new); diff --git a/kernel/src/x86_64/idt.rs b/kernel/src/x86_64/idt.rs index da7c4a7..67675c6 100644 --- a/kernel/src/x86_64/idt.rs +++ b/kernel/src/x86_64/idt.rs @@ -28,6 +28,36 @@ impl core::fmt::Debug for Entry { } } +pub struct ExceptionVector; +impl ExceptionVector { + pub const DIVIDE_BY_ZERO: u8 = 0; + pub const DEBUG: u8 = 1; + pub const NON_MASKABLE_INTERRUPT: u8 = 2; + pub const BREAKPOINT: u8 = 3; + pub const OVERFLOW: u8 = 4; + pub const BOUND_RANGE_EXCEEDED: u8 = 5; + pub const INVALID_OPCODE: u8 = 6; + pub const DEVICE_NOT_AVAILABLE: u8 = 7; + pub const DOUBLE_FAULT: u8 = 8; + pub const COPROCESSOR_SEGMENT_OVERRUN: u8 = 9; + pub const INVALID_TSS: u8 = 10; + pub const SEGMENT_NOT_PRESENT: u8 = 11; + pub const STACK_SEGMENT_FAULT: u8 = 12; + pub const GENERAL_PROTECTION_FAULT: u8 = 13; + pub const PAGE_FAULT: u8 = 14; + + pub const X87_FLOATING_POINT_EXCEPTION: u8 = 16; + pub const ALIGNMENT_CHECK: u8 = 17; + pub const MACHINE_CHECK: u8 = 18; + pub const SIMD_FLOATING_POINT_EXCEPTION: u8 = 19; + pub const VIRTUALIZATION_EXCEPTION: u8 = 20; + pub const CONTROL_PROTECTION_EXCEPTION: u8 = 21; + + pub const HYPERVISOR_EXCEPTION: u8 = 28; + pub const VMM_COMMUNICATION_EXCEPTION: u8 = 29; + pub const SECURITY_EXCEPTION: u8 = 30; +} + impl Entry { pub unsafe fn new(handler: T, selector: u16, options: EntryOptions) -> Self { let handler_addr = handler.to_address(); @@ -52,6 +82,19 @@ impl Entry { _ => interrupt_trampoline_no_err:: as *const () as u64, }; + match IDX { + ExceptionVector::DOUBLE_FAULT => { + options.set_interrupt_stack_table_index(super::gdt::DF_STACK); + } + ExceptionVector::NON_MASKABLE_INTERRUPT => { + options.set_interrupt_stack_table_index(super::gdt::NMI_STACK); + } + ExceptionVector::MACHINE_CHECK => { + options.set_interrupt_stack_table_index(super::gdt::MC_STACK); + } + _ => {} + } + unsafe { Self::new(handler, cs, options) } } @@ -176,6 +219,24 @@ impl EntryOptions { pub const fn empty_trap_gate() -> Self { Self(0b1111_0000_0000) } + + pub fn with_present(mut self, present: bool) -> Self { + self.set_present(present); + self + } + pub fn with_privilege_level(mut self, level: u8) -> Self { + self.set_privilege_level(level); + self + } + pub fn with_interrupt_stack_table_index(mut self, index: u8) -> Self { + self.set_interrupt_stack_table_index(index); + self + } + pub fn with_kind(mut self, kind: EntryType) -> Self { + self.set_kind(kind); + self + } + pub fn present(&self) -> bool { self.0.get_bit(15) } @@ -258,7 +319,7 @@ bitflags::bitflags! { #[derive(Debug, Clone, Copy)] #[repr(C, packed(2))] -pub struct InterruptDescriptorTablePointer<'idt> { +pub struct IdtRegister<'idt> { pub limit: u16, pub base: u64, _pd: PhantomData<&'idt ()>, @@ -348,17 +409,21 @@ impl InterruptDescriptorTable { idt } - pub unsafe fn pointer(&self) -> InterruptDescriptorTablePointer<'_> { - InterruptDescriptorTablePointer { + pub unsafe fn register(&self) -> IdtRegister<'_> { + IdtRegister { limit: (core::mem::size_of::() - 1) as u16, base: self as *const _ as u64, _pd: PhantomData, } } - pub unsafe fn load(&self) { + pub fn load(&'static self) { + unsafe { Self::load_unsafe(self) }; + } + + pub unsafe fn load_unsafe(&self) { unsafe { - super::instructions::lidt(&self.pointer()); + super::instructions::lidt(&self.register()); } } @@ -457,7 +522,7 @@ fn test_interrupt_handler_trait() { static IDT: LazyLock = crate::sync::LazyLock::new(InterruptDescriptorTable::new_default); - unsafe { IDT.load() }; + unsafe { IDT.load_unsafe() }; super::instructions::int3(); // Trigger a breakpoint interrupt (interrupt 3) diff --git a/kernel/src/x86_64/instructions.rs b/kernel/src/x86_64/instructions.rs index b4d8b13..e113f8f 100644 --- a/kernel/src/x86_64/instructions.rs +++ b/kernel/src/x86_64/instructions.rs @@ -6,7 +6,7 @@ pub fn hlt() { } #[inline] -pub unsafe fn lidt(idt: &super::idt::InterruptDescriptorTablePointer) { +pub unsafe fn lidt(idt: &super::idt::IdtRegister) { unsafe { core::arch::asm!( "lidt [{}]", @@ -43,3 +43,67 @@ macro_rules! read_segment { pub unsafe fn get_cs() -> u16 { read_segment!("cs") } + +pub mod msr { + use core::arch::asm; + + pub const MSR_STAR: u32 = 0xC000_0081; + pub const MSR_LSTAR: u32 = 0xC000_0082; + pub const MSR_CSTAR: u32 = 0xC000_0083; + pub const MSR_SFMASK: u32 = 0xC000_0084; + pub const MSR_FS_BASE: u32 = 0xC000_0100; + pub const MSR_GS_BASE: u32 = 0xC000_0101; + pub const MSR_KERNEL_GS_BASE: u32 = 0xC000_0102; + + /// Reads the value of the specified Model-Specific Register (MSR). + /// # Safety + /// This operation is inherently unsafe. + #[inline] + pub unsafe fn read_msr(msr: u32) -> u64 { + unsafe { + let eax: u32; + let edx: u32; + asm!( + "rdmsr", + in("ecx") msr, + out("eax") eax, + out("edx") edx, + options(nomem, nostack, preserves_flags) + ); + ((edx as u64) << 32) | (eax as u64) + } + } + + /// Writes the value to the specified Model-Specific Register (MSR). + /// # Safety + /// This operation is inherently unsafe. + #[inline] + pub unsafe fn write_msr(msr: u32, value: u64) { + unsafe { + let eax = value as u32; + let edx = (value >> 32) as u32; + asm!( + "wrmsr", + in("ecx") msr, + in("eax") eax, + in("edx") edx, + options(nomem, nostack, preserves_flags) + ); + } + } +} + +#[inline] +pub fn rdtsc() -> u64 { + let low: u32; + let high: u32; + unsafe { + core::arch::asm!( + "rdtsc", + out("eax") low, + out("edx") high, + options(nomem, nostack, preserves_flags) + ); + } + ((high as u64) << 32) | (low as u64) +} diff --git a/kernel/tests/stack_overflow.rs b/kernel/tests/stack_overflow.rs new file mode 100644 index 0000000..ac84138 --- /dev/null +++ b/kernel/tests/stack_overflow.rs @@ -0,0 +1,65 @@ +#![no_main] +#![no_std] +#![feature(abi_x86_interrupt)] + +use core::{cell::UnsafeCell, mem::offset_of}; + +use kernel::{ + sync::LazyLock, + x86_64::{ + gdt::{DF_STACK, GDT, GlobalDescriptorTable, RING0}, + idt::{self, Entry, InterruptDescriptorTable}, + }, +}; + +#[unsafe(export_name = "_start")] +pub extern "C" fn main() -> ! { + kernel::serial_println!("Hello, world!"); + + GDT.load(); + kernel::serial_println!("[ok] GDT loaded"); + + extern "x86-interrupt" fn double_fault_handler( + _stack_frame: &mut idt::InterruptStackFrame, + _error_code: u64, + ) -> ! { + kernel::serial_println!("[ok] Double fault handler called"); + kernel::testing::exit_qemu(kernel::testing::QemuExitCode::Success) + } + + static IDT: LazyLock = LazyLock::new(|| { + let mut idt = InterruptDescriptorTable::new_default(); + idt.double_fault = unsafe { + Entry::new( + double_fault_handler as *const (), + offset_of!(GlobalDescriptorTable, kernel_code) as u16, + idt::EntryOptions::empty_interrupt_gate() + .with_present(true) + .with_privilege_level(RING0) + .with_interrupt_stack_table_index(DF_STACK), + ) + }; + + idt + }); + + IDT.load(); + + stack_overflow(); + + panic!("Triggering a stack overflow to test double fault handling"); +} + +#[allow(unconditional_recursion)] +fn stack_overflow() { + stack_overflow(); // for each recursion, the return address is pushed + unsafe { + let cell = UnsafeCell::new(0); + cell.get().write_volatile(0); + }; // prevent tail recursion optimizations +} + +#[panic_handler] +fn panic_thunk(info: &core::panic::PanicInfo) -> ! { + kernel::testing::test_panic_handler(info) +}