diff --git a/Cargo.lock b/Cargo.lock index 16db54d..caceaa5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,12 @@ dependencies = [ "syn", ] +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + [[package]] name = "fnv" version = "1.0.7" @@ -111,7 +117,9 @@ version = "0.1.0" dependencies = [ "cc", "crossbeam", + "elf", "futures-util", + "gimli", "libm", "limine", "linked_list_allocator", @@ -145,6 +153,12 @@ 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 b82aae5..1f8948d 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -19,13 +19,8 @@ futures-util = { version = "0.3.31", default-features = false, features = [ "alloc", ] } linked_list_allocator = { version = "0.10.5", features = ["use_spin"] } -# unwinding = { version = "0.2.5", default-features = false, features = [ -# "unwinder", -# "fde-static", -# "personality", -# "panic", -# "hide-trace", -# ] } +gimli = { version = "0.31.1", default-features = false, features = ["read"] } +elf = { version = "0.7.4", default-features = false, features = ["nightly"] } [build-dependencies] cc = "1.2.14" diff --git a/kernel/linker.ld b/kernel/linker.ld index a193ba6..bd24c18 100644 --- a/kernel/linker.ld +++ b/kernel/linker.ld @@ -46,12 +46,6 @@ SECTIONS *(.rodata .rodata.*) } :rodata - /* Adds support for stack unwinding using the unwinding crate. */ - . = ALIGN(8); - PROVIDE(__eh_frame = .); - .eh_frame : { KEEP (*(.eh_frame)) *(.eh_frame.*) } - - /* Move to the next memory page for .data */ . = ALIGN(CONSTANT(MAXPAGESIZE)); diff --git a/kernel/src/arch/x86_64/memory/mapping.rs b/kernel/src/arch/x86_64/memory/mapping.rs index 305d800..a64ceaf 100644 --- a/kernel/src/arch/x86_64/memory/mapping.rs +++ b/kernel/src/arch/x86_64/memory/mapping.rs @@ -16,7 +16,7 @@ static HIGHER_HALF_DIRECT_MAP_REQUEST: HhdmRequest = HhdmRequest::new(); #[used] #[unsafe(link_section = ".requests")] -static KERNEL_ADDRESS_REQUEST: KernelAddressRequest = +pub static KERNEL_ADDRESS_REQUEST: KernelAddressRequest = KernelAddressRequest::new(); /// ```rs diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 59d616f..7d0748b 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -19,6 +19,8 @@ use arch::x86_64::memory::allocation::page_alloc::FoundryOSFrameAllocator; 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; pub mod arch; @@ -64,7 +66,7 @@ pub fn hcf() -> ! { pub fn boot() -> Result<(), &'static str> { if !BASE_REVISION.is_supported() { - return Err("base revision not supported"); + return Err("Base revision not supported"); } use arch::x86_64::{gdt, interrupts}; @@ -118,5 +120,16 @@ pub fn boot() -> Result<(), &'static str> { x86_64::instructions::interrupts::enable(); println_log!("[Success]"); + print_log!(" 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) }; + println_log!("[Success]"); + Ok(()) } diff --git a/kernel/src/std/elf/mod.rs b/kernel/src/std/elf/mod.rs new file mode 100644 index 0000000..021f0c7 --- /dev/null +++ b/kernel/src/std/elf/mod.rs @@ -0,0 +1,114 @@ +//! Basic ELF parsing functionality using the `elf` crate. +//! +//! This may be extended in the future to support loading programs, however we +//! currently use this for getting the sizes of sections in our kernel ELF at +//! runtime. +//! +//! This is used for implementing stacktraces in std::unwind. +//! +//! # TODO +//! +//! * Add support for loading binary programs (this should probably be written +//! in a different module) + +use alloc::format; +use elf::{ + ElfBytes, ParseError, + endian::LittleEndian, + parse::{ParseAt, ParsingTable}, + section::{SectionHeader, SectionHeaderTable}, + string_table::StringTable, +}; +use limine::request::KernelFileRequest; + +use crate::prelude::*; + +#[cfg(target_arch = "x86_64")] +/// The length of the ELF header in bytes. +pub const ELF_HEADER_LEN: usize = 64; + +/// Information about our own ELF file to make ELF parsing easier, such as the +/// length of the file and a pointer to the contents. +#[used] +pub static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new(); + +/// A list of errors that may occur when parsing ELF files. +#[derive(Debug)] +pub enum ElfError { + /// Returned if a section did not exist in [ElfReader::get_section_size]. + SectionNotExists, + /// Parse errors returned by the `elf` crate. + OtherParseError(elf::ParseError), +} + +impl From for ElfError { + fn from(err: elf::ParseError) -> Self { + Self::OtherParseError(err) + } +} + +pub struct ElfReader { + /// Structure returned by the `elf` crate having parsed the ELF header. + elf: ElfBytes<'static, LittleEndian>, +} + +impl ElfReader { + /// Parses the ELF file for the kernel, this uses data from Limine's Kernel + /// File Request to get a slice over the whole executable file. + /// + /// # Safety + /// + /// Assumes a properly formed ELF file, and that Limine returns a correct + /// pointer to the start of the file as well as a valid length in bytes. + /// + /// 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) }; + + let elf: ElfBytes<'static, LittleEndian> = + elf::ElfBytes::minimal_parse(elf_hdr_slice)?; + + Ok(Self { elf }) + } + + /// Gets the section size of `section_name` in bytes. + pub fn get_section_size( + &self, + section_name: &'static str, + ) -> Result { + Ok(self.get_section_header(section_name)?.sh_size) + } + + /// Gets the start address of the section `section_name` in memory. + pub fn get_section_addr( + &self, + section_name: &'static str, + ) -> Result<*const u8, ElfError> { + Ok(self.get_section_header(section_name)?.sh_addr as *const u8) + } + + /// Gets the section header of `section_name`. + pub fn get_section_header( + &self, + section_name: &'static str, + ) -> Result { + let section_hdr = self + .elf + .section_header_by_name(section_name) + .map_err(|_e| ElfError::SectionNotExists)?; + + section_hdr.ok_or(ElfError::SectionNotExists) + } +} diff --git a/kernel/src/std/mod.rs b/kernel/src/std/mod.rs index f174d93..8466fda 100644 --- a/kernel/src/std/mod.rs +++ b/kernel/src/std/mod.rs @@ -1,4 +1,6 @@ pub mod application; pub mod ascii; +pub mod elf; pub mod io; pub mod maths; +pub mod unwind; diff --git a/kernel/src/std/unwind/eh_info.rs b/kernel/src/std/unwind/eh_info.rs new file mode 100644 index 0000000..8048d12 --- /dev/null +++ b/kernel/src/std/unwind/eh_info.rs @@ -0,0 +1,135 @@ +//! Contains a [EhInfo] struct that contains the parsed DWARF exception header +//! data from the ELF .eh_frame and .eh_frame_hdr sections. + +use alloc::{boxed::Box, slice}; +use gimli::{ + BaseAddresses, EhFrame, EhFrameHdr, EhHdrTable, EndianSlice, LittleEndian, + ParsedEhFrameHdr, +}; +use spin::Lazy; + +use crate::{println_log, std::elf::ElfReader}; + +/// Contains useful data parsed from the ELF file in question. In the kernel +/// this will be our own process. +/// +/// We use this to implement stack traces and potential unwinding. +/// +/// # Sources +/// +/// This code is reproduced from [lesenchal.fr](https://lesenechal.fr/en/linux/unwinding-the-stack-the-hard-way#h5.1-parsing-eh_frame-and-eh_frame_hdr-with-gimli) +/// and will later be extended as required. +pub struct EhInfo { + /// A set of base addresses used for relative addressing. + base_addrs: BaseAddresses, + /// The parsed `.eh_frame_hdr` section. + 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>>, + /// The parsed `.eh_frame` containing the CFIs (call frame information). + eh_frame: EhFrame>, +} + +/// Stores the [ElfReader] struct for this ELF file. +pub static ELF: Lazy = + Lazy::new(|| unsafe { ElfReader::new().unwrap() }); + +impl EhInfo { + /// Gets the `.eh_frame_hdr` size in bytes. + /// + /// # Panics + /// + /// If we can't get the size of `.eh_frame_hdr`. + pub fn eh_frame_hdr_size() -> usize { + ELF.get_section_size(".eh_frame_hdr") + .expect("Cannot get size of `.eh_frame_hdr`.") as usize + } + + /// Gets the `.eh_frame` size in bytes. + /// + /// # Panics + /// + /// If we can't get the size of `.eh_frame`. + pub fn eh_frame_size() -> usize { + ELF.get_section_size(".eh_frame") + .expect("Cannot get size of `.eh_frame`.") as usize + } + + /// Constructs a [EhInfo] from the base address. This is defined for a + /// symbol in the linker script so we can initialise stack traces and -- in + /// the future -- unwinding. + /// + /// # Safety + /// + /// Assumes the `.eh_frame_hdr` pointer to be valid, as well as the sizes of + /// the containing sections. These sizes are computed using [ElfReader]. + /// + /// # Panics + /// + /// This function panics if Gimli throws parsing errors -- for example due + /// to a malformed or corrupted binary, or because this is called in + /// release-mode, or on a stripped binary. + /// + /// # TODOs + /// + /// * Support external System.map files which list symbols and contain + /// debugging information. + pub unsafe fn from_hdr_ptr(eh_frame_hdr: *const u8) -> Self { + let mut base_addrs = BaseAddresses::default(); + // We add the `.eh_frame_hdr` pointer to the set of base addresses which + // are used by Gimli for later parsing. This may be used to compute a + // pointer to `.eh_frame`. + base_addrs = base_addrs.set_eh_frame_hdr(eh_frame_hdr as u64); + + // Leaking the Box gives us a reference with `'static` lifetime to use + // in Self. + let hdr = Box::leak(Box::new( + // We need to construct a slice as input for `EhFrameHdr::new`. + // This is sound if data pointer and length are known to be + // correct. + EhFrameHdr::new( + unsafe { + core::slice::from_raw_parts( + eh_frame_hdr, + Self::eh_frame_hdr_size(), + ) + }, + LittleEndian, + ) // Parse the header using the base address we provided (virtual + // memory). Address size is how many bytes make an + // address (64 bits). + .parse(&base_addrs, 8) + .expect( + "Could not parse `.eh_frame_hdr`. The ELF must be malformed.", + ), + )); + + // Create a pointer to the `.eh_frame` ready to parse it. + let eh_frame = match hdr.eh_frame_ptr() { + gimli::Pointer::Direct(addr) => addr as *mut u8, + _ => unimplemented!(), + }; + + // Add the `.eh_frame` address for addresses relative to this section. + base_addrs = base_addrs.set_eh_frame(eh_frame as u64); + + // Finally parse the `.eh_frame` section of our ELF. + let eh_frame = EhFrame::new( + unsafe { + core::slice::from_raw_parts(eh_frame, Self::eh_frame_size()) + }, + LittleEndian, + ); + + Self { + base_addrs, + hdr, + hdr_table: hdr.table().expect( + "The CFI binary search table was not present in this binary, oh dear.", + ), + eh_frame, + } + } +} diff --git a/kernel/src/std/unwind/mod.rs b/kernel/src/std/unwind/mod.rs new file mode 100644 index 0000000..99eb251 --- /dev/null +++ b/kernel/src/std/unwind/mod.rs @@ -0,0 +1 @@ +pub mod eh_info;