diff --git a/.cargo/config.toml b/.cargo/config.toml index b9d643f..5d46d9d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -17,8 +17,5 @@ runner = "scripts/run_debug.sh" [target.'cfg(all(target_arch = "x86_64", target_os = "none", not(debug_assertions)))'] runner = "scripts/run_release.sh" -[target.x86_64-kernel] -rustflags = ["-C", "force-unwind-tables"] - [registries.gitea] index = "sparse+https://git.zxq5.dev/api/packages/OsDev/cargo/" # Sparse index diff --git a/Cargo.lock b/Cargo.lock index 5974ee0..16db54d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,18 +99,6 @@ dependencies = [ "syn", ] -[[package]] -name = "elf" -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" @@ -123,10 +111,7 @@ version = "0.1.0" dependencies = [ "cc", "crossbeam", - "elf", - "fallible-iterator", "futures-util", - "gimli", "libm", "limine", "linked_list_allocator", @@ -160,12 +145,6 @@ dependencies = [ "pin-utils", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "ident_case" version = "1.0.1" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 17ca83e..ad0445a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -19,9 +19,11 @@ futures-util = { version = "0.3.31", default-features = false, features = [ "alloc", ] } 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" +# 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" +# framehop = { version = "0.13.2", default-features = false } +# object = { version = "0.36.7", default-features = false, features = ["read"] } [build-dependencies] cc = "1.2.14" diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index 3758cdd..2d8bc41 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -138,10 +138,10 @@ extern "x86-interrupt" fn page_fault_handler( _stack_frame: InterruptStackFrame, _error_code: PageFaultErrorCode, ) { - serial_println!("Exception: Page Fault"); - serial_println!("Accessed Address: {:?}", Cr2::read()); - serial_println!("Error Code: {:?}", _error_code); - serial_println!("{:#?}", _stack_frame); + // serial_println!("Exception: Page Fault"); + // serial_println!("Accessed Address: {:?}", Cr2::read()); + // serial_println!("Error Code: {:?}", _error_code); + // serial_println!("{:#?}", _stack_frame); if let Some(frame_allocator) = FRAME_ALLOCATOR.get() { let mut f = frame_allocator.lock(); diff --git a/kernel/src/arch/x86_64/memory/mod.rs b/kernel/src/arch/x86_64/memory/mod.rs index 0ee8807..dec0843 100644 --- a/kernel/src/arch/x86_64/memory/mod.rs +++ b/kernel/src/arch/x86_64/memory/mod.rs @@ -11,12 +11,15 @@ use x86_64::{ structures::paging::{OffsetPageTable, PageTable}, }; -pub const STACK_VIRTUAL_SPACE: usize = 0x5555_5555_0000; // start address of the memory space where we store allocated stacks -pub const HEAP_VIRTUAL_SPACE: usize = 0x4444_4444_0000; // start address of heap allocated memory +/// Start address of the memory space where we store allocated stacks. +pub const STACK_VIRTUAL_SPACE: usize = 0x5555_5555_0000; +/// Start address of heap allocated memory. +pub const HEAP_VIRTUAL_SPACE: usize = 0x4444_4444_0000; pub const HEAP_SIZE: usize = MiB(1).to_bytes(); pub static FRAME_ALLOCATOR: Once> = Once::new(); pub static OFFSET_PAGE_TABLE: Once> = Once::new(); + /// Returns a mutable reference to the current level 4 page table. /// /// # Safety diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index f4e174e..496e819 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,5 +1,5 @@ #![no_std] -#![feature(abi_x86_interrupt)] +#![feature(abi_x86_interrupt, breakpoint)] #![warn( clippy::correctness, clippy::nursery, @@ -35,7 +35,6 @@ use crate::{ use alloc::{boxed::Box, format}; use core::arch::asm; use limine::BaseRevision; -use std::{debug, unwind::UNWINDER}; use x86_64::VirtAddr; pub mod arch; @@ -83,6 +82,7 @@ impl core::error::Error for NoError {} /// 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. +#[inline(never)] pub fn boot() -> Result<(), Box> { if !BASE_REVISION.is_supported() { return Err("Base revision not supported.".into()); @@ -149,10 +149,9 @@ pub fn boot() -> Result<(), Box> { x86_64::instructions::interrupts::enable(); debugln!("[Success]"); - // Initialises the stack unwinder once and only once because this makes a - // heap allocation. debug!(" Initializing Stack Unwinder... "); - UNWINDER.lock(); + // Force evaluate the constructor. + // let _unwinder = &*UNWINDER; debugln!("[Success]"); Ok(()) diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 6ef4d73..45c695c 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,9 +3,18 @@ extern crate alloc; -use foundry_os::arch::x86_64::processing::async_io::task::{Executor, Task}; -use foundry_os::prelude::*; -use foundry_os::util::shell::shell; +use core::arch::asm; + +use foundry_os::{ + // arch::x86_64::processing::async_io::task::{Executor, Task}, + prelude::*, + // std::unwind::UNWINDER, + // util::shell::shell, +}; +// use framehop::{ +// x86_64::{CacheX86_64, UnwindRegsX86_64}, +// *, +// }; #[unsafe(no_mangle)] extern "C" fn kmain() -> ! { @@ -24,15 +33,69 @@ extern "C" fn kmain() -> ! { test1(); - let mut executor = Executor::new(); - executor.spawn(Task::new(shell())); - executor.run() + // let mut executor = Executor::new(); + // executor.spawn(Task::new(shell())); + // executor.run() + + loop {} } -fn test1() { - test2() +#[inline(never)] +pub fn test1() { + test2(); } -fn test2() { - panic!("Test"); +#[inline(never)] +pub fn test2() { + test3(); +} + +const fn read_stack(addr: u64) -> Result { + Ok(unsafe { *(addr as *const u64) }) +} + +#[inline(never)] +pub fn test3() { + // let mut cache = CacheX86_64::new(); + // let unwinder = &*UNWINDER; + + // let mut rip: u64; + // let mut rsp: u64; + // let mut rbp: u64; + // let mut read_stack_fn = read_stack; + + // unsafe { + // asm!("lea {0}, [rip]", + // "mov {1}, rsp", + // "mov {2}, rbp", + // out(reg) rip, + // out(reg) rsp, + // out(reg) rbp); + // } + + // let regs = UnwindRegsX86_64::new(rip, rsp, rbp); + // let mut frame_iter = + // unwinder.iter_frames(rip, regs, &mut cache, &mut read_stack_fn); + + // // This is just me testing the unwinding. It seems to not return any + // stack // frames for some odd reason. + // loop { + // match frame_iter.next() { + // Ok(Some(frame)) => { + // debugln!("Got function with {:x?}", + // frame.address_for_lookup()); // match frame { + // // FrameAddress::InstructionPointer(rip) => (), + // // FrameAddress::ReturnAddress(ra) => {} + // // } + // } + // Ok(None) => { + // debugln!("Hit end of stack."); + // break; + // } + // Err(why) => { + // debugln!("{}", why); + // break; + // } + // } + // } } diff --git a/kernel/src/prelude.rs b/kernel/src/prelude.rs index f74b661..639aeb9 100644 --- a/kernel/src/prelude.rs +++ b/kernel/src/prelude.rs @@ -1,7 +1,7 @@ pub use crate::{ arch::x86_64::drivers::framebuffer::colour::Colour, - debug, debugln, eprint, eprintln, print, print_log, print_oneshot, println, - println_log, serial_print, serial_println, + debug, debugln, eprint, eprintln, hcf, print, print_log, print_oneshot, + println, println_log, serial_print, serial_println, std::debug::_debug, std::io::{_print, _print_err, _print_log, _serial_write}, }; diff --git a/kernel/src/std/elf/mod.rs b/kernel/src/std/elf/mod.rs index 4b36c61..e48061d 100644 --- a/kernel/src/std/elf/mod.rs +++ b/kernel/src/std/elf/mod.rs @@ -11,14 +11,14 @@ //! * Add support for loading binary programs (this should probably be written //! in a different module) -use alloc::format; +use alloc::{format, vec::Vec}; use elf::{ ElfBytes, ParseError, endian::LittleEndian, parse::{ParseAt, ParsingTable}, section::{SectionHeader, SectionHeaderTable}, string_table::StringTable, - symbol::SymbolTable, + symbol::{Symbol, SymbolTable}, }; use limine::request::KernelFileRequest; @@ -38,6 +38,8 @@ pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new(); pub enum ElfError { /// Returned if a section did not exist in [ElfReader::get_section_size]. SectionNotExists, + /// Returned if we failed to fetch the symbol table. + Symtab, /// Parse errors returned by the `elf` crate. OtherParseError(elf::ParseError), } @@ -49,8 +51,14 @@ impl From for ElfError { } pub struct ElfReader { + /// The underlying bytes for the ELF file. + pub bytes: &'static [u8], /// Structure returned by the `elf` crate having parsed the ELF header. - elf: ElfBytes<'static, LittleEndian>, + pub elf: ElfBytes<'static, LittleEndian>, + /// A sorted list of symbols to binary search. + pub sorted_syms: Vec, + /// The string table for looking up symbol names. + pub symbol_strtab: StringTable<'static>, } impl ElfReader { @@ -65,23 +73,26 @@ impl ElfReader { /// Both of these should be satisfied, but this function is marked unsafe /// just in case, because we are derefererencing arbitrary pointers. pub unsafe fn new() -> Result { - let response = KERNEL_FILE_REQUEST - .get_response() - .expect("Didn't get the kernel file from Limine. That's odd."); - - // We fetch these from Limine and use them to parse our own ELF file. - let file = response.file(); - let file_start_ptr = file.addr(); - let file_size = file.size() as usize; - - // Safety: This slice should contain the whole bytes of the ELF file. - let elf_hdr_slice = - unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) }; + // Store this for use with other libraries. + let bytes = get_elf_slice(); let elf: ElfBytes<'static, LittleEndian> = - elf::ElfBytes::minimal_parse(elf_hdr_slice)?; + elf::ElfBytes::minimal_parse(bytes)?; - Ok(Self { elf }) + let Some((symtab, strtab)) = elf.symbol_table()? else { + return Err(ElfError::Symtab); + }; + + // Sort the symtab for later use and store the strtab. + let mut symbols = symtab.into_iter().collect::>(); + symbols.sort_by_key(|sym| sym.st_value); + + Ok(Self { + elf, + sorted_syms: symbols, + symbol_strtab: strtab, + bytes, + }) } pub fn get_symbol_table( @@ -112,7 +123,7 @@ impl ElfReader { /// Gets the section header of `section_name`. pub fn get_section_header( &self, - section_name: &'static str, + section_name: &str, ) -> Result { let section_hdr = self .elf @@ -121,4 +132,37 @@ impl ElfReader { section_hdr.ok_or(ElfError::SectionNotExists) } + + pub fn search_symbol(&self, address: u64) -> Option { + let entries = self.sorted_syms.iter().collect::>(); + + let idx = entries + .as_slice() + .binary_search_by_key(&address, |sym| sym.st_value) + .ok()?; + + Some(entries[idx].clone()) + } + + pub fn get_symbol_name( + &self, + sym: elf::symbol::Symbol, + ) -> Option<&'static str> { + self.symbol_strtab.get(sym.st_name as usize).ok() + } +} + +/// Gets a slice of the bytes of the kernel ELF file. +pub fn get_elf_slice() -> &'static [u8] { + let response = KERNEL_FILE_REQUEST + .get_response() + .expect("Didn't get the kernel file from Limine. That's odd."); + + // We fetch these from Limine and use them to parse our own ELF file. + let file = response.file(); + let file_start_ptr = file.addr(); + let file_size = file.size() as usize; + + // Safety: This slice should contain the whole bytes of the ELF file. + (unsafe { core::slice::from_raw_parts(file_start_ptr, file_size) }) as _ } diff --git a/kernel/src/std/mod.rs b/kernel/src/std/mod.rs index 6c936ba..5305aee 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 elf; pub mod io; pub mod maths; -pub mod unwind; +// pub mod unwind; diff --git a/kernel/src/std/unwind/mod.rs b/kernel/src/std/unwind/mod.rs index dc512dd..d28ec2d 100644 --- a/kernel/src/std/unwind/mod.rs +++ b/kernel/src/std/unwind/mod.rs @@ -1,23 +1,31 @@ use core::arch::asm; -use eh_info::ELF; +use alloc::vec::Vec; +use framehop::{ + x86_64::{CacheX86_64, UnwinderX86_64}, + *, +}; use spin::{Lazy, Mutex}; -use unwinder::{RegisterSet, Unwinder}; + +use crate::arch::x86_64::memory::mapping::KERNEL_ADDRESS_REQUEST; pub mod eh_info; pub mod panic; pub mod unwinder; /// We should initialise on program start. -pub static UNWINDER: Lazy> = Lazy::new(|| { - // 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."); +pub static UNWINDER: Lazy>> = + Lazy::new(|| { + let mut unwinder: UnwinderX86_64<_, MayAllocateDuringUnwind> = + UnwinderX86_64::new(); - let eh_info = unsafe { eh_info::EhInfo::from_hdr_ptr(eh_frame_ptr) }; - let mut registers = RegisterSet::default(); + panic::add_object( + &mut unwinder, + KERNEL_ADDRESS_REQUEST + .get_response() + .unwrap() + .virtual_base(), + ); - Mutex::new(Unwinder::new(eh_info, registers)) -}); + unwinder + }); diff --git a/kernel/src/std/unwind/panic.rs b/kernel/src/std/unwind/panic.rs index 1c04791..df39bfa 100644 --- a/kernel/src/std/unwind/panic.rs +++ b/kernel/src/std/unwind/panic.rs @@ -1,16 +1,25 @@ //! Defines a simple panic handler which handles stack traces as required. -use core::{arch::asm, panic::PanicInfo}; +use core::{arch::asm, ops::Range, panic::PanicInfo}; -use alloc::string::ToString; -use fallible_iterator::FallibleIterator; -use gimli::{Register, X86_64}; +use alloc::{ + borrow::{Cow, ToOwned}, + slice, + string::{String, ToString}, + vec::Vec, +}; +use elf::segment::ProgramHeader; +use framehop::x86_64::{CacheX86_64, UnwinderX86_64}; +use framehop::{Unwinder, x86_64::UnwindRegsX86_64}; -use super::unwinder::Unwinder; use crate::{ - hcf, + arch::x86_64::memory::mapping::KERNEL_ADDRESS_REQUEST, prelude::*, - std::unwind::{UNWINDER, eh_info::ELF, unwinder::RegisterSet}, + std::{ + self, + elf::ElfReader, + unwind::{UNWINDER, eh_info::ELF}, + }, }; #[panic_handler] @@ -18,37 +27,145 @@ use crate::{ pub fn panic_handler(info: &PanicInfo<'_>) -> ! { debugln!("Kernel panic: {}", info); - let mut rip: u64; - let mut rsp: u64; - unsafe { - asm!("lea {0}, [rip]", - "mov {1}, rsp", - out(reg) rip, out(reg) rsp); - } - - let mut unwinder = UNWINDER.lock(); - unwinder.regs.set_pc(rip); - unwinder.regs.set_stack_ptr(rsp); - - while let Some(call_frame) = unwinder.next().unwrap_or_else(|err| { - // If an unwind error occurred. - debugln!("{:?}", err); - hcf() - }) { - serial_println!("Got frame: {:x?}", call_frame); - let Some((symtab, strtab)) = - ELF.get_symbol_table().unwrap_or_else(|e| { - debugln!("{:?}", e); - hcf() - }) - else { - // TODO: Omit symbol names but just print addresses. - debugln!("Didn't find symtab and strtab!"); - hcf() - }; - - // let sym_name = symtab.get(call_frame.pc as usize).unwrap(); - } - crate::hcf() } + +use object::{Object, ObjectSection, ObjectSegment}; + +use framehop::*; + +pub fn add_object(unwinder: &mut U, base_avma: u64) +where + U: Unwinder>>, +{ + let mut buf = ELF.bytes; + + let file = object::File::parse(buf).expect("Could not parse object file"); + + struct Module<'a>(object::File<'a, &'a [u8]>); + + impl ModuleSectionInfo> for Module<'_> { + fn base_svma(&self) -> u64 { + relative_address_base(&self.0) + } + + fn section_svma_range(&mut self, name: &[u8]) -> Option> { + let section = self.0.section_by_name_bytes(name)?; + Some(section.address()..section.address() + section.size()) + } + + fn section_data(&mut self, name: &[u8]) -> Option> { + match self.0.section_by_name_bytes(name) { + Some(section) => { + section.data().ok().map(|data| data.to_owned()) + } + None if name == b".debug_frame" => { + let section = + self.0.section_by_name_bytes(b"__zdebug_frame")?; + get_uncompressed_section_data(§ion) + .map(|d| d.into_owned()) + } + None => None, + } + } + + fn segment_svma_range(&mut self, name: &[u8]) -> Option> { + let segment = self + .0 + .segments() + .find(|s| s.name_bytes() == Ok(Some(name)))?; + Some(segment.address()..segment.address() + segment.size()) + } + + fn segment_data(&mut self, name: &[u8]) -> Option> { + let segment = self + .0 + .segments() + .find(|s| s.name_bytes() == Ok(Some(name)))?; + segment.data().ok().map(|data| data.to_owned()) + } + } + + let module = framehop::Module::new( + "Kernel".to_string(), + base_avma..(base_avma + buf.len() as u64), + base_avma, + Module(file), + ); + unwinder.add_module(module); +} + +fn get_uncompressed_section_data<'a>( + section: &impl object::ObjectSection<'a>, +) -> Option> { + // let section_data = section.uncompressed_data().ok()?; + + // // Make sure the data is actually decompressed. + // if section.name_bytes().ok()?.starts_with(b"__zdebug_") + // && section_data.starts_with(b"ZLIB\0\0\0\0") + // { + // // Object's built-in compressed section handling didn't detect this + // as a // compressed section. This happens on Go binaries which use + // compressed // sections like __zdebug_ranges, which is generally + // uncommon on macOS, // so object's mach-O parser doesn't handle + // them. // But we want to handle them. + // // Go stopped using zdebug sections for ELF files in https://github.com/golang/go/issues/50796 + // // but still uses them for mach-O builds. + // let b = section_data.get(8..12)?; + // let uncompressed_size = u32::from_be_bytes([b[0], b[1], b[2], b[3]]); + // let compressed_bytes = §ion_data[12..]; + + // let mut decompressed = Vec::with_capacity(uncompressed_size as + // usize); let mut decompress = flate2::Decompress::new(true); + // decompress + // .decompress_vec( + // compressed_bytes, + // &mut decompressed, + // flate2::FlushDecompress::Finish, + // ) + // .ok()?; + // Some(Cow::Owned(decompressed)) + // } else { + // Some(section_data) + // } + + todo!() +} + +/// Relative addresses are u32 offsets which are relative to some "base +/// address". +/// +/// This function computes that base address. It is defined as follows: +/// +/// - For Windows binaries, the base address is the "image base address". +/// - For mach-O binaries, the base address is the vmaddr of the __TEXT +/// segment. +/// - For ELF binaries, the base address is zero. +/// +/// Stand-alone mach-O dylibs usually have a base address of zero because their +/// __TEXT segment is at address zero. +/// +/// In the following cases, the base address is usually non-zero: +/// +/// - The "image base address" of Windows binaries is usually non-zero. +/// - mach-O executable files (not dylibs) usually have their __TEXT segment at +/// address 0x100000000. +/// - mach-O libraries in the dyld shared cache have a __TEXT segment at some +/// non-zero address in the cache. +pub fn relative_address_base<'data>( + object_file: &impl object::Object<'data>, +) -> u64 { + if let Some(text_segment) = object_file + .segments() + .find(|s| s.name() == Ok(Some("__TEXT"))) + { + // This is a mach-O image. "Relative addresses" are relative to the + // vmaddr of the __TEXT segment. + return text_segment.address(); + } + + // For PE binaries, relative_address_base() returns the image base address. + // Otherwise it returns zero. This gives regular ELF images a base address + // of zero, which is what we want. + object_file.relative_address_base() +} diff --git a/kernel/src/std/unwind/unwinder.rs b/kernel/src/std/unwind/unwinder.rs index 2eda690..7a4929a 100644 --- a/kernel/src/std/unwind/unwinder.rs +++ b/kernel/src/std/unwind/unwinder.rs @@ -46,6 +46,7 @@ impl FallibleIterator for Unwinder { let Some(pc) = self.regs.get_pc() else { return Err(UnwinderError::NoPcRegister); }; + // 0xffffffff8000014b b - 7 = 11 - 7 = 4 if self.is_first { self.is_first = false; diff --git a/scripts/run_debug.sh b/scripts/run_debug.sh index 72f4a73..37a97c1 100755 --- a/scripts/run_debug.sh +++ b/scripts/run_debug.sh @@ -32,7 +32,7 @@ else fi # Set up test-specific flags -if [ $is_test -eq 1 ]; then +if [$is_test]; then test_flags="-device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none" serial_flags="-serial stdio" else