commit b4dab5c048498746bc2457e11f2b69423c710757 Author: mtgmonkey Date: Fri Mar 28 16:07:34 2025 -0400 initial commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..0a504cf --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[build] +target = "x86_64-rustos.json" # target OS specifications + +[target.'cfg(target_os="none")'] +runner = "bootimage runner" # run with QEMU + +[unstable] +build-std = ["core", "compiler_builtins"] +build-std-features = ["compiler-builtins-mem"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7308ffb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,118 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + +[[package]] +name = "bootloader" +version = "0.9.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "974e79cf1b0b737839f01330fb5393095daf1124d52693696494e32523ae9ef5" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "pc-keyboard" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed089a1fbffe3337a1a345501c981f1eb1e47e69de5a40e852433e12953c3174" + +[[package]] +name = "pic8259" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb844b5b01db1e0b17938685738f113bfc903846f18932b378bc0eabfa40e194" +dependencies = [ + "x86_64", +] + +[[package]] +name = "rustos" +version = "0.1.0" +dependencies = [ + "bootloader", + "lazy_static", + "pc-keyboard", + "pic8259", + "spin 0.5.2", + "uart_16550", + "volatile 0.2.7", + "x86_64", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "uart_16550" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614ff2a87880d4bd4374722268598a970bbad05ced8bf630439417347254ab2e" +dependencies = [ + "bitflags 1.3.2", + "rustversion", + "x86_64", +] + +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags 2.9.0", + "rustversion", + "volatile 0.4.6", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..dc58b4a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "rustos" +version = "0.1.0" +edition = "2024" +authors = [ "mtgmonkey" ] + +# Special debug shutdown device in QEMU +[package.metadata.bootimage] +test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", +"-serial", "stdio", +"-display", "none"] +test-success-exit-code = 33 # (0x10 << 1) | 1 +test-timeout = 30 # in seconds + +[dependencies] +bootloader = { version = "0.9", features = ["map_physical_memory"] } +pc-keyboard = "0.7.0" +pic8259 = "0.10.1" +spin = "0.5.2" +uart_16550 = "0.2.0" +volatile = "0.2.6" +x86_64 = "0.14.2" + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] + +[[test]] +name = "should_panic" +harness = false + +[[test]] +name = "stack_overflow" +harness = false diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..26a022f --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +{ pkgs ? import { } }: +pkgs.mkShell { + nativeBuildInputs = [ + pkgs.qemu + pkgs.rustup + ]; + RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}"; +} diff --git a/src/gdt.rs b/src/gdt.rs new file mode 100644 index 0000000..378a889 --- /dev/null +++ b/src/gdt.rs @@ -0,0 +1,53 @@ +use lazy_static::lazy_static; +use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector}; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::VirtAddr; + +struct Selectors { + code_selector: SegmentSelector, + tss_selector: SegmentSelector, +} + +lazy_static! { + static ref GDT: (GlobalDescriptorTable, Selectors) = { + let mut gdt = GlobalDescriptorTable::new(); + // SAFETY ensure these values are safe in init() + let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); + let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); + ( + gdt, + Selectors { + code_selector, + tss_selector, + }, + ) + }; +} + +// IST index to use for double fault stack +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +lazy_static! { + static ref TSS: TaskStateSegment = { + let mut tss = TaskStateSegment::new(); + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { + const STACK_SIZE: usize = 4096 * 5; + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; // TODO make real stack + let stack_start = VirtAddr::from_ptr(&raw const STACK); + let stack_end = stack_start + STACK_SIZE; + stack_end + }; + tss + }; +} + +pub fn init() { + use x86_64::instructions::segmentation::{Segment, CS}; + use x86_64::instructions::tables::load_tss; + GDT.0.load(); + // SAFETY ensure GDT returns the right values + unsafe { + CS::set_reg(GDT.1.code_selector); + load_tss(GDT.1.tss_selector); + } +} diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..e82a6d8 --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,125 @@ +use crate::{gdt, hlt_loop, print, println}; +use lazy_static::lazy_static; +use pic8259::ChainedPics; +use spin; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, + Keyboard, +} + +impl InterruptIndex { + fn as_u8(self) -> u8 { + self as u8 + } + + fn as_usize(self) -> usize { + usize::from(self.as_u8()) + } +} + +// set offsets for PICs +// SAFETY +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + +// only init idt in memory when first referred to +// allows static mut +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + // SAFETY ensure index is valid and not passed to any other exceptions + unsafe { + idt.double_fault.set_handler_fn(double_fault_handler).set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + } + idt[InterruptIndex::Timer.as_usize()].set_handler_fn(timer_interrupt_handler); + idt[InterruptIndex::Keyboard.as_usize()].set_handler_fn(keyboard_interrupt_handler); + idt.page_fault.set_handler_fn(page_fault_handler); + idt + }; +} + +// handle breakpoint exceptions +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} + +// handle double fault exceptions +extern "x86-interrupt" fn double_fault_handler( + stack_frame: InterruptStackFrame, + _error_code: u64, +) -> ! { + panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); +} + +// init idt +pub fn init_idt() { + IDT.load(); +} + +extern "x86-interrupt" fn keyboard_interrupt_handler(_keyboard_stack_frame: InterruptStackFrame) { + use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; + use spin::Mutex; + use x86_64::instructions::port::Port; + + lazy_static! { + static ref KEYBOARD: Mutex> = + Mutex::new(Keyboard::new( + ScancodeSet1::new(), + layouts::Us104Key, + HandleControl::Ignore + )); + } + + let mut keyboard = KEYBOARD.lock(); + let mut port = Port::new(0x60); + + let scancode: u8 = unsafe { port.read() }; + if let Ok(Some(key_event)) = keyboard.add_byte(scancode) { + if let Some(key) = keyboard.process_keyevent(key_event) { + match key { + DecodedKey::Unicode(character) => print!("{}", character), + DecodedKey::RawKey(key) => print!("{:?}", key), + } + } + } + + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); + } +} + +extern "x86-interrupt" fn page_fault_handler( + stack_frame: InterruptStackFrame, + error_code: PageFaultErrorCode, +) { + use x86_64::registers::control::Cr2; + + println!("EXCEPTION: PAGE FAULT"); + println!("Accessed Address: {:?}", Cr2::read()); + println!("Error Code: {:?}?", error_code); + println!("{:#?}", stack_frame); + hlt_loop(); +} + +// forcefully throws breakpoint exception +#[test_case] +fn test_breakpoint_exception() { + x86_64::instructions::interrupts::int3(); +} + +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { + print!("."); + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); + }; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..8eeb13c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,93 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(abi_x86_interrupt)] +#![feature(custom_test_frameworks)] +#![test_runner(test_runner)] +#![reexport_test_harness_main = "test_main"] + +pub mod gdt; +pub mod interrupts; +pub mod serial; +pub mod vga_buffer; + +use core::panic::PanicInfo; + +// wizardry for Testable Fn() type +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); + self(); + serial_println!("[ok]"); + } +} + +// defines QemuExitCodes +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +#[cfg(test)] +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + init(); + test_main(); + hlt_loop(); +} + +// exits qemu with an exit code +// qemu has special escape port at 0xf4 +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} +// common init fn for kernel +pub fn init() { + gdt::init(); + interrupts::init_idt(); + // SAFETY ensure the PICs are properly defined in interrupts.rs + unsafe { interrupts::PICS.lock().initialize() }; + x86_64::instructions::interrupts::enable(); +} + +// prints error code for failed test and exits qemu with a failure +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + hlt_loop(); +} + +// runs tests +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +// called on panic +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e2cc647 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(rustos::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use rustos::println; + +// SAFETY: make sure no other global fn _start exists +// called on start +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + println!("Hello World{}", "!"); + rustos::init(); // initializes kernel + + use x86_64::registers::control::Cr3; + + let (level_4_page_table, _) = Cr3::read(); + println!( + "Level 4 page table at: {:?}", + level_4_page_table.start_address() + ); + + #[cfg(test)] + test_main(); + + println!("Hey, it didn't crash!"); + rustos::hlt_loop(); +} + +// called on test panic +// prints to console error message +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rustos::test_panic_handler(info) +} + +// called on panic +// prints to vga error message +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + rustos::hlt_loop(); +} diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..f5fd2fb --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,35 @@ +use lazy_static::lazy_static; +use spin::Mutex; +use uart_16550::SerialPort; + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { $crate::serial::_print(format_args!($($arg)*)); }; +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( concat!($fmt, "\n"), $($arg)*)); +} + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + interrupts::without_interrupts(|| { + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); + }); +} diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs new file mode 100644 index 0000000..594e33e --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,191 @@ +use core::fmt; +use lazy_static::lazy_static; +use spin::Mutex; +use volatile::Volatile; + +// macros exported on root ie $crate::print +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +// program ignores unused code +// enable semantics for the type +// represent it as u8 +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +const BUFFER_HEIGHT: usize = 25; +const BUFFER_WIDTH: usize = 80; + +#[repr(transparent)] +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +// full byte color code +// repr(transparent) ensures struct has same memory layout as type, in this case u8 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +// implementation of colorCode +impl ColorCode { + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + +// uses C-like struct to gurantee field ordering +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +// 'static tells the runtime how long the reference is valid; 'static is forever +pub struct Writer { + column_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + +impl Writer { + // writes byte to screen, automatically handling newlines + // expects a printable byte + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte => { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = BUFFER_HEIGHT - 1; + let col = self.column_position; + + // .write with Volatile ensures this will never be optimised away + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + // doesn't expect printable bytes + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // filters out non-printable bytes + _ => self.write_byte(0xfe), + } + } + } + // iterates each character 1 row up and replaces cursor + fn new_line(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT - 1); + self.column_position = 0; + } + // overwrites a row with spaces + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } +} + +// required by fmt: write_str (self, str) -> fmt::Result +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +// static WRITER evaluates lazily +lazy_static! { + // spin Mutex 'locks' thread without OS features + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::Magenta, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + +// hides from generated docs despite publicity +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; + interrupts::without_interrupts(|| { + WRITER.lock().write_fmt(args).unwrap(); + }); +} + +#[test_case] +fn test_println_many() { + for _ in 0..1023 { + println!("test_println_many output"); + } +} + +#[test_case] +fn test_println_output() { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + let s = "Some test string that fits in a single line"; + interrupts::without_interrupts(|| { + // prevent race condition where an exception prints while it io + let mut writer = WRITER.lock(); + writeln!(writer, "\n{}", s).expect("writeln failed"); + for (i, c) in s.chars().enumerate() { + let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } + }); +} + +#[test_case] +fn test_println_simple() { + println!("test_println_simple output"); +} diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs new file mode 100644 index 0000000..224359a --- /dev/null +++ b/tests/basic_boot.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(rustos::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use rustos::println; + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + test_main(); + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rustos::test_panic_handler(info) +} + +#[test_case] +fn test_println() { + println!("test_println output"); +} diff --git a/tests/should_panic.rs b/tests/should_panic.rs new file mode 100644 index 0000000..912763a --- /dev/null +++ b/tests/should_panic.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use rustos::{QemuExitCode, exit_qemu, serial_print, serial_println}; + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + should_fail(); + serial_println!("[test did not panic]"); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +fn should_fail() { + serial_print!("should_panic::should_fail...\t"); + assert_eq!(0, 1); +} diff --git a/tests/stack_overflow.rs b/tests/stack_overflow.rs new file mode 100644 index 0000000..550e209 --- /dev/null +++ b/tests/stack_overflow.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +#![feature(abi_x86_interrupt)] + +use core::panic::PanicInfo; +use lazy_static::lazy_static; +use rustos::{exit_qemu, serial_print, serial_println, QemuExitCode}; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; + +lazy_static! { + static ref TEST_IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + unsafe { + idt.double_fault + .set_handler_fn(test_double_fault_handler) + .set_stack_index(rustos::gdt::DOUBLE_FAULT_IST_INDEX); + } + idt + }; +} + +#[unsafe(no_mangle)] +pub extern "C" fn _start() -> ! { + serial_print!("stack_overflow::stack_overflow...\t"); + + rustos::gdt::init(); + init_test_idt(); + stack_overflow(); + + panic!("Execution continued after stack overflow"); +} + +pub fn init_test_idt() { + TEST_IDT.load(); +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rustos::test_panic_handler(info) +} + +#[allow(unconditional_recursion)] +fn stack_overflow() { + stack_overflow(); + volatile::Volatile::new(0).read(); // prevent optimizations +} + +extern "x86-interrupt" fn test_double_fault_handler( + _stack_frame: InterruptStackFrame, + _error_code: u64, +) -> ! { + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} diff --git a/x86_64-rustos.json b/x86_64-rustos.json new file mode 100644 index 0000000..4e974e3 --- /dev/null +++ b/x86_64-rustos.json @@ -0,0 +1,19 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + + "features": "-mmx,-sse,+soft-float", + "rustc-abi": "x86-softfloat", + + "disable-redzone": true, + "panic-strategy": "abort", + + "linker-flavor": "ld.lld", + "linker": "rust-lld" +}