diff --git a/libdyld/Cargo.toml b/libdyld/Cargo.toml index 1069d526..3138f50d 100644 --- a/libdyld/Cargo.toml +++ b/libdyld/Cargo.toml @@ -5,3 +5,6 @@ version = "0.1.0" [lib] name = "dyld" + +[dependencies] +log = "0.4" diff --git a/libdyld/src/file.rs b/libdyld/src/file.rs new file mode 100644 index 00000000..80de1b39 --- /dev/null +++ b/libdyld/src/file.rs @@ -0,0 +1,73 @@ +use core::{mem, ptr, fmt, slice, str, convert, ops::{Deref, Range}}; +use super::{ + Arch, + elf::*, +}; + +fn read_unaligned(data: &[u8], offset: usize) -> Option { + if data.len() < offset + mem::size_of::() { + None + } else { + let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T; + Some(unsafe { ptr::read_unaligned(ptr) }) + } +} + +/// ELF file +pub struct File<'a> { + pub ehdr: Elf32_Ehdr, + data: &'a [u8], +} + +impl<'a> File<'a> { + pub fn new(data: &'a [u8]) -> Option { + let ehdr = read_unaligned(data, 0)?; + Some(File { ehdr, data }) + } + + fn read_unaligned(&self, offset: usize) -> Option { + read_unaligned(self.data, offset) + } + + pub fn arch(&self) -> Option { + const IDENT_OPENRISC: [u8; EI_NIDENT] = [ + ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, + ELFCLASS32, ELFDATA2MSB, EV_CURRENT, ELFOSABI_NONE, + /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 + ]; + const IDENT_ARM: [u8; EI_NIDENT] = [ + ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, + ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, + /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 + ]; + + match (self.ehdr.e_ident, self.ehdr.e_machine) { + (IDENT_ARM, EM_ARM) => Some(Arch::Arm), + (IDENT_OPENRISC, EM_OPENRISC) => Some(Arch::OpenRisc), + _ => None, + } + } + + pub fn program_headers<'b>(&'b self) -> impl Iterator> + 'b + { + (0..self.ehdr.e_phnum).map(move |i| { + let phdr_off = self.ehdr.e_phoff as usize + mem::size_of::() * i as usize; + self.read_unaligned::(phdr_off) + }) + } + + pub fn dynamic_header_vaddr(&self) -> Option> { + self.program_headers() + .filter_map(|phdr| phdr) + .find(|phdr| phdr.p_type == PT_DYNAMIC) + .map(|phdr| phdr.p_vaddr as usize..(phdr.p_vaddr + phdr.p_filesz) as usize) + } +} + +impl Deref for File<'_> { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.data + } +} diff --git a/libdyld/src/image.rs b/libdyld/src/image.rs new file mode 100644 index 00000000..873de015 --- /dev/null +++ b/libdyld/src/image.rs @@ -0,0 +1,245 @@ +use core::{ + ops::{Deref, DerefMut, Range}, + mem, + slice, +}; +use alloc::alloc::{alloc_zeroed, dealloc, Layout, LayoutErr}; +use super::{ + elf::*, + Error, +}; + +fn elf_hash(name: &[u8]) -> u32 { + let mut h: u32 = 0; + for c in name { + h = (h << 4) + *c as u32; + let g = h & 0xf0000000; + if g != 0 { + h ^= g >> 24; + h &= !g; + } + } + h +} + +pub struct DynamicSection<'a> { + pub strtab: &'a [u8], + pub symtab: &'a [Elf32_Sym], + pub hash_bucket: &'a [Elf32_Word], + pub hash_chain: &'a [Elf32_Word], + pub rel: &'a [Elf32_Rel], + pub rela: &'a [Elf32_Rela], + pub pltrel: &'a [Elf32_Rel], +} + +impl<'a> DynamicSection<'a> { + pub fn lookup(&self, name: &[u8]) -> Option { + let hash = elf_hash(name); + let mut index = self.hash_bucket[hash as usize % self.hash_bucket.len()] as usize; + + loop { + if index == STN_UNDEF { return None } + + let sym = &self.symtab[index]; + let sym_name_off = sym.st_name as usize; + match self.strtab.get(sym_name_off..sym_name_off + name.len()) { + Some(sym_name) if sym_name == name => { + if ELF32_ST_BIND(sym.st_info) & STB_GLOBAL == 0 { + return None + } + + match sym.st_shndx { + SHN_UNDEF => return None, + SHN_ABS => return Some(sym.st_value), + _ => return Some(sym.st_value) + } + } + _ => (), + } + + index = self.hash_chain[index] as usize; + } + } + + pub fn name_starting_at(&self, offset: usize) -> Result<&'a [u8], Error> { + let size = self.strtab.iter().skip(offset).position(|&x| x == 0) + .ok_or("symbol in symbol table not null-terminated")?; + Ok(self.strtab.get(offset..offset + size) + .ok_or("cannot read symbol name")?) + } +} + +/// target memory image +pub struct Image { + layout: Layout, + data: &'static mut [u8], +} + +impl Image { + pub fn new(size: usize, align: usize) -> Result { + let layout = Layout::from_size_align(size, align)?; + let data = unsafe { + let ptr = alloc_zeroed(layout); + slice::from_raw_parts_mut(ptr, size) + }; + + Ok(Image { + layout, + data, + }) + } + + /// assumes that self.data is properly aligned + pub fn get_ref(&self, offset: usize) -> Option<&T> + where + T: Copy, + { + if self.data.len() < offset + mem::size_of::() { + None + } else if (self.data.as_ptr() as usize + offset) & (mem::align_of::() - 1) != 0 { + None + } else { + let ptr = self.data.as_ptr().wrapping_offset(offset as isize) as *const T; + Some(unsafe { &*ptr }) + } + } + + fn get_ref_slice(&self, offset: usize, len: usize) -> Option<&[T]> { + if self.data.len() < offset + mem::size_of::() * len { + None + } else if (self.data.as_ptr() as usize + offset) & (mem::align_of::() - 1) != 0 { + None + } else { + let ptr = self.data.as_ptr().wrapping_offset(offset as isize) as *const T; + Some(unsafe { slice::from_raw_parts(ptr, len) }) + } + } + + fn dynamic_headers<'a>(&'a self, range: Range) -> + impl Iterator + 'a + { + range + .step_by(mem::size_of::()) + .filter_map(move |offset| { + self.get_ref::(offset) + }) + .take_while(|d| unsafe { d.d_un.d_val } as i32 != DT_NULL) + } + + pub fn dynamic_section(&self, range: Range) -> Result { + let (mut strtab_off, mut strtab_sz) = (0, 0); + let (mut symtab_off, mut symtab_sz) = (0, 0); + let (mut rel_off, mut rel_sz) = (0, 0); + let (mut rela_off, mut rela_sz) = (0, 0); + let (mut pltrel_off, mut pltrel_sz) = (0, 0); + let (mut hash_off, mut hash_sz) = (0, 0); + let mut sym_ent = 0; + let mut rel_ent = 0; + let mut rela_ent = 0; + let mut nbucket = 0; + let mut nchain = 0; + + for dyn_header in self.dynamic_headers(range) { + let val = unsafe { dyn_header.d_un.d_val } as usize; + match dyn_header.d_tag { + DT_NULL => break, + DT_STRTAB => strtab_off = val, + DT_STRSZ => strtab_sz = val, + DT_SYMTAB => symtab_off = val, + DT_SYMENT => sym_ent = val, + DT_REL => rel_off = val, + DT_RELSZ => rel_sz = val / mem::size_of::(), + DT_RELENT => rel_ent = val, + DT_RELA => rela_off = val, + DT_RELASZ => rela_sz = val / mem::size_of::(), + DT_RELAENT => rela_ent = val, + DT_JMPREL => pltrel_off = val, + DT_PLTRELSZ => pltrel_sz = val / mem::size_of::(), + DT_HASH => { + nbucket = *self.get_ref::(val + 0) + .ok_or("cannot read hash bucket count")? as usize; + nchain = *self.get_ref::(val + 4) + .ok_or("cannot read hash chain count")? as usize; + hash_off = val + 8; + hash_sz = nbucket + nchain; + } + _ => () + } + } + + if sym_ent != mem::size_of::() { + return Err("incorrect symbol entry size")? + } + if rel_ent != 0 && rel_ent != mem::size_of::() { + return Err("incorrect relocation entry size")? + } + if rela_ent != 0 && rela_ent != mem::size_of::() { + return Err("incorrect relocation entry size")? + } + + // These are the same--there are as many chains as buckets, and the chains only contain + // the symbols that overflowed the bucket. + symtab_sz = nchain; + + let hash = self.get_ref_slice::(hash_off, hash_sz) + .ok_or("cannot read hash entries")?; + let strtab = self.get_ref_slice(strtab_off, strtab_sz) + .ok_or("cannot read string table")?; + let symtab = self.get_ref_slice::(symtab_off, symtab_sz) + .ok_or("cannot read symbol table")?; + let hash_bucket = &hash[..nbucket]; + let hash_chain = &hash[nbucket..nbucket + nchain]; + let rel = self.get_ref_slice::(rel_off, rel_sz) + .ok_or("cannot read rel entries")?; + let rela = self.get_ref_slice::(rela_off, rela_sz) + .ok_or("cannot read rela entries")?; + let pltrel = self.get_ref_slice::(pltrel_off, pltrel_sz) + .ok_or("cannot read pltrel entries")?; + // debug!("ELF: {} rela, {} rel, {} pltrel entries", rela_sz, rel_sz, pltrel_sz); + + Ok(DynamicSection { + strtab, + symtab, + hash_bucket, + hash_chain, + rel, + rela, + pltrel, + }) + } + + pub fn ptr(&self) -> *const u8 { + self.data.as_ptr() + } + + pub fn write(&self, offset: usize, value: Elf32_Word) -> Result<(), Error> { + if offset + mem::size_of::() > self.data.len() { + return Err("relocation out of image bounds")? + } + + let ptr = (self.data.as_ptr() as usize + offset) as *mut Elf32_Addr; + Ok(unsafe { *ptr = value }) + } +} + +impl Drop for Image { + fn drop(&mut self) { + unsafe { + dealloc(self.data.as_mut_ptr(), self.layout); + } + } +} + +impl Deref for Image { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + self.data + } +} + +impl DerefMut for Image { + fn deref_mut(&mut self) -> &mut Self::Target { + self.data + } +} diff --git a/libdyld/src/lib.rs b/libdyld/src/lib.rs index 7219f00c..71f5551a 100644 --- a/libdyld/src/lib.rs +++ b/libdyld/src/lib.rs @@ -1,9 +1,17 @@ #![no_std] +extern crate alloc; +extern crate log; + use core::{mem, ptr, fmt, slice, str, convert}; +use alloc::string::String; +use log::{info, trace, error}; use elf::*; pub mod elf; +mod file; +mod image; +mod reloc; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Arch { @@ -11,470 +19,96 @@ pub enum Arch { OpenRisc, } -impl Arch { - fn detect(ehdr: &Elf32_Ehdr) -> Option { - const IDENT_OPENRISC: [u8; EI_NIDENT] = [ - ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, - ELFCLASS32, ELFDATA2MSB, EV_CURRENT, ELFOSABI_NONE, - /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 - ]; - const IDENT_ARM: [u8; EI_NIDENT] = [ - ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, - ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, - /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 - ]; - - match (ehdr.e_ident, ehdr.e_machine) { - (IDENT_ARM, EM_ARM) => Some(Arch::Arm), - (IDENT_OPENRISC, EM_OPENRISC) => Some(Arch::OpenRisc), - _ => None, - } - } -} - -fn read_unaligned(data: &[u8], offset: usize) -> Result { - if data.len() < offset + mem::size_of::() { - Err(()) - } else { - let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T; - Ok(unsafe { ptr::read_unaligned(ptr) }) - } -} - -fn get_ref(data: &[u8], offset: usize) -> Result<&T, ()> { - if data.len() < offset + mem::size_of::() { - Err(()) - } else if (data.as_ptr() as usize + offset) & (mem::align_of::() - 1) != 0 { - Err(()) - } else { - let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T; - Ok(unsafe { &*ptr }) - } -} - -fn get_ref_slice(data: &[u8], offset: usize, len: usize) -> Result<&[T], ()> { - if data.len() < offset + mem::size_of::() * len { - Err(()) - } else if (data.as_ptr() as usize + offset) & (mem::align_of::() - 1) != 0 { - Err(()) - } else { - let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T; - Ok(unsafe { slice::from_raw_parts(ptr, len) }) - } -} - -fn elf_hash(name: &[u8]) -> u32 { - let mut h: u32 = 0; - for c in name { - h = (h << 4) + *c as u32; - let g = h & 0xf0000000; - if g != 0 { - h ^= g >> 24; - h &= !g; - } - } - h -} #[derive(Debug)] -pub enum Error<'a> { +pub enum Error { Parsing(&'static str), - Lookup(&'a [u8]) + Lookup(String) } -impl<'a> convert::From<&'static str> for Error<'a> { - fn from(desc: &'static str) -> Error<'a> { +impl convert::From<&'static str> for Error { + fn from(desc: &'static str) -> Error { Error::Parsing(desc) } } -impl<'a> fmt::Display for Error<'a> { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { &Error::Parsing(desc) => write!(f, "parse error: {}", desc), - &Error::Lookup(sym) => - match str::from_utf8(sym) { - Ok(sym) => write!(f, "symbol lookup error: {}", sym), - Err(_) => write!(f, "symbol lookup error: {:?}", sym) - } + &Error::Lookup(ref sym) => + write!(f, "symbol lookup error: {}", sym), } } } -pub struct Library<'a> { - image_off: Elf32_Addr, - image_sz: usize, - strtab: &'a [u8], - symtab: &'a [Elf32_Sym], - pltrel: &'a [Elf32_Rel], - hash_bucket: &'a [Elf32_Word], - hash_chain: &'a [Elf32_Word], - arch: Arch, -} - -impl<'a> Library<'a> { - pub fn lookup(&self, name: &[u8]) -> Option { - let hash = elf_hash(name); - let mut index = self.hash_bucket[hash as usize % self.hash_bucket.len()] as usize; - - loop { - if index == STN_UNDEF { return None } - - let sym = &self.symtab[index]; - let sym_name_off = sym.st_name as usize; - match self.strtab.get(sym_name_off..sym_name_off + name.len()) { - Some(sym_name) if sym_name == name => { - if ELF32_ST_BIND(sym.st_info) & STB_GLOBAL == 0 { - return None - } - - match sym.st_shndx { - SHN_UNDEF => return None, - SHN_ABS => return Some(sym.st_value), - _ => return Some(self.image_off + sym.st_value) - } - } - _ => (), - } - - index = self.hash_chain[index] as usize; - } - } - - fn name_starting_at(&self, offset: usize) -> Result<&'a [u8], Error<'a>> { - let size = self.strtab.iter().skip(offset).position(|&x| x == 0) - .ok_or("symbol in symbol table not null-terminated")?; - Ok(self.strtab.get(offset..offset + size) - .ok_or("cannot read symbol name")?) - } - - fn update_rela(&self, rela: &Elf32_Rela, value: Elf32_Word) -> Result<(), Error<'a>> { - if rela.r_offset as usize + mem::size_of::() > self.image_sz { - return Err("relocation out of image bounds")? - } - - let ptr = (self.image_off + rela.r_offset) as *mut Elf32_Addr; - Ok(unsafe { *ptr = value }) - } - - - fn update_rel(&self, rel: &Elf32_Rel, value: Elf32_Word) -> Result<(), Error<'a>> { - if rel.r_offset as usize + mem::size_of::() > self.image_sz { - return Err("relocation out of image bounds")? - } - - let ptr = (self.image_off + rel.r_offset) as *mut Elf32_Addr; - Ok(unsafe { *ptr = value }) - } - - // This is unsafe because it mutates global data (the PLT). - pub unsafe fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> { - for rel in self.pltrel.iter() { - let is_rebind_type = match ELF32_R_TYPE(rel.r_info) { - R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT - if self.arch == Arch::OpenRisc => true, - R_ARM_GLOB_DAT | R_ARM_JUMP_SLOT - if self.arch == Arch::Arm => true, - _ => - // No associated symbols for other relocation types. - false, - }; - - if is_rebind_type { - let sym = self.symtab.get(ELF32_R_SYM(rel.r_info) as usize) - .ok_or("symbol out of bounds of symbol table")?; - let sym_name = self.name_starting_at(sym.st_name as usize)?; - - if sym_name == name { - self.update_rel(rel, addr)? - } - } - } - Ok(()) - } - - fn resolve_rela(&self, rela: &Elf32_Rela, resolve: &dyn Fn(&[u8]) -> Option) - -> Result<(), Error<'a>> { - let sym; - if ELF32_R_SYM(rela.r_info) == 0 { - sym = None; - } else { - sym = Some(self.symtab.get(ELF32_R_SYM(rela.r_info) as usize) - .ok_or("symbol out of bounds of symbol table")?) - } - - enum RelaType { - None, - Relative, - Lookup, - }; - let rela_type = match ELF32_R_TYPE(rela.r_info) { - R_OR1K_NONE if self.arch == Arch::OpenRisc => - RelaType::None, - R_ARM_NONE if self.arch == Arch::Arm => - RelaType::None, - - R_OR1K_RELATIVE if self.arch == Arch::OpenRisc => - RelaType::Relative, - R_ARM_RELATIVE if self.arch == Arch::Arm => - RelaType::Relative, - - R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT - if self.arch == Arch::OpenRisc => RelaType::Lookup, - R_ARM_GLOB_DAT | R_ARM_JUMP_SLOT - if self.arch == Arch::Arm => RelaType::Lookup, - - _ => - return Err("unsupported relocation type")?, - }; - let value; - match rela_type { - RelaType::None => - return Ok(()), - - RelaType::Relative => - value = self.image_off + rela.r_addend as Elf32_Word, - - RelaType::Lookup => { - let sym = sym.ok_or("relocation requires an associated symbol")?; - let sym_name = self.name_starting_at(sym.st_name as usize)?; - - // First, try to resolve against itself. - match self.lookup(sym_name) { - Some(addr) => value = addr, - None => { - // Second, call the user-provided function. - match resolve(sym_name) { - Some(addr) => value = addr, - None => { - // We couldn't find it anywhere. - return Err(Error::Lookup(sym_name)) - } - } - } - } - } - } - - self.update_rela(rela, value) - } - - fn resolve_rel(&self, rel: &Elf32_Rel, resolve: &dyn Fn(&[u8]) -> Option) - -> Result<(), Error<'a>> { - #[derive(Debug)] - enum RelType { - None, - Relative, - Lookup, - }; - let rel_type = match ELF32_R_TYPE(rel.r_info) { - R_OR1K_NONE if self.arch == Arch::OpenRisc => - RelType::None, - R_ARM_NONE if self.arch == Arch::Arm => - RelType::None, - - R_OR1K_RELATIVE if self.arch == Arch::OpenRisc => - RelType::Relative, - R_ARM_RELATIVE if self.arch == Arch::Arm => - RelType::Relative, - - R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT - if self.arch == Arch::OpenRisc => RelType::Lookup, - R_ARM_GLOB_DAT | R_ARM_JUMP_SLOT - if self.arch == Arch::Arm => RelType::Lookup, - - _ => - return Err("unsupported relocation type")?, - }; - let value; - match rel_type { - RelType::None => - return Ok(()), - - RelType::Relative => { - let addend = unsafe { - *((self.image_off + rel.r_offset) as *const Elf32_Addr) - }; - value = self.image_off + addend as Elf32_Word; - }, - - RelType::Lookup => { - let sym; - if ELF32_R_SYM(rel.r_info) == 0 { - return Err("relocation requires an associated symbol")?; - } - sym = self.symtab.get(ELF32_R_SYM(rel.r_info) as usize) - .ok_or("symbol out of bounds of symbol table")?; - let sym_name = self.name_starting_at(sym.st_name as usize)?; - - // First, try to resolve against itself. - match self.lookup(sym_name) { - Some(addr) => value = addr, - None => { - // Second, call the user-provided function. - match resolve(sym_name) { - Some(addr) => value = addr, - None => { - // We couldn't find it anywhere. - return Err(Error::Lookup(sym_name)) - } - } - } - } - } - } - - self.update_rel(rel, value) - } - - pub fn load(data: &[u8], image: &'a mut [u8], resolve: &dyn Fn(&[u8]) -> Option) - -> Result, Error<'a>> { - #![allow(unused_assignments)] - - let ehdr = read_unaligned::(data, 0) - .map_err(|()| "cannot read ELF header")?; - - if ehdr.e_type != ET_DYN { - return Err("not a shared library")? - } - - let arch = Arch::detect(&ehdr) - .ok_or("not for a supported architecture")?; - - let mut dyn_off = None; - for i in 0..ehdr.e_phnum { - let phdr_off = ehdr.e_phoff as usize + mem::size_of::() * i as usize; - let phdr = read_unaligned::(data, phdr_off) - .map_err(|()| "cannot read program header")?; - - match phdr.p_type { - PT_LOAD => { - if (phdr.p_vaddr + phdr.p_filesz) as usize > image.len() { - return Err("program header requests an out of bounds load (in image)")? - } - if (phdr.p_offset + phdr.p_filesz) as usize > data.len() { - return Err("program header requests an out of bounds load (in data)")? - } - let dst = image.get_mut(phdr.p_vaddr as usize.. - (phdr.p_vaddr + phdr.p_filesz) as usize) - .ok_or("cannot write to program header destination")?; - let src = data.get(phdr.p_offset as usize.. - (phdr.p_offset + phdr.p_filesz) as usize) - .ok_or("cannot read from program header source")?; - dst.copy_from_slice(src); - } - - PT_DYNAMIC => - dyn_off = Some(phdr.p_vaddr), - - _ => () - } - } - - let (mut strtab_off, mut strtab_sz) = (0, 0); - let (mut symtab_off, mut symtab_sz) = (0, 0); - let (mut rel_off, mut rel_sz) = (0, 0); - let (mut rela_off, mut rela_sz) = (0, 0); - let (mut pltrel_off, mut pltrel_sz) = (0, 0); - let (mut hash_off, mut hash_sz) = (0, 0); - let mut sym_ent = 0; - let mut rel_ent = 0; - let mut rela_ent = 0; - let mut nbucket = 0; - let mut nchain = 0; - - let dyn_off = dyn_off.ok_or("cannot find a dynamic header")?; - for i in 0.. { - let dyn_off = dyn_off as usize + i * mem::size_of::(); - let dyn = get_ref::(image, dyn_off) - .map_err(|()| "cannot read dynamic header")?; - - let val = unsafe { dyn.d_un.d_val } as usize; - match dyn.d_tag { - DT_NULL => break, - DT_STRTAB => strtab_off = val, - DT_STRSZ => strtab_sz = val, - DT_SYMTAB => symtab_off = val, - DT_SYMENT => sym_ent = val, - DT_REL => rel_off = val, - DT_RELSZ => rel_sz = val / mem::size_of::(), - DT_RELENT => rel_ent = val, - DT_RELA => rela_off = val, - DT_RELASZ => rela_sz = val / mem::size_of::(), - DT_RELAENT => rela_ent = val, - DT_JMPREL => pltrel_off = val, - DT_PLTRELSZ => pltrel_sz = val / mem::size_of::(), - DT_HASH => { - nbucket = *get_ref::(image, val + 0) - .map_err(|()| "cannot read hash bucket count")? as usize; - nchain = *get_ref::(image, val + 4) - .map_err(|()| "cannot read hash chain count")? as usize; - hash_off = val + 8; - hash_sz = nbucket + nchain; - } - _ => () - } - } - - if sym_ent != mem::size_of::() { - return Err("incorrect symbol entry size")? - } - if rel_ent != 0 && rel_ent != mem::size_of::() { - return Err("incorrect relocation entry size")? - } - if rela_ent != 0 && rela_ent != mem::size_of::() { - return Err("incorrect relocation entry size")? - } - - // These are the same--there are as many chains as buckets, and the chains only contain - // the symbols that overflowed the bucket. - symtab_sz = nchain; - - // Drop the mutability. See also the comment below. - let image = &*image; - - let strtab = get_ref_slice::(image, strtab_off, strtab_sz) - .map_err(|()| "cannot read string table")?; - let symtab = get_ref_slice::(image, symtab_off, symtab_sz) - .map_err(|()| "cannot read symbol table")?; - let rel = get_ref_slice::(image, rel_off, rel_sz) - .map_err(|()| "cannot read rel entries")?; - let rela = get_ref_slice::(image, rela_off, rela_sz) - .map_err(|()| "cannot read rela entries")?; - let pltrel = get_ref_slice::(image, pltrel_off, pltrel_sz) - .map_err(|()| "cannot read pltrel entries")?; - let hash = get_ref_slice::(image, hash_off, hash_sz) - .map_err(|()| "cannot read hash entries")?; - - let library = Library { - image_off: image.as_ptr() as Elf32_Word, - image_sz: image.len(), - strtab, - symtab, - pltrel, - hash_bucket: &hash[..nbucket], - hash_chain: &hash[nbucket..nbucket + nchain], - arch, - }; - - // If a borrow exists anywhere, the borrowed memory cannot be mutated except - // through that pointer or it's UB. However, we need to retain pointers - // to the symbol tables and relocations, and at the same time mutate the code - // to resolve the relocations. - // - // To avoid invoking UB, we drop the only pointer to the entire area (which is - // unique since it's a &mut); we retain pointers to the various tables, but - // we never write to the memory they refer to, so it's safe. - mem::drop(image); - - for r in rela { library.resolve_rela(r, resolve)? } - for r in rel { library.resolve_rel(r, resolve)? } - /// TODO: processing of pltrel has been changed from - /// resolve_rela() to resolve_rel(). verify if this is - /// specific to eg. architecture? - for r in pltrel { library.resolve_rel(r, resolve)? } - - Ok(library) - } +pub fn load( + data: &[u8], + resolve: &dyn Fn(&[u8]) -> Option +) -> Result { + // validate ELF file + let file = file::File::new(data) + .ok_or("cannot read ELF header")?; + if file.ehdr.e_type != ET_DYN { + return Err("not a shared library")? + } + let arch = file.arch() + .ok_or("not for a supported architecture")?; + + // prepare target memory + let image_size = file.program_headers() + .filter_map(|phdr| phdr.map(|phdr| phdr.p_vaddr + phdr.p_memsz)) + .max() + .unwrap_or(0) as usize; + let image_align = file.program_headers() + .filter_map(|phdr| phdr.and_then(|phdr| { + if phdr.p_type == PT_LOAD { + Some(phdr.p_align) + } else { + None + } + })) + .max() + .unwrap_or(4) as usize; + // 1 image for all segments + let mut image = image::Image::new(image_size, image_align) + .map_err(|_| "cannot allocate target image")?; + info!("ELF target: {} bytes, align to {:X}, allocated at {:08X}", image_size, image_align, image.ptr() as usize); + + // LOAD + for phdr in file.program_headers() { + let phdr = phdr.ok_or("cannot read program header")?; + if phdr.p_type != PT_LOAD { continue; } + + trace!("Program header: {:08X}+{:08X} to {:08X}", + phdr.p_offset, phdr.p_filesz, + image.ptr() as u32 + ); + let src = file.get(phdr.p_offset as usize..(phdr.p_offset + phdr.p_filesz) as usize) + .ok_or("program header requests an out of bounds load (in file)")?; + let dst = image.get_mut(phdr.p_vaddr as usize.. + (phdr.p_vaddr + phdr.p_filesz) as usize) + .ok_or("program header requests an out of bounds load (in target)")?; + dst.copy_from_slice(src); + } + + // relocate DYNAMIC + let dyn_range = file.dynamic_header_vaddr() + .ok_or("cannot find a dynamic header")?; + let dynamic_section = image.dynamic_section(dyn_range)?; + info!("Relocating {} rela, {} rel, {} pltrel", + dynamic_section.rela.len(), dynamic_section.rel.len(), dynamic_section.pltrel.len()); + + for rela in dynamic_section.rela { + reloc::relocate(arch, &image, &dynamic_section, rela, resolve)?; + } + for rel in dynamic_section.rela { + reloc::relocate(arch, &image, &dynamic_section, rel, resolve)?; + } + for pltrel in dynamic_section.pltrel { + reloc::relocate(arch, &image, &dynamic_section, pltrel, resolve)?; + } + + Ok(image) } diff --git a/libdyld/src/reloc.rs b/libdyld/src/reloc.rs new file mode 100644 index 00000000..c9a15e4a --- /dev/null +++ b/libdyld/src/reloc.rs @@ -0,0 +1,136 @@ +use alloc::string::String; +use log::{debug, trace}; +use super::{ + Arch, + elf::*, + Error, + image::{DynamicSection, Image}, +}; + +pub trait Relocatable { + fn offset(&self) -> usize; + fn type_info(&self) -> u8; + fn sym_info(&self) -> u32; + fn addend(&self, image: &Image) -> i32; +} + +impl Relocatable for Elf32_Rel { + fn offset(&self) -> usize { + self.r_offset as usize + } + + fn type_info(&self) -> u8 { + ELF32_R_TYPE(self.r_info) + } + + fn sym_info(&self) -> u32 { + ELF32_R_SYM(self.r_info) + } + + fn addend(&self, image: &Image) -> i32 { + *image.get_ref(self.offset()).unwrap() + } +} + +impl Relocatable for Elf32_Rela { + fn offset(&self) -> usize { + self.r_offset as usize + } + + fn type_info(&self) -> u8 { + ELF32_R_TYPE(self.r_info) + } + + fn sym_info(&self) -> u32 { + ELF32_R_SYM(self.r_info) + } + + fn addend(&self, _: &Image) -> i32 { + self.r_addend + } +} + +#[derive(Clone, Copy, Debug)] +enum RelType { + None, + Relative, + Lookup, +} + +impl RelType { + pub fn new(arch: Arch, type_info: u8) -> Option { + match type_info { + R_OR1K_NONE if arch == Arch::OpenRisc => + Some(RelType::None), + R_ARM_NONE if arch == Arch::Arm => + Some(RelType::None), + + R_OR1K_RELATIVE if arch == Arch::OpenRisc => + Some(RelType::Relative), + R_ARM_RELATIVE if arch == Arch::Arm => + Some(RelType::Relative), + + R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT + if arch == Arch::OpenRisc => Some(RelType::Lookup), + R_ARM_GLOB_DAT | R_ARM_JUMP_SLOT + if arch == Arch::Arm => Some(RelType::Lookup), + + _ => + None + } + } +} + +fn format_sym_name(sym_name: &[u8]) -> String { + core::str::from_utf8(sym_name) + .map(String::from) + .unwrap_or(String::from("")) +} + +pub fn relocate<'a, R: Relocatable>( + arch: Arch, image: &'a Image, dynamic_section: &'a DynamicSection<'a>, + rel: &'a R, resolve: &dyn Fn(&[u8]) -> Option +) -> Result<(), Error> { + // debug!("rel r_offset={:08X} r_info={:08X} r_addend={:08X}", rel.offset(), rel.r_info, rela.r_addend); + let sym; + if rel.sym_info() == 0 { + sym = None; + } else { + sym = Some(dynamic_section.symtab.get(rel.sym_info() as usize) + .ok_or("symbol out of bounds of symbol table")?) + } + + let rel_type = RelType::new(arch, rel.type_info()) + .ok_or("unsupported relocation type")?; + let value; + match rel_type { + RelType::None => + return Ok(()), + + RelType::Relative => { + let addend = rel.addend(image); + value = image.ptr().wrapping_offset(addend as isize) as Elf32_Word; + } + + RelType::Lookup => { + let sym = sym.ok_or("relocation requires an associated symbol")?; + let sym_name = dynamic_section.name_starting_at(sym.st_name as usize)?; + + if let Some(addr) = dynamic_section.lookup(sym_name) { + // First, try to resolve against itself. + trace!("looked up symbol {} in image", format_sym_name(sym_name)); + value = image.ptr() as u32 + addr; + } else if let Some(addr) = resolve(sym_name) { + // Second, call the user-provided function. + trace!("resolved symbol {:?}", format_sym_name(sym_name)); + value = addr; + } else { + // We couldn't find it anywhere. + return Err(Error::Lookup(format_sym_name(sym_name))) + } + } + } + + debug!("rel_type={:?} write at {:08X} value {:08X}", rel_type, rel.offset(), value); + image.write(rel.offset(), value) +} diff --git a/runtime/src/kernel.rs b/runtime/src/kernel.rs index 23686ccd..f6dce012 100644 --- a/runtime/src/kernel.rs +++ b/runtime/src/kernel.rs @@ -107,12 +107,12 @@ pub fn main_core1() { } let core1_rx = core1_rx.unwrap(); - let mut image = vec![0; 1024*1024]; for message in core1_rx { match *message { Message::LoadRequest(data) => { - match dyld::Library::load(&data, &mut image, &resolve) { - Ok(library) => { + match dyld::load(&data, &resolve) { + Ok(image) => { + println!("image loaded"); core1_tx.send(Message::LoadCompleted) }, Err(error) => {