Skip to content

add missing functionality in multiboot2-header (finding the header, getting tags) #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 16, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 145 additions & 26 deletions multiboot2-header/src/header.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{
AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag,
EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, RelocatableHeaderTag,
HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag,
RelocatableHeaderTag,
};
use core::convert::TryInto;
use core::fmt::{Debug, Formatter};
use core::mem::size_of;

Expand All @@ -21,12 +23,10 @@ pub struct Multiboot2Header<'a> {
}

impl<'a> Multiboot2Header<'a> {
/// Public constructor for this type with various validations. It panics if the address is invalid.
/// It panics rather than returning a result, because if this fails, it is
/// a fatal, unrecoverable error anyways and a bug in your code.
/// Public constructor for this type with various validations.
///
/// # Panics
/// Panics if one of the following conditions is true:
/// If the header is invalid, it returns a [`LoadError`].
/// This may be because:
/// - `addr` is a null-pointer
/// - `addr` isn't 8-byte aligned
/// - the magic value of the header is not present
Expand All @@ -35,28 +35,69 @@ impl<'a> Multiboot2Header<'a> {
/// # Safety
/// This function may produce undefined behaviour, if the provided `addr` is not a valid
/// Multiboot2 header pointer.
pub unsafe fn from_addr(addr: usize) -> Self {
assert_ne!(0, addr, "`addr` is null pointer");
assert_eq!(
addr % 8,
0,
"`addr` must be 8-byte aligned, see Multiboot2 spec"
);
// This function can be `const` on newer Rust versions.
#[allow(clippy::missing_const_for_fn)]
pub unsafe fn from_addr(addr: usize) -> Result<Self, LoadError> {
if addr == 0 || addr % 8 != 0 {
return Err(LoadError::InvalidAddress);
}
let ptr = addr as *const Multiboot2BasicHeader;
let reference = &*ptr;
assert_eq!(
reference.header_magic(),
MULTIBOOT2_HEADER_MAGIC,
"The Multiboot2 header must contain the MULTIBOOT2_HEADER_MAGIC={:x}",
MULTIBOOT2_HEADER_MAGIC
);
assert!(
reference.verify_checksum(),
"checksum invalid! Is {:x}, expected {:x}",
reference.checksum(),
Self::calc_checksum(reference.header_magic, reference.arch, reference.length)
);
Self { inner: reference }
if reference.header_magic() != MULTIBOOT2_HEADER_MAGIC {
return Err(LoadError::MagicNotFound);
}
if !reference.verify_checksum() {
return Err(LoadError::ChecksumMismatch);
}
Ok(Self { inner: reference })
}

/// Find the header in a given slice.
///
/// If it succeeds, it returns a tuple consisting of the subslice containing
/// just the header and the index of the header in the given slice.
/// If it fails (either because the header is not properply 64-bit aligned
/// or because it is truncated), it returns a [`LoadError`].
/// If there is no header, it returns `None`.
pub fn find_header(buffer: &[u8]) -> Result<Option<(&[u8], u32)>, LoadError> {
// the magic is 32 bit aligned and inside the first 8192 bytes
assert!(buffer.len() >= 8192);
let mut windows = buffer[0..8192].windows(4);
let magic_index = match windows.position(|vals| {
u32::from_le_bytes(vals.try_into().unwrap()) // yes, there's 4 bytes here
== MULTIBOOT2_HEADER_MAGIC
}) {
Some(idx) => {
if idx % 8 == 0 {
idx
} else {
return Err(LoadError::InvalidAddress);
}
}
None => return Ok(None),
};
// skip over rest of magic
windows.next();
windows.next();
windows.next();
// arch
windows.next();
windows.next();
windows.next();
windows.next();
let header_length: usize = u32::from_le_bytes(
windows
.next()
.ok_or(LoadError::TooSmall)?
.try_into()
.unwrap(), // 4 bytes are a u32
)
.try_into()
.unwrap();
Ok(Some((
&buffer[magic_index..magic_index + header_length],
magic_index as u32,
)))
}

/// Wrapper around [`Multiboot2BasicHeader::verify_checksum`].
Expand Down Expand Up @@ -87,6 +128,66 @@ impl<'a> Multiboot2Header<'a> {
pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
Multiboot2BasicHeader::calc_checksum(magic, arch, length)
}

/// Search for the address header tag.
pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
self.get_tag(HeaderTagType::Address)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const AddressHeaderTag) })
}

/// Search for the entry address header tag.
pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
self.get_tag(HeaderTagType::EntryAddress)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryAddressHeaderTag) })
}

/// Search for the EFI32 entry address header tag.
pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
self.get_tag(HeaderTagType::EntryAddressEFI32)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi32HeaderTag) })
}

/// Search for the EFI64 entry address header tag.
pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
self.get_tag(HeaderTagType::EntryAddressEFI64)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi64HeaderTag) })
}

/// Search for the console flags header tag.
pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
self.get_tag(HeaderTagType::ConsoleFlags)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const ConsoleHeaderTag) })
}

/// Search for the framebuffer header tag.
pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
self.get_tag(HeaderTagType::Framebuffer)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const FramebufferHeaderTag) })
}

/// Search for the module align header tag.
pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
self.get_tag(HeaderTagType::ModuleAlign)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const ModuleAlignHeaderTag) })
}

/// Search for the EFI Boot Services header tag.
pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
self.get_tag(HeaderTagType::EfiBS)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const EfiBootServiceHeaderTag) })
}

/// Search for the EFI32 entry address header tag.
pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
self.get_tag(HeaderTagType::Relocatable)
.map(|tag| unsafe { &*(tag as *const HeaderTag as *const RelocatableHeaderTag) })
}

fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTag> {
self.iter()
.map(|tag| unsafe { tag.as_ref() }.unwrap())
.find(|tag| tag.typ() == typ)
}
}

impl<'a> Debug for Multiboot2Header<'a> {
Expand All @@ -97,6 +198,20 @@ impl<'a> Debug for Multiboot2Header<'a> {
}
}

/// Errors that can occur when parsing a header from a slice.
/// See [`Multiboot2Header::find_header`].
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadError {
/// The checksum does not match the data.
ChecksumMismatch,
/// The header is not properly 64-bit aligned (or a null pointer).
InvalidAddress,
/// The header does not contain the correct magic number.
MagicNotFound,
/// The header is truncated.
TooSmall,
}

/// **Use this only if you know what you do. You probably want to use
/// [`Multiboot2Header`] instead.**
///
Expand Down Expand Up @@ -313,6 +428,10 @@ impl Debug for Multiboot2HeaderTagIter {
let entry = t as *const EntryEfi64HeaderTag;
let entry = &*(entry);
debug.entry(entry);
} else if typ == HeaderTagType::ModuleAlign {
let entry = t as *const ModuleAlignHeaderTag;
let entry = &*(entry);
debug.entry(entry);
} else if typ == HeaderTagType::Relocatable {
let entry = t as *const RelocatableHeaderTag;
let entry = &*(entry);
Expand Down