linux_bootloader/
pe_loader.rs1use core::ffi::c_void;
2
3use alloc::vec::Vec;
4use goblin::pe::PE;
5use uefi::{
6 prelude::BootServices,
7 proto::loaded_image::LoadedImage,
8 table::{
9 boot::{AllocateType, MemoryType},
10 Boot, SystemTable,
11 },
12 CStr16, Handle, Status,
13};
14
15const UEFI_PAGE_BITS: usize = 12;
17const UEFI_PAGE_MASK: usize = (1 << UEFI_PAGE_BITS) - 1;
18
19#[cfg(target_arch = "aarch64")]
20fn make_instruction_cache_coherent(memory: &[u8]) {
21 use core::arch::asm;
22 const CACHE_LINE_SIZE: usize = 16;
25
26 let start_address = memory.as_ptr() as usize & CACHE_LINE_SIZE.wrapping_neg();
29 let end_address = ((memory.as_ptr() as usize + memory.len() - 1) | (CACHE_LINE_SIZE - 1)) + 1;
30
31 for address in (start_address..end_address).step_by(CACHE_LINE_SIZE) {
35 unsafe {
36 asm!("dc cvau, {address}", address = in(reg) address);
38 }
39 }
40 unsafe {
41 asm!("dsb ish");
43 }
44
45 for address in (start_address..end_address).step_by(4) {
47 unsafe {
48 asm!("ic ivau, {address}", address = in(reg) address);
50 }
51 }
52 unsafe {
53 asm! {
55 "dsb ish",
56 "isb",
57 }
58 }
59}
60
61#[cfg(target_arch = "x86")]
62fn make_instruction_cache_coherent(_memory: &[u8]) {
63 }
65
66#[cfg(target_arch = "x86_64")]
67fn make_instruction_cache_coherent(_memory: &[u8]) {
68 }
70
71pub struct Image {
72 image: &'static mut [u8],
73 entry: extern "efiapi" fn(Handle, SystemTable<Boot>) -> Status,
74}
75
76fn bytes_to_pages(bytes: usize) -> usize {
78 bytes
79 .checked_add(UEFI_PAGE_MASK)
80 .map(|rounded_up| rounded_up >> UEFI_PAGE_BITS)
81 .unwrap_or(1 << (usize::try_from(usize::BITS).unwrap() - UEFI_PAGE_BITS))
82}
83
84impl Image {
85 pub fn load(boot_services: &BootServices, file_data: &[u8]) -> uefi::Result<Image> {
91 let pe = PE::parse(file_data).map_err(|_| Status::LOAD_ERROR)?;
92
93 let image = {
96 let section_lengths = pe
97 .sections
98 .iter()
99 .map(|section| {
100 section
101 .virtual_address
102 .checked_add(section.virtual_size)
103 .ok_or(Status::LOAD_ERROR)
104 })
105 .collect::<Result<Vec<u32>, uefi::Status>>()?;
106
107 let length = usize::try_from(section_lengths.into_iter().max().unwrap_or(0)).unwrap();
108
109 let base = boot_services.allocate_pages(
110 AllocateType::AnyPages,
111 MemoryType::LOADER_CODE,
112 bytes_to_pages(length),
113 )? as *mut u8;
114
115 unsafe {
116 core::ptr::write_bytes(base, 0, length);
117 core::slice::from_raw_parts_mut(base, length)
118 }
119 };
120
121 for section in &pe.sections {
123 let copy_size =
124 usize::try_from(u32::min(section.virtual_size, section.size_of_raw_data)).unwrap();
125 let raw_start = usize::try_from(section.pointer_to_raw_data).unwrap();
126 let raw_end = raw_start.checked_add(copy_size).ok_or(Status::LOAD_ERROR)?;
127 let virt_start = usize::try_from(section.virtual_address).unwrap();
128 let virt_end = virt_start
129 .checked_add(copy_size)
130 .ok_or(Status::LOAD_ERROR)?;
131
132 if virt_end > image.len() || raw_end > file_data.len() {
133 return Err(Status::LOAD_ERROR.into());
134 }
135 image[virt_start..virt_end].copy_from_slice(&file_data[raw_start..raw_end]);
136 }
137
138 if pe
140 .header
141 .optional_header
142 .and_then(|h| *h.data_directories.get_base_relocation_table())
143 .is_some()
144 {
145 return Err(Status::INCOMPATIBLE_VERSION.into());
146 }
147
148 make_instruction_cache_coherent(image);
152
153 if pe.entry >= image.len() {
154 return Err(Status::LOAD_ERROR.into());
155 }
156 let entry = unsafe { core::mem::transmute(&image[pe.entry]) };
157
158 Ok(Image { image, entry })
159 }
160
161 pub unsafe fn start(
174 self,
175 handle: Handle,
176 system_table: &SystemTable<Boot>,
177 load_options: &CStr16,
178 ) -> Status {
179 let mut loaded_image = system_table
180 .boot_services()
181 .open_protocol_exclusive::<LoadedImage>(handle)
182 .expect("Failed to open the LoadedImage protocol");
183
184 let (our_data, our_size) = loaded_image.info();
185 let our_load_options = loaded_image
186 .load_options_as_bytes()
187 .map(|options| options.as_ptr_range());
188
189 unsafe {
193 loaded_image.set_image(
194 self.image.as_ptr() as *const c_void,
195 self.image.len().try_into().unwrap(),
196 );
197 loaded_image.set_load_options(
198 load_options.as_ptr() as *const u8,
199 u32::try_from(load_options.num_bytes()).unwrap(),
200 );
201 }
202
203 let status = (self.entry)(handle, unsafe { system_table.unsafe_clone() });
204
205 system_table
209 .boot_services()
210 .free_pages(self.image.as_ptr() as u64, bytes_to_pages(self.image.len()))
211 .expect("Double free attempted");
212
213 unsafe {
214 loaded_image.set_image(our_data, our_size);
215 match our_load_options {
216 Some(options) => loaded_image.set_load_options(
217 options.start,
218 options.end.offset_from(options.start).try_into().unwrap(),
219 ),
220 None => loaded_image.set_load_options(core::ptr::null(), 0),
221 }
222 }
223
224 status
225 }
226}