Skip to content

init multiboot2-common and use same safe memory abstractions also in multiboot2-header crate #227

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 9 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
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
16 changes: 14 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
resolver = "2"
members = [
"multiboot2",
"multiboot2-common",
"multiboot2-header",
]
exclude = [
Expand All @@ -12,9 +13,11 @@ exclude = [
bitflags = "2.6.0"
derive_more = { version = "~0.99.18", default-features = false, features = ["display"] }
log = { version = "~0.4", default-features = false }
ptr_meta = { version = "~0.2", default-features = false }

# This way, the "multiboot2" dependency in the multiboot2-header crate can be
# referenced by version, while still the repository version is used
# transparently during local development.
# This way, the corresponding crate dependency can be normalley referenced by
# version, while still the repository version is used transparently during local
# development.
[patch.crates-io]
multiboot2 = { path = "multiboot2" }
multiboot2-common = { path = "multiboot2-common" }
16 changes: 14 additions & 2 deletions integration-test/bins/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions integration-test/bins/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ util = { path = "./util" }
# transparently during local development.
[patch.crates-io]
multiboot2 = { path = "../../multiboot2" }
multiboot2-common = { path = "../../multiboot2-common" }
multiboot2-header = { path = "../../multiboot2-header" }
26 changes: 13 additions & 13 deletions integration-test/bins/multiboot2_chainloader/src/loader.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use core::ops::Deref;
use alloc::boxed::Box;
use elf_rs::{ElfFile, ProgramHeaderEntry, ProgramType};
use multiboot2::{
BootLoaderNameTag, CommandLineTag, MemoryArea, MemoryAreaType, MemoryMapTag, ModuleTag,
SmbiosTag,
BootLoaderNameTag, CommandLineTag, MaybeDynSized, MemoryArea, MemoryAreaType, MemoryMapTag,
ModuleTag, SmbiosTag,
};

/// Loads the first module into memory. Assumes that the module is a ELF file.
Expand Down Expand Up @@ -43,27 +43,27 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
// that the basic data structures are usable.

// build MBI
let mbi = multiboot2::builder::InformationBuilder::new()
.bootloader_name_tag(&BootLoaderNameTag::new("mb2_integrationtest_chainloader"))
.command_line_tag(&CommandLineTag::new("chainloaded YEAH"))
let mbi = multiboot2::Builder::new()
.bootloader(BootLoaderNameTag::new("mb2_integrationtest_chainloader"))
.cmdline(CommandLineTag::new("chainloaded YEAH"))
// random non-sense memory map
.memory_map_tag(&MemoryMapTag::new(&[MemoryArea::new(
.mmap(MemoryMapTag::new(&[MemoryArea::new(
0,
0xffffffff,
MemoryAreaType::Reserved,
)]))
.add_module_tag(&ModuleTag::new(
.add_module(ModuleTag::new(
elf_mod.start as u32,
elf_mod.end as u32,
elf_mod.string.unwrap(),
))
// Test that we can add SmbiosTag multiple times.
.add_tag(SmbiosTag::new(1, 1, &[1, 2, 3]).deref())
.unwrap()
.add_tag(SmbiosTag::new(1, 2, &[1, 2, 3]).deref())
.expect("should allow tag multiple times")
.add_smbios(SmbiosTag::new(1, 1, &[1, 2, 3]))
.add_smbios(SmbiosTag::new(2, 3, &[4, 5, 6]))
.build();

let mbi = Box::leak(mbi);

log::info!(
"Handing over to ELF: {}",
elf_mod.string.unwrap_or("<unknown>")
Expand All @@ -74,7 +74,7 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {
core::arch::asm!(
"jmp *%ecx",
in("eax") multiboot2::MAGIC,
in("ebx") mbi.as_ptr() as u32,
in("ebx") mbi.as_ptr(),
in("ecx") elf.entry_point() as u32,
options(noreturn, att_syntax));
}
Expand Down
2 changes: 1 addition & 1 deletion integration-test/bins/multiboot2_payload/src/verify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alloc::vec::Vec;
use multiboot2::BootInformation;

pub fn run(mbi: &BootInformation) -> anyhow::Result<()> {
println!("{mbi:#x?}");
println!("MBI: {mbi:#x?}");
println!();

let bootloader = mbi
Expand Down
37 changes: 37 additions & 0 deletions multiboot2-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "multiboot2-common"
description = """
Common helpers for the `multiboot2` and `multiboot2-header` crates.
"""
version = "0.1.0"
authors = [
"Philipp Schuster <phip1611@gmail.com>"
]
license = "MIT/Apache-2.0"
edition = "2021"
categories = [
"no-std",
"no-std::no-alloc",
]
keywords = [
"Multiboot2"
]
readme = "README.md"
homepage = "https://github.com/rust-osdev/multiboot2"
repository = "https://github.com/rust-osdev/multiboot2"
documentation = "https://docs.rs/multiboot2-common"
rust-version = "1.70"

[features]
default = ["builder"]
alloc = []
builder = ["alloc"]
unstable = []


[dependencies]
derive_more.workspace = true
ptr_meta.workspace = true

[package.metadata.docs.rs]
all-features = true
5 changes: 5 additions & 0 deletions multiboot2-common/Changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# CHANGELOG for crate `multiboot2`

## 0.1.0 (2024-08-20)

Initial release.
1 change: 1 addition & 0 deletions multiboot2-common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# multiboot2-common
116 changes: 116 additions & 0 deletions multiboot2-common/src/boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//! Module for [`new_boxed`].

use crate::{increase_to_alignment, Header, MaybeDynSized, ALIGNMENT};
use alloc::boxed::Box;
use core::alloc::Layout;
use core::mem;
use core::ops::Deref;
use core::ptr;

/// Creates a new tag implementing [`MaybeDynSized`] on the heap. This works for
/// sized and unsized tags. However, it only makes sense to use this for tags
/// that are DSTs (unsized). For regular sized structs, you can just create a
/// typical constructor and box the result.
///
/// The provided `header`' total size (see [`Header`]) will be set dynamically
/// by this function using [`Header::set_size`]. However, it must contain all
/// other relevant metadata or update it in the `set_size` callback.
///
/// # Parameters
/// - `additional_bytes_slices`: Array of byte slices that should be included
/// without additional padding in-between. You don't need to add the bytes
/// for [`Header`], but only additional payload.
#[must_use]
pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
mut header: T::Header,
additional_bytes_slices: &[&[u8]],
) -> Box<T> {
let additional_size = additional_bytes_slices
.iter()
.map(|b| b.len())
.sum::<usize>();

let tag_size = mem::size_of::<T::Header>() + additional_size;
header.set_size(tag_size);

// Allocation size is multiple of alignment.
// See <https://doc.rust-lang.org/reference/type-layout.html>
let alloc_size = increase_to_alignment(tag_size);
let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
assert!(!heap_ptr.is_null());

// write header
{
let len = mem::size_of::<T::Header>();
let ptr = core::ptr::addr_of!(header);
unsafe {
ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
}
}

// write body
{
let mut write_offset = mem::size_of::<T::Header>();
for &bytes in additional_bytes_slices {
let len = bytes.len();
let src = bytes.as_ptr();
unsafe {
let dst = heap_ptr.add(write_offset);
ptr::copy_nonoverlapping(src, dst, len);
write_offset += len;
}
}
}

// This is a fat pointer for DSTs and a thin pointer for sized `T`s.
let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header));
let reference = unsafe { Box::from_raw(ptr) };

// If this panic triggers, there is a fundamental flaw in my logic. This is
// not the fault of an API user.
assert_eq!(
mem::size_of_val(reference.deref()),
alloc_size,
"Allocation should match Rusts expectation"
);

reference
}

/// Clones a [`MaybeDynSized`] by calling [`new_boxed`].
#[must_use]
pub fn clone_dyn<T: MaybeDynSized<Metadata = usize> + ?Sized>(tag: &T) -> Box<T> {
new_boxed(tag.header().clone(), &[tag.payload()])
}

#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::{DummyDstTag, DummyTestHeader};
use crate::Tag;

#[test]
fn test_new_boxed() {
let header = DummyTestHeader::new(DummyDstTag::ID, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
assert_eq!(tag.header().typ(), 42);
assert_eq!(tag.payload(), &[0, 1, 2, 3]);

// Test that bytes are added consecutively without gaps.
let header = DummyTestHeader::new(0xdead_beef, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0], &[1], &[2, 3]]);
assert_eq!(tag.header().typ(), 0xdead_beef);
assert_eq!(tag.payload(), &[0, 1, 2, 3]);
}

#[test]
fn test_clone_tag() {
let header = DummyTestHeader::new(DummyDstTag::ID, 0);
let tag = new_boxed::<DummyDstTag>(header, &[&[0, 1, 2, 3]]);
assert_eq!(tag.header().typ(), 42);
assert_eq!(tag.payload(), &[0, 1, 2, 3]);

let _cloned = clone_dyn(tag.as_ref());
}
}
Loading
Loading