commit 42735d2abf0796df0baa8250b75b957ca5bca101 Author: charles Date: Mon Jul 19 17:33:12 2021 +0200 initial commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..9d8d08c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins"] + +[build] +target = "x86_64-rust_os.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" \ No newline at end of file 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..75d1dc9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,81 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "bit_field" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bootloader" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3c1ceed1cd9e61c7998100cc18c13d413aa40d018992b871ab8e7435ce6372" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "rust_os" +version = "0.1.0" +dependencies = [ + "bootloader", + "lazy_static", + "spin", + "uart_16550", + "volatile 0.2.7", + "x86_64", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "uart_16550" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad019480ef5ff8ffe66d6f6a259cd87cf317649481394981db1739d844f374" +dependencies = [ + "bitflags", + "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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c2dbd44eb8b53973357e6e207e370f0c1059990df850aca1eca8947cf464f0" + +[[package]] +name = "x86_64" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95947de37ad0d2d9a4a4dd22e0d042e034e5cbd7ab53edbca0d8035e0a6a64d" +dependencies = [ + "bit_field", + "bitflags", + "volatile 0.4.4", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a341d9a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "rust_os" +version = "0.1.0" +edition = "2018" + +[dependencies] +bootloader = "0.9.8" +volatile = "0.2.6" +spin = "0.5.2" +x86_64 = "0.14.2" +uart_16550 = "0.2.0" + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] + +[package.metadata.bootimage] +test-args = [ + "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", + "-display", "none" +] +test-success-exit-code = 33 +test-timeout = 100 + +[[test]] +name = "should_panic" +harness = false + +[[test]] +name = "stack_overflow" +harness = false \ No newline at end of file diff --git a/src/gdt.rs b/src/gdt.rs new file mode 100644 index 0000000..4acc941 --- /dev/null +++ b/src/gdt.rs @@ -0,0 +1,47 @@ +use x86_64::VirtAddr; +use x86_64::structures::tss::TaskStateSegment; +use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor}; +use x86_64::structures::gdt::SegmentSelector; +use lazy_static::lazy_static; + +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]; + + let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); + let stack_end = stack_start + STACK_SIZE; + stack_end + }; + tss + }; +} + +lazy_static! { + static ref GDT: (GlobalDescriptorTable, Selectors) = { + let mut gdt = GlobalDescriptorTable::new(); + 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 }) + }; +} + +struct Selectors { + code_selector: SegmentSelector, + tss_selector: SegmentSelector, +} + +pub fn init() { + use x86_64::instructions::segmentation::set_cs; + use x86_64::instructions::tables::load_tss; + + GDT.0.load(); + unsafe { + set_cs(GDT.1.code_selector); + load_tss(GDT.1.tss_selector); + } +} \ No newline at end of file diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 0000000..06590b5 --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,39 @@ +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use crate::println; + +use lazy_static::lazy_static; +use crate::gdt; + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + unsafe { + idt.double_fault.set_handler_fn(double_fault_handler) + .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); // new + } + + idt + }; +} + +extern "x86-interrupt" fn double_fault_handler( + stack_frame: InterruptStackFrame, _error_code: u64) -> ! +{ + panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); +} + +pub fn init_idt() { + IDT.load(); +} + +#[test_case] +fn test_breakpoint_exception() { + x86_64::instructions::interrupts::int3(); +} + +extern "x86-interrupt" fn breakpoint_handler( + stack_frame: InterruptStackFrame) +{ + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9e77f4b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,78 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] +#![feature(abi_x86_interrupt)] + +use core::panic::PanicInfo; + +pub mod serial; +pub mod vga_buffer; +pub mod interrupts; +pub mod gdt; + +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]"); + } +} + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + loop {} +} + +pub fn init() { + gdt::init(); + interrupts::init_idt(); +} + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn _start() -> ! { + init(); // new + test_main(); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +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); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..a27e532 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![test_runner(rust_os::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use rust_os::println; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + println!("Hello World{}", "!"); + + rust_os::init(); // new + + // as before + #[cfg(test)] + test_main(); + + println!("It did not crash!"); + loop {} +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + loop {} +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rust_os::test_panic_handler(info) +} \ No newline at end of file diff --git a/src/serial.rs b/src/serial.rs new file mode 100644 index 0000000..07129bb --- /dev/null +++ b/src/serial.rs @@ -0,0 +1,34 @@ +use uart_16550::SerialPort; +use spin::Mutex; +use lazy_static::lazy_static; + +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; + SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); +} + +/// Prints to the host through the serial interface. +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +/// Prints to the host through the serial interface, appending a newline. +#[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)*)); +} \ No newline at end of file diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs new file mode 100644 index 0000000..365aa0e --- /dev/null +++ b/src/vga_buffer.rs @@ -0,0 +1,168 @@ +use volatile::Volatile; + +const BUFFER_HEIGHT: usize = 25; +const BUFFER_WIDTH: usize = 80; + +#[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, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +#[repr(transparent)] +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +pub struct Writer { + column_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + +impl ColorCode { + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } + } + +impl Writer { + 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; + + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + // printable ASCII byte or newline + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // not part of printable ASCII range + _ => self.write_byte(0xfe), + } + + } + } + + 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; + } + + 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); + } + } +} + +use spin::Mutex; +use lazy_static::lazy_static; +lazy_static! { + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + +use core::fmt; + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +#[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)*))); +} + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +} + +#[test_case] +fn test_println_simple() { + println!("test_println_simple output"); +} + +#[test_case] +fn test_println_many() { + for _ in 0..200 { + println!("test_println_many output"); + } +} + +#[test_case] +fn test_println_output() { + let s = "Some test string that fits on a single line"; + println!("{}", s); + for (i, c) in s.chars().enumerate() { + let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } +} \ No newline at end of file diff --git a/tests/basic_boot.rs b/tests/basic_boot.rs new file mode 100644 index 0000000..17eba8f --- /dev/null +++ b/tests/basic_boot.rs @@ -0,0 +1,26 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![reexport_test_harness_main = "test_main"] +#![test_runner(rust_os::test_runner)] + +use core::panic::PanicInfo; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + test_main(); + + loop {} +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rust_os::test_panic_handler(info) +} + +use rust_os::println; + +#[test_case] +fn test_println() { + println!("test_println output"); +} \ No newline at end of file diff --git a/tests/should_panic.rs b/tests/should_panic.rs new file mode 100644 index 0000000..0c7df47 --- /dev/null +++ b/tests/should_panic.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use core::panic::PanicInfo; +use rust_os::{exit_qemu, serial_print, serial_println, QemuExitCode}; + +#[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); +} + +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} \ No newline at end of file diff --git a/tests/stack_overflow.rs b/tests/stack_overflow.rs new file mode 100644 index 0000000..8a23aec --- /dev/null +++ b/tests/stack_overflow.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] +#![feature(abi_x86_interrupt)] + +use core::panic::PanicInfo; +use rust_os::serial_print; +use lazy_static::lazy_static; +use x86_64::structures::idt::InterruptDescriptorTable; +use rust_os::{exit_qemu, QemuExitCode, serial_println}; +use x86_64::structures::idt::InterruptStackFrame; + +#[no_mangle] +pub extern "C" fn _start() -> ! { + serial_print!("stack_overflow::stack_overflow...\t"); + + rust_os::gdt::init(); + init_test_idt(); + + // trigger a stack overflow + stack_overflow(); + + panic!("Execution continued after stack overflow"); +} + +#[allow(unconditional_recursion)] +fn stack_overflow() { + stack_overflow(); // for each recursion, the return address is pushed + volatile::Volatile::new(0).read(); // prevent tail recursion optimizations +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + rust_os::test_panic_handler(info) +} + +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(rust_os::gdt::DOUBLE_FAULT_IST_INDEX); + } + + idt + }; +} + +pub fn init_test_idt() { + TEST_IDT.load(); +} + +extern "x86-interrupt" fn test_double_fault_handler( + _stack_frame: InterruptStackFrame, + _error_code: u64, +) -> ! { + serial_println!("[ok]"); + exit_qemu(QemuExitCode::Success); + loop {} +} \ No newline at end of file diff --git a/x86_64-rust_os.json b/x86_64-rust_os.json new file mode 100644 index 0000000..b95e621 --- /dev/null +++ b/x86_64-rust_os.json @@ -0,0 +1,15 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-i64:64-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, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "disable-redzone": true, + "features": "-mmx,-sse,+soft-float" +} \ No newline at end of file