1use crate::common::ChunkBuf;
14use crate::common::ImportFileRegion;
15use crate::common::ImportFileRegionError;
16use crate::importer::GuestArch;
17use crate::importer::GuestArchKind;
18use crate::importer::ImageLoad;
19use hvdef::HV_PAGE_SIZE;
20use object::ReadCache;
21use object::ReadRef;
22use object::elf;
23use object::read::elf::FileHeader;
24use std::io::Read;
25use std::io::Seek;
26use thiserror::Error;
27
28type LE = object::LittleEndian;
29const LE: LE = LE {};
30
31#[derive(Debug, Error)]
32pub enum Error {
33 #[error("failed to read file header")]
34 ReadFileHeader,
35 #[error("invalid file header")]
36 InvalidFileHeader,
37 #[error("target machine mismatch")]
38 TargetMachineMismatch,
39 #[error("unsupported ELF file byte order")]
40 BigEndianElfOnLittle,
41 #[error(
42 "invalid entry address found in ELF header: {e_entry:#x}, start address: {start_address:#x}, load offset: {load_offset:#x}"
43 )]
44 InvalidEntryAddress {
45 e_entry: u64,
46 start_address: u64,
47 load_offset: u64,
48 },
49 #[error("failed to parse ELF program header")]
50 InvalidProgramHeader(#[source] object::read::Error),
51 #[error("ELF image contains no non-empty loadable segments")]
52 NoLoadableSegments,
53 #[error("adding load offset {load_offset} to paddr {p_paddr} overflowed")]
54 LoadOffsetOverflow { load_offset: u64, p_paddr: u64 },
55 #[error("invalid ELF program header memory offset {mem_offset}, below start {start_address}")]
56 InvalidProgramHeaderMemoryOffset { mem_offset: u64, start_address: u64 },
57 #[error(
58 "ELF program header memory range at {mem_offset:#x} with size {p_memsz:#x} cannot be represented"
59 )]
60 InvalidProgramHeaderMemoryRange { mem_offset: u64, p_memsz: u64 },
61 #[error("ELF entry address {entry:#x} is not in a non-empty loadable segment")]
62 EntryPointNotInLoadableSegment { entry: u64 },
63 #[error(
64 "adding reloc bias {reloc_bias} and load offset {load_offset} to paddr {p_paddr} overflowed"
65 )]
66 RelocBiasOverflow {
67 load_offset: u64,
68 reloc_bias: u64,
69 p_paddr: u64,
70 },
71 #[error("failed to read kernel image")]
72 ReadKernelImage,
73 #[error("failed to import file region")]
74 ImportFileRegion(#[source] ImportFileRegionError),
75 #[error("failed to seek to offset of kernel image")]
76 SeekKernelImage,
77}
78
79pub type Result<T> = std::result::Result<T, Error>;
80
81#[derive(Debug)]
83pub struct LoadInfo {
84 pub minimum_address_used: u64,
87 pub next_available_address: u64,
89 pub entrypoint: u64,
91}
92
93pub fn load_static_elf<F, R: GuestArch>(
107 importer: &mut dyn ImageLoad<R>,
108 kernel_image: &mut F,
109 start_address: u64,
110 load_offset: u64,
111 assume_pic: bool,
112 acceptance: crate::importer::BootPageAcceptance,
113 tag: &'static str,
114) -> Result<LoadInfo>
115where
116 F: Read + Seek,
117{
118 let reader = ReadCache::new(&mut *kernel_image);
119 let ehdr: &elf::FileHeader64<LE> = reader.read_at(0).map_err(|_| Error::ReadFileHeader)?;
120
121 if !ehdr.is_supported() {
123 return Err(Error::InvalidFileHeader);
124 }
125 if ehdr.is_big_endian() {
126 return Err(Error::BigEndianElfOnLittle);
127 }
128
129 match R::arch() {
130 GuestArchKind::Aarch64 => {
131 if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_AARCH64) {
132 tracing::error!(
133 "ELF file target machine mismatch, was the file built for aarch64?"
134 );
135 return Err(Error::TargetMachineMismatch);
136 }
137 }
138 GuestArchKind::X86_64 => {
139 if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_X86_64) {
140 tracing::error!("ELF file target machine mismatch, was the file built for X86_64?");
141 return Err(Error::TargetMachineMismatch);
142 }
143 }
144 }
145
146 let e_entry = ehdr.e_entry.get(LE);
147 let phdrs = ehdr
148 .program_headers(LE, &reader)
149 .map_err(Error::InvalidProgramHeader)?;
150 if !phdrs
151 .iter()
152 .any(|phdr| phdr.p_type.get(LE) == elf::PT_LOAD && phdr.p_memsz.get(LE) != 0)
153 {
154 return Err(Error::NoLoadableSegments);
155 }
156
157 let load_offset = if assume_pic {
164 let mut lowest_paddr = u64::MAX;
165 for phdr in phdrs {
166 if phdr.p_type.get(LE) == elf::PT_LOAD && phdr.p_memsz.get(LE) != 0 {
167 let p_paddr = phdr.p_paddr.get(LE);
168 lowest_paddr = lowest_paddr.min(p_paddr);
169 }
170 }
171 if lowest_paddr < start_address {
172 start_address - lowest_paddr + load_offset
173 } else {
174 load_offset
175 }
176 } else {
177 load_offset
178 };
179
180 let entry = e_entry
181 .checked_add(load_offset)
182 .ok_or(Error::InvalidEntryAddress {
183 e_entry,
184 start_address,
185 load_offset,
186 })?;
187 if entry < start_address {
188 return Err(Error::InvalidEntryAddress {
189 e_entry,
190 start_address,
191 load_offset,
192 });
193 }
194
195 struct SegmentInfo {
198 p_offset: u64,
199 p_paddr: u64,
200 p_filesz: u64,
201 p_memsz: u64,
202 mem_offset: u64,
203 mem_end: u64,
204 }
205 let mut segments = Vec::new();
206
207 let (lowest_addr, last_offset, reloc_bias) = {
208 let mut lowest_addr = u64::MAX;
209 let mut last_offset = load_offset;
210
211 for phdr in phdrs {
213 let p_memsz = phdr.p_memsz.get(LE);
214 if phdr.p_type.get(LE) != elf::PT_LOAD || p_memsz == 0 {
215 continue;
216 }
217
218 let p_paddr = phdr.p_paddr.get(LE);
219 let mem_offset = p_paddr
220 .checked_add(load_offset)
221 .ok_or(Error::LoadOffsetOverflow {
222 load_offset,
223 p_paddr,
224 })?;
225
226 if mem_offset < start_address {
227 return Err(Error::InvalidProgramHeaderMemoryOffset {
228 mem_offset,
229 start_address,
230 });
231 }
232
233 let mem_end =
234 mem_offset
235 .checked_add(p_memsz)
236 .ok_or(Error::InvalidProgramHeaderMemoryRange {
237 mem_offset,
238 p_memsz,
239 })?;
240 let page_base = mem_offset & !(HV_PAGE_SIZE - 1);
241 let page_end = mem_end.checked_next_multiple_of(HV_PAGE_SIZE).ok_or(
242 Error::InvalidProgramHeaderMemoryRange {
243 mem_offset,
244 p_memsz,
245 },
246 )?;
247
248 lowest_addr = lowest_addr.min(page_base);
249 last_offset = last_offset.max(page_end);
250
251 segments.push(SegmentInfo {
252 p_offset: phdr.p_offset.get(LE),
253 p_paddr,
254 p_filesz: phdr.p_filesz.get(LE),
255 p_memsz,
256 mem_offset,
257 mem_end,
258 });
259 }
260
261 (
262 lowest_addr,
263 last_offset,
264 if assume_pic {
265 lowest_addr - start_address
266 } else {
267 0
268 },
269 )
270 };
271 let relocated_entry = entry
272 .checked_sub(reloc_bias)
273 .ok_or(Error::InvalidEntryAddress {
274 e_entry,
275 start_address,
276 load_offset,
277 })?;
278 if !segments.iter().any(|seg| {
279 seg.mem_offset
280 .checked_sub(reloc_bias)
281 .zip(seg.mem_end.checked_sub(reloc_bias))
282 .is_some_and(|(start, end)| (start..end).contains(&relocated_entry))
283 }) {
284 return Err(Error::EntryPointNotInLoadableSegment {
285 entry: relocated_entry,
286 });
287 }
288
289 drop(reader);
291
292 let mut buf = ChunkBuf::new();
294 for seg in &segments {
295 let mem_offset =
296 seg.mem_offset
297 .checked_sub(reloc_bias)
298 .ok_or(Error::RelocBiasOverflow {
299 load_offset,
300 reloc_bias,
301 p_paddr: seg.p_paddr,
302 })?;
303
304 if mem_offset < start_address {
305 return Err(Error::InvalidProgramHeaderMemoryOffset {
306 mem_offset,
307 start_address,
308 });
309 }
310
311 if seg.p_memsz > 0 {
312 buf.import_file_region(
313 importer,
314 ImportFileRegion {
315 file: kernel_image,
316 file_offset: seg.p_offset,
317 file_length: seg.p_filesz,
318 gpa: mem_offset,
319 memory_length: seg.p_memsz,
320 acceptance,
321 tag,
322 },
323 )
324 .map_err(Error::ImportFileRegion)?;
325 }
326 }
327
328 Ok(LoadInfo {
329 minimum_address_used: lowest_addr - reloc_bias,
330 next_available_address: last_offset - reloc_bias,
331 entrypoint: relocated_entry,
332 })
333}