Start work on x86_64 boot

This commit is contained in:
Artemis Tosini 2024-08-04 06:19:04 +00:00
parent a0a26b745f
commit 08291808c7
Signed by: artemist
GPG key ID: EE5227935FE3FF18
6 changed files with 190 additions and 37 deletions

View file

@ -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

View file

@ -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<String, String>);
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::<usize>()
+ 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;
}
}

77
src/boot.rs Normal file
View file

@ -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<Boot>,
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),
);
}
}
}

View file

@ -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<Boot>) -> 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::<usize>()
+ 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
}

View file

@ -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)> {

View file

@ -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)