use std::ptr;
const PAGE_SIZE: usize = 4096;
#[derive(Clone, Copy)]
struct LoadSegment {
file_offset: usize,
virtual_address: u64,
file_size: usize,
memory_size: usize,
}
#[derive(Clone, Copy)]
pub enum MappingPermissions {
ReadWrite,
ReadWriteExecute,
}
impl MappingPermissions {
fn protection(self) -> libc::c_int {
match self {
Self::ReadWrite => libc::PROT_READ | libc::PROT_WRITE,
Self::ReadWriteExecute => libc::PROT_READ | libc::PROT_WRITE | libc::PROT_EXEC,
}
}
}
pub struct ElfImage {
link_base: usize,
entry_offset: usize,
mapping_size: usize,
segments: Vec<LoadSegment>,
}
impl ElfImage {
pub fn parse(image: &[u8]) -> Self {
assert_eq!(&image[..4], b"\x7fELF", "fixture must be an ELF image");
assert_eq!(image[4], 2, "fixture must use ELF64");
let entry = read_u64(image, 24);
let program_offset = usize::try_from(read_u64(image, 32)).unwrap();
let program_size = usize::from(read_u16(image, 54));
let program_count = usize::from(read_u16(image, 56));
assert_eq!(program_size, 56, "fixture must use native ELF64 phdrs");
let mut segments = Vec::new();
for index in 0..program_count {
let offset = program_offset + index * program_size;
if read_u32(image, offset) != 1 {
continue;
}
let segment = LoadSegment {
file_offset: usize::try_from(read_u64(image, offset + 8)).unwrap(),
virtual_address: read_u64(image, offset + 16),
file_size: usize::try_from(read_u64(image, offset + 32)).unwrap(),
memory_size: usize::try_from(read_u64(image, offset + 40)).unwrap(),
};
assert!(segment.file_size <= segment.memory_size);
assert!(segment.file_offset + segment.file_size <= image.len());
segments.push(segment);
}
assert!(!segments.is_empty(), "fixture must contain PT_LOAD");
let link_base = segments
.iter()
.map(|segment| segment.virtual_address as usize & !(PAGE_SIZE - 1))
.min()
.unwrap();
let link_end = segments
.iter()
.map(|segment| usize::try_from(segment.virtual_address).unwrap() + segment.memory_size)
.max()
.unwrap();
let mapping_size = align_up(link_end.wrapping_sub(link_base), PAGE_SIZE);
let entry_offset = usize::try_from(entry).unwrap().wrapping_sub(link_base);
assert!(entry_offset < mapping_size, "ELF entry must lie in PT_LOAD");
Self {
link_base,
entry_offset,
mapping_size,
segments,
}
}
pub fn load(&self, image: &[u8], permissions: MappingPermissions) -> LoadedElf {
let base = unsafe {
libc::mmap(
ptr::null_mut(),
self.mapping_size,
permissions.protection(),
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
};
assert_ne!(base, libc::MAP_FAILED, "fixture mmap must succeed");
let base = base.cast::<u8>();
for segment in &self.segments {
let destination_offset = usize::try_from(segment.virtual_address)
.unwrap()
.wrapping_sub(self.link_base);
assert!(destination_offset + segment.memory_size <= self.mapping_size);
unsafe {
ptr::copy_nonoverlapping(
image.as_ptr().add(segment.file_offset),
base.add(destination_offset),
segment.file_size,
);
base.add(destination_offset + segment.file_size)
.write_bytes(0, segment.memory_size - segment.file_size);
}
}
LoadedElf {
base,
size: self.mapping_size,
}
}
pub fn link_base(&self) -> usize {
self.link_base
}
pub fn load_bias(&self, mapping: &LoadedElf) -> i128 {
mapping.base_address() as i128 - self.link_base as i128
}
pub fn entry_address(&self, mapping: &LoadedElf) -> *mut u8 {
mapping.base.wrapping_add(self.entry_offset)
}
pub fn runtime_address(&self, mapping: &LoadedElf, linked_address: usize) -> *mut u8 {
let offset = linked_address
.checked_sub(self.link_base)
.expect("fixture symbol must not precede PT_LOAD");
assert!(
offset <= self.mapping_size,
"fixture symbol must lie in PT_LOAD or point one byte past it"
);
mapping.base.wrapping_add(offset)
}
}
pub struct LoadedElf {
base: *mut u8,
size: usize,
}
impl LoadedElf {
pub fn base_address(&self) -> usize {
self.base as usize
}
}
impl Drop for LoadedElf {
fn drop(&mut self) {
assert_eq!(unsafe { libc::munmap(self.base.cast(), self.size) }, 0);
}
}
fn read_u16(bytes: &[u8], offset: usize) -> u16 {
u16::from_le_bytes(bytes[offset..offset + 2].try_into().unwrap())
}
fn read_u32(bytes: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap())
}
fn read_u64(bytes: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(bytes[offset..offset + 8].try_into().unwrap())
}
fn align_up(value: usize, alignment: usize) -> usize {
value
.checked_add(alignment - 1)
.expect("fixture size must not overflow")
& !(alignment - 1)
}