Skip to content

Commit b166aee

Browse files
authored
Merge pull request #165 from rust-osdev/foobar
multiboot2: simplify tag_type module
2 parents 989d05e + f22d93a commit b166aee

File tree

5 files changed

+288
-259
lines changed

5 files changed

+288
-259
lines changed

multiboot2/src/end.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//! Module for [`EndTag`].
2+
3+
use crate::{Tag, TagTrait, TagType, TagTypeId};
4+
5+
/// The end tag ends the information struct.
6+
#[repr(C)]
7+
#[derive(Debug)]
8+
pub struct EndTag {
9+
pub typ: TagTypeId,
10+
pub size: u32,
11+
}
12+
13+
impl Default for EndTag {
14+
fn default() -> Self {
15+
Self {
16+
typ: TagType::End.into(),
17+
size: 8,
18+
}
19+
}
20+
}
21+
22+
impl TagTrait for EndTag {
23+
const ID: TagType = TagType::End;
24+
25+
fn dst_size(_base_tag: &Tag) {}
26+
}
27+
28+
#[cfg(test)]
29+
mod tests {
30+
use super::*;
31+
32+
#[test]
33+
/// Compile time test for [`EndTag`].
34+
fn test_end_tag_size() {
35+
unsafe {
36+
core::mem::transmute::<[u8; 8], EndTag>([0u8; 8]);
37+
}
38+
}
39+
}

multiboot2/src/lib.rs

Lines changed: 32 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,35 @@ extern crate alloc;
4343
#[cfg(test)]
4444
extern crate std;
4545

46-
use core::fmt;
47-
use core::mem::size_of;
48-
use derive_more::Display;
49-
// Must be public so that custom tags can be DSTs.
46+
#[macro_use]
47+
extern crate bitflags;
48+
5049
#[cfg(feature = "builder")]
51-
use crate::builder::AsBytes;
52-
use crate::framebuffer::UnknownFramebufferType;
50+
pub mod builder;
51+
52+
mod boot_loader_name;
53+
mod command_line;
54+
mod efi;
55+
mod elf_sections;
56+
mod end;
57+
mod framebuffer;
58+
mod image_load_addr;
59+
mod memory_map;
60+
mod module;
61+
mod rsdp;
62+
mod smbios;
63+
mod tag;
64+
mod tag_trait;
65+
mod tag_type;
66+
mod vbe_info;
67+
5368
pub use boot_loader_name::BootLoaderNameTag;
5469
pub use command_line::CommandLineTag;
5570
pub use efi::{EFIImageHandle32Tag, EFIImageHandle64Tag, EFISdt32Tag, EFISdt64Tag};
5671
pub use elf_sections::{
5772
ElfSection, ElfSectionFlags, ElfSectionIter, ElfSectionType, ElfSectionsTag,
5873
};
74+
pub use end::EndTag;
5975
pub use framebuffer::{FramebufferColor, FramebufferField, FramebufferTag, FramebufferType};
6076
pub use image_load_addr::ImageLoadPhysAddrTag;
6177
pub use memory_map::{
@@ -66,31 +82,22 @@ pub use module::{ModuleIter, ModuleTag};
6682
pub use ptr_meta::Pointee;
6783
pub use rsdp::{RsdpV1Tag, RsdpV2Tag};
6884
pub use smbios::SmbiosTag;
69-
use tag_type::TagIter;
70-
pub use tag_type::{EndTag, Tag, TagType, TagTypeId};
85+
pub use tag::Tag;
86+
pub use tag_trait::TagTrait;
87+
pub use tag_type::{TagType, TagTypeId};
7188
pub use vbe_info::{
7289
VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag,
7390
VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes,
7491
};
7592

76-
#[macro_use]
77-
extern crate bitflags;
78-
79-
mod boot_loader_name;
80-
mod command_line;
81-
mod efi;
82-
mod elf_sections;
83-
mod framebuffer;
84-
mod image_load_addr;
85-
mod memory_map;
86-
mod module;
87-
mod rsdp;
88-
mod smbios;
89-
mod tag_type;
90-
mod vbe_info;
91-
93+
use core::fmt;
94+
use core::mem::size_of;
95+
use derive_more::Display;
96+
// Must be public so that custom tags can be DSTs.
9297
#[cfg(feature = "builder")]
93-
pub mod builder;
98+
use crate::builder::AsBytes;
99+
use crate::framebuffer::UnknownFramebufferType;
100+
use tag::TagIter;
94101

95102
/// Magic number that a multiboot2-compliant boot loader will store in `eax` register
96103
/// right before handoff to the payload (the kernel). This value can be used to check,
@@ -503,61 +510,6 @@ impl fmt::Debug for BootInformation<'_> {
503510
}
504511
}
505512

506-
/// A trait to abstract over all sized and unsized tags (DSTs). For sized tags,
507-
/// this trait does not much. For DSTs, a `TagTrait::dst_size` implementation
508-
/// must me provided, which returns the right size hint for the dynamically
509-
/// sized portion of the struct.
510-
///
511-
/// # Trivia
512-
/// This crate uses the [`Pointee`]-abstraction of the [`ptr_meta`] crate to
513-
/// create fat pointers for tags that are DST.
514-
pub trait TagTrait: Pointee {
515-
/// The numeric ID of this tag.
516-
const ID: TagType;
517-
518-
/// Returns the amount of items in the dynamically sized portion of the
519-
/// DST. Note that this is not the amount of bytes. So if the dynamically
520-
/// sized portion is 16 bytes in size and each element is 4 bytes big, then
521-
/// this function must return 4.
522-
///
523-
/// For sized tags, this just returns `()`. For DSTs, this returns an
524-
/// `usize`.
525-
fn dst_size(base_tag: &Tag) -> Self::Metadata;
526-
527-
/// Returns the tag as the common base tag structure.
528-
fn as_base_tag(&self) -> &Tag {
529-
let ptr = core::ptr::addr_of!(*self);
530-
unsafe { &*ptr.cast::<Tag>() }
531-
}
532-
533-
/// Returns the total size of the tag. The depends on the `size` field of
534-
/// the tag.
535-
fn size(&self) -> usize {
536-
self.as_base_tag().size as usize
537-
}
538-
539-
/// Returns a slice to the underlying bytes of the tag. This includes all
540-
/// bytes, also for tags that are DSTs. The slice length depends on the
541-
/// `size` field of the tag.
542-
fn as_bytes(&self) -> &[u8] {
543-
let ptr = core::ptr::addr_of!(*self);
544-
unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) }
545-
}
546-
547-
/// Creates a reference to a (dynamically sized) tag type in a safe way.
548-
/// DST tags need to implement a proper [`Self::dst_size`] implementation.
549-
///
550-
/// # Safety
551-
/// Callers must be sure that the "size" field of the provided [`Tag`] is
552-
/// sane and the underlying memory valid. The implementation of this trait
553-
/// **must have** a correct [`Self::dst_size`] implementation.
554-
unsafe fn from_base_tag<'a>(tag: &Tag) -> &'a Self {
555-
let ptr = core::ptr::addr_of!(*tag);
556-
let ptr = ptr_meta::from_raw_parts(ptr.cast(), Self::dst_size(tag));
557-
&*ptr
558-
}
559-
}
560-
561513
#[cfg(test)]
562514
mod tests {
563515
use super::*;

multiboot2/src/tag.rs

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
//! Module for the base tag definition.
2+
//!
3+
//! The relevant exports of this module is [`Tag`].
4+
5+
use crate::{TagTrait, TagType, TagTypeId};
6+
use core::fmt;
7+
use core::fmt::{Debug, Formatter};
8+
use core::marker::PhantomData;
9+
use core::str::Utf8Error;
10+
11+
/// Common base structure for all tags that can be passed via the Multiboot2
12+
/// Information Structure (MBI) to a Multiboot2 payload/program/kernel.
13+
///
14+
/// Can be transformed to any other tag (sized or unsized/DST) via
15+
/// [`Tag::cast_tag`].
16+
///
17+
/// Do not confuse them with the Multiboot2 header tags. They are something
18+
/// different.
19+
#[derive(Clone, Copy)]
20+
#[repr(C)]
21+
pub struct Tag {
22+
pub typ: TagTypeId, // u32
23+
pub size: u32,
24+
// followed by additional, tag specific fields
25+
}
26+
27+
impl Tag {
28+
/// Returns the underlying type of the tag.
29+
pub fn typ(&self) -> TagType {
30+
self.typ.into()
31+
}
32+
33+
/// Casts the base tag to the specific tag type.
34+
pub fn cast_tag<'a, T: TagTrait + ?Sized + 'a>(&'a self) -> &'a T {
35+
assert_eq!(self.typ, T::ID);
36+
// Safety: At this point, we trust that "self.size" and the size hint
37+
// for DST tags are sane.
38+
unsafe { TagTrait::from_base_tag(self) }
39+
}
40+
41+
/// Some multiboot2 tags are a DST as they end with a dynamically sized byte
42+
/// slice. This function parses this slice as [`str`] so that either a valid
43+
/// UTF-8 Rust string slice without a terminating null byte or an error is
44+
/// returned.
45+
pub fn get_dst_str_slice(bytes: &[u8]) -> Result<&str, Utf8Error> {
46+
if bytes.is_empty() {
47+
// Very unlikely. A sane bootloader would omit the tag in this case.
48+
// But better be safe.
49+
return Ok("");
50+
}
51+
52+
// Return without a trailing null byte. By spec, the null byte should
53+
// always terminate the string. However, for safety, we do make an extra
54+
// check.
55+
let str_slice = if bytes.ends_with(&[b'\0']) {
56+
let str_len = bytes.len() - 1;
57+
&bytes[0..str_len]
58+
} else {
59+
// Unlikely that a bootloader doesn't follow the spec and does not
60+
// add a terminating null byte.
61+
bytes
62+
};
63+
core::str::from_utf8(str_slice)
64+
}
65+
}
66+
67+
impl Debug for Tag {
68+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69+
let tag_type = TagType::from(self.typ);
70+
71+
let mut debug = f.debug_struct("Tag");
72+
debug.field("typ", &tag_type);
73+
74+
if !matches!(tag_type, TagType::Custom(_)) {
75+
debug.field("typ (numeric)", &(u32::from(self.typ)));
76+
}
77+
78+
debug.field("size", &(self.size));
79+
80+
debug.finish()
81+
}
82+
}
83+
84+
/// Iterates the MBI's tags from the first tag to the end tag.
85+
#[derive(Clone, Debug)]
86+
pub struct TagIter<'a> {
87+
/// Pointer to the next tag. Updated in each iteration.
88+
pub current: *const Tag,
89+
/// The pointer right after the MBI. Used for additional bounds checking.
90+
end_ptr_exclusive: *const u8,
91+
/// Lifetime capture of the MBI's memory.
92+
_mem: PhantomData<&'a ()>,
93+
}
94+
95+
impl<'a> TagIter<'a> {
96+
/// Creates a new iterator
97+
pub fn new(mem: &'a [u8]) -> Self {
98+
assert_eq!(mem.as_ptr().align_offset(8), 0);
99+
TagIter {
100+
current: mem.as_ptr().cast(),
101+
end_ptr_exclusive: unsafe { mem.as_ptr().add(mem.len()) },
102+
_mem: PhantomData,
103+
}
104+
}
105+
}
106+
107+
impl<'a> Iterator for TagIter<'a> {
108+
type Item = &'a Tag;
109+
110+
fn next(&mut self) -> Option<&'a Tag> {
111+
// This never failed so far. But better be safe.
112+
assert!(self.current.cast::<u8>() < self.end_ptr_exclusive);
113+
114+
let tag = unsafe { &*self.current };
115+
match tag.typ() {
116+
TagType::End => None, // end tag
117+
_ => {
118+
// We return the tag and update self.current already to the next
119+
// tag.
120+
121+
// next pointer (rounded up to 8-byte alignment)
122+
let ptr_offset = (tag.size as usize + 7) & !7;
123+
124+
// go to next tag
125+
self.current = unsafe { self.current.cast::<u8>().add(ptr_offset).cast::<Tag>() };
126+
127+
Some(tag)
128+
}
129+
}
130+
}
131+
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
137+
#[test]
138+
fn test_get_dst_str_slice() {
139+
// unlikely case
140+
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[]));
141+
// also unlikely case
142+
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[b'\0']));
143+
// unlikely case: missing null byte. but the lib can cope with that
144+
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice("foobar".as_bytes()));
145+
// test that the null bytes is not included in the string slice
146+
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice("foobar\0".as_bytes()));
147+
// test invalid utf8
148+
assert!(matches!(
149+
Tag::get_dst_str_slice(&[0xff, 0xff]),
150+
Err(Utf8Error { .. })
151+
));
152+
}
153+
}

multiboot2/src/tag_trait.rs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//! Module for [`TagTrait`].
2+
3+
use crate::{Tag, TagType};
4+
use ptr_meta::Pointee;
5+
6+
/// A trait to abstract over all sized and unsized tags (DSTs). For sized tags,
7+
/// this trait does not much. For DSTs, a `TagTrait::dst_size` implementation
8+
/// must me provided, which returns the right size hint for the dynamically
9+
/// sized portion of the struct.
10+
///
11+
/// # Trivia
12+
/// This crate uses the [`Pointee`]-abstraction of the [`ptr_meta`] crate to
13+
/// create fat pointers for tags that are DST.
14+
pub trait TagTrait: Pointee {
15+
/// The numeric ID of this tag.
16+
const ID: TagType;
17+
18+
/// Returns the amount of items in the dynamically sized portion of the
19+
/// DST. Note that this is not the amount of bytes. So if the dynamically
20+
/// sized portion is 16 bytes in size and each element is 4 bytes big, then
21+
/// this function must return 4.
22+
///
23+
/// For sized tags, this just returns `()`. For DSTs, this returns an
24+
/// `usize`.
25+
fn dst_size(base_tag: &Tag) -> Self::Metadata;
26+
27+
/// Returns the tag as the common base tag structure.
28+
fn as_base_tag(&self) -> &Tag {
29+
let ptr = core::ptr::addr_of!(*self);
30+
unsafe { &*ptr.cast::<Tag>() }
31+
}
32+
33+
/// Returns the total size of the tag. The depends on the `size` field of
34+
/// the tag.
35+
fn size(&self) -> usize {
36+
self.as_base_tag().size as usize
37+
}
38+
39+
/// Returns a slice to the underlying bytes of the tag. This includes all
40+
/// bytes, also for tags that are DSTs. The slice length depends on the
41+
/// `size` field of the tag.
42+
fn as_bytes(&self) -> &[u8] {
43+
let ptr = core::ptr::addr_of!(*self);
44+
unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) }
45+
}
46+
47+
/// Creates a reference to a (dynamically sized) tag type in a safe way.
48+
/// DST tags need to implement a proper [`Self::dst_size`] implementation.
49+
///
50+
/// # Safety
51+
/// Callers must be sure that the "size" field of the provided [`Tag`] is
52+
/// sane and the underlying memory valid. The implementation of this trait
53+
/// **must have** a correct [`Self::dst_size`] implementation.
54+
unsafe fn from_base_tag<'a>(tag: &Tag) -> &'a Self {
55+
let ptr = core::ptr::addr_of!(*tag);
56+
let ptr = ptr_meta::from_raw_parts(ptr.cast(), Self::dst_size(tag));
57+
&*ptr
58+
}
59+
}

0 commit comments

Comments
 (0)