diff --git a/Cargo.lock b/Cargo.lock index caceaa5..5974ee0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,12 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + [[package]] name = "fnv" version = "1.0.7" @@ -118,6 +124,7 @@ dependencies = [ "cc", "crossbeam", "elf", + "fallible-iterator", "futures-util", "gimli", "libm", diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1f8948d..17ca83e 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -21,6 +21,7 @@ futures-util = { version = "0.3.31", default-features = false, features = [ linked_list_allocator = { version = "0.10.5", features = ["use_spin"] } gimli = { version = "0.31.1", default-features = false, features = ["read"] } elf = { version = "0.7.4", default-features = false, features = ["nightly"] } +fallible-iterator = "0.3.0" [build-dependencies] cc = "1.2.14" diff --git a/kernel/src/arch/x86_64/cpu/apic.rs b/kernel/src/arch/x86_64/cpu/apic.rs index 669db9e..2339011 100644 --- a/kernel/src/arch/x86_64/cpu/apic.rs +++ b/kernel/src/arch/x86_64/cpu/apic.rs @@ -2,15 +2,15 @@ use core::arch::x86_64::__cpuid; +use crate::arch::x86_64::cpu::msr::*; use crate::arch::x86_64::memory::mapping::PHYSICAL_MEMORY_OFFSET; +use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE}; use crate::{debugln, serial_print, serial_println}; +use x86_64::structures::paging::Translate; use x86_64::{ PhysAddr, VirtAddr, structures::paging::{Mapper, Page, PageTableFlags, PhysFrame, Size4KiB}, }; -use x86_64::structures::paging::Translate; -use crate::arch::x86_64::cpu::msr::*; -use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE}; const IA32_APIC_BASE_MSR: u32 = 0x1b; const IA32_APIC_BASE_MSR_BSP: u64 = 0x100; @@ -53,7 +53,8 @@ fn write_apic_register(apic_base: &PhysAddr, reg: u64, value: u32) { let virt_addr = unsafe { phys_to_virt(PhysAddr::new(reg_addr)) }; - let phys_check = OFFSET_PAGE_TABLE.get().unwrap().lock().translate(virt_addr); + let phys_check = + OFFSET_PAGE_TABLE.get().unwrap().lock().translate(virt_addr); debugln!("{:?}", phys_check); unsafe { *(virt_addr.as_u64() as *mut u32) = value }; @@ -88,9 +89,8 @@ pub fn enable_apic() { write_apic_register( &apic_base_physical_addr, 0xF0, - read_apic_register(&apic_base_physical_addr, 0xF0) | 0x1FF + read_apic_register(&apic_base_physical_addr, 0xF0) | 0x1FF, ); - } pub fn enable_timer() { diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index 2626e0b..3758cdd 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -1,9 +1,13 @@ use crate::{debug, serial_print}; use pic8259::ChainedPics; use x86_64::registers::control::Cr2; -use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode}; +use x86_64::structures::idt::{ + InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode, +}; use x86_64::structures::paging::mapper::MapperFlushAll; -use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB}; +use x86_64::structures::paging::{ + FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, +}; use super::gdt; use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE}; @@ -25,7 +29,8 @@ static IDT: Lazy = Lazy::new(|| { idt.page_fault.set_handler_fn(page_fault_handler); idt[InterruptIndex::Timer.as_u8()].set_handler_fn(timer_interrupt_handler); - idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler); + idt[InterruptIndex::Keyboard.as_u8()] + .set_handler_fn(keyboard_interrupt_handler); idt }); @@ -90,19 +95,22 @@ extern "x86-interrupt" fn double_fault_handler( panic!("Exception: Double Fault\n{:#?}", stack_frame); } -extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) { +extern "x86-interrupt" fn keyboard_interrupt_handler( + _stack_frame: InterruptStackFrame, +) { use pc_keyboard::{HandleControl, Keyboard, ScancodeSet1, layouts}; // use pc_keyboard::DecodedKey; use spin::Mutex; use x86_64::instructions::port::Port; - static KEYBOARD: Lazy>> = Lazy::new(|| { - Mutex::new(Keyboard::new( - ScancodeSet1::new(), - layouts::Uk105Key, - HandleControl::Ignore, - )) - }); + static KEYBOARD: Lazy>> = + Lazy::new(|| { + Mutex::new(Keyboard::new( + ScancodeSet1::new(), + layouts::Uk105Key, + HandleControl::Ignore, + )) + }); let _keyboard = KEYBOARD.lock(); let mut port = Port::new(0x60); @@ -116,7 +124,9 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStac } } -extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) { +extern "x86-interrupt" fn timer_interrupt_handler( + _stack_frame: InterruptStackFrame, +) { debug!("Timer Interrupt"); unsafe { PICS.lock() diff --git a/kernel/src/arch/x86_64/memory/allocation/heap_alloc.rs b/kernel/src/arch/x86_64/memory/allocation/heap_alloc.rs index 59b6026..6f8f8f9 100644 --- a/kernel/src/arch/x86_64/memory/allocation/heap_alloc.rs +++ b/kernel/src/arch/x86_64/memory/allocation/heap_alloc.rs @@ -1,19 +1,25 @@ +use crate::arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator; +use crate::arch::x86_64::memory::units::MemoryUnits; +use crate::arch::x86_64::memory::{ + FRAME_ALLOCATOR, HEAP_SIZE, HEAP_VIRTUAL_SPACE, +}; +use crate::serial_println; use crate::{debugln, serial_print}; use core::alloc::{GlobalAlloc, Layout}; use core::ptr; use spin::{Mutex, MutexGuard}; -use x86_64::structures::paging::{Size4KiB, mapper::MapToError, PageTableFlags}; use x86_64::VirtAddr; -use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, HEAP_SIZE, HEAP_VIRTUAL_SPACE}; -use crate::arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator; -use crate::arch::x86_64::memory::units::MemoryUnits; -use crate::serial_println; +use x86_64::structures::paging::{ + PageTableFlags, Size4KiB, mapper::MapToError, +}; -/// We are currently using a linked list heap allocator which uses our underlying page allocator. +/// We are currently using a linked list heap allocator which uses our +/// underlying page allocator. #[global_allocator] -/// This is now Rust's global allocator, so we can use stuff requiring heap allocations. -static ALLOCATOR: Locked = Locked::new(FoundryAllocator::new()); - +/// This is now Rust's global allocator, so we can use stuff requiring heap +/// allocations. +static ALLOCATOR: Locked = + Locked::new(FoundryAllocator::new()); /// Initializes the heap. /// @@ -38,7 +44,8 @@ static ALLOCATOR: Locked = Locked::new(FoundryAllocator::new() /// initialized successfully. pub unsafe fn init_heap() -> Result<(), MapToError> { unsafe { - // code to allocate frames is now done in the page fault interrupt handler! + // code to allocate frames is now done in the page fault interrupt + // handler! ALLOCATOR.lock().init(HEAP_VIRTUAL_SPACE, HEAP_SIZE); Ok(()) } @@ -85,15 +92,17 @@ impl FoundryAllocator { fallback: Locked::new(FoundryFallbackAllocator::new()), } } - /// Initializes the fallback allocator with the given heap start address and size. + /// Initializes the fallback allocator with the given heap start address and + /// size. /// /// # Safety /// - /// This function is unsafe because it does not check whether the given heap start address and size are valid. + /// This function is unsafe because it does not check whether the given heap + /// start address and size are valid. pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { debugln!(" => Start: {:?}", VirtAddr::new(heap_start as u64)); debugln!(" => Size: {}", MemoryUnits::from_bytes(heap_size)); - + unsafe { self.fallback.lock().init(heap_start, heap_size); } @@ -127,7 +136,9 @@ unsafe impl GlobalAlloc for Locked { let block_size = BLOCK_SIZES[index]; // only works if all block sizes are a power of 2 let block_align = block_size; - let layout = Layout::from_size_align(block_size, block_align).unwrap(); + let layout = + Layout::from_size_align(block_size, block_align) + .unwrap(); unsafe { allocator.fallback_alloc(layout) } } } @@ -195,7 +206,8 @@ impl FoundryFallbackAllocator { /// /// # Safety /// - /// This function is unsafe because it does not check whether the given heap start address and size are valid. + /// This function is unsafe because it does not check whether the given heap + /// start address and size are valid. pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { unsafe { self.add_region(heap_start, heap_size) }; } @@ -222,7 +234,9 @@ impl FoundryFallbackAllocator { let mut current = &mut self.head; // look for a large enough memory region in linked list while let Some(ref mut region) = current.next { - if let Ok(alloc_start) = Self::alloc_from_region(region, size, align) { + if let Ok(alloc_start) = + Self::alloc_from_region(region, size, align) + { // region suitable for allocation -> remove node from list let next = region.next.take(); let ret = Some((current.next.take().unwrap(), alloc_start)); @@ -275,7 +289,8 @@ unsafe impl GlobalAlloc for Locked { // perform layout adjustments let (size, align) = FoundryFallbackAllocator::size_align(layout); - if let Some((region, alloc_start)) = allocator.find_region(size, align) { + if let Some((region, alloc_start)) = allocator.find_region(size, align) + { let alloc_end = alloc_start.checked_add(size).expect("overflow"); let excess_size = region.end_addr() - alloc_end; if excess_size > 0 { @@ -291,7 +306,7 @@ unsafe impl GlobalAlloc for Locked { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { let mut allocator = self.lock(); - + // perform layout adjustments let (size, _) = FoundryFallbackAllocator::size_align(layout); unsafe { allocator.add_region(ptr as usize, size) } @@ -299,8 +314,13 @@ unsafe impl GlobalAlloc for Locked { } fn ensure_mapped(virt_addr: VirtAddr) { - if ! FoundryOSFrameAllocator::is_mapped(virt_addr) { + if !FoundryOSFrameAllocator::is_mapped(virt_addr) { let mut foundry_alloc = FRAME_ALLOCATOR.get().unwrap().lock(); - foundry_alloc.allocate_page(virt_addr, PageTableFlags::PRESENT | PageTableFlags::WRITABLE).unwrap(); + foundry_alloc + .allocate_page( + virt_addr, + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + ) + .unwrap(); } -} \ No newline at end of file +} diff --git a/kernel/src/arch/x86_64/memory/allocation/page_alloc.rs b/kernel/src/arch/x86_64/memory/allocation/page_alloc.rs index 6d393df..2fe1b1a 100644 --- a/kernel/src/arch/x86_64/memory/allocation/page_alloc.rs +++ b/kernel/src/arch/x86_64/memory/allocation/page_alloc.rs @@ -1,10 +1,13 @@ -use x86_64::structures::paging::{FrameAllocator, Mapper, Page, PageTableFlags, PhysFrame, Size4KiB, Translate}; +use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE}; +use limine::memory_map::EntryType; use limine::response::MemoryMapResponse; use spin::Mutex; -use limine::memory_map::EntryType; -use x86_64::{PhysAddr, VirtAddr}; use x86_64::structures::paging::mapper::{MapToError, TranslateResult}; -use crate::arch::x86_64::memory::{FRAME_ALLOCATOR, OFFSET_PAGE_TABLE}; +use x86_64::structures::paging::{ + FrameAllocator, Mapper, Page, PageTableFlags, PhysFrame, Size4KiB, + Translate, +}; +use x86_64::{PhysAddr, VirtAddr}; pub struct FoundryOSFrameAllocator { memory_map: &'static MemoryMapResponse, @@ -36,7 +39,9 @@ impl FoundryOSFrameAllocator { .entries() .iter() .map(|region| region.base..region.base + region.length) - .flat_map(|r| r.step_by(4096)).count() as u64 * 4096 + .flat_map(|r| r.step_by(4096)) + .count() as u64 + * 4096 } /// An iterator over all usable frames in the memory map. @@ -46,10 +51,14 @@ impl FoundryOSFrameAllocator { /// This function is used to allocate frames for the page map. fn usable_frames(&self) -> impl Iterator + use<> { let regions = self.memory_map.entries().iter(); - let usable_regions = regions.filter(|region| region.entry_type == EntryType::USABLE); - let addr_ranges = usable_regions.map(|region| region.base..region.base + region.length); + let usable_regions = + regions.filter(|region| region.entry_type == EntryType::USABLE); + let addr_ranges = usable_regions + .map(|region| region.base..region.base + region.length); let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); - frame_addresses.map(|addr| PhysFrame::from_start_address(PhysAddr::new(addr)).unwrap()) + frame_addresses.map(|addr| { + PhysFrame::from_start_address(PhysAddr::new(addr)).unwrap() + }) } pub(crate) fn is_mapped(virt_addr: VirtAddr) -> bool { @@ -57,14 +66,18 @@ impl FoundryOSFrameAllocator { matches!(mapper.translate(virt_addr), TranslateResult::Mapped { .. }) } - pub(crate) fn allocate_page(&mut self, start_addr: VirtAddr, flags: PageTableFlags) -> Result<(), MapToError> { + pub(crate) fn allocate_page( + &mut self, + start_addr: VirtAddr, + flags: PageTableFlags, + ) -> Result<(), MapToError> { let page = Page::containing_address(start_addr); - let frame = self.allocate_frame().ok_or(MapToError::::FrameAllocationFailed)?; + let frame = self + .allocate_frame() + .ok_or(MapToError::::FrameAllocationFailed)?; let mut mapper = OFFSET_PAGE_TABLE.get().unwrap().lock(); - unsafe { - mapper.map_to(page, frame, flags, self)? - }.flush(); + unsafe { mapper.map_to(page, frame, flags, self)? }.flush(); Ok(()) } @@ -86,4 +99,4 @@ unsafe impl FrameAllocator for FoundryOSFrameAllocator { self.next += 1; frame } -} \ No newline at end of file +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index dd69f1b..9af8c25 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -12,23 +12,33 @@ )] extern crate alloc; -use crate::{arch::x86_64::memory::init_page_table, prelude::*}; -use arch::x86_64::memory::allocation::heap_alloc::init_heap; + +use crate::{ + // TODO: Fix nesting under `arch`. A lot of code does not NEED to run on + // x86_64. Note that the panic handler does. + arch::x86_64::{ + cpu::apic::enable_apic, + drivers::{ + ascii::screensize_chars, framebuffer::display::screensize_px, + }, + memory::{ + FRAME_ALLOCATOR, + allocation::{ + heap_alloc::init_heap, page_alloc::FoundryOSFrameAllocator, + }, + init_page_table, + units::MemoryUnits, + }, + }, + prelude::*, +}; + use arch::x86_64::memory::mapping; use core::arch::asm; use limine::BaseRevision; -use std::unwind; -use std::unwind::eh_info::ELF; use x86_64::VirtAddr; -use arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator; -use crate::arch::x86_64::cpu::apic::enable_apic; -use crate::arch::x86_64::drivers::ascii::screensize_chars; -use crate::arch::x86_64::drivers::framebuffer::display::screensize_px; -use crate::arch::x86_64::memory::FRAME_ALLOCATOR; -use crate::arch::x86_64::memory::units::MemoryUnits; pub mod arch; -mod panic; pub mod resources; #[allow(unused)] // We aren't using much of this right now. pub mod std; @@ -36,11 +46,10 @@ pub mod util; pub mod prelude { pub use crate::{ - eprint, eprintln, print, print_log, println, println_log, serial_print, - serial_println, - debug, debugln, - std::io::{_print, _print_log, _serial_write, _print_err}, + debug, debugln, eprint, eprintln, print, print_log, println, + println_log, serial_print, serial_println, std::debug::_debug, + std::io::{_print, _print_err, _print_log, _serial_write}, }; } @@ -54,13 +63,6 @@ pub mod prelude { #[unsafe(link_section = ".requests")] static BASE_REVISION: BaseRevision = BaseRevision::new(); -#[panic_handler] -fn rust_panic(_info: &core::panic::PanicInfo) -> ! { - println!("Kernel panic: {}", _info); - serial_println!("Kernel panic: {}", _info); - hcf(); -} - pub fn hcf() -> ! { loop { unsafe { @@ -70,6 +72,9 @@ pub fn hcf() -> ! { } } +/// Panicking before this is initialised is unwise. We should probably extract +/// very early init into it's own function because Stack Traces may require +/// allocations etc. pub fn boot() -> Result<(), &'static str> { if !BASE_REVISION.is_supported() { return Err("Base revision not supported"); @@ -104,11 +109,16 @@ pub fn boot() -> Result<(), &'static str> { debugln!("[Success]"); debugln!(" Initialising Memory Subsystem... "); - let physical_memory_offset = VirtAddr::new(*mapping::PHYSICAL_MEMORY_OFFSET); + let physical_memory_offset = + VirtAddr::new(*mapping::PHYSICAL_MEMORY_OFFSET); init_page_table(physical_memory_offset); FoundryOSFrameAllocator::init(memory_map); - let available_bytes = FRAME_ALLOCATOR.get().unwrap().lock().available_memory(); - debugln!(" => Available Memory: {}", MemoryUnits::from_bytes(available_bytes as usize)); + let available_bytes = + FRAME_ALLOCATOR.get().unwrap().lock().available_memory(); + debugln!( + " => Available Memory: {}", + MemoryUnits::from_bytes(available_bytes as usize) + ); debugln!("[Success]"); @@ -134,16 +144,5 @@ pub fn boot() -> Result<(), &'static str> { x86_64::instructions::interrupts::enable(); debugln!("[Success]"); - debug!(" Setting up stack unwinder, panic handler... "); - // Setup stack traces and proper panic handler. TODO: Handle panics - // differently if not initialised. - let eh_frame_ptr = ELF - .get_section_addr(".eh_frame_hdr") - .expect("Could not get `.eh_frame_hdr` address."); - - let _eh_info = - unsafe { unwind::eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) }; - debugln!("[Success]"); - Ok(()) } diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 114e257..6ef4d73 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -2,11 +2,10 @@ #![no_main] extern crate alloc; -use foundry_os::arch::x86_64::drivers::ascii::screensize_chars; -use foundry_os::arch::x86_64::drivers::framebuffer::display::screensize_px; + use foundry_os::arch::x86_64::processing::async_io::task::{Executor, Task}; +use foundry_os::prelude::*; use foundry_os::util::shell::shell; -use foundry_os::{println, println_log}; #[unsafe(no_mangle)] extern "C" fn kmain() -> ! { @@ -16,14 +15,24 @@ extern "C" fn kmain() -> ! { } println_log!(" [ Kernel Initialised Successfully ] "); - + // println!("TESTING :: Allocation"); // let somevec = vec![0; 1_000_000]; // println!("{:?}", somevec); // println!("{}", somevec.len()); // println!("PASSED!"); + test1(); + let mut executor = Executor::new(); executor.spawn(Task::new(shell())); executor.run() } + +fn test1() { + test2() +} + +fn test2() { + panic!("Test"); +} diff --git a/kernel/src/panic.rs b/kernel/src/panic.rs deleted file mode 100644 index a10718d..0000000 --- a/kernel/src/panic.rs +++ /dev/null @@ -1,75 +0,0 @@ -// //! Stack-unwinding code for the Kernel. This is a modified version of https://github.com/nbdd0121/unwinding/blob/trunk/src/panic_handler.rs -// //! which does not support environment variables for obvious reasons, however we -// //! may implement a cmdline similar to Linux in the near future. - -// #![expect( -// clippy::empty_loop, -// reason = "We don't yet have working power management. This is OK for now." -// )] -// use crate::prelude::*; -// use alloc::boxed::Box; -// use core::any::Any; -// use core::cell::Cell; -// use core::ffi::c_void; -// use core::panic::{Location, PanicInfo}; -// use unwinding::abi::*; -// use unwinding::panic::begin_panic; - -// #[thread_local] -// static PANIC_COUNT: Cell = Cell::new(0); - -// #[link(name = "c")] -// unsafe extern "C" {} - -// fn stack_trace() { -// struct CallbackData { -// counter: usize, -// } - -// extern "C" fn callback( -// unwind_ctx: &UnwindContext<'_>, -// arg: *mut c_void, -// ) -> UnwindReasonCode { -// let data = unsafe { &mut *(arg as *mut CallbackData) }; -// data.counter += 1; -// eprintln!( -// "{:4}:{:#19x} - ", -// data.counter, -// _Unwind_GetIP(unwind_ctx) -// ); -// UnwindReasonCode::NO_REASON -// } -// let mut data = CallbackData { counter: 0 }; -// _Unwind_Backtrace(callback, &mut data as *mut _ as _); -// } - -// fn do_panic(msg: Box) -> ! { -// if PANIC_COUNT.get() >= 1 { -// stack_trace(); -// eprintln!("Thread panicked while processing panic. aborting."); -// loop {} -// } - -// PANIC_COUNT.set(1); -// stack_trace(); - -// let code = begin_panic(Box::new(msg)); -// eprintln!("Failed to initiate panic: error {}", code.0); - -// loop {} -// } - -// #[panic_handler] -// fn panic(info: &PanicInfo<'_>) -> ! { -// eprintln!("{}", info); - -// struct NoPayload; -// do_panic(Box::new(NoPayload)) -// } - -// #[track_caller] -// #[expect(unused, reason = "May still be used in the future.")] -// pub fn panic_any(msg: M) -> ! { -// eprintln!("Panicked at {}", Location::caller()); -// do_panic(Box::new(msg)) -// } diff --git a/kernel/src/std/debug.rs b/kernel/src/std/debug.rs index a6ad975..7b26e21 100644 --- a/kernel/src/std/debug.rs +++ b/kernel/src/std/debug.rs @@ -1,6 +1,6 @@ +use crate::prelude::{_print_log, _serial_write}; use core::fmt; use x86_64::instructions::interrupts; -use crate::prelude::{_print_log, _serial_write}; #[macro_export] macro_rules! debugln { @@ -19,4 +19,3 @@ pub fn _debug(args: fmt::Arguments) { _serial_write(args); }); } - diff --git a/kernel/src/std/mod.rs b/kernel/src/std/mod.rs index 678d2ec..6c936ba 100644 --- a/kernel/src/std/mod.rs +++ b/kernel/src/std/mod.rs @@ -1,7 +1,7 @@ pub mod application; pub mod ascii; +pub mod debug; pub mod elf; pub mod io; pub mod maths; pub mod unwind; -pub mod debug; diff --git a/kernel/src/std/unwind/eh_info.rs b/kernel/src/std/unwind/eh_info.rs index 8048d12..247fd55 100644 --- a/kernel/src/std/unwind/eh_info.rs +++ b/kernel/src/std/unwind/eh_info.rs @@ -21,15 +21,15 @@ use crate::{println_log, std::elf::ElfReader}; /// and will later be extended as required. pub struct EhInfo { /// A set of base addresses used for relative addressing. - base_addrs: BaseAddresses, + pub base_addrs: BaseAddresses, /// The parsed `.eh_frame_hdr` section. - hdr: &'static ParsedEhFrameHdr>, + pub hdr: &'static ParsedEhFrameHdr>, /// The lookup table in the parsed `.eh_frame_hdr` section. /// This is a binary search table, it is optional but should be present as /// we are linking with LLD(?) \[needs citation]. - hdr_table: EhHdrTable<'static, EndianSlice<'static, LittleEndian>>, + pub hdr_table: EhHdrTable<'static, EndianSlice<'static, LittleEndian>>, /// The parsed `.eh_frame` containing the CFIs (call frame information). - eh_frame: EhFrame>, + pub eh_frame: EhFrame>, } /// Stores the [ElfReader] struct for this ELF file. diff --git a/kernel/src/std/unwind/mod.rs b/kernel/src/std/unwind/mod.rs index 99eb251..cb3bc81 100644 --- a/kernel/src/std/unwind/mod.rs +++ b/kernel/src/std/unwind/mod.rs @@ -1 +1,3 @@ pub mod eh_info; +pub mod panic; +pub mod unwinder; diff --git a/kernel/src/std/unwind/panic.rs b/kernel/src/std/unwind/panic.rs new file mode 100644 index 0000000..7dca632 --- /dev/null +++ b/kernel/src/std/unwind/panic.rs @@ -0,0 +1,52 @@ +//! Defines a simple panic handler which handles stack traces as required. + +use core::{arch::asm, panic::PanicInfo}; + +use alloc::string::ToString; +use fallible_iterator::FallibleIterator; +use gimli::{Register, X86_64}; + +use super::unwinder::Unwinder; +use crate::{ + hcf, + prelude::*, + std::unwind::{eh_info::ELF, unwinder::RegisterSet}, +}; + +#[panic_handler] +/// A basic panic handler which can produce a helpful stack trace. +pub fn panic_handler(info: &PanicInfo<'_>) -> ! { + println!("Kernel panic: {}", info); + serial_println!("Kernel panic: {}", info); + + // Setup stack traces and proper panic handler. TODO: Handle panics + // differently if not initialised. + let eh_frame_ptr = ELF + .get_section_addr(".eh_frame_hdr") + .expect("Could not get `.eh_frame_hdr` address."); + + let eh_info = unsafe { super::eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) }; + let mut registers = RegisterSet::default(); + + let mut rip: u64; + let mut rsp: u64; + + unsafe { + asm!("lea {0}, [rip]", + "mov {1}, rsp", + out(reg) rip, out(reg) rsp); + } + registers.set_pc(rip); + registers.set_stack_ptr(rsp); + + let mut unwinder = Unwinder::new(eh_info, registers); + while let Some(call_frame) = unwinder.next().unwrap_or_else(|err| { + // If an unwind error occurred. + eprintln!("{:?}", err); + hcf() + }) { + eprintln!("Frame: {:x?}", call_frame); + } + + crate::hcf() +} diff --git a/kernel/src/std/unwind/unwinder.rs b/kernel/src/std/unwind/unwinder.rs new file mode 100644 index 0000000..8861d02 --- /dev/null +++ b/kernel/src/std/unwind/unwinder.rs @@ -0,0 +1,223 @@ +//! Implements the core stack unwinding logic. +//! +//! # TODOs +//! +//! * Support evaluation of DWARF expressions, this might not be required +//! however, because Rust doesn't tend to use this DWARF feature. + +use fallible_iterator::FallibleIterator; +use gimli::{ + CfaRule, EndianSlice, LittleEndian, Register, RegisterRule, StoreOnHeap, + UnwindContext, UnwindSection, X86_64, +}; + +use super::eh_info::EhInfo; + +/// Implements the core stack unwinding logic by parsing the call frame +/// information. This also stores current (DWARF) register values. +/// +/// # Sources +/// +/// Taken from [lesenechal.fr](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way) +/// with some additional features to be added soon. +pub struct Unwinder { + /// The call frame information. + eh_info: EhInfo, + /// An [UnwindContext] used by Gimli for optimisations. + unwind_ctx: UnwindContext, + /// The current values of ABI/architecture independent registers. There are + /// used by DWARF. + regs: RegisterSet, + /// The current CFA address. + cfa: u64, + /// Is this the first iteration? + is_first: bool, +} + +// TODO: Use map_err et al. +impl FallibleIterator for Unwinder { + type Item = CallFrame; + type Error = UnwinderError; + + /// Returns call frames of calling functions. This may be called to produce + /// a stack trace or otherwise support physical unwinding. + fn next(&mut self) -> Result, Self::Error> { + // Gets the current program counter from the DWARF register set. + let Some(pc) = self.regs.get_pc() else { + return Err(UnwinderError::NoPcRegister); + }; + + if self.is_first { + self.is_first = false; + + return Ok(Some(CallFrame { pc })); + } + + // This is a row in the virtual unwind table AKA the CFI which will help + // us find the CFA (canonical frame address) for a given program + // counter. + let Ok(row) = self.eh_info.hdr_table.unwind_info_for_address( + &self.eh_info.eh_frame, + &self.eh_info.base_addrs, + &mut self.unwind_ctx, + pc, + |section, bases, offset| + // Finds a DWARF CIE using an offset as given. + section.cie_from_offset(bases, offset), + ) else { + return Err(UnwinderError::NoUnwindInfo); + }; + + // We compute the CFA (canonical frame address from its rule). + // TODO: Support other rules such as DWARF expressions. + match row.cfa() { + CfaRule::RegisterAndOffset { register, offset } => { + let Some(reg_val) = self.regs.get(*register) else { + return Err(UnwinderError::CfaRuleUnknownRegister( + *register, + )); + }; + + self.cfa = (reg_val as i64 + offset) as u64; + } + + // TODO: Support other rules for computing the CFA. + _ => return Err(UnwinderError::UnsupportedCfaRule), + } + + for reg in RegisterSet::iter() { + match row.register(reg) { + RegisterRule::Undefined => self.regs.undef(reg), + RegisterRule::SameValue => (), + RegisterRule::Offset(offset) => { + // Adds the given offset to the register contents and + // retrieve the value from the stack at address CFA + + // offset. + let ptr = (self.cfa as i64 + offset) as u64 as *const usize; + + self.regs.set(reg, unsafe { ptr.read() } as u64)? + } + + _ => { + return Err(UnwinderError::UnimplementedRegisterRule); + } + } + } + + // Get the new value for %rip from the function return value and + // subtract one from it, because the address actually points the the + // next instruction after the call. + let Some(pc) = self.regs.get_ret() else { + return Err(UnwinderError::NoReturnAddr); + }; + // REVIEWME: Must be a nicer way of doing this. + let pc = pc - 1; + + self.regs.set_pc(pc); + + // Set %rsp to the CFA. This simulates returning from the function, + // destroying the call frame, so we were able to virtually unwind (to + // the caller function). + self.regs.set_stack_ptr(self.cfa); + + Ok(Some(CallFrame { pc })) + } + // fn next(&mut self) -> Option, UnwinderError>> {} +} + +impl Unwinder { + pub fn new(eh_info: EhInfo, regset: RegisterSet) -> Self { + Self { + eh_info, + regs: regset, + unwind_ctx: UnwindContext::new(), + cfa: 0, + is_first: true, + } + } +} + +/// The set of registers used by DWARF. This struct allows future portability if +/// we want to target other architectures than x86_64. +#[derive(Debug, Default)] +pub struct RegisterSet { + rip: Option, + rsp: Option, + rbp: Option, + /// The return address register. + ret: Option, +} + +impl RegisterSet { + pub const fn get(&self, reg: Register) -> Option { + match reg { + X86_64::RSP => self.rsp, + X86_64::RBP => self.rbp, + X86_64::RA => self.ret, + _ => None, + } + } + + pub const fn set( + &mut self, + reg: Register, + val: u64, + ) -> Result<(), UnwinderError> { + *match reg { + X86_64::RSP => &mut self.rsp, + X86_64::RBP => &mut self.rbp, + X86_64::RA => &mut self.ret, + _ => return Err(UnwinderError::UnexpectedRegister(reg)), + } = Some(val); + + Ok(()) + } + + const fn undef(&mut self, reg: Register) { + *match reg { + X86_64::RSP => &mut self.rsp, + X86_64::RBP => &mut self.rbp, + X86_64::RA => &mut self.ret, + _ => return, + } = None; + } + + const fn get_pc(&self) -> Option { + self.rip + } + + pub const fn set_pc(&mut self, val: u64) { + self.rip = Some(val); + } + + const fn get_ret(&self) -> Option { + self.ret + } + + pub const fn set_stack_ptr(&mut self, val: u64) { + self.rsp = Some(val); + } + + fn iter() -> impl Iterator { + [X86_64::RSP, X86_64::RBP, X86_64::RA].into_iter() + } +} + +/// The current instruction pointer. +#[derive(Debug)] +pub struct CallFrame { + /// The current instruction pointer. + pub pc: u64, +} + +#[derive(Debug)] +/// A list of errors that could occur whilst unwinding the stack. +pub enum UnwinderError { + UnexpectedRegister(Register), + UnsupportedCfaRule, + UnimplementedRegisterRule, + CfaRuleUnknownRegister(Register), + NoUnwindInfo, + NoPcRegister, + NoReturnAddr, +}