541 lines
16 KiB
Rust
541 lines
16 KiB
Rust
//! ELF binary format support
|
|
use core::ops::DerefMut;
|
|
|
|
use alloc::sync::Arc;
|
|
use cfg_if::cfg_if;
|
|
use elf::{endian::AnyEndian, relocation::Rela, segment::ProgramHeader, ElfStream, ParseError};
|
|
use libk_mm::{
|
|
pointer::{PhysicalRef, PhysicalRefMut},
|
|
process::{ProcessAddressSpace, VirtualRangeBacking},
|
|
table::MapAttributes,
|
|
};
|
|
use libk_util::io::{Read, Seek};
|
|
use yggdrasil_abi::{error::Error, io::SeekFrom, path::PathBuf};
|
|
|
|
use crate::{
|
|
random,
|
|
task::{
|
|
process::ProcessImage,
|
|
types::{ProcessTlsInfo, ProcessTlsLayout},
|
|
},
|
|
};
|
|
|
|
cfg_if! {
|
|
if #[cfg(target_arch = "x86_64")] {
|
|
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_X86_64;
|
|
} else if #[cfg(target_arch = "aarch64")] {
|
|
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_AARCH64;
|
|
} else if #[cfg(target_arch = "x86")] {
|
|
const EXPECTED_ELF_MACHINE: u16 = elf::abi::EM_386;
|
|
}
|
|
}
|
|
|
|
cfg_if! {
|
|
if #[cfg(target_pointer_width = "64")] {
|
|
const EXPECTED_ELF_CLASS: elf::file::Class = elf::file::Class::ELF64;
|
|
} else {
|
|
const EXPECTED_ELF_CLASS: elf::file::Class = elf::file::Class::ELF32;
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct FileReader<F: Read + Seek> {
|
|
file: F,
|
|
}
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
pub enum ElfSegmentType {
|
|
Load,
|
|
Dynamic,
|
|
Tls,
|
|
}
|
|
|
|
pub enum ElfKind<F: Read + Seek> {
|
|
Dynamic(Option<PathBuf>),
|
|
Static(ElfStream<AnyEndian, FileReader<Arc<F>>>, FileReader<Arc<F>>),
|
|
}
|
|
|
|
pub struct ElfSegment {
|
|
pub ty: ElfSegmentType,
|
|
pub attrs: MapAttributes,
|
|
pub offset: u64,
|
|
pub align: u64,
|
|
pub vaddr: u64,
|
|
pub full_size: u64,
|
|
pub data_size: u64,
|
|
}
|
|
|
|
impl<F: Read + Seek> elf::io_traits::InputStream for FileReader<F> {
|
|
fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), elf::io_traits::StreamError> {
|
|
self.file.read_exact(buf).map_err(conv_stream_error)
|
|
}
|
|
|
|
fn seek(&mut self, pos: elf::io_traits::SeekFrom) -> Result<u64, elf::io_traits::StreamError> {
|
|
self.file
|
|
.seek(conv_seek_from(pos))
|
|
.map_err(conv_stream_error)
|
|
}
|
|
}
|
|
|
|
impl ElfSegmentType {
|
|
pub fn from_phdr(phdr: &ProgramHeader) -> Option<Self> {
|
|
match phdr.p_type {
|
|
elf::abi::PT_LOAD => Some(Self::Load),
|
|
elf::abi::PT_DYNAMIC => Some(Self::Dynamic),
|
|
elf::abi::PT_TLS => Some(Self::Tls),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ElfSegment {
|
|
pub fn from_phdr(phdr: &ProgramHeader) -> Option<Self> {
|
|
let ty = ElfSegmentType::from_phdr(phdr)?;
|
|
|
|
let attrs = match (phdr.p_flags & elf::abi::PF_W, phdr.p_flags & elf::abi::PF_X) {
|
|
(0, 0) => MapAttributes::USER_READ,
|
|
(_, 0) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
|
|
(0, _) => MapAttributes::USER_READ,
|
|
(_, _) => MapAttributes::USER_WRITE | MapAttributes::USER_READ,
|
|
} | MapAttributes::NON_GLOBAL;
|
|
|
|
Some(Self {
|
|
ty,
|
|
attrs,
|
|
offset: phdr.p_offset,
|
|
vaddr: phdr.p_vaddr,
|
|
align: phdr.p_align,
|
|
full_size: phdr.p_memsz,
|
|
data_size: phdr.p_filesz,
|
|
})
|
|
}
|
|
|
|
fn aligned_start(&self) -> usize {
|
|
(self.vaddr & !(self.align - 1)) as usize
|
|
}
|
|
|
|
fn aligned_end(&self) -> usize {
|
|
((self.vaddr + self.full_size + self.align - 1) & !(self.align - 1)) as usize
|
|
}
|
|
|
|
fn vaddr_bounds(&self) -> (usize, usize) {
|
|
(self.aligned_start(), self.aligned_end())
|
|
}
|
|
|
|
// For LOAD type segments, return the range covered by vaddr..vaddr+memsz, alignment applied
|
|
fn into_vaddr_bounds(self) -> Option<(usize, usize)> {
|
|
if self.ty != ElfSegmentType::Load {
|
|
return None;
|
|
}
|
|
Some((self.aligned_start(), self.aligned_end()))
|
|
}
|
|
}
|
|
|
|
pub fn open_elf_file<F: Read + Seek>(file: Arc<F>) -> Result<ElfKind<F>, Error> {
|
|
let (mut elf, file) = open_elf_direct(file)?;
|
|
|
|
if is_dynamic(&mut elf)? {
|
|
// TODO populate interp value
|
|
Ok(ElfKind::Dynamic(None))
|
|
} else {
|
|
Ok(ElfKind::Static(elf, file))
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
pub fn open_elf_direct<F: Read + Seek>(
|
|
file: Arc<F>,
|
|
) -> Result<(ElfStream<AnyEndian, FileReader<Arc<F>>>, FileReader<Arc<F>>), Error> {
|
|
let file = FileReader { file };
|
|
let elf = ElfStream::<AnyEndian, _>::open_stream(file.clone()).map_err(from_parse_error)?;
|
|
|
|
if elf.ehdr.e_machine != EXPECTED_ELF_MACHINE {
|
|
log::warn!("e_machine in ELF ({:#x}) does not match expected value for current architecture ({:#x})", elf.ehdr.e_machine, EXPECTED_ELF_MACHINE);
|
|
return Err(Error::UnrecognizedExecutable);
|
|
}
|
|
|
|
// No 32-bit executables currently supported
|
|
if elf.ehdr.class != EXPECTED_ELF_CLASS {
|
|
log::warn!("ELF class is not {:?}", EXPECTED_ELF_CLASS);
|
|
return Err(Error::UnrecognizedExecutable);
|
|
}
|
|
|
|
if elf.ehdr.e_type != elf::abi::ET_DYN && elf.ehdr.e_type != elf::abi::ET_EXEC {
|
|
log::warn!("ELF is not an executable");
|
|
return Err(Error::UnrecognizedExecutable);
|
|
}
|
|
|
|
Ok((elf, file))
|
|
}
|
|
|
|
/// Loads an ELF binary from `file` into the target address space
|
|
pub fn load_elf_from_file<F: Read + Seek>(
|
|
space: &ProcessAddressSpace,
|
|
mut elf: ElfStream<AnyEndian, FileReader<F>>,
|
|
file: FileReader<F>,
|
|
) -> Result<ProcessImage, Error> {
|
|
// TODO get information about interpreter: this could be a dynamic (not just relocatable)
|
|
// executable with required dependency libraries
|
|
|
|
// Find out image size
|
|
let (vaddr_min, vaddr_max) = elf_virtual_range(&elf);
|
|
let image_load_base = elf_load_address(elf.ehdr.e_type, vaddr_min);
|
|
let image_load_size = vaddr_max - vaddr_min;
|
|
|
|
log::debug!(
|
|
"Loading ELF virtual {:#x?} -> real {:#x?}",
|
|
vaddr_min..vaddr_max,
|
|
image_load_base..image_load_base + image_load_size
|
|
);
|
|
|
|
// Load the segments
|
|
for segment in elf.segments().iter().filter_map(ElfSegment::from_phdr) {
|
|
match segment.ty {
|
|
ElfSegmentType::Load => {
|
|
load_segment(space, &file, &segment, image_load_base, vaddr_min)?;
|
|
}
|
|
// TODO not yet handled
|
|
ElfSegmentType::Tls => (),
|
|
// TODO not yet handled, contains __tls_get_addr
|
|
ElfSegmentType::Dynamic => (),
|
|
}
|
|
}
|
|
|
|
// Load TLS master copy
|
|
let tls = handle_tls(&elf, &file, space)?;
|
|
|
|
// Fixup relocations
|
|
handle_relocations(&mut elf, space, image_load_base)?;
|
|
|
|
let entry = elf.ehdr.e_entry as usize + image_load_base - vaddr_min;
|
|
|
|
log::debug!("Entry: {:#x}", entry);
|
|
|
|
Ok(ProcessImage {
|
|
entry,
|
|
tls,
|
|
load_base: image_load_base,
|
|
})
|
|
}
|
|
|
|
/// Creates a new copy of the TLS from given master image
|
|
pub fn clone_tls(space: &ProcessAddressSpace, image: &ProcessImage) -> Result<usize, Error> {
|
|
let Some(tls) = image.tls.as_ref() else {
|
|
// No TLS
|
|
return Ok(0);
|
|
};
|
|
|
|
assert_ne!(tls.master_copy_base, 0);
|
|
assert_ne!(tls.layout.mem_size, 0);
|
|
|
|
let address = space.allocate(
|
|
None,
|
|
0x1000,
|
|
VirtualRangeBacking::anonymous(),
|
|
MapAttributes::USER_READ | MapAttributes::USER_WRITE | MapAttributes::NON_GLOBAL,
|
|
)?;
|
|
|
|
let src_phys = space.translate(tls.master_copy_base)?;
|
|
let dst_phys = space.translate(address)?;
|
|
|
|
// Copy data
|
|
unsafe {
|
|
let src =
|
|
PhysicalRef::<u8>::map_slice(src_phys.add(tls.layout.data_offset), tls.layout.mem_size);
|
|
let mut dst =
|
|
PhysicalRefMut::map_slice(dst_phys.add(tls.layout.data_offset), tls.layout.mem_size);
|
|
|
|
dst.copy_from_slice(&src);
|
|
}
|
|
|
|
// Setup self-pointer
|
|
unsafe {
|
|
let mut dst = PhysicalRefMut::<usize>::map(dst_phys.add(tls.layout.ptr_offset));
|
|
|
|
*dst = address + tls.layout.ptr_offset;
|
|
}
|
|
|
|
Ok(address + tls.layout.ptr_offset)
|
|
}
|
|
|
|
fn is_dynamic<F: Read + Seek>(
|
|
elf: &mut ElfStream<AnyEndian, FileReader<F>>,
|
|
) -> Result<bool, Error> {
|
|
if elf.ehdr.e_type != elf::abi::ET_DYN {
|
|
return Ok(false);
|
|
}
|
|
|
|
let dynamic = elf.dynamic().map_err(from_parse_error)?;
|
|
let Some(dynamic) = dynamic else {
|
|
// No .dynamic section -> not dynamic
|
|
return Ok(false);
|
|
};
|
|
|
|
Ok(dynamic.into_iter().any(|e| e.d_tag == elf::abi::DT_NEEDED))
|
|
}
|
|
|
|
pub fn elf_virtual_range<F: Read + Seek>(
|
|
elf: &ElfStream<AnyEndian, FileReader<F>>,
|
|
) -> (usize, usize) {
|
|
let mut vaddr_min = usize::MAX;
|
|
let mut vaddr_max = usize::MIN;
|
|
|
|
for (start, end) in elf
|
|
.segments()
|
|
.iter()
|
|
.filter_map(ElfSegment::from_phdr)
|
|
.filter_map(ElfSegment::into_vaddr_bounds)
|
|
{
|
|
debug_assert!(start < end);
|
|
if start < vaddr_min {
|
|
vaddr_min = start;
|
|
}
|
|
if end > vaddr_max {
|
|
vaddr_max = end;
|
|
}
|
|
}
|
|
|
|
assert_ne!(vaddr_min, vaddr_max);
|
|
(vaddr_min, vaddr_max)
|
|
}
|
|
|
|
fn elf_load_address(elf_type: u16, virtual_address: usize) -> usize {
|
|
match elf_type {
|
|
elf::abi::ET_EXEC => virtual_address,
|
|
// TODO ASLR through random?
|
|
elf::abi::ET_DYN => {
|
|
let index = random::range(0x5000, 0x20000);
|
|
(index as usize) * 0x1000
|
|
}
|
|
// Handled in load_elf_from_file()
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
fn handle_relocations<F: Read + Seek>(
|
|
elf: &mut ElfStream<AnyEndian, FileReader<F>>,
|
|
space: &ProcessAddressSpace,
|
|
image_load_base: usize,
|
|
) -> Result<(), Error> {
|
|
// TODO handle SHT_REL as well
|
|
let rela_section = elf
|
|
.section_headers()
|
|
.iter()
|
|
.find(|sh| sh.sh_type == elf::abi::SHT_RELA);
|
|
|
|
if let Some(&rela_section) = rela_section {
|
|
let rela_iter = elf
|
|
.section_data_as_relas(&rela_section)
|
|
.map_err(from_parse_error)?;
|
|
|
|
for rela in rela_iter {
|
|
write_rela(&rela, space, image_load_base)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn handle_tls<F: Read + Seek>(
|
|
elf: &ElfStream<AnyEndian, FileReader<F>>,
|
|
file: &FileReader<F>,
|
|
space: &ProcessAddressSpace,
|
|
) -> Result<Option<ProcessTlsInfo>, Error> {
|
|
// TODO handle different TLS models
|
|
// TODO handle TLS segment attributes
|
|
// TODO check if it's possible to have more than one TLS segment
|
|
|
|
// Locate TLS master copy information, if any
|
|
let tls_segment = elf.segments().iter().find(|s| s.p_type == elf::abi::PT_TLS);
|
|
|
|
if let Some(tls_segment) = tls_segment {
|
|
// Can't yet handle higher align values
|
|
assert!(tls_segment.p_align.is_power_of_two());
|
|
assert!(tls_segment.p_align < 0x1000);
|
|
|
|
let layout = ProcessTlsLayout::new(
|
|
tls_segment.p_align as _,
|
|
tls_segment.p_filesz as _,
|
|
tls_segment.p_memsz as _,
|
|
);
|
|
let data_offset = layout.data_offset;
|
|
let data_size = layout.data_size;
|
|
let mem_size = layout.mem_size;
|
|
let aligned_size = (layout.full_size + 0xFFF) & !0xFFF;
|
|
|
|
let attrs =
|
|
MapAttributes::USER_WRITE | MapAttributes::USER_READ | MapAttributes::NON_GLOBAL;
|
|
let master_copy_base =
|
|
space.allocate(None, aligned_size, VirtualRangeBacking::anonymous(), attrs)?;
|
|
|
|
if data_size > 0 {
|
|
load_bytes(
|
|
space,
|
|
master_copy_base,
|
|
master_copy_base + data_offset,
|
|
|off, mut dst| {
|
|
file.file
|
|
.seek(SeekFrom::Start(tls_segment.p_offset + off as u64))?;
|
|
file.file.read_exact(dst.deref_mut())
|
|
},
|
|
)?;
|
|
}
|
|
|
|
if mem_size > data_size {
|
|
load_bytes(
|
|
space,
|
|
master_copy_base + data_offset + data_size,
|
|
mem_size - data_size,
|
|
|_, mut dst| {
|
|
dst.fill(0);
|
|
Ok(())
|
|
},
|
|
)?;
|
|
}
|
|
|
|
Ok(Some(ProcessTlsInfo {
|
|
master_copy_base,
|
|
layout,
|
|
}))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn load_segment<F: Read + Seek>(
|
|
space: &ProcessAddressSpace,
|
|
file: &FileReader<F>,
|
|
seg: &ElfSegment,
|
|
load_base: usize,
|
|
elf_virtual_base: usize,
|
|
) -> Result<(), Error> {
|
|
assert_ne!(seg.full_size, 0);
|
|
|
|
let (virt_start, virt_end) = seg.vaddr_bounds();
|
|
let size = virt_end - virt_start;
|
|
|
|
// For ET_EXEC (non-relocatable) elf_virtual_base = load_base, so all addresses will appear the
|
|
// same
|
|
// For ET_DYN (relocatable or shared object) this will setup an offset to operate on
|
|
// "virtual" virtual addresses and "real" virtual addresses
|
|
let relative_offset = seg.vaddr as usize - elf_virtual_base;
|
|
let real_vaddr = load_base + relative_offset;
|
|
let real_vaddr_aligned = real_vaddr & !(seg.align as usize - 1);
|
|
|
|
space.map(
|
|
real_vaddr_aligned,
|
|
size,
|
|
VirtualRangeBacking::anonymous(),
|
|
seg.attrs,
|
|
)?;
|
|
|
|
if seg.data_size > 0 {
|
|
// Load the actual data
|
|
load_bytes(space, real_vaddr, seg.data_size as _, |off, mut dst| {
|
|
file.file.seek(SeekFrom::Start(seg.offset + off as u64))?;
|
|
file.file.read_exact(dst.deref_mut())
|
|
})?;
|
|
}
|
|
|
|
if seg.full_size > seg.data_size {
|
|
let addr = real_vaddr + seg.data_size as usize;
|
|
let len = (seg.full_size - seg.data_size) as usize;
|
|
|
|
// Zero the rest
|
|
load_bytes(space, addr, len, |_off, mut dst| {
|
|
dst.fill(0);
|
|
Ok(())
|
|
})?;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn write_rela(rela: &Rela, space: &ProcessAddressSpace, b: usize) -> Result<(), Error> {
|
|
// A: Addend (from entry)
|
|
// B: Image base
|
|
let a = rela.r_addend;
|
|
|
|
let rel_field = rela.r_offset as usize + b;
|
|
|
|
let (value, width) = match rela.r_type {
|
|
elf::abi::R_X86_64_RELATIVE | elf::abi::R_AARCH64_RELATIVE => {
|
|
// B + A
|
|
// Width: qword
|
|
(b as i64 + a, 8)
|
|
}
|
|
_ => todo!("Unsupported relocation type: {:#x}", rela.r_type),
|
|
};
|
|
|
|
load_bytes(space, rel_field, width, |off, mut dst| {
|
|
assert_eq!(off, 0);
|
|
|
|
match width {
|
|
8 => {
|
|
todo!();
|
|
// unsafe { (dst.as_mut_ptr() as *mut u64).write_volatile(value as u64) };
|
|
// Ok(())
|
|
}
|
|
_ => todo!("Unhandled relocation width: {}", width),
|
|
}
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
fn conv_stream_error(_v: Error) -> elf::io_traits::StreamError {
|
|
elf::io_traits::StreamError {
|
|
message: "Elf read error",
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn conv_seek_from(v: elf::io_traits::SeekFrom) -> SeekFrom {
|
|
match v {
|
|
elf::io_traits::SeekFrom::End(off) => SeekFrom::End(off),
|
|
elf::io_traits::SeekFrom::Start(off) => SeekFrom::Start(off),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn from_parse_error(v: ParseError) -> Error {
|
|
log::warn!("ELF loading error: {:?}", v);
|
|
Error::InvalidFile
|
|
}
|
|
|
|
fn load_bytes<F>(
|
|
space: &ProcessAddressSpace,
|
|
addr: usize,
|
|
len: usize,
|
|
mut src: F,
|
|
) -> Result<(), Error>
|
|
where
|
|
F: FnMut(usize, PhysicalRefMut<'_, [u8]>) -> Result<(), Error>,
|
|
{
|
|
// TODO check for crazy addresses here
|
|
|
|
let dst_page_off = addr & 0xFFF;
|
|
let dst_page_aligned = addr & !0xFFF;
|
|
let mut off = 0usize;
|
|
let mut rem = len;
|
|
|
|
while rem != 0 {
|
|
let page_idx = (dst_page_off + off) / 0x1000;
|
|
let page_off = (dst_page_off + off) % 0x1000;
|
|
let count = core::cmp::min(rem, 0x1000 - page_off);
|
|
|
|
let virt_page = dst_page_aligned + page_idx * 0x1000;
|
|
assert_eq!(virt_page & 0xFFF, 0);
|
|
|
|
let phys_page = space.translate(virt_page)?;
|
|
let dst_slice = unsafe { PhysicalRefMut::map_slice(phys_page.add(page_off), count) };
|
|
|
|
src(off, dst_slice)?;
|
|
|
|
rem -= count;
|
|
off += count;
|
|
}
|
|
|
|
Ok(())
|
|
}
|