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("adding load offset {load_offset} to paddr {p_paddr} overflowed")]
52 LoadOffsetOverflow { load_offset: u64, p_paddr: u64 },
53 #[error("invalid ELF program header memory offset {mem_offset}, below start {start_address}")]
54 InvalidProgramHeaderMemoryOffset { mem_offset: u64, start_address: u64 },
55 #[error(
56 "adding reloc bias {reloc_bias} and load offset {load_offset} to paddr {p_paddr} overflowed"
57 )]
58 RelocBiasOverflow {
59 load_offset: u64,
60 reloc_bias: u64,
61 p_paddr: u64,
62 },
63 #[error("failed to read kernel image")]
64 ReadKernelImage,
65 #[error("failed to import file region")]
66 ImportFileRegion(#[source] ImportFileRegionError),
67 #[error("failed to seek to offset of kernel image")]
68 SeekKernelImage,
69}
70
71pub type Result<T> = std::result::Result<T, Error>;
72
73#[derive(Debug)]
75pub struct LoadInfo {
76 pub minimum_address_used: u64,
79 pub next_available_address: u64,
81 pub entrypoint: u64,
83}
84
85pub fn load_static_elf<F, R: GuestArch>(
99 importer: &mut dyn ImageLoad<R>,
100 kernel_image: &mut F,
101 start_address: u64,
102 load_offset: u64,
103 assume_pic: bool,
104 acceptance: crate::importer::BootPageAcceptance,
105 tag: &str,
106) -> Result<LoadInfo>
107where
108 F: Read + Seek,
109{
110 let reader = ReadCache::new(&mut *kernel_image);
111 let ehdr: &elf::FileHeader64<LE> = reader.read_at(0).map_err(|_| Error::ReadFileHeader)?;
112
113 if !ehdr.is_supported() {
115 return Err(Error::InvalidFileHeader);
116 }
117 if ehdr.is_big_endian() {
118 return Err(Error::BigEndianElfOnLittle);
119 }
120
121 match R::arch() {
122 GuestArchKind::Aarch64 => {
123 if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_AARCH64) {
124 tracing::error!(
125 "ELF file target machine mismatch, was the file built for aarch64?"
126 );
127 return Err(Error::TargetMachineMismatch);
128 }
129 }
130 GuestArchKind::X86_64 => {
131 if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_X86_64) {
132 tracing::error!("ELF file target machine mismatch, was the file built for X86_64?");
133 return Err(Error::TargetMachineMismatch);
134 }
135 }
136 }
137
138 let e_entry = ehdr.e_entry.get(LE);
139 let phdrs = ehdr
140 .program_headers(LE, &reader)
141 .map_err(Error::InvalidProgramHeader)?;
142
143 let load_offset = if assume_pic {
150 let mut lowest_paddr = u64::MAX;
151 for phdr in phdrs {
152 if phdr.p_type.get(LE) == elf::PT_LOAD {
153 let p_paddr = phdr.p_paddr.get(LE);
154 lowest_paddr = lowest_paddr.min(p_paddr);
155 }
156 }
157 if lowest_paddr < start_address {
158 start_address - lowest_paddr + load_offset
159 } else {
160 load_offset
161 }
162 } else {
163 load_offset
164 };
165
166 let entry = e_entry
167 .checked_add(load_offset)
168 .ok_or(Error::InvalidEntryAddress {
169 e_entry,
170 start_address,
171 load_offset,
172 })?;
173 if entry < start_address {
174 return Err(Error::InvalidEntryAddress {
175 e_entry,
176 start_address,
177 load_offset,
178 });
179 }
180
181 struct SegmentInfo {
184 p_offset: u64,
185 p_paddr: u64,
186 p_filesz: u64,
187 p_memsz: u64,
188 }
189 let mut segments = Vec::new();
190
191 let (lowest_addr, last_offset, reloc_bias) = {
192 let mut lowest_addr = u64::MAX;
193 let mut last_offset = load_offset;
194
195 for phdr in phdrs {
197 if phdr.p_type.get(LE) != elf::PT_LOAD {
198 continue;
199 }
200
201 let p_paddr = phdr.p_paddr.get(LE);
202 let mem_offset = p_paddr
203 .checked_add(load_offset)
204 .ok_or(Error::LoadOffsetOverflow {
205 load_offset,
206 p_paddr,
207 })?;
208
209 if mem_offset < start_address {
210 return Err(Error::InvalidProgramHeaderMemoryOffset {
211 mem_offset,
212 start_address,
213 });
214 }
215
216 let page_mask = HV_PAGE_SIZE - 1;
217 let page_base = mem_offset / HV_PAGE_SIZE;
218 let page_count: u64 =
219 ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
220
221 lowest_addr = lowest_addr.min(page_base * HV_PAGE_SIZE);
222 last_offset = last_offset.max((page_base + page_count) * HV_PAGE_SIZE);
223
224 segments.push(SegmentInfo {
225 p_offset: phdr.p_offset.get(LE),
226 p_paddr,
227 p_filesz: phdr.p_filesz.get(LE),
228 p_memsz: phdr.p_memsz.get(LE),
229 });
230 }
231
232 (
233 lowest_addr,
234 last_offset,
235 if assume_pic {
236 lowest_addr - start_address
237 } else {
238 0
239 },
240 )
241 };
242
243 drop(reader);
245
246 let mut buf = ChunkBuf::new();
248 for seg in &segments {
249 let mem_offset = seg
250 .p_paddr
251 .checked_add(load_offset)
252 .ok_or(Error::LoadOffsetOverflow {
253 load_offset,
254 p_paddr: seg.p_paddr,
255 })?
256 .checked_sub(reloc_bias)
257 .ok_or(Error::RelocBiasOverflow {
258 load_offset,
259 reloc_bias,
260 p_paddr: seg.p_paddr,
261 })?;
262
263 if mem_offset < start_address {
264 return Err(Error::InvalidProgramHeaderMemoryOffset {
265 mem_offset,
266 start_address,
267 });
268 }
269
270 if seg.p_memsz > 0 {
271 buf.import_file_region(
272 importer,
273 ImportFileRegion {
274 file: kernel_image,
275 file_offset: seg.p_offset,
276 file_length: seg.p_filesz,
277 gpa: mem_offset,
278 memory_length: seg.p_memsz,
279 acceptance,
280 tag,
281 },
282 )
283 .map_err(Error::ImportFileRegion)?;
284 }
285 }
286
287 Ok(LoadInfo {
288 minimum_address_used: lowest_addr - reloc_bias,
289 next_available_address: last_offset - reloc_bias,
290 entrypoint: entry - reloc_bias,
291 })
292}