header.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. //! Exports item [`HeaderBuilder`].
  2. use crate::builder::information_request::InformationRequestHeaderTagBuilder;
  3. use crate::builder::traits::StructAsBytes;
  4. use crate::HeaderTagISA;
  5. use crate::{
  6. AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag,
  7. EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag,
  8. ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag,
  9. };
  10. use alloc::boxed::Box;
  11. use alloc::vec::Vec;
  12. use core::mem::size_of;
  13. use core::ops::Deref;
  14. /// Holds the raw bytes of a boot information built with [`HeaderBuilder`]
  15. /// on the heap. The bytes returned by [`HeaderBytes::as_bytes`] are
  16. /// guaranteed to be properly aligned.
  17. #[derive(Clone, Debug)]
  18. pub struct HeaderBytes {
  19. // Offset into the bytes where the header starts. This is necessary to
  20. // guarantee alignment at the moment.
  21. offset: usize,
  22. structure_len: usize,
  23. bytes: Box<[u8]>,
  24. }
  25. impl HeaderBytes {
  26. /// Returns the bytes. They are guaranteed to be correctly aligned.
  27. pub fn as_bytes(&self) -> &[u8] {
  28. let slice = &self.bytes[self.offset..self.offset + self.structure_len];
  29. // At this point, the alignment is guaranteed. If not, something is
  30. // broken fundamentally.
  31. assert_eq!(slice.as_ptr().align_offset(8), 0);
  32. slice
  33. }
  34. }
  35. impl Deref for HeaderBytes {
  36. type Target = [u8];
  37. fn deref(&self) -> &Self::Target {
  38. self.as_bytes()
  39. }
  40. }
  41. /// Builder to construct a valid Multiboot2 header dynamically at runtime.
  42. /// The tags will appear in the order of their corresponding enumeration,
  43. /// except for the END tag.
  44. #[derive(Clone, Debug, PartialEq, Eq)]
  45. pub struct HeaderBuilder {
  46. arch: HeaderTagISA,
  47. // first
  48. information_request_tag: Option<InformationRequestHeaderTagBuilder>,
  49. // second
  50. address_tag: Option<AddressHeaderTag>,
  51. // third
  52. entry_tag: Option<EntryAddressHeaderTag>,
  53. // fourth
  54. console_tag: Option<ConsoleHeaderTag>,
  55. // fifth
  56. framebuffer_tag: Option<FramebufferHeaderTag>,
  57. // sixth
  58. module_align_tag: Option<ModuleAlignHeaderTag>,
  59. // seventh
  60. efi_bs_tag: Option<EfiBootServiceHeaderTag>,
  61. // eighth
  62. efi_32_tag: Option<EntryEfi32HeaderTag>,
  63. // ninth
  64. efi_64_tag: Option<EntryEfi64HeaderTag>,
  65. // tenth (last)
  66. relocatable_tag: Option<RelocatableHeaderTag>,
  67. }
  68. impl HeaderBuilder {
  69. pub const fn new(arch: HeaderTagISA) -> Self {
  70. Self {
  71. arch,
  72. information_request_tag: None,
  73. address_tag: None,
  74. entry_tag: None,
  75. console_tag: None,
  76. framebuffer_tag: None,
  77. module_align_tag: None,
  78. efi_bs_tag: None,
  79. efi_32_tag: None,
  80. efi_64_tag: None,
  81. relocatable_tag: None,
  82. }
  83. }
  84. /// Returns the size, if the value is a multiple of 8 or returns
  85. /// the next number that is a multiple of 8. With this, one can
  86. /// easily calculate the size of a Multiboot2 header, where
  87. /// all the tags are 8-byte aligned.
  88. const fn size_or_up_aligned(size: usize) -> usize {
  89. (size + 7) & !7
  90. }
  91. /// Returns the expected length of the Multiboot2 header, when the
  92. /// [`Self::build`]-method gets called.
  93. pub fn expected_len(&self) -> usize {
  94. let base_len = size_of::<Multiboot2BasicHeader>();
  95. // size_or_up_aligned not required, because basic header length is 16 and the
  96. // begin is 8 byte aligned => first tag automatically 8 byte aligned
  97. let mut len = Self::size_or_up_aligned(base_len);
  98. if let Some(tag_builder) = self.information_request_tag.as_ref() {
  99. // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address.
  100. // Attention: expected len from builder, not the size of the builder itself!
  101. len += Self::size_or_up_aligned(tag_builder.expected_len())
  102. }
  103. if self.address_tag.is_some() {
  104. // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address
  105. len += Self::size_or_up_aligned(size_of::<AddressHeaderTag>())
  106. }
  107. if self.entry_tag.is_some() {
  108. len += Self::size_or_up_aligned(size_of::<EntryAddressHeaderTag>())
  109. }
  110. if self.console_tag.is_some() {
  111. len += Self::size_or_up_aligned(size_of::<ConsoleHeaderTag>())
  112. }
  113. if self.framebuffer_tag.is_some() {
  114. len += Self::size_or_up_aligned(size_of::<FramebufferHeaderTag>())
  115. }
  116. if self.module_align_tag.is_some() {
  117. len += Self::size_or_up_aligned(size_of::<ModuleAlignHeaderTag>())
  118. }
  119. if self.efi_bs_tag.is_some() {
  120. len += Self::size_or_up_aligned(size_of::<EfiBootServiceHeaderTag>())
  121. }
  122. if self.efi_32_tag.is_some() {
  123. len += Self::size_or_up_aligned(size_of::<EntryEfi32HeaderTag>())
  124. }
  125. if self.efi_64_tag.is_some() {
  126. len += Self::size_or_up_aligned(size_of::<EntryEfi64HeaderTag>())
  127. }
  128. if self.relocatable_tag.is_some() {
  129. len += Self::size_or_up_aligned(size_of::<RelocatableHeaderTag>())
  130. }
  131. // only here size_or_up_aligned is not important, because it is the last tag
  132. len += size_of::<EndHeaderTag>();
  133. len
  134. }
  135. /// Adds the bytes of a tag to the final Multiboot2 header byte vector.
  136. fn build_add_bytes(dest: &mut Vec<u8>, source: &[u8], is_end_tag: bool) {
  137. let vec_next_write_ptr = unsafe { dest.as_ptr().add(dest.len()) };
  138. // At this point, the alignment is guaranteed. If not, something is
  139. // broken fundamentally.
  140. assert_eq!(vec_next_write_ptr.align_offset(8), 0);
  141. dest.extend(source);
  142. if !is_end_tag {
  143. let size = source.len();
  144. let size_to_8_align = Self::size_or_up_aligned(size);
  145. let size_to_8_align_diff = size_to_8_align - size;
  146. // fill zeroes so that next data block is 8-byte aligned
  147. dest.extend([0].repeat(size_to_8_align_diff));
  148. }
  149. }
  150. /// Constructs the bytes for a valid Multiboot2 header with the given properties.
  151. pub fn build(mut self) -> HeaderBytes {
  152. const ALIGN: usize = 8;
  153. // PHASE 1/3: Prepare Vector
  154. // We allocate more than necessary so that we can ensure an correct
  155. // alignment within this data.
  156. let expected_len = self.expected_len();
  157. let alloc_len = expected_len + 7;
  158. let mut bytes = Vec::<u8>::with_capacity(alloc_len);
  159. // Pointer to check that no relocation happened.
  160. let alloc_ptr = bytes.as_ptr();
  161. // As long as there is no nice way in stable Rust to guarantee the
  162. // alignment of a vector, I add zero bytes at the beginning and the
  163. // header might not start at the start of the allocation.
  164. //
  165. // Unfortunately, it is not possible to reliably test this in a unit
  166. // test as long as the allocator_api feature is not stable.
  167. // Due to my manual testing, however, it works.
  168. let offset = bytes.as_ptr().align_offset(ALIGN);
  169. bytes.extend([0].repeat(offset));
  170. // -----------------------------------------------
  171. // PHASE 2/3: Add Tags
  172. self.build_add_tags(&mut bytes);
  173. // -----------------------------------------------
  174. // PHASE 3/3: Finalize Vector
  175. // Ensure that the vector has the same length as it's capacity. This is
  176. // important so that miri doesn't complain that the boxed memory is
  177. // smaller than the original allocation.
  178. bytes.extend([0].repeat(bytes.capacity() - bytes.len()));
  179. assert_eq!(
  180. alloc_ptr,
  181. bytes.as_ptr(),
  182. "Vector was reallocated. Alignment of header probably broken!"
  183. );
  184. assert_eq!(
  185. bytes[0..offset].iter().sum::<u8>(),
  186. 0,
  187. "The offset to alignment area should be zero."
  188. );
  189. // Construct a box from a vec without `into_boxed_slice`. The latter
  190. // calls `shrink` on the allocator, which might reallocate this memory.
  191. // We don't want that!
  192. let bytes = unsafe { Box::from_raw(bytes.leak()) };
  193. HeaderBytes {
  194. offset,
  195. bytes,
  196. structure_len: expected_len,
  197. }
  198. }
  199. /// Helper method that adds all the tags to the given vector.
  200. fn build_add_tags(&mut self, bytes: &mut Vec<u8>) {
  201. Self::build_add_bytes(
  202. bytes,
  203. // important that we write the correct expected length into the header!
  204. &Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(),
  205. false,
  206. );
  207. if let Some(irs) = self.information_request_tag.clone() {
  208. Self::build_add_bytes(bytes, &irs.build(), false)
  209. }
  210. if let Some(tag) = self.address_tag.as_ref() {
  211. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  212. }
  213. if let Some(tag) = self.entry_tag.as_ref() {
  214. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  215. }
  216. if let Some(tag) = self.console_tag.as_ref() {
  217. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  218. }
  219. if let Some(tag) = self.framebuffer_tag.as_ref() {
  220. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  221. }
  222. if let Some(tag) = self.module_align_tag.as_ref() {
  223. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  224. }
  225. if let Some(tag) = self.efi_bs_tag.as_ref() {
  226. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  227. }
  228. if let Some(tag) = self.efi_32_tag.as_ref() {
  229. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  230. }
  231. if let Some(tag) = self.efi_64_tag.as_ref() {
  232. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  233. }
  234. if let Some(tag) = self.relocatable_tag.as_ref() {
  235. Self::build_add_bytes(bytes, &tag.struct_as_bytes(), false)
  236. }
  237. Self::build_add_bytes(bytes, &EndHeaderTag::new().struct_as_bytes(), true);
  238. }
  239. // clippy thinks this can be a const fn but the compiler denies it
  240. #[allow(clippy::missing_const_for_fn)]
  241. pub fn information_request_tag(
  242. mut self,
  243. information_request_tag: InformationRequestHeaderTagBuilder,
  244. ) -> Self {
  245. self.information_request_tag = Some(information_request_tag);
  246. self
  247. }
  248. pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
  249. self.address_tag = Some(address_tag);
  250. self
  251. }
  252. pub const fn entry_tag(mut self, entry_tag: EntryAddressHeaderTag) -> Self {
  253. self.entry_tag = Some(entry_tag);
  254. self
  255. }
  256. pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
  257. self.console_tag = Some(console_tag);
  258. self
  259. }
  260. pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
  261. self.framebuffer_tag = Some(framebuffer_tag);
  262. self
  263. }
  264. pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
  265. self.module_align_tag = Some(module_align_tag);
  266. self
  267. }
  268. pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
  269. self.efi_bs_tag = Some(efi_bs_tag);
  270. self
  271. }
  272. pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
  273. self.efi_32_tag = Some(efi_32_tag);
  274. self
  275. }
  276. pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
  277. self.efi_64_tag = Some(efi_64_tag);
  278. self
  279. }
  280. pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self {
  281. self.relocatable_tag = Some(relocatable_tag);
  282. self
  283. }
  284. }
  285. #[cfg(test)]
  286. mod tests {
  287. use crate::builder::header::HeaderBuilder;
  288. use crate::builder::information_request::InformationRequestHeaderTagBuilder;
  289. use crate::{
  290. HeaderTagFlag, HeaderTagISA, MbiTagType, Multiboot2Header, RelocatableHeaderTag,
  291. RelocatableHeaderTagPreference,
  292. };
  293. #[test]
  294. fn test_size_or_up_aligned() {
  295. assert_eq!(0, HeaderBuilder::size_or_up_aligned(0));
  296. assert_eq!(8, HeaderBuilder::size_or_up_aligned(1));
  297. assert_eq!(8, HeaderBuilder::size_or_up_aligned(8));
  298. assert_eq!(16, HeaderBuilder::size_or_up_aligned(9));
  299. }
  300. #[test]
  301. fn test_builder() {
  302. // Step 1/2: Build Header
  303. let (mb2_hdr_data, expected_len) = {
  304. let builder = HeaderBuilder::new(HeaderTagISA::I386);
  305. // Multiboot2 basic header + end tag
  306. let mut expected_len = 16 + 8;
  307. assert_eq!(builder.expected_len(), expected_len);
  308. // add information request tag
  309. let ifr_builder = InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required)
  310. .add_irs(&[
  311. MbiTagType::EfiMmap,
  312. MbiTagType::Cmdline,
  313. MbiTagType::ElfSections,
  314. ]);
  315. let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
  316. assert_eq!(
  317. ifr_tag_size_with_padding % 8,
  318. 0,
  319. "the length of the IFR tag with padding must be a multiple of 8"
  320. );
  321. expected_len += ifr_tag_size_with_padding;
  322. let builder = builder.information_request_tag(ifr_builder);
  323. assert_eq!(builder.expected_len(), expected_len);
  324. let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
  325. HeaderTagFlag::Required,
  326. 0x1337,
  327. 0xdeadbeef,
  328. 4096,
  329. RelocatableHeaderTagPreference::None,
  330. ));
  331. expected_len += 0x18;
  332. assert_eq!(builder.expected_len(), expected_len);
  333. println!("builder: {:#?}", builder);
  334. println!("expected_len: {} bytes", builder.expected_len());
  335. (builder.build(), expected_len)
  336. };
  337. assert_eq!(mb2_hdr_data.as_bytes().len(), expected_len);
  338. // Step 2/2: Test the built Header
  339. {
  340. let mb2_hdr = mb2_hdr_data.as_ptr().cast();
  341. let mb2_hdr = unsafe { Multiboot2Header::load(mb2_hdr) }
  342. .expect("the generated header to be loadable");
  343. println!("{:#?}", mb2_hdr);
  344. assert_eq!(
  345. mb2_hdr.relocatable_tag().unwrap().flags(),
  346. HeaderTagFlag::Required
  347. );
  348. assert_eq!(mb2_hdr.relocatable_tag().unwrap().min_addr(), 0x1337);
  349. assert_eq!(mb2_hdr.relocatable_tag().unwrap().max_addr(), 0xdeadbeef);
  350. assert_eq!(mb2_hdr.relocatable_tag().unwrap().align(), 4096);
  351. assert_eq!(
  352. mb2_hdr.relocatable_tag().unwrap().preference(),
  353. RelocatableHeaderTagPreference::None
  354. );
  355. /* you can write the binary to a file and a tool such as crate "bootinfo"
  356. will be able to fully parse the MB2 header
  357. let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
  358. use std::io::Write;
  359. file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
  360. }
  361. }
  362. }