1use crate::importer::GuestArch;
14use crate::importer::GuestArchKind;
15use crate::importer::ImageLoad;
16use hvdef::HV_PAGE_SIZE;
17use object::ReadCache;
18use object::ReadRef;
19use object::elf;
20use object::read::elf::FileHeader;
21use std::io::Read;
22use std::io::Seek;
23use thiserror::Error;
24
25type LE = object::LittleEndian;
26const LE: LE = LE {};
27
28#[derive(Debug, Error)]
29pub enum Error {
30    #[error("failed to read file header")]
31    ReadFileHeader,
32    #[error("invalid file header")]
33    InvalidFileHeader,
34    #[error("target machine mismatch")]
35    TargetMachineMismatch,
36    #[error("unsupported ELF file byte order")]
37    BigEndianElfOnLittle,
38    #[error(
39        "invalid entry address found in ELF header: {e_entry:#x}, start address: {start_address:#x}, load offset: {load_offset:#x}"
40    )]
41    InvalidEntryAddress {
42        e_entry: u64,
43        start_address: u64,
44        load_offset: u64,
45    },
46    #[error("failed to parse ELF program header")]
47    InvalidProgramHeader(#[source] object::read::Error),
48    #[error("adding load offset {load_offset} to paddr {p_paddr} overflowed")]
49    LoadOffsetOverflow { load_offset: u64, p_paddr: u64 },
50    #[error("invalid ELF program header memory offset {mem_offset}, below start {start_address}")]
51    InvalidProgramHeaderMemoryOffset { mem_offset: u64, start_address: u64 },
52    #[error(
53        "adding reloc bias {reloc_bias} and load offset {load_offset} to paddr {p_paddr} overflowed"
54    )]
55    RelocBiasOverflow {
56        load_offset: u64,
57        reloc_bias: u64,
58        p_paddr: u64,
59    },
60    #[error("failed to read kernel image")]
61    ReadKernelImage,
62    #[error("failed during import pages call")]
63    ImportPages(#[source] anyhow::Error),
64    #[error("failed to seek to offset of kernel image")]
65    SeekKernelImage,
66}
67
68pub type Result<T> = std::result::Result<T, Error>;
69
70#[derive(Debug)]
72pub struct LoadInfo {
73    pub minimum_address_used: u64,
76    pub next_available_address: u64,
78    pub entrypoint: u64,
80}
81
82pub fn load_static_elf<F, R: GuestArch>(
96    importer: &mut dyn ImageLoad<R>,
97    kernel_image: &mut F,
98    start_address: u64,
99    load_offset: u64,
100    assume_pic: bool,
101    acceptance: crate::importer::BootPageAcceptance,
102    tag: &str,
103) -> Result<LoadInfo>
104where
105    F: Read + Seek,
106{
107    let reader = ReadCache::new(kernel_image);
108    let ehdr: &elf::FileHeader64<LE> = reader.read_at(0).map_err(|_| Error::ReadFileHeader)?;
109
110    if !ehdr.is_supported() {
112        return Err(Error::InvalidFileHeader);
113    }
114    if ehdr.is_big_endian() {
115        return Err(Error::BigEndianElfOnLittle);
116    }
117
118    match R::arch() {
119        GuestArchKind::Aarch64 => {
120            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_AARCH64) {
121                tracing::error!(
122                    "ELF file target machine mismatch, was the file built for aarch64?"
123                );
124                return Err(Error::TargetMachineMismatch);
125            }
126        }
127        GuestArchKind::X86_64 => {
128            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_X86_64) {
129                tracing::error!("ELF file target machine mismatch, was the file built for X86_64?");
130                return Err(Error::TargetMachineMismatch);
131            }
132        }
133    }
134
135    let e_entry = ehdr.e_entry.get(LE);
136    let phdrs = ehdr
137        .program_headers(LE, &reader)
138        .map_err(Error::InvalidProgramHeader)?;
139
140    let load_offset = if assume_pic {
147        let mut lowest_paddr = u64::MAX;
148        for phdr in phdrs {
149            if phdr.p_type.get(LE) == elf::PT_LOAD {
150                let p_paddr = phdr.p_paddr.get(LE);
151                lowest_paddr = lowest_paddr.min(p_paddr);
152            }
153        }
154        if lowest_paddr < start_address {
155            start_address - lowest_paddr + load_offset
156        } else {
157            load_offset
158        }
159    } else {
160        load_offset
161    };
162
163    let entry = e_entry
164        .checked_add(load_offset)
165        .ok_or(Error::InvalidEntryAddress {
166            e_entry,
167            start_address,
168            load_offset,
169        })?;
170    if entry < start_address {
171        return Err(Error::InvalidEntryAddress {
172            e_entry,
173            start_address,
174            load_offset,
175        });
176    }
177
178    let (lowest_addr, last_offset, reloc_bias) = {
180        let mut lowest_addr = u64::MAX;
181        let mut last_offset = load_offset;
182
183        for phdr in phdrs {
185            if phdr.p_type.get(LE) != elf::PT_LOAD {
186                continue;
187            }
188
189            let p_paddr = phdr.p_paddr.get(LE);
190            let mem_offset = p_paddr
191                .checked_add(load_offset)
192                .ok_or(Error::LoadOffsetOverflow {
193                    load_offset,
194                    p_paddr,
195                })?;
196
197            if mem_offset < start_address {
198                return Err(Error::InvalidProgramHeaderMemoryOffset {
199                    mem_offset,
200                    start_address,
201                });
202            }
203
204            let page_mask = HV_PAGE_SIZE - 1;
205            let page_base = mem_offset / HV_PAGE_SIZE;
206            let page_count: u64 =
207                ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
208
209            lowest_addr = lowest_addr.min(page_base * HV_PAGE_SIZE);
210            last_offset = last_offset.max((page_base + page_count) * HV_PAGE_SIZE);
211        }
212
213        (
214            lowest_addr,
215            last_offset,
216            if assume_pic {
217                lowest_addr - start_address
218            } else {
219                0
220            },
221        )
222    };
223
224    for phdr in phdrs {
227        if phdr.p_type.get(LE) != elf::PT_LOAD {
228            continue;
229        }
230
231        let p_paddr = phdr.p_paddr.get(LE);
232        let mem_offset = p_paddr
233            .checked_add(load_offset)
234            .ok_or(Error::LoadOffsetOverflow {
235                load_offset,
236                p_paddr,
237            })?
238            .checked_sub(reloc_bias)
239            .ok_or(Error::RelocBiasOverflow {
240                load_offset,
241                reloc_bias,
242                p_paddr,
243            })?;
244
245        if mem_offset < start_address {
246            return Err(Error::InvalidProgramHeaderMemoryOffset {
247                mem_offset,
248                start_address,
249            });
250        }
251
252        let page_mask = HV_PAGE_SIZE - 1;
253
254        let filesz = phdr.p_filesz.get(LE);
255        let mut v = vec![0; ((mem_offset & page_mask) + filesz) as usize];
256        if filesz != 0 {
257            let v_start_offset = (mem_offset & page_mask) as usize;
258            let read_length = (v.len() - v_start_offset) as u64;
259            v[v_start_offset..].copy_from_slice(
260                reader
261                    .read_bytes_at(phdr.p_offset.get(LE), read_length)
262                    .map_err(|_| Error::ReadKernelImage)?,
263            );
264        }
265
266        let page_base = mem_offset / HV_PAGE_SIZE;
267        let page_count =
268            ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
269        if page_count > 0 {
270            importer
271                .import_pages(page_base, page_count, tag, acceptance, &v)
272                .map_err(Error::ImportPages)?;
273        }
274    }
275
276    Ok(LoadInfo {
277        minimum_address_used: lowest_addr - reloc_bias,
278        next_available_address: last_offset - reloc_bias,
279        entrypoint: entry - reloc_bias,
280    })
281}