Skip to content

Commit 5de87ae

Browse files
authored
Merge pull request #166 from rust-osdev/foobar
multiboot2: cleanup builder module
2 parents b166aee + df8f64e commit 5de87ae

File tree

3 files changed

+153
-152
lines changed

3 files changed

+153
-152
lines changed

multiboot2/src/builder/boxed_dst.rs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
//! Module for [`BoxedDst`].
2+
3+
use crate::{Tag, TagTrait, TagTypeId};
4+
use alloc::alloc::alloc;
5+
use core::alloc::Layout;
6+
use core::marker::PhantomData;
7+
use core::mem::size_of;
8+
use core::ops::Deref;
9+
use core::ptr::NonNull;
10+
11+
/// A helper type to create boxed DST, i.e., tags with a dynamic size for the
12+
/// builder. This is tricky in Rust. This type behaves similar to the regular
13+
/// `Box` type except that it ensure the same layout is used for the (explicit)
14+
/// allocation and the (implicit) deallocation of memory. Otherwise, I didn't
15+
/// found any way to figure out the right layout for a DST. Miri always reported
16+
/// issues that the deallocation used a wrong layout.
17+
///
18+
/// Technically, I'm certain this code is memory safe. But with this type, I
19+
/// also can convince miri that it is.
20+
#[derive(Debug, Eq)]
21+
pub struct BoxedDst<T: ?Sized> {
22+
ptr: core::ptr::NonNull<T>,
23+
layout: Layout,
24+
// marker: I used this only as the regular Box impl also does it.
25+
_marker: PhantomData<T>,
26+
}
27+
28+
impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
29+
/// Create a boxed tag with the given content.
30+
///
31+
/// # Parameters
32+
/// - `content` - All payload bytes of the DST tag without the tag type or
33+
/// the size. The memory is only read and can be discarded
34+
/// afterwards.
35+
pub(crate) fn new(content: &[u8]) -> Self {
36+
// Currently, I do not find a nice way of making this dynamic so that
37+
// also miri is guaranteed to be happy. But it seems that 4 is fine
38+
// here. I do have control over allocation and deallocation.
39+
const ALIGN: usize = 4;
40+
41+
let tag_size = size_of::<TagTypeId>() + size_of::<u32>() + content.len();
42+
43+
// By using miri, I could figure out that there often are problems where
44+
// miri thinks an allocation is smaller then necessary. Most probably
45+
// due to not packed structs. Using packed structs however
46+
// (especially with DSTs), is a crazy ass pain and unusable :/ Therefore,
47+
// the best solution I can think of is to allocate a few byte more than
48+
// necessary. I think that during runtime, everything works fine and
49+
// that no memory issues are present.
50+
let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary
51+
let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap();
52+
let ptr = unsafe { alloc(layout) };
53+
assert!(!ptr.is_null());
54+
55+
// write tag content to memory
56+
unsafe {
57+
// write tag type
58+
let ptrx = ptr.cast::<TagTypeId>();
59+
ptrx.write(T::ID.into());
60+
61+
// write tag size
62+
let ptrx = ptrx.add(1).cast::<u32>();
63+
ptrx.write(tag_size as u32);
64+
65+
// write rest of content
66+
let ptrx = ptrx.add(1).cast::<u8>();
67+
let tag_content_slice = core::slice::from_raw_parts_mut(ptrx, content.len());
68+
for (i, &byte) in content.iter().enumerate() {
69+
tag_content_slice[i] = byte;
70+
}
71+
}
72+
73+
let base_tag = unsafe { &*ptr.cast::<Tag>() };
74+
let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag));
75+
76+
Self {
77+
ptr: NonNull::new(raw).unwrap(),
78+
layout,
79+
_marker: PhantomData,
80+
}
81+
}
82+
}
83+
84+
impl<T: ?Sized> Drop for BoxedDst<T> {
85+
fn drop(&mut self) {
86+
unsafe { alloc::alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) }
87+
}
88+
}
89+
90+
impl<T: ?Sized> Deref for BoxedDst<T> {
91+
type Target = T;
92+
fn deref(&self) -> &Self::Target {
93+
unsafe { self.ptr.as_ref() }
94+
}
95+
}
96+
97+
impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
98+
fn eq(&self, other: &Self) -> bool {
99+
self.deref().eq(other.deref())
100+
}
101+
}
102+
103+
#[cfg(test)]
104+
mod tests {
105+
use super::*;
106+
use crate::TagType;
107+
108+
const METADATA_SIZE: usize = 8;
109+
110+
#[derive(ptr_meta::Pointee)]
111+
#[repr(C)]
112+
struct CustomTag {
113+
typ: TagTypeId,
114+
size: u32,
115+
string: [u8],
116+
}
117+
118+
impl CustomTag {
119+
fn string(&self) -> Result<&str, core::str::Utf8Error> {
120+
Tag::get_dst_str_slice(&self.string)
121+
}
122+
}
123+
124+
impl TagTrait for CustomTag {
125+
const ID: TagType = TagType::Custom(0x1337);
126+
127+
fn dst_size(base_tag: &Tag) -> usize {
128+
assert!(base_tag.size as usize >= METADATA_SIZE);
129+
base_tag.size as usize - METADATA_SIZE
130+
}
131+
}
132+
133+
#[test]
134+
fn test_boxed_dst_tag() {
135+
let content = "hallo";
136+
137+
let tag = BoxedDst::<CustomTag>::new(content.as_bytes());
138+
assert_eq!(tag.typ, CustomTag::ID);
139+
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
140+
assert_eq!(tag.string(), Ok(content));
141+
}
142+
}

multiboot2/src/builder/information.rs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,15 @@
11
//! Exports item [`InformationBuilder`].
2+
use crate::builder::{AsBytes, BoxedDst};
23
use crate::{
34
BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag,
45
EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag,
56
EFISdt32Tag, EFISdt64Tag, ElfSectionsTag, EndTag, FramebufferTag, ImageLoadPhysAddrTag,
67
MemoryMapTag, ModuleTag, RsdpV1Tag, RsdpV2Tag, SmbiosTag, TagTrait, TagType,
78
};
8-
9-
use crate::builder::BoxedDst;
109
use alloc::vec::Vec;
1110
use core::mem::size_of;
1211
use core::ops::Deref;
1312

14-
/// Helper trait for all structs that need to be serialized that do not
15-
/// implement `TagTrait`.
16-
pub trait AsBytes: Sized {
17-
fn as_bytes(&self) -> &[u8] {
18-
let ptr = core::ptr::addr_of!(*self);
19-
let size = core::mem::size_of::<Self>();
20-
unsafe { core::slice::from_raw_parts(ptr.cast(), size) }
21-
}
22-
}
23-
2413
/// Holds the raw bytes of a boot information built with [`InformationBuilder`]
2514
/// on the heap. The bytes returned by [`BootInformationBytes::as_bytes`] are
2615
/// guaranteed to be properly aligned.

multiboot2/src/builder/mod.rs

Lines changed: 10 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,18 @@
11
//! Module for the builder-feature.
22
3+
mod boxed_dst;
34
mod information;
45

5-
pub(crate) use information::AsBytes;
6+
// This must by public to support external people to create boxed DSTs.
7+
pub use boxed_dst::BoxedDst;
68
pub use information::InformationBuilder;
79

8-
use alloc::alloc::alloc;
9-
use core::alloc::Layout;
10-
use core::marker::PhantomData;
11-
use core::mem::size_of;
12-
use core::ops::Deref;
13-
use core::ptr::NonNull;
14-
15-
use crate::{Tag, TagTrait, TagTypeId};
16-
17-
/// A helper type to create boxed DST, i.e., tags with a dynamic size for the
18-
/// builder. This is tricky in Rust. This type behaves similar to the regular
19-
/// `Box` type except that it ensure the same layout is used for the (explicit)
20-
/// allocation and the (implicit) deallocation of memory. Otherwise, I didn't
21-
/// found any way to figure out the right layout for a DST. Miri always reported
22-
/// issues that the deallocation used a wrong layout.
23-
///
24-
/// Technically, I'm certain this code is memory safe. But with this type, I
25-
/// also can convince miri that it is.
26-
#[derive(Debug, Eq)]
27-
pub struct BoxedDst<T: ?Sized> {
28-
ptr: core::ptr::NonNull<T>,
29-
layout: Layout,
30-
// marker: I used this only as the regular Box impl also does it.
31-
_marker: PhantomData<T>,
32-
}
33-
34-
impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
35-
/// Create a boxed tag with the given content.
36-
///
37-
/// # Parameters
38-
/// - `content` - All payload bytes of the DST tag without the tag type or
39-
/// the size. The memory is only read and can be discarded
40-
/// afterwards.
41-
pub(crate) fn new(content: &[u8]) -> Self {
42-
// Currently, I do not find a nice way of making this dynamic so that
43-
// also miri is guaranteed to be happy. But it seems that 4 is fine
44-
// here. I do have control over allocation and deallocation.
45-
const ALIGN: usize = 4;
46-
47-
let tag_size = size_of::<TagTypeId>() + size_of::<u32>() + content.len();
48-
49-
// By using miri, I could figure out that there often are problems where
50-
// miri thinks an allocation is smaller then necessary. Most probably
51-
// due to not packed structs. Using packed structs however
52-
// (especially with DSTs), is a crazy ass pain and unusable :/ Therefore,
53-
// the best solution I can think of is to allocate a few byte more than
54-
// necessary. I think that during runtime, everything works fine and
55-
// that no memory issues are present.
56-
let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary
57-
let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap();
58-
let ptr = unsafe { alloc(layout) };
59-
assert!(!ptr.is_null());
60-
61-
// write tag content to memory
62-
unsafe {
63-
// write tag type
64-
let ptrx = ptr.cast::<TagTypeId>();
65-
ptrx.write(T::ID.into());
66-
67-
// write tag size
68-
let ptrx = ptrx.add(1).cast::<u32>();
69-
ptrx.write(tag_size as u32);
70-
71-
// write rest of content
72-
let ptrx = ptrx.add(1).cast::<u8>();
73-
let tag_content_slice = core::slice::from_raw_parts_mut(ptrx, content.len());
74-
for (i, &byte) in content.iter().enumerate() {
75-
tag_content_slice[i] = byte;
76-
}
77-
}
78-
79-
let base_tag = unsafe { &*ptr.cast::<Tag>() };
80-
let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag));
81-
82-
Self {
83-
ptr: NonNull::new(raw).unwrap(),
84-
layout,
85-
_marker: PhantomData,
86-
}
87-
}
88-
}
89-
90-
impl<T: ?Sized> Drop for BoxedDst<T> {
91-
fn drop(&mut self) {
92-
unsafe { alloc::alloc::dealloc(self.ptr.as_ptr().cast(), self.layout) }
93-
}
94-
}
95-
96-
impl<T: ?Sized> Deref for BoxedDst<T> {
97-
type Target = T;
98-
fn deref(&self) -> &Self::Target {
99-
unsafe { self.ptr.as_ref() }
100-
}
101-
}
102-
103-
impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
104-
fn eq(&self, other: &Self) -> bool {
105-
self.deref().eq(other.deref())
106-
}
107-
}
108-
109-
#[cfg(test)]
110-
mod tests {
111-
use super::*;
112-
use crate::TagType;
113-
114-
const METADATA_SIZE: usize = 8;
115-
116-
#[derive(ptr_meta::Pointee)]
117-
#[repr(C)]
118-
struct CustomTag {
119-
typ: TagTypeId,
120-
size: u32,
121-
string: [u8],
122-
}
123-
124-
impl CustomTag {
125-
fn string(&self) -> Result<&str, core::str::Utf8Error> {
126-
Tag::get_dst_str_slice(&self.string)
127-
}
128-
}
129-
130-
impl TagTrait for CustomTag {
131-
const ID: TagType = TagType::Custom(0x1337);
132-
133-
fn dst_size(base_tag: &Tag) -> usize {
134-
assert!(base_tag.size as usize >= METADATA_SIZE);
135-
base_tag.size as usize - METADATA_SIZE
136-
}
137-
}
138-
139-
#[test]
140-
fn test_boxed_dst_tag() {
141-
let content = "hallo";
142-
143-
let tag = BoxedDst::<CustomTag>::new(content.as_bytes());
144-
assert_eq!(tag.typ, CustomTag::ID);
145-
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
146-
assert_eq!(tag.string(), Ok(content));
10+
/// Helper trait for all structs that need to be serialized that do not
11+
/// implement `TagTrait`.
12+
pub trait AsBytes: Sized {
13+
fn as_bytes(&self) -> &[u8] {
14+
let ptr = core::ptr::addr_of!(*self);
15+
let size = core::mem::size_of::<Self>();
16+
unsafe { core::slice::from_raw_parts(ptr.cast(), size) }
14717
}
14818
}

0 commit comments

Comments
 (0)