Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
elf-loader
src
elf_library.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
elf_library.rs
Blame
History
Raw
use crate::elf_hash_table::GnuHash; use crate::linux_cc; use goblin::elf::Elf; use goblin::elf::program_header::{PF_R, PF_W, PF_X, PT_LOAD}; use goblin::elf::section_header::{SHN_ABS, SHN_UNDEF, SHT_GNU_HASH}; use goblin::elf::sym::STB_WEAK; use log::{trace, warn}; use memmap2::{MmapOptions, MmapRaw}; use region::Protection; use std::ffi::c_void; use std::marker::PhantomData; use thiserror::Error; /// The machine the host can run, and how its relocations are numbered. #[derive(Clone, Copy)] struct Architecture { machine: u16, relative: u32, absolute: u32, // GLOB_DAT and JUMP_SLOT store a symbol's address, whatever the word held before. slots: [u32; 2], } #[cfg(target_arch = "x86_64")] const HOST: Option<Architecture> = { use goblin::elf::header::EM_X86_64; use goblin::elf::reloc::{ R_X86_64_64, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, R_X86_64_RELATIVE, }; Some(Architecture { machine: EM_X86_64, relative: R_X86_64_RELATIVE, absolute: R_X86_64_64, slots: [R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT], }) }; #[cfg(target_arch = "aarch64")] const HOST: Option<Architecture> = { use goblin::elf::header::EM_AARCH64; use goblin::elf::reloc::{ R_AARCH64_ABS64, R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_RELATIVE, }; Some(Architecture { machine: EM_AARCH64, relative: R_AARCH64_RELATIVE, absolute: R_AARCH64_ABS64, slots: [R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT], }) }; #[cfg(target_arch = "x86")] const HOST: Option<Architecture> = { use goblin::elf::header::EM_386; use goblin::elf::reloc::{R_386_32, R_386_GLOB_DAT, R_386_JMP_SLOT, R_386_RELATIVE}; Some(Architecture { machine: EM_386, relative: R_386_RELATIVE, absolute: R_386_32, slots: [R_386_GLOB_DAT, R_386_JMP_SLOT], }) }; #[cfg(target_arch = "arm")] const HOST: Option<Architecture> = { use goblin::elf::header::EM_ARM; use goblin::elf::reloc::{R_ARM_ABS32, R_ARM_GLOB_DAT, R_ARM_JUMP_SLOT, R_ARM_RELATIVE}; Some(Architecture { machine: EM_ARM, relative: R_ARM_RELATIVE, absolute: R_ARM_ABS32, slots: [R_ARM_GLOB_DAT, R_ARM_JUMP_SLOT], }) }; #[cfg(not(any( target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86", target_arch = "arm" )))] const HOST: Option<Architecture> = None; pub struct Library<'data> { elf: Elf<'data>, gnu_hash: Option<GnuHash>, memory_map: MmapRaw, } impl<'data> Library<'data> { pub fn load<F: Fn(&str) -> Option<*const c_void>>( data: &'data [u8], hooks: F, ) -> Result<Self, LibraryLoadingError> { let elf = Elf::parse(data)?; let architecture = HOST .filter(|host| host.machine == elf.header.e_machine) .ok_or(LibraryLoadingError::IncompatibleArchitecture( elf.header.e_machine, ))?; // The library is mapped at the start of the memory map, so addresses are offsets in it. let page_size = region::page::size(); let segments: Vec<_> = elf .program_headers .iter() .filter(|header| header.p_type == PT_LOAD) .collect(); let memory_size = segments .iter() .map(|segment| { ((segment.p_vaddr + segment.p_memsz) as usize).next_multiple_of(page_size) }) .max() .unwrap_or(0); let mut memory_map = MmapOptions::new().len(memory_size).map_anon()?; for segment in &segments { let file_range = segment.p_offset as usize..(segment.p_offset + segment.p_filesz) as usize; let address = segment.p_vaddr as usize; memory_map[address..address + file_range.len()].copy_from_slice(&data[file_range]); } // Relocate before protecting the segments, as some of them may become read-only. let base_address = memory_map.as_ptr() as usize; let relocations = [&elf.dynrelas, &elf.dynrels, &elf.pltrelocs] .into_iter() .flat_map(|relocations| relocations.iter()); for relocation in relocations { let word = relocation.r_offset as usize..relocation.r_offset as usize + size_of::<usize>(); // REL relocations (on 32-bit hosts) keep their addend in the word they relocate. let addend = relocation.r_addend.map_or_else( || { let word: [u8; size_of::<usize>()] = memory_map[word.clone()] .try_into() .expect("The relocated word has the size of a word."); usize::from_ne_bytes(word) as isize }, |addend| addend as isize, ); let value = match relocation.r_type { r_type if r_type == architecture.relative => { Some(base_address.wrapping_add_signed(addend)) } r_type if r_type == architecture.absolute => Some( symbol_address(&elf, relocation.r_sym, base_address, &hooks) .wrapping_add_signed(addend), ), r_type if architecture.slots.contains(&r_type) => { let addend = relocation.r_addend.unwrap_or(0) as isize; Some( symbol_address(&elf, relocation.r_sym, base_address, &hooks) .wrapping_add_signed(addend), ) } _ => { warn!("Unknown relocation: {relocation:?}"); None } }; if let Some(value) = value { memory_map[word].copy_from_slice(&value.to_ne_bytes()); } } // From now on, some pages may not be readable anymore, so no more slices. let memory_map = MmapRaw::from(memory_map); // Segments aligned on smaller pages than ours can share a page, which then needs // the rights of both. let mut page_protections = vec![Protection::NONE; memory_size / page_size]; for segment in &segments { let protection = [ (PF_R, Protection::READ), (PF_W, Protection::WRITE), (PF_X, Protection::EXECUTE), ] .into_iter() .filter(|(flag, _)| segment.p_flags & flag != 0) .fold(Protection::NONE, |protection, (_, flag)| protection | flag); let pages = segment.p_vaddr as usize / page_size ..((segment.p_vaddr + segment.p_memsz) as usize).div_ceil(page_size); for page_protection in &mut page_protections[pages] { *page_protection |= protection; } } let mut page = 0; for pages in page_protections.chunk_by(|left, right| left == right) { let start = memory_map.as_mut_ptr().wrapping_add(page * page_size); let size = pages.len() * page_size; trace!( "Protecting {start:p} ({size} bytes) with the protection {}", pages[0] ); unsafe { region::protect(start, size, pages[0]) }?; page += pages.len(); } let gnu_hash = elf .section_headers .iter() .find(|section| section.sh_type == SHT_GNU_HASH) .and_then(|section| { let start = section.sh_offset as usize; data.get(start..start + section.sh_size as usize) }) .and_then(GnuHash::parse); Ok(Library { elf, gnu_hash, memory_map, }) } /// Looks up the address of a function or a variable defined by the library. /// /// # Safety /// `T` must be a pointer type matching the symbol, which must not be used once the /// library is dropped. pub unsafe fn get<'library, T: Copy>( &'library self, symbol_name: &str, ) -> Option<Symbol<'library, 'data, T>> { const { assert!( size_of::<T>() == size_of::<*const u8>(), "Symbols can only be loaded as pointers." ) }; let symbol = match &self.gnu_hash { Some(gnu_hash) => gnu_hash.find(symbol_name, &self.elf.dynsyms, &self.elf.dynstrtab), None => self .elf .dynsyms .iter() .find(|symbol| self.elf.dynstrtab.get_at(symbol.st_name) == Some(symbol_name)), } .filter(|symbol| !symbol.is_import())?; let address = self .memory_map .as_ptr() .wrapping_add(symbol.st_value as usize); Some(Symbol { ptr: unsafe { std::mem::transmute_copy(&address) }, library: PhantomData, }) } } /// Where a relocated symbol lives: the hooks come first, then the library's own definitions. fn symbol_address<F: Fn(&str) -> Option<*const c_void>>( elf: &Elf, index: usize, base_address: usize, hooks: &F, ) -> usize { let symbol = elf.dynsyms.get(index); let hook = symbol .and_then(|symbol| elf.dynstrtab.get_at(symbol.st_name)) .and_then(hooks); match (hook, symbol) { (Some(address), _) => address as usize, (None, Some(symbol)) if symbol.st_shndx == SHN_ABS as usize => symbol.st_value as usize, (None, Some(symbol)) if symbol.st_shndx != SHN_UNDEF as usize => { base_address.wrapping_add(symbol.st_value as usize) } // Missing weak symbols are null, and so is the symbol of a relocation without one. (None, Some(symbol)) if symbol.st_bind() == STB_WEAK || index == 0 => 0, (None, _) => unknown_symbol as *const c_void as usize, } } #[linux_cc] fn unknown_symbol() -> ! { panic!("Unknown symbol called."); } #[derive(Debug, Error)] pub enum LibraryLoadingError { #[error("Incompatible architecture (e_machine = {0})")] IncompatibleArchitecture(u16), #[error("Unable to parse the ELF file: {0}")] Parsing(#[from] goblin::error::Error), #[error("Cannot map the library in memory: {0}")] Mapping(#[from] std::io::Error), #[error("Cannot protect the memory correctly: {0}")] Protection(#[from] region::Error), } pub struct Symbol<'library, 'data, T: Copy> { pub ptr: T, library: PhantomData<&'library Library<'data>>, }