diff --git a/notes.md b/notes.md index 3f50c36..59d161a 100644 --- a/notes.md +++ b/notes.md @@ -19,7 +19,7 @@ Loader must provide modinfo to kernel, a TLV structure * `MODINFOMD_EFI_FB`: Some structure describing UEFI framebuffer * `MODINFOMD_KEYBUF`: `struct keybuf` object with cached keys, don't really need it * `MODINFOMD_FW_HANDLE`: physical address of RuntimeServices system table -* `MODINFOMD_MODULEP`: Base physical address of modinfo, probably +* `MODINFOMD_MODULEP`: Base physical address of TLV structure, only seems to matter for x86\_64 Xen * `MODINFOMD_KERNEND`: Last physical address of kernel, should be free after * `MODINFOMD_HOWTO`: u32 with a bunch of bitflags that start with `RB_` in `sys/sys/reboot.h` * `MODINFOMD_ELFHDR`: copy of the elf header diff --git a/src/abi.rs b/src/abi.rs index 2963dfe..7ab9d2e 100644 --- a/src/abi.rs +++ b/src/abi.rs @@ -1,7 +1,10 @@ extern crate alloc; -use alloc::string::String; use alloc::vec::Vec; -use uefi::proto::console::gop::{GraphicsOutput, PixelFormat}; +use alloc::{collections::BTreeMap, string::String}; +use uefi::{ + proto::console::gop::{GraphicsOutput, PixelFormat}, + table::boot::PAGE_SIZE, +}; fn align_slice(unaligned: &mut [u8], alignment: usize) -> &mut [u8] { let base_addr = &unaligned[0] as *const u8 as usize; @@ -17,17 +20,29 @@ pub trait Serialize { /// Calculate output size of object, from base address to final byte written fn size(&self) -> usize; + /// Conservative estimate of size to allocate + fn alloc_size(&self) -> usize { + self.size() + Self::alignment() - 1 + } + /// Serliaze data into a buffer. Buffer must be aligned at alignment and size of size(). fn serialize_raw(&self, out: &mut [u8]); - /// Serialize to an unaligned buffer, returning the end address + /// Serialize to an unaligned buffer, returning the pointer to the base and the end address as + /// a slice #[must_use] - fn serialize_unaligned<'a>(&self, out: &'a mut [u8]) -> &'a mut [u8] { + fn serialize_unaligned_base<'a>(&self, out: &'a mut [u8]) -> (*const u8, &'a mut [u8]) { let aligned = align_slice(out, Self::alignment()); let size = self.size(); self.serialize_raw(&mut aligned[..size]); - &mut aligned[size..] + (aligned.as_ptr(), &mut aligned[size..]) + } + + /// Serialize to an unaligned buffer, returning the end address as a slice + #[must_use] + fn serialize_unaligned<'a>(&self, out: &'a mut [u8]) -> &'a mut [u8] { + self.serialize_unaligned_base(out).1 } } @@ -99,6 +114,7 @@ impl Serialize for EFIFramebufferParams { #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(u32)] +#[allow(dead_code)] /// Tag in the module info TLV pub enum ModuleInfoType { /// Module name (MODINFO_NAME) @@ -123,6 +139,12 @@ pub enum ModuleInfoType { EndSymbols = 0x8004, /// Base virtual address of `PT_DYNAMIC` segment (MODINFOMD_DYNAMIC) Dynamic = 0x8005, + /// Loader environment variables (MODINFOMD_ENVP) + Environment = 0x8006, + /// Boot bitflags starting with `RB_` (MODINFOMD_HOWTO) + Howto = 0x8007, + /// Free virtual address after staging buffer (MODINFOMD_KERNEND) + FreeAddress = 0x8008, /// Section header table (MODINFOMD_SHDR) SectionHeader = 0x8009, /// Base virtual address of constructors (MODINFOMD_CTORS_ADDR) @@ -200,7 +222,7 @@ impl core::fmt::Debug for ModuleInfoValue { Self::String(arg0) => f.debug_tuple("String").field(arg0).finish(), Self::Pointer(arg0) => f.debug_tuple("Pointer").field(arg0).finish(), Self::Size(arg0) => f.debug_tuple("Size").field(arg0).finish(), - Self::Buffer(_) => write!(f, "Buffer(elided)"), + Self::Buffer(arg0) => write!(f, "Buffer(len={})", arg0.len()), } } } @@ -228,3 +250,34 @@ impl Serialize for ModuleInfoItem { self.value.serialize_raw(&mut out[8..]); } } + +#[derive(Debug, Clone)] +pub struct Environment(pub BTreeMap); + +impl Serialize for Environment { + fn alignment() -> usize { + // FreeBSD aligns to pages, might not matter though + PAGE_SIZE + } + + fn size(&self) -> usize { + self.0 + .iter() + .map(|(k, v)| k.len() + v.len() + 2) + .sum::() + + 1 + } + + fn serialize_raw(&self, mut out: &mut [u8]) { + // Format is `key1=value1\0key2=value2\0\0` + for (key, value) in self.0.iter() { + let entry_len = key.len() + 1 + value.len(); + out[..key.len()].copy_from_slice(key.as_bytes()); + out[key.len()] = b'='; + out[key.len() + 1..entry_len].copy_from_slice(value.as_bytes()); + out[entry_len] = 0; + out = &mut out[entry_len + 1..]; + } + out[out.len() - 1] = 0; + } +} diff --git a/src/boot.rs b/src/boot.rs new file mode 100644 index 0000000..c6a7fe6 --- /dev/null +++ b/src/boot.rs @@ -0,0 +1,77 @@ +#[cfg(target_arch = "x86_64")] +pub use x86_64::boot_kernel; + +#[cfg(target_arch = "x86_64")] +mod x86_64 { + use core::{arch::global_asm, ptr::null, slice}; + + + use uefi::table::{Boot, SystemTable}; + + use crate::{object::Kernel, staging::allocate_code}; + + global_asm!( + r#" + .text + .globl trampoline_code +trampoline_code: + cli + mov rsp,rdi + push rcx + sal rdx, #32 + push rdx + push r8 + mov cr3,rsi + ret + +trampoline_end: + .data + .globl trampoline_size +trampoline_size: + .long trampoline_end-trampoline_code +"# + ); + + // use sysv64 because i don't want to pop from the stack + extern "sysv64" { + static trampoline_size: u32; + + fn trampoline_code( + stack: *const u8, + pagetable: *const u8, + modinfo_base: *const u8, + free_ptr: *const u8, + entry: *const u8, + ); + } + + type TrampolineFunction = + extern "sysv64" fn(*const u8, *const u8, *const u8, *const u8, *const u8); + + pub fn boot_kernel( + system_table: SystemTable, + kernel: Kernel, + kernel_base: *const u8, + modinfo_base: *const u8, + free_ptr: *const u8, + ) { + let trampoline_code_size = unsafe { trampoline_size } as usize; + let misc_buf = allocate_code(system_table.boot_services(), trampoline_code_size + 64); + + let (stack_buf, trampoline_buf) = misc_buf.split_at_mut(64); + trampoline_buf[..trampoline_code_size].copy_from_slice(unsafe { + slice::from_raw_parts(trampoline_code as *const u8, trampoline_code_size) + }); + + unsafe { + let trampoline_func: TrampolineFunction = core::mem::transmute(trampoline_buf.as_ptr()); + trampoline_func( + stack_buf.as_ptr(), + null(), + modinfo_base, + free_ptr, + kernel.entry_addr(kernel_base), + ); + } + } +} diff --git a/src/main.rs b/src/main.rs index 12c3abb..22afb6d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,14 @@ #![no_std] mod abi; +mod boot; mod object; mod staging; -use abi::{ModuleInfoItem, Serialize}; +extern crate alloc; + +use abi::{Environment, ModuleInfoItem, ModuleInfoType, ModuleInfoValue, Serialize}; +use alloc::{collections::btree_map::BTreeMap, string::ToString}; use log::info; use object::{Kernel, Module}; use uefi::{fs::FileSystem, prelude::*}; @@ -16,42 +20,61 @@ fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { info!("Starting freeloader"); - let mut esp = FileSystem::new( - system_table - .boot_services() - .get_image_file_system(image_handle) - .expect("Failed to get ESP handle"), - ); - - let kernel_data = esp - .read(cstr16!("efi\\freebsd\\kernel")) - .expect("Failed to read kernel"); + let kernel_data = { + let mut esp = FileSystem::new( + system_table + .boot_services() + .get_image_file_system(image_handle) + .expect("Failed to get ESP handle"), + ); + esp.read(cstr16!("efi\\freebsd\\kernel")) + .expect("Failed to read kernel") + }; info!("Read kernel"); let kernel = Kernel::from_elf_bytes(&kernel_data); - let kernel_metadata = kernel.metadata(); + let mut kernel_metadata = kernel.metadata(); info!("Kernel metadata is {:x?}", kernel_metadata); - let metadata_size: usize = kernel_metadata - .iter() - .map(|item| item.size().next_multiple_of(ModuleInfoItem::alignment())) - .sum(); + let environment = Environment(BTreeMap::from([( + "kernel".to_string(), + "kernel".to_string(), + )])); // Allocate an extra 16KiB for items we have to add to metadata - let alloc_size = kernel.size() + Kernel::alignment() + metadata_size + 16 * 1024; + let metadata_size: usize = kernel_metadata + .iter() + .map(|item| item.alloc_size()) + .sum::() + + 16 * 1024; - let staging = staging::allocate_staging(system_table.boot_services(), alloc_size); + let alloc_size = kernel.alloc_size() + environment.alloc_size() + metadata_size; + let staging = staging::allocate_code(system_table.boot_services(), alloc_size); - { - let mut next = staging; - next = kernel.serialize_unaligned(next); - for item in kernel_metadata.iter() { - next = item.serialize_unaligned(next); - } + let (environ_base, next) = environment.serialize_unaligned_base(staging); + + let (kernel_base, mut next) = kernel.serialize_unaligned_base(next); + + kernel_metadata.push(abi::ModuleInfoItem { + tag: ModuleInfoType::Environment, + value: ModuleInfoValue::Pointer(environ_base), + }); + + let modinfo_base = next.as_ptr(); + + let free_ptr = unsafe { next.as_ptr().byte_add(metadata_size) }; + kernel_metadata.push(ModuleInfoItem { + tag: ModuleInfoType::FreeAddress, + value: ModuleInfoValue::Pointer(free_ptr), + }); + + for item in kernel_metadata.iter() { + next = item.serialize_unaligned(next); } - loop { - system_table.boot_services().stall(10_000_000); - } + boot::boot_kernel(system_table, kernel, kernel_base, modinfo_base, free_ptr); + + // boot_kernel shouldn't return + Status::ABORTED } diff --git a/src/object.rs b/src/object.rs index 4388ab2..aa2c569 100644 --- a/src/object.rs +++ b/src/object.rs @@ -62,8 +62,8 @@ impl<'a> Kernel<'a> { } } - pub fn entry_addr(&self, load_addr: usize) -> usize { - self.parsed.header.e_entry as usize - self.min_loadaddr + load_addr + pub fn entry_addr(&self, serialize_addr: *const u8) -> *const u8 { + unsafe { serialize_addr.byte_add(self.parsed.header.e_entry as usize - self.min_loadaddr) } } fn find_symbol_headers(parsed: &Elf) -> Option<(SectionHeader, SectionHeader)> { diff --git a/src/staging.rs b/src/staging.rs index eeeca5e..d3b475f 100644 --- a/src/staging.rs +++ b/src/staging.rs @@ -1,7 +1,7 @@ use log::info; use uefi::table::boot::{AllocateType, BootServices, MemoryType, PAGE_SIZE}; -pub fn allocate_staging(boot_services: &BootServices, size: usize) -> &'static mut [u8] { +pub fn allocate_code(boot_services: &BootServices, size: usize) -> &'static mut [u8] { let allocation_type = if cfg!(target_arch = "x86_64") { // Must be under 4GiB because we need to pass a 32-bit pointer AllocateType::MaxAddress(4 * 1024 * 1024 * 1024)