builder.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. //! Exports item [`Multiboot2HeaderBuilder`].
  2. use crate::HeaderTagISA;
  3. use crate::{
  4. AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryEfi32HeaderTag,
  5. EntryEfi64HeaderTag, EntryHeaderTag, FramebufferHeaderTag, InformationRequestHeaderTagBuilder,
  6. ModuleAlignHeaderTag, Multiboot2BasicHeader, RelocatableHeaderTag, StructAsBytes,
  7. };
  8. use core::mem::size_of;
  9. /// Builder to construct a valid Multiboot2 header dynamically at runtime.
  10. /// The tags will appear in the order of their corresponding enumeration,
  11. /// except for the END tag.
  12. #[derive(Debug)]
  13. pub struct Multiboot2HeaderBuilder {
  14. arch: HeaderTagISA,
  15. // first
  16. information_request_tag: Option<InformationRequestHeaderTagBuilder>,
  17. // second
  18. address_tag: Option<AddressHeaderTag>,
  19. // third
  20. entry_tag: Option<EntryHeaderTag>,
  21. // fourth
  22. console_tag: Option<ConsoleHeaderTag>,
  23. // fifth
  24. framebuffer_tag: Option<FramebufferHeaderTag>,
  25. // sixth
  26. module_align_tag: Option<ModuleAlignHeaderTag>,
  27. // seventh
  28. efi_bs_tag: Option<EfiBootServiceHeaderTag>,
  29. // eighth
  30. efi_32_tag: Option<EntryEfi32HeaderTag>,
  31. // ninth
  32. efi_64_tag: Option<EntryEfi64HeaderTag>,
  33. // tenth (last)
  34. relocatable_tag: Option<RelocatableHeaderTag>,
  35. }
  36. impl Multiboot2HeaderBuilder {
  37. pub const fn new(arch: HeaderTagISA) -> Self {
  38. Self {
  39. arch,
  40. information_request_tag: None,
  41. address_tag: None,
  42. entry_tag: None,
  43. console_tag: None,
  44. framebuffer_tag: None,
  45. module_align_tag: None,
  46. efi_bs_tag: None,
  47. efi_32_tag: None,
  48. efi_64_tag: None,
  49. relocatable_tag: None,
  50. }
  51. }
  52. /// Returns the size, if the value is a multiple of 8 or returns
  53. /// the next number that is a multiple of 8. With this, one can
  54. /// easily calculate the size of a Multiboot2 header, where
  55. /// all the tags are 8-byte aligned.
  56. const fn size_or_up_aligned(size: usize) -> usize {
  57. let remainder = size % 8;
  58. if remainder == 0 {
  59. size
  60. } else {
  61. size + 8 - remainder
  62. }
  63. }
  64. /// Returns the expected length of the Multiboot2 header,
  65. /// when the `build()`-method gets called.
  66. pub fn expected_len(&self) -> usize {
  67. let base_len = size_of::<Multiboot2BasicHeader>();
  68. // size_or_up_aligned not required, because basic header length is 16 and the
  69. // begin is 8 byte aligned => first tag automatically 8 byte aligned
  70. let mut len = Self::size_or_up_aligned(base_len);
  71. if let Some(tag_builder) = self.information_request_tag.as_ref() {
  72. // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address.
  73. // Attention: expected len from builder, not the size of the builder itself!
  74. len += Self::size_or_up_aligned(tag_builder.expected_len())
  75. }
  76. if self.address_tag.is_some() {
  77. // we use size_or_up_aligned, because each tag will start at an 8 byte aligned address
  78. len += Self::size_or_up_aligned(size_of::<AddressHeaderTag>())
  79. }
  80. if self.entry_tag.is_some() {
  81. len += Self::size_or_up_aligned(size_of::<EntryHeaderTag>())
  82. }
  83. if self.console_tag.is_some() {
  84. len += Self::size_or_up_aligned(size_of::<ConsoleHeaderTag>())
  85. }
  86. if self.framebuffer_tag.is_some() {
  87. len += Self::size_or_up_aligned(size_of::<FramebufferHeaderTag>())
  88. }
  89. if self.module_align_tag.is_some() {
  90. len += Self::size_or_up_aligned(size_of::<ModuleAlignHeaderTag>())
  91. }
  92. if self.efi_bs_tag.is_some() {
  93. len += Self::size_or_up_aligned(size_of::<EfiBootServiceHeaderTag>())
  94. }
  95. if self.efi_32_tag.is_some() {
  96. len += Self::size_or_up_aligned(size_of::<EntryEfi32HeaderTag>())
  97. }
  98. if self.efi_64_tag.is_some() {
  99. len += Self::size_or_up_aligned(size_of::<EntryEfi64HeaderTag>())
  100. }
  101. if self.relocatable_tag.is_some() {
  102. len += Self::size_or_up_aligned(size_of::<RelocatableHeaderTag>())
  103. }
  104. // only here size_or_up_aligned is not important, because it is the last tag
  105. len += size_of::<EndHeaderTag>();
  106. len
  107. }
  108. /// Adds the bytes of a tag to the final Multiboot2 header byte vector.
  109. /// Align should be true for all tags except the end tag.
  110. fn build_add_bytes(dest: &mut Vec<u8>, source: &[u8], is_end_tag: bool) {
  111. dest.extend(source);
  112. if !is_end_tag {
  113. let size = source.len();
  114. let size_to_8_align = Self::size_or_up_aligned(size);
  115. let size_to_8_align_diff = size_to_8_align - size;
  116. // fill zeroes so that next data block is 8-byte aligned
  117. dest.extend([0].repeat(size_to_8_align_diff));
  118. }
  119. }
  120. /// Constructs the bytes for a valid Multiboot2 header with the given properties.
  121. /// The bytes can be casted to a Multiboot2 structure.
  122. pub fn build(mut self) -> Vec<u8> {
  123. let mut data = Vec::new();
  124. Self::build_add_bytes(
  125. &mut data,
  126. // important that we write the correct expected length into the header!
  127. &Multiboot2BasicHeader::new(self.arch, self.expected_len() as u32).struct_as_bytes(),
  128. false,
  129. );
  130. if self.information_request_tag.is_some() {
  131. Self::build_add_bytes(
  132. &mut data,
  133. &self.information_request_tag.take().unwrap().build(),
  134. false,
  135. )
  136. }
  137. if let Some(tag) = self.address_tag.as_ref() {
  138. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  139. }
  140. if let Some(tag) = self.entry_tag.as_ref() {
  141. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  142. }
  143. if let Some(tag) = self.console_tag.as_ref() {
  144. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  145. }
  146. if let Some(tag) = self.framebuffer_tag.as_ref() {
  147. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  148. }
  149. if let Some(tag) = self.module_align_tag.as_ref() {
  150. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  151. }
  152. if let Some(tag) = self.efi_bs_tag.as_ref() {
  153. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  154. }
  155. if let Some(tag) = self.efi_32_tag.as_ref() {
  156. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  157. }
  158. if let Some(tag) = self.efi_64_tag.as_ref() {
  159. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  160. }
  161. if let Some(tag) = self.relocatable_tag.as_ref() {
  162. Self::build_add_bytes(&mut data, &tag.struct_as_bytes(), false)
  163. }
  164. Self::build_add_bytes(&mut data, &EndHeaderTag::new().struct_as_bytes(), true);
  165. data
  166. }
  167. // clippy thinks this can be a const fn but the compiler denies it
  168. #[allow(clippy::missing_const_for_fn)]
  169. pub fn information_request_tag(
  170. mut self,
  171. information_request_tag: InformationRequestHeaderTagBuilder,
  172. ) -> Self {
  173. self.information_request_tag = Some(information_request_tag);
  174. self
  175. }
  176. pub const fn address_tag(mut self, address_tag: AddressHeaderTag) -> Self {
  177. self.address_tag = Some(address_tag);
  178. self
  179. }
  180. pub const fn entry_tag(mut self, entry_tag: EntryHeaderTag) -> Self {
  181. self.entry_tag = Some(entry_tag);
  182. self
  183. }
  184. pub const fn console_tag(mut self, console_tag: ConsoleHeaderTag) -> Self {
  185. self.console_tag = Some(console_tag);
  186. self
  187. }
  188. pub const fn framebuffer_tag(mut self, framebuffer_tag: FramebufferHeaderTag) -> Self {
  189. self.framebuffer_tag = Some(framebuffer_tag);
  190. self
  191. }
  192. pub const fn module_align_tag(mut self, module_align_tag: ModuleAlignHeaderTag) -> Self {
  193. self.module_align_tag = Some(module_align_tag);
  194. self
  195. }
  196. pub const fn efi_bs_tag(mut self, efi_bs_tag: EfiBootServiceHeaderTag) -> Self {
  197. self.efi_bs_tag = Some(efi_bs_tag);
  198. self
  199. }
  200. pub const fn efi_32_tag(mut self, efi_32_tag: EntryEfi32HeaderTag) -> Self {
  201. self.efi_32_tag = Some(efi_32_tag);
  202. self
  203. }
  204. pub const fn efi_64_tag(mut self, efi_64_tag: EntryEfi64HeaderTag) -> Self {
  205. self.efi_64_tag = Some(efi_64_tag);
  206. self
  207. }
  208. pub const fn relocatable_tag(mut self, relocatable_tag: RelocatableHeaderTag) -> Self {
  209. self.relocatable_tag = Some(relocatable_tag);
  210. self
  211. }
  212. }
  213. #[cfg(test)]
  214. mod tests {
  215. use crate::{
  216. HeaderTagFlag, HeaderTagISA, InformationRequestHeaderTagBuilder, MbiTagType,
  217. Multiboot2Header, Multiboot2HeaderBuilder, RelocatableHeaderTag,
  218. RelocatableHeaderTagPreference,
  219. };
  220. #[test]
  221. fn test_size_or_up_aligned() {
  222. assert_eq!(0, Multiboot2HeaderBuilder::size_or_up_aligned(0));
  223. assert_eq!(8, Multiboot2HeaderBuilder::size_or_up_aligned(1));
  224. assert_eq!(8, Multiboot2HeaderBuilder::size_or_up_aligned(8));
  225. assert_eq!(16, Multiboot2HeaderBuilder::size_or_up_aligned(9));
  226. }
  227. #[test]
  228. fn test_size_builder() {
  229. let builder = Multiboot2HeaderBuilder::new(HeaderTagISA::I386);
  230. // Multiboot2 basic header + end tag
  231. let mut expected_len = 16 + 8;
  232. assert_eq!(builder.expected_len(), expected_len);
  233. // add information request tag
  234. let ifr_builder =
  235. InformationRequestHeaderTagBuilder::new(HeaderTagFlag::Required).add_irs(&[
  236. MbiTagType::EfiMmap,
  237. MbiTagType::Cmdline,
  238. MbiTagType::ElfSections,
  239. ]);
  240. let ifr_tag_size_with_padding = ifr_builder.expected_len() + 4;
  241. assert_eq!(
  242. ifr_tag_size_with_padding % 8,
  243. 0,
  244. "the length of the IFR tag with padding must be a multiple of 8"
  245. );
  246. expected_len += ifr_tag_size_with_padding;
  247. let builder = builder.information_request_tag(ifr_builder);
  248. assert_eq!(builder.expected_len(), expected_len);
  249. let builder = builder.relocatable_tag(RelocatableHeaderTag::new(
  250. HeaderTagFlag::Required,
  251. 0x1337,
  252. 0xdeadbeef,
  253. 4096,
  254. RelocatableHeaderTagPreference::None,
  255. ));
  256. println!("builder: {:#?}", builder);
  257. println!("expected_len: {} bytes", builder.expected_len());
  258. let mb2_hdr_data = builder.build();
  259. let mb2_hdr = mb2_hdr_data.as_ptr() as usize;
  260. let mb2_hdr = unsafe { Multiboot2Header::from_addr(mb2_hdr) };
  261. println!("{:#?}", mb2_hdr);
  262. /* you can write the binary to a file and a tool such as crate "bootinfo"
  263. will be able to fully parse the MB2 header
  264. let mut file = std::file::File::create("mb2_hdr.bin").unwrap();
  265. use std::io::Write;
  266. file.write_all(mb2_hdr_data.as_slice()).unwrap();*/
  267. }
  268. }