libdyld: refactor

This commit is contained in:
Astro 2020-04-29 22:01:12 +02:00 committed by Sebastien Bourdeauducq
parent 22531b14c0
commit 656222ff06
7 changed files with 563 additions and 449 deletions

3
Cargo.lock generated
View File

@ -61,6 +61,9 @@ checksum = "7313c0d620d0cb4dbd9d019e461a4beb501071ff46ec0ab933efb4daa76d73e3"
[[package]] [[package]]
name = "dyld" name = "dyld"
version = "0.1.0" version = "0.1.0"
dependencies = [
"log",
]
[[package]] [[package]]
name = "embedded-hal" name = "embedded-hal"

View File

@ -5,3 +5,6 @@ version = "0.1.0"
[lib] [lib]
name = "dyld" name = "dyld"
[dependencies]
log = "0.4"

73
libdyld/src/file.rs Normal file
View File

@ -0,0 +1,73 @@
use core::{mem, ptr, fmt, slice, str, convert, ops::{Deref, Range}};
use super::{
Arch,
elf::*,
};
fn read_unaligned<T: Copy>(data: &[u8], offset: usize) -> Option<T> {
if data.len() < offset + mem::size_of::<T>() {
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<Self> {
let ehdr = read_unaligned(data, 0)?;
Some(File { ehdr, data })
}
fn read_unaligned<T: Copy>(&self, offset: usize) -> Option<T> {
read_unaligned(self.data, offset)
}
pub fn arch(&self) -> Option<Arch> {
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<Item = Option<Elf32_Phdr>> + 'b
{
(0..self.ehdr.e_phnum).map(move |i| {
let phdr_off = self.ehdr.e_phoff as usize + mem::size_of::<Elf32_Phdr>() * i as usize;
self.read_unaligned::<Elf32_Phdr>(phdr_off)
})
}
pub fn dyn_header_vaddr(&self) -> Option<Range<usize>> {
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
}
}

245
libdyld/src/image.rs Normal file
View File

@ -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<Elf32_Word> {
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<Self, LayoutErr> {
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<T>(&self, offset: usize) -> Option<&T>
where
T: Copy,
{
if self.data.len() < offset + mem::size_of::<T>() {
None
} else if (self.data.as_ptr() as usize + offset) & (mem::align_of::<T>() - 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<T: Copy>(&self, offset: usize, len: usize) -> Option<&[T]> {
if self.data.len() < offset + mem::size_of::<T>() * len {
None
} else if (self.data.as_ptr() as usize + offset) & (mem::align_of::<T>() - 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 dyn_headers<'a>(&'a self, range: Range<usize>) ->
impl Iterator<Item = &'a Elf32_Dyn> + 'a
{
range
.step_by(mem::size_of::<Elf32_Dyn>())
.filter_map(move |offset| {
self.get_ref::<Elf32_Dyn>(offset)
})
.take_while(|d| unsafe { d.d_un.d_val } as i32 != DT_NULL)
}
pub fn dyn_section(&self, range: Range<usize>) -> Result<DynamicSection, Error> {
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.dyn_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::<Elf32_Rel>(),
DT_RELENT => rel_ent = val,
DT_RELA => rela_off = val,
DT_RELASZ => rela_sz = val / mem::size_of::<Elf32_Rela>(),
DT_RELAENT => rela_ent = val,
DT_JMPREL => pltrel_off = val,
DT_PLTRELSZ => pltrel_sz = val / mem::size_of::<Elf32_Rel>(),
DT_HASH => {
nbucket = *self.get_ref::<Elf32_Word>(val + 0)
.ok_or("cannot read hash bucket count")? as usize;
nchain = *self.get_ref::<Elf32_Word>(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::<Elf32_Sym>() {
return Err("incorrect symbol entry size")?
}
if rel_ent != 0 && rel_ent != mem::size_of::<Elf32_Rel>() {
return Err("incorrect relocation entry size")?
}
if rela_ent != 0 && rela_ent != mem::size_of::<Elf32_Rela>() {
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::<Elf32_Word>(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::<Elf32_Sym>(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::<Elf32_Rel>(rel_off, rel_sz)
.ok_or("cannot read rel entries")?;
let rela = self.get_ref_slice::<Elf32_Rela>(rela_off, rela_sz)
.ok_or("cannot read rela entries")?;
let pltrel = self.get_ref_slice::<Elf32_Rel>(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::<Elf32_Addr>() > 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
}
}

View File

@ -1,9 +1,18 @@
#![no_std] #![no_std]
use core::{mem, ptr, fmt, slice, str, convert}; extern crate alloc;
extern crate log;
use core::{mem, ptr, fmt, slice, str, convert, ops::Range};
use alloc::string::String;
use log::{info, trace, error};
use elf::*; use elf::*;
pub mod elf; pub mod elf;
mod file;
mod image;
use image::{DynamicSection, Image};
mod reloc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Arch { pub enum Arch {
@ -11,470 +20,116 @@ pub enum Arch {
OpenRisc, OpenRisc,
} }
impl Arch {
fn detect(ehdr: &Elf32_Ehdr) -> Option<Self> {
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<T: Copy>(data: &[u8], offset: usize) -> Result<T, ()> {
if data.len() < offset + mem::size_of::<T>() {
Err(())
} else {
let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T;
Ok(unsafe { ptr::read_unaligned(ptr) })
}
}
fn get_ref<T: Copy>(data: &[u8], offset: usize) -> Result<&T, ()> {
if data.len() < offset + mem::size_of::<T>() {
Err(())
} else if (data.as_ptr() as usize + offset) & (mem::align_of::<T>() - 1) != 0 {
Err(())
} else {
let ptr = data.as_ptr().wrapping_offset(offset as isize) as *const T;
Ok(unsafe { &*ptr })
}
}
fn get_ref_slice<T: Copy>(data: &[u8], offset: usize, len: usize) -> Result<&[T], ()> {
if data.len() < offset + mem::size_of::<T>() * len {
Err(())
} else if (data.as_ptr() as usize + offset) & (mem::align_of::<T>() - 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)] #[derive(Debug)]
pub enum Error<'a> { pub enum Error {
Parsing(&'static str), Parsing(&'static str),
Lookup(&'a [u8]) Lookup(String)
} }
impl<'a> convert::From<&'static str> for Error<'a> { impl convert::From<&'static str> for Error {
fn from(desc: &'static str) -> Error<'a> { fn from(desc: &'static str) -> Error {
Error::Parsing(desc) Error::Parsing(desc)
} }
} }
impl<'a> fmt::Display for Error<'a> { impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
&Error::Parsing(desc) => &Error::Parsing(desc) =>
write!(f, "parse error: {}", desc), write!(f, "parse error: {}", desc),
&Error::Lookup(sym) => &Error::Lookup(ref sym) =>
match str::from_utf8(sym) { write!(f, "symbol lookup error: {}", sym),
Ok(sym) => write!(f, "symbol lookup error: {}", sym),
Err(_) => write!(f, "symbol lookup error: {:?}", sym)
}
} }
} }
} }
pub struct Library<'a> { pub struct Library {
image_off: Elf32_Addr, image: Image,
image_sz: usize, dyn_range: Range<usize>,
strtab: &'a [u8], dyn_section: DynamicSection<'static>,
symtab: &'a [Elf32_Sym],
pltrel: &'a [Elf32_Rel],
hash_bucket: &'a [Elf32_Word],
hash_chain: &'a [Elf32_Word],
arch: Arch,
} }
impl<'a> Library<'a> { impl Library {
pub fn lookup(&self, name: &[u8]) -> Option<Elf32_Word> { pub fn lookup(&self, name: &[u8]) -> Option<u32> {
let hash = elf_hash(name); self.dyn_section.lookup(name)
let mut index = self.hash_bucket[hash as usize % self.hash_bucket.len()] as usize; .map(|addr| self.image.ptr() as u32 + addr)
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 { pub fn load(
SHN_UNDEF => return None, data: &[u8],
SHN_ABS => return Some(sym.st_value), resolve: &dyn Fn(&[u8]) -> Option<Elf32_Word>
_ => return Some(self.image_off + sym.st_value) ) -> Result<Library, Error> {
} // validate ELF file
} let file = file::File::new(data)
_ => (), .ok_or("cannot read ELF header")?;
} if file.ehdr.e_type != ET_DYN {
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::<Elf32_Addr>() > 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::<Elf32_Addr>() > 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<Elf32_Word>)
-> 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<Elf32_Word>)
-> 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<Elf32_Word>)
-> Result<Library<'a>, Error<'a>> {
#![allow(unused_assignments)]
let ehdr = read_unaligned::<Elf32_Ehdr>(data, 0)
.map_err(|()| "cannot read ELF header")?;
if ehdr.e_type != ET_DYN {
return Err("not a shared library")? return Err("not a shared library")?
} }
let arch = file.arch()
let arch = Arch::detect(&ehdr)
.ok_or("not for a supported architecture")?; .ok_or("not for a supported architecture")?;
let mut dyn_off = None; // prepare target memory
for i in 0..ehdr.e_phnum { let image_size = file.program_headers()
let phdr_off = ehdr.e_phoff as usize + mem::size_of::<Elf32_Phdr>() * i as usize; .filter_map(|phdr| phdr.map(|phdr| phdr.p_vaddr + phdr.p_memsz))
let phdr = read_unaligned::<Elf32_Phdr>(data, phdr_off) .max()
.map_err(|()| "cannot read program header")?; .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);
match phdr.p_type { // LOAD
PT_LOAD => { for phdr in file.program_headers() {
if (phdr.p_vaddr + phdr.p_filesz) as usize > image.len() { let phdr = phdr.ok_or("cannot read program header")?;
return Err("program header requests an out of bounds load (in image)")? if phdr.p_type != PT_LOAD { continue; }
}
if (phdr.p_offset + phdr.p_filesz) as usize > data.len() { trace!("Program header: {:08X}+{:08X} to {:08X}",
return Err("program header requests an out of bounds load (in data)")? 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.. let dst = image.get_mut(phdr.p_vaddr as usize..
(phdr.p_vaddr + phdr.p_filesz) as usize) (phdr.p_vaddr + phdr.p_filesz) as usize)
.ok_or("cannot write to program header destination")?; .ok_or("program header requests an out of bounds load (in target)")?;
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); dst.copy_from_slice(src);
} }
PT_DYNAMIC => // relocate DYNAMIC
dyn_off = Some(phdr.p_vaddr), let dyn_range = file.dyn_header_vaddr()
.ok_or("cannot find a dynamic header")?;
let dyn_section = image.dyn_section(dyn_range.clone())?;
info!("Relocating {} rela, {} rel, {} pltrel",
dyn_section.rela.len(), dyn_section.rel.len(), dyn_section.pltrel.len());
_ => () for rela in dyn_section.rela {
reloc::relocate(arch, &image, &dyn_section, rela, resolve)?;
} }
for rel in dyn_section.rela {
reloc::relocate(arch, &image, &dyn_section, rel, resolve)?;
}
for pltrel in dyn_section.pltrel {
reloc::relocate(arch, &image, &dyn_section, pltrel, resolve)?;
} }
let (mut strtab_off, mut strtab_sz) = (0, 0); let dyn_section = unsafe {
let (mut symtab_off, mut symtab_sz) = (0, 0); core::mem::transmute(dyn_section)
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::<Elf32_Dyn>();
let dyn = get_ref::<Elf32_Dyn>(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::<Elf32_Rel>(),
DT_RELENT => rel_ent = val,
DT_RELA => rela_off = val,
DT_RELASZ => rela_sz = val / mem::size_of::<Elf32_Rela>(),
DT_RELAENT => rela_ent = val,
DT_JMPREL => pltrel_off = val,
DT_PLTRELSZ => pltrel_sz = val / mem::size_of::<Elf32_Rel>(),
DT_HASH => {
nbucket = *get_ref::<Elf32_Word>(image, val + 0)
.map_err(|()| "cannot read hash bucket count")? as usize;
nchain = *get_ref::<Elf32_Word>(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::<Elf32_Sym>() {
return Err("incorrect symbol entry size")?
}
if rel_ent != 0 && rel_ent != mem::size_of::<Elf32_Rel>() {
return Err("incorrect relocation entry size")?
}
if rela_ent != 0 && rela_ent != mem::size_of::<Elf32_Rela>() {
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::<u8>(image, strtab_off, strtab_sz)
.map_err(|()| "cannot read string table")?;
let symtab = get_ref_slice::<Elf32_Sym>(image, symtab_off, symtab_sz)
.map_err(|()| "cannot read symbol table")?;
let rel = get_ref_slice::<Elf32_Rel>(image, rel_off, rel_sz)
.map_err(|()| "cannot read rel entries")?;
let rela = get_ref_slice::<Elf32_Rela>(image, rela_off, rela_sz)
.map_err(|()| "cannot read rela entries")?;
let pltrel = get_ref_slice::<Elf32_Rel>(image, pltrel_off, pltrel_sz)
.map_err(|()| "cannot read pltrel entries")?;
let hash = get_ref_slice::<Elf32_Word>(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,
}; };
Ok(Library {
// If a borrow exists anywhere, the borrowed memory cannot be mutated except image,
// through that pointer or it's UB. However, we need to retain pointers dyn_range,
// to the symbol tables and relocations, and at the same time mutate the code dyn_section,
// 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)
}
} }

136
libdyld/src/reloc.rs Normal file
View File

@ -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<Self> {
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("<invalid symbol name>"))
}
pub fn relocate<'a, R: Relocatable>(
arch: Arch, image: &'a Image, dynamic_section: &'a DynamicSection<'a>,
rel: &'a R, resolve: &dyn Fn(&[u8]) -> Option<Elf32_Word>
) -> 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)
}

View File

@ -110,12 +110,11 @@ pub fn main_core1() {
} }
let core1_rx = core1_rx.unwrap(); let core1_rx = core1_rx.unwrap();
let mut image = vec![0; 1024*1024];
let mut current_modinit: Option<u32> = None; let mut current_modinit: Option<u32> = None;
for message in core1_rx { for message in core1_rx {
match *message { match *message {
Message::LoadRequest(data) => { Message::LoadRequest(data) => {
match dyld::Library::load(&data, &mut image, &resolve) { match dyld::load(&data, &resolve) {
Ok(library) => { Ok(library) => {
let bss_start = library.lookup(b"__bss_start"); let bss_start = library.lookup(b"__bss_start");
let end = library.lookup(b"_end"); let end = library.lookup(b"_end");