Skip to content

Add a builder to multiboot2 #133

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 20 commits into from
Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
152c985
multiboot2: Add basic information builder
YtvwlD Mar 6, 2023
544e1ca
multiboot2: Implement setting the command line
YtvwlD Mar 6, 2023
535c9e7
multiboot2: Implement adding module tags
YtvwlD Mar 7, 2023
41428fb
multiboot2: Implement setting ELF section tag
YtvwlD Mar 7, 2023
08df6c2
multiboot2: Implement setting boot loader name
YtvwlD Mar 7, 2023
bb21998
multiboot2: Implement setting the framebuffer tag
YtvwlD Mar 8, 2023
0320148
multiboot2: Implement setting the BasicMemoryInfoTag
YtvwlD Mar 8, 2023
d198dce
multiboot2: Implement setting memory map tag
YtvwlD Mar 8, 2023
122fc9c
multiboot2: Implement building the multiboot2 information
YtvwlD Mar 8, 2023
6379b42
multiboot2: Implement Debug and PartialEq for packed structs
YtvwlD Mar 14, 2023
d5e99f7
multiboot2: Support passing the EFI System Table
YtvwlD Mar 27, 2023
888dd27
multiboot2: Allow setting the SMBIOS tag
YtvwlD Mar 28, 2023
79646bb
multiboot2: Allow setting the RSDP tags
YtvwlD Mar 28, 2023
690c84a
multiboot2: Support setting the EFI memory map tag
YtvwlD Apr 3, 2023
12f4642
multiboot2: Support setting Boot Services not exited tag
YtvwlD Apr 3, 2023
ed316cb
multiboot2: Support setting the image handle pointer
YtvwlD Apr 4, 2023
cbc47ab
multiboot2: Support setting the image load address
YtvwlD Apr 4, 2023
2f2a058
multiboot2: Improve builder test
YtvwlD Jun 4, 2023
09de523
multiboot2-header: Improve builder test
YtvwlD Jun 4, 2023
69630b3
multiboot2: Don't require an offset for the ELF sections
YtvwlD Jun 19, 2023
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
5 changes: 4 additions & 1 deletion multiboot2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ repository = "https://github.com/rust-osdev/multiboot2"
documentation = "https://docs.rs/multiboot2"

[features]
default = []
# by default, builder is included
default = ["builder"]
std = []
builder = ["std"]
# Nightly-only features that will eventually be stabilized.
unstable = []

Expand Down
38 changes: 38 additions & 0 deletions multiboot2/src/builder/information.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Exports item [`Multiboot2InformationBuilder`].
use crate::builder::traits::StructAsBytes;
use crate::{CommandLineTag, ElfSectionsTag, ModuleTag};

use alloc::boxed::Box;
use alloc::vec::Vec;

/// Builder to construct a valid Multiboot2 information dynamically at runtime.
/// The tags will appear in the order of their corresponding enumeration,
/// except for the END tag.
#[derive(Debug)]
pub struct Multiboot2InformationBuilder {
command_line_tag: Option<Box<CommandLineTag>>,
elf_sections_tag: Option<Box<ElfSectionsTag>>,
module_tags: Vec<Box<ModuleTag>>,
}

impl Multiboot2InformationBuilder {
pub const fn new() -> Self {
Self {
command_line_tag: None,
elf_sections_tag: None,
module_tags: Vec::new(),
}
}

pub fn command_line_tag(&mut self, command_line_tag: Box<CommandLineTag>) {
self.command_line_tag = Some(command_line_tag);
}

pub fn elf_sections_tag(&mut self, elf_sections_tag: Box<ElfSectionsTag>) {
self.elf_sections_tag = Some(elf_sections_tag);
}

pub fn add_module_tag(&mut self, module_tag: Box<ModuleTag>) {
self.module_tags.push(module_tag);
}
}
44 changes: 44 additions & 0 deletions multiboot2/src/builder/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//! Module for the builder-feature.

mod information;
pub(self) mod traits;

pub use information::Multiboot2InformationBuilder;

use alloc::alloc::alloc;
use alloc::boxed::Box;
use core::alloc::Layout;
use core::mem::size_of;

use crate::{TagTrait, TagTypeId};

/// Create a boxed tag with the given content.
pub(super) fn boxed_dst_tag<T: TagTrait<Metadata = usize> + ?Sized>(
typ: impl Into<TagTypeId>,
content: &[u8],
) -> Box<T> {
// based on https://stackoverflow.com/a/64121094/2192464
let (layout, size_offset) = Layout::new::<TagTypeId>()
.extend(Layout::new::<u32>())
.unwrap();
let (layout, inner_offset) = layout
.extend(Layout::array::<usize>(content.len()).unwrap())
.unwrap();
let ptr = unsafe { alloc(layout) };
assert!(!ptr.is_null());
unsafe {
// initialize the content as good as we can
ptr.cast::<TagTypeId>().write(typ.into());
ptr.add(size_offset).cast::<u32>().write(
(content.len() + size_of::<TagTypeId>() + size_of::<u32>())
.try_into()
.unwrap(),
);
// initialize body
let content_ptr = ptr.add(inner_offset);
for (idx, val) in content.iter().enumerate() {
content_ptr.add(idx).write(*val);
}
Box::from_raw(ptr_meta::from_raw_parts_mut(ptr as *mut (), content.len()))
}
}
30 changes: 30 additions & 0 deletions multiboot2/src/builder/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Module for the helper trait [`StructAsBytes`].

use core::mem::size_of;

/// Trait for all tags that helps to create a byte array from the tag.
/// Useful in builders to construct a byte vector that
/// represents the Multiboot2 information with all its tags.
pub(crate) trait StructAsBytes: Sized {
/// Returns the size in bytes of the struct, as known during compile
/// time. This doesn't use read the "size" field of tags.
fn byte_size(&self) -> usize {
size_of::<Self>()
}

/// Returns a byte pointer to the begin of the struct.
fn as_ptr(&self) -> *const u8 {
self as *const Self as *const u8
}

/// Returns the structure as a vector of its bytes.
/// The length is determined by [`Self::byte_size`].
fn struct_as_bytes(&self) -> alloc::vec::Vec<u8> {
let ptr = self.as_ptr();
let mut vec = alloc::vec::Vec::with_capacity(self.byte_size());
for i in 0..self.byte_size() {
vec.push(unsafe { *ptr.add(i) })
}
vec
}
}
22 changes: 17 additions & 5 deletions multiboot2/src/command_line.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
//! Module for [CommandLineTag].

use crate::{Tag, TagTrait, TagTypeId};
use crate::{Tag, TagTrait, TagType, TagTypeId};
use core::fmt::{Debug, Formatter};
use core::mem;
use core::str;

#[cfg(feature = "builder")]
use {crate::builder::boxed_dst_tag, alloc::boxed::Box, alloc::vec::Vec};

pub(crate) const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + mem::size_of::<u32>();

/// This tag contains the command line string.
///
/// The string is a normal C-style UTF-8 zero-terminated string that can be
Expand All @@ -18,6 +24,14 @@ pub struct CommandLineTag {
}

impl CommandLineTag {
/// Create a new command line tag from the given string.
#[cfg(feature = "builder")]
pub fn new(command_line: &str) -> Box<Self> {
let mut bytes: Vec<_> = command_line.bytes().collect();
bytes.push(0);
boxed_dst_tag(TagType::Cmdline, &bytes)
}

/// Reads the command line of the kernel as Rust string slice without
/// the null-byte.
///
Expand Down Expand Up @@ -52,10 +66,8 @@ impl Debug for CommandLineTag {

impl TagTrait for CommandLineTag {
fn dst_size(base_tag: &Tag) -> usize {
// The size of the sized portion of the command line tag.
let tag_base_size = 8;
assert!(base_tag.size >= 8);
base_tag.size as usize - tag_base_size
assert!(base_tag.size as usize >= METADATA_SIZE);
base_tag.size as usize - METADATA_SIZE
}
}

Expand Down
71 changes: 40 additions & 31 deletions multiboot2/src/elf_sections.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
use crate::tag_type::Tag;
use crate::TagType;
use crate::{Tag, TagTrait, TagType, TagTypeId};

use core::fmt::{Debug, Formatter};
use core::mem::size_of;
use core::str::Utf8Error;

#[cfg(feature = "builder")]
use {crate::builder::boxed_dst_tag, alloc::boxed::Box};

const METADATA_SIZE: usize = size_of::<TagTypeId>() + 4 * size_of::<u32>();

/// This tag contains section header table from an ELF kernel.
///
/// The sections iterator is provided via the `sections` method.
#[derive(Debug)]
#[derive(Debug, ptr_meta::Pointee)]
#[repr(C, packed)]
pub struct ElfSectionsTag {
inner: *const ElfSectionsTagInner,
offset: usize,
}

pub unsafe fn elf_sections_tag(tag: &Tag, offset: usize) -> ElfSectionsTag {
assert_eq!(TagType::ElfSections.val(), tag.typ);
let es = ElfSectionsTag {
inner: (tag as *const Tag).offset(1) as *const ElfSectionsTagInner,
offset,
};
assert!((es.get().entry_size * es.get().shndx) <= tag.size);
es
}

#[derive(Clone, Copy, Debug)]
#[repr(C, packed)] // only repr(C) would add unwanted padding at the end
struct ElfSectionsTagInner {
typ: TagTypeId,
pub(crate) size: u32,
number_of_sections: u32,
entry_size: u32,
shndx: u32, // string table
pub(crate) entry_size: u32,
pub(crate) shndx: u32, // string table
sections: [u8],
}

impl ElfSectionsTag {
/// Create a new ElfSectionsTag with the given data.
#[cfg(feature = "builder")]
pub fn new(number_of_sections: u32, entry_size: u32, shndx: u32, sections: &[u8]) -> Box<Self> {
let mut bytes = [
number_of_sections.to_le_bytes(),
entry_size.to_le_bytes(),
shndx.to_le_bytes(),
]
.concat();
bytes.extend_from_slice(sections);
boxed_dst_tag(TagType::ElfSections, &bytes)
}

/// Get an iterator of loaded ELF sections.
///
/// # Examples
Expand All @@ -39,31 +45,34 @@ impl ElfSectionsTag {
/// # let boot_info = unsafe { multiboot2::load(0xdeadbeef).unwrap() };
/// if let Some(elf_tag) = boot_info.elf_sections_tag() {
/// let mut total = 0;
/// for section in elf_tag.sections() {
/// for section in elf_tag.sections(0) {
/// println!("Section: {:?}", section);
/// total += 1;
/// }
/// }
/// ```
pub fn sections(&self) -> ElfSectionIter {
let string_section_offset = (self.get().shndx * self.get().entry_size) as isize;
pub fn sections(&self, offset: usize) -> ElfSectionIter {
let string_section_offset = (self.shndx * self.entry_size) as isize;
let string_section_ptr =
unsafe { self.first_section().offset(string_section_offset) as *const _ };
ElfSectionIter {
current_section: self.first_section(),
remaining_sections: self.get().number_of_sections,
entry_size: self.get().entry_size,
remaining_sections: self.number_of_sections,
entry_size: self.entry_size,
string_section: string_section_ptr,
offset: self.offset,
offset,
}
}

fn first_section(&self) -> *const u8 {
(unsafe { self.inner.offset(1) }) as *const _
&(self.sections[0]) as *const _
}
}

fn get(&self) -> &ElfSectionsTagInner {
unsafe { &*self.inner }
impl TagTrait for ElfSectionsTag {
fn dst_size(base_tag: &Tag) -> usize {
assert!(base_tag.size as usize >= METADATA_SIZE);
base_tag.size as usize - METADATA_SIZE
}
}

Expand Down
33 changes: 16 additions & 17 deletions multiboot2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
//! ## MSRV
//! The MSRV is 1.56.1 stable.

#[cfg(feature = "builder")]
extern crate alloc;

// this crate can use std in tests only
#[cfg_attr(test, macro_use)]
#[cfg(test)]
Expand Down Expand Up @@ -81,6 +84,9 @@ mod smbios;
mod tag_type;
mod vbe_info;

#[cfg(feature = "builder")]
pub mod builder;

/// Magic number that a multiboot2-compliant boot loader will store in `eax` register
/// right before handoff to the payload (the kernel). This value can be used to check,
/// that the kernel was indeed booted via multiboot2.
Expand Down Expand Up @@ -226,9 +232,12 @@ impl BootInformation {
}

/// Search for the ELF Sections tag.
pub fn elf_sections_tag(&self) -> Option<ElfSectionsTag> {
self.get_tag::<Tag, _>(TagType::ElfSections)
.map(|tag| unsafe { elf_sections::elf_sections_tag(tag, self.offset) })
pub fn elf_sections_tag(&self) -> Option<&ElfSectionsTag> {
let tag = self.get_tag::<ElfSectionsTag, _>(TagType::ElfSections);
if let Some(t) = tag {
assert!((t.entry_size * t.shndx) <= t.size);
}
tag
}

/// Search for the Memory map tag.
Expand Down Expand Up @@ -439,7 +448,7 @@ impl fmt::Debug for BootInformation {

let elf_sections_tag_entries_count = self
.elf_sections_tag()
.map(|x| x.sections().count())
.map(|x| x.sections(self.offset).count())
.unwrap_or(0);

if elf_sections_tag_entries_count > ELF_SECTIONS_LIMIT {
Expand All @@ -449,7 +458,7 @@ impl fmt::Debug for BootInformation {
"elf_sections_tags",
&self
.elf_sections_tag()
.map(|x| x.sections())
.map(|x| x.sections(self.offset))
.unwrap_or_default(),
);
}
Expand Down Expand Up @@ -1256,7 +1265,7 @@ mod tests {
assert_eq!(addr + bytes.len(), bi.end_address());
assert_eq!(bytes.len(), bi.total_size());
let es = bi.elf_sections_tag().unwrap();
let mut s = es.sections();
let mut s = es.sections(bi.offset);
let s1 = s.next().expect("Should have one more section");
assert_eq!(".rodata", s1.name().expect("Should be valid utf-8"));
assert_eq!(0xFFFF_8000_0010_0000, s1.start_address());
Expand Down Expand Up @@ -1441,7 +1450,7 @@ mod tests {
assert_eq!(addr + bytes.0.len(), bi.end_address());
assert_eq!(bytes.0.len(), bi.total_size());
let es = bi.elf_sections_tag().unwrap();
let mut s = es.sections();
let mut s = es.sections(bi.offset);
let s1 = s.next().expect("Should have one more section");
assert_eq!(".shstrtab", s1.name().expect("Should be valid utf-8"));
assert_eq!(string_addr, s1.start_address());
Expand All @@ -1452,16 +1461,6 @@ mod tests {
assert!(s.next().is_none());
}

#[test]
/// Compile time test for `ElfSectionsTag`.
fn elf_sections_tag_size() {
use super::ElfSectionsTag;
unsafe {
// `ElfSectionsTagInner` is 12 bytes + 4 in the offset.
core::mem::transmute::<[u8; 16], ElfSectionsTag>([0u8; 16]);
}
}

#[test]
fn efi_memory_map() {
use memory_map::EFIMemoryAreaType;
Expand Down
Loading