Skip to main content

multiboot2_header/
relocatable.rs

1use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType};
2use core::fmt;
3use core::fmt::{Debug, Formatter};
4use multiboot2_common::{MaybeDynSized, Tag};
5
6multiboot2_common::raw_type! {
7    /// ABI compatible representation of the placement preference of the
8    /// relocatable header tag.
9    ///
10    /// This type matches the binary representation (`u32`).
11    pub struct RelocatableHeaderTagPreferenceRaw(u32);
12
13    /// The bootloader's preferred placement for a relocatable image.
14    ///
15    /// This is a higher level abstraction for
16    /// [`RelocatableHeaderTagPreferenceRaw`].
17    pub enum RelocatableHeaderTagPreference {
18        /// Let the bootloader choose the image location.
19        None = 0,
20        /// Load the image at the lowest possible address that is not below
21        /// `min_addr`.
22        Low = 1,
23        /// Load the image at the highest possible address that does not end above
24        /// `max_addr`.
25        High = 2,
26    }
27}
28
29/// This tag indicates that the image is relocatable.
30#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[repr(C, align(8))]
32pub struct RelocatableHeaderTag {
33    header: HeaderTagHeader,
34    /// Lowest physical address at which the image may be loaded.
35    ///
36    /// The bootloader cannot load any part of the image below this address.
37    min_addr: u32,
38    /// Highest physical address at which the loaded image may end.
39    ///
40    /// The bootloader cannot load any part of the image above this address.
41    max_addr: u32,
42    /// Image alignment in memory, e.g. 4096.
43    align: u32,
44    preference: RelocatableHeaderTagPreferenceRaw,
45}
46
47const _: () = assert!(size_of::<RelocatableHeaderTag>() == 2 + 2 + 4 + 4 + 4 + 4 + 4);
48
49impl RelocatableHeaderTag {
50    /// Constructs a new tag.
51    #[must_use]
52    pub const fn new(
53        flags: HeaderTagFlag,
54        min_addr: u32,
55        max_addr: u32,
56        align: u32,
57        preference: RelocatableHeaderTagPreference,
58    ) -> Self {
59        let header =
60            HeaderTagHeader::new(HeaderTagType::Relocatable, flags, size_of::<Self>() as u32);
61        Self {
62            header,
63            min_addr,
64            max_addr,
65            align,
66            preference: RelocatableHeaderTagPreferenceRaw::new(preference.val()),
67        }
68    }
69
70    /// Returns the [`HeaderTagType`].
71    #[must_use]
72    pub const fn typ(&self) -> HeaderTagType {
73        self.header.typ()
74    }
75
76    /// Returns the [`HeaderTagFlag`]s.
77    #[must_use]
78    pub const fn flags(&self) -> HeaderTagFlag {
79        self.header.flags()
80    }
81
82    /// Returns the size.
83    #[must_use]
84    pub const fn size(&self) -> u32 {
85        self.header.size()
86    }
87
88    /// Return the minimum address.
89    #[must_use]
90    pub const fn min_addr(&self) -> u32 {
91        self.min_addr
92    }
93
94    /// Return the maximum address.
95    #[must_use]
96    pub const fn max_addr(&self) -> u32 {
97        self.max_addr
98    }
99
100    /// Return the alignment.
101    #[must_use]
102    pub const fn align(&self) -> u32 {
103        self.align
104    }
105
106    /// Return the preference.
107    #[must_use]
108    pub const fn preference(&self) -> RelocatableHeaderTagPreference {
109        RelocatableHeaderTagPreference::from_val(self.preference.get())
110    }
111}
112
113impl Debug for RelocatableHeaderTag {
114    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
115        f.debug_struct("RelocatableHeaderTag")
116            .field("type", &self.typ())
117            .field("flags", &self.flags())
118            .field("size", &self.size())
119            // trick to print this as hexadecimal pointer
120            .field("min_addr", &self.min_addr)
121            .field("max_addr", &self.max_addr)
122            .field("align", &self.align)
123            .field("preference", &self.preference)
124            .finish()
125    }
126}
127
128// SAFETY: The tag is repr(C) with the header as first field, any bit
129// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
130unsafe impl MaybeDynSized for RelocatableHeaderTag {
131    type Header = HeaderTagHeader;
132
133    const BASE_SIZE: usize = size_of::<Self>();
134}
135
136impl Tag for RelocatableHeaderTag {
137    type IDType = HeaderTagType;
138    const ID: HeaderTagType = HeaderTagType::Relocatable;
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use crate::GenericHeaderTag;
145    use core::borrow::Borrow;
146    use multiboot2_common::test_utils::AlignedBytes;
147
148    /// A tag with a placement preference unknown to the specification must
149    /// be parsable without undefined behavior.
150    #[test]
151    fn unknown_preference_is_not_ub() {
152        #[rustfmt::skip]
153        let bytes = AlignedBytes::new([
154            /* typ = relocatable */
155            10, 0,
156            /* flags */
157            0, 0,
158            /* size */
159            24, 0, 0, 0,
160            /* min_addr, max_addr, align */
161            0, 0, 0, 0,
162            0, 0, 0, 0,
163            0, 0, 0, 0,
164            /* preference = 99 (unknown) */
165            99, 0, 0, 0,
166        ]);
167        let tag = GenericHeaderTag::ref_from_slice(bytes.borrow())
168            .unwrap()
169            .cast::<RelocatableHeaderTag>();
170
171        assert_eq!(tag.preference(), RelocatableHeaderTagPreference::Custom(99));
172        // The Debug implementation must also cope with unknown values.
173        let debug = format!("{tag:?}");
174        assert!(debug.contains("Custom(99)"));
175    }
176}