diff --git a/artiq/firmware/libdyld/lib.rs b/artiq/firmware/libdyld/lib.rs index 1d89edae..f814f6b9 100644 --- a/artiq/firmware/libdyld/lib.rs +++ b/artiq/firmware/libdyld/lib.rs @@ -36,17 +36,11 @@ fn get_ref_slice(data: &[u8], offset: usize, len: usize) -> Result<&[T] } } -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 +fn name_starting_at_slice(slice: &[u8], offset: usize) -> Result<&[u8], Error> { + let size = slice.iter().skip(offset).position(|&x| x == 0) + .ok_or("symbol in symbol table not null-terminated")?; + Ok(slice.get(offset..offset + size) + .ok_or("cannot read symbol name")?) } #[derive(Debug)] @@ -75,34 +69,20 @@ impl<'a> fmt::Display for Error<'a> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Arch { - RiscV, - OpenRisc, -} - pub struct Library<'a> { image_off: Elf32_Addr, image_sz: usize, strtab: &'a [u8], symtab: &'a [Elf32_Sym], - pltrel: &'a [Elf32_Rela], - hash_bucket: &'a [Elf32_Word], - hash_chain: &'a [Elf32_Word], + textrel: &'a [Elf32_Rela], } 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]; + for sym in self.symtab { 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 => { + match name_starting_at_slice(self.strtab, sym_name_off) { + Ok(sym_name) if sym_name == name => { if ELF32_ST_BIND(sym.st_info) & STB_GLOBAL == 0 { return None } @@ -110,51 +90,24 @@ impl<'a> Library<'a> { match sym.st_shndx { SHN_UNDEF => return None, SHN_ABS => return Some(sym.st_value), + // TODO: Make sure that st_shndx is indeed pointing to .text _ => 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")?) + return None; } 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 }) + unimplemented!() } // This is unsafe because it mutates global data (the PLT). pub unsafe fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> { - for rela in self.pltrel.iter() { - match ELF32_R_TYPE(rela.r_info) { - R_RISCV_32 | R_RISCV_JUMP_SLOT => { - let sym = self.symtab.get(ELF32_R_SYM(rela.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_rela(rela, addr)? - } - } - - // No associated symbols for other relocation types. - _ => () - } - } - Ok(()) + unimplemented!() } fn resolve_rela(&self, rela: &Elf32_Rela, resolve: &dyn Fn(&[u8]) -> Option) @@ -167,43 +120,70 @@ impl<'a> Library<'a> { .ok_or("symbol out of bounds of symbol table")?) } - let value; - match ELF32_R_TYPE(rela.r_info) { - R_RISCV_NONE => - return Ok(()), - - R_RISCV_RELATIVE => - value = self.image_off + rela.r_addend as Elf32_Word, - - R_RISCV_32 | R_RISCV_JUMP_SLOT => { - 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)) - } + let resolve_symbol_addr = |symbol: Option<&Elf32_Sym>| -> Result> { + let sym = symbol.ok_or("relocation requires an associated symbol")?; + let sym_name = name_starting_at_slice(self.strtab, sym.st_name as usize)?; + + // First, try to resolve against itself. + match self.lookup(sym_name) { + Some(addr) => Ok(addr), + None => { + // Second, call the user-provided function. + match resolve(sym_name) { + Some(addr) => Ok(addr), + None => { + // We couldn't find it anywhere. + Err(Error::Lookup(sym_name)) } } } } + }; - _ => return Err("unsupported relocation type")? - } + match ELF32_R_TYPE(rela.r_info) { + R_RISCV_CALL_PLT => { + let addr = resolve_symbol_addr(sym)?; + + // Modify the auipc+jalr sequence to jump + // Replaced the sequence with lui+jalr jsut to makes it cleaner + unsafe { + let auipc_insn_ptr = (self.image_off + rela.r_offset) as *mut u32; + let jalr_insn_ptr = auipc_insn_ptr.offset(1); + + *auipc_insn_ptr = 0b0110111 | (*auipc_insn_ptr & 0xF80) | (addr & 0xFFFFF000); + *jalr_insn_ptr = (*jalr_insn_ptr & 0xFFFFF) | ((addr & 0xFFF) << 20); + } + + Ok(()) + } + + R_RISCV_GOT_HI20 => { + let addr = resolve_symbol_addr(sym)?; + + // Modify the auipc+lw sequence to get the desired value directly + // Use lui+addi for easier math + unsafe { + let auipc_insn_ptr = (self.image_off + rela.r_offset) as *mut u32; + let lw_insn_ptr = auipc_insn_ptr.offset(1); + + *auipc_insn_ptr = 0b0110111 | (*auipc_insn_ptr & 0xF80) | (addr & 0xFFFFF000); + *lw_insn_ptr = 0b0010011 | 0b000 << 12 | (*lw_insn_ptr & 0xF8F80) | ((addr & 0xFFF) << 20) + } + + Ok(()) + } + + R_RISCV_PCREL_LO12_I => { + // Already relocated when processing the GOT_HI20 relocation + Ok(()) + } - self.update_rela(rela, value) + _ => Err("unsupported relocation type")? + } } 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")?; @@ -225,112 +205,91 @@ impl<'a> Library<'a> { #[cfg(not(all(target_feature = "f", target_feature = "d")))] const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT; - if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS { + if ehdr.e_ident != IDENT || ehdr.e_type != ET_REL || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS { return Err("not a shared library for current 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() || - (phdr.p_offset + phdr.p_filesz) as usize > data.len() { - return Err("program header requests an out of bounds load")? - } - 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), - - _ => () - } - } + // Prepare string table + let strtab_shdr_off = ehdr.e_shoff as usize + mem::size_of::() * ehdr.e_shstrndx as usize; + let strtab_shdr = read_unaligned::(data, strtab_shdr_off) + .map_err(|()| "cannot read string table header")?; + let (strtab_off, strtab_sz) = (strtab_shdr.sh_offset as usize, strtab_shdr.sh_size as usize); + let strtab = get_ref_slice::(data, strtab_off, strtab_sz) + .map_err(|()| "cannot read string table")?; - let (mut strtab_off, mut strtab_sz) = (0, 0); + // Retrieve .text, .rela.text & .symtab + // Note: may need to parse more sections in the future + let (mut text_off, mut text_sz) = (0, 0); let (mut symtab_off, mut symtab_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 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_REL => return Err("relocations with implicit addend are not supported")?, - DT_STRTAB => strtab_off = val, - DT_STRSZ => strtab_sz = val, - DT_SYMTAB => symtab_off = val, - DT_SYMENT => sym_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; + let (mut rela_text_off, mut rela_text_sz) = (0, 0); + + for i in 0..ehdr.e_shnum { + let shdr_off = ehdr.e_shoff as usize + mem::size_of::() * i as usize; + let shdr = read_unaligned::(data, shdr_off) + .map_err(|()| "cannot read section header")?; + + let shdr_name = name_starting_at_slice(strtab, shdr.sh_name as usize) + .map_err(|_| "cannot read section name")?; + + match core::str::from_utf8(shdr_name).map_err(|_| "cannot parse section name to str")? { + ".text" => { + text_off = shdr.sh_offset as usize; + text_sz = shdr.sh_size as usize; + } + ".symtab" => { + symtab_off = shdr.sh_offset as usize; + symtab_sz = shdr.sh_size as usize; } - _ => () + ".rela.text" => { + rela_text_off = shdr.sh_offset as usize; + rela_text_sz = shdr.sh_size as usize; + } + _ => {} } } - if sym_ent != mem::size_of::() { - return Err("incorrect symbol entry size")? - } - if rela_ent != 0 && rela_ent != mem::size_of::() { - return Err("incorrect relocation entry size")? + // Copy .text, .symtab, .rela.text & .strtab to target + // Create reference to all sections (except .text) from image + let mut target_off = 0; + + macro_rules! load_section_from_data { + ($src_off:ident, $size: ident) => { + { + let dest = image.get_mut(target_off..target_off+$size) + .ok_or("cannot write to section header destination")?; + let src = data.get($src_off..$src_off+$size) + .ok_or("cannot read from section header destination")?; + dest.copy_from_slice(src); + // Increment dest offset, maintain 4-bytes alignment + let loaded_off = target_off; + target_off += $size + ((4 - ($size % 4)) % 4); + loaded_off + } + }; } - // 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; + // .text must be loaded first, the start address is fixed at 0x0 in the .o file + load_section_from_data!(text_off, text_sz); + let symtab_off = load_section_from_data!(symtab_off, symtab_sz); + let rela_text_off = load_section_from_data!(rela_text_off, rela_text_sz); + let strtab_off = load_section_from_data!(strtab_off, strtab_sz); // Drop the mutability. See also the comment below. let image = &*image; + let symtab = get_ref_slice::(image, symtab_off, symtab_sz / mem::size_of::()) + .map_err(|()| "cannot read symbol table")?; + let textrel = get_ref_slice::(image, rela_text_off, rela_text_sz / mem::size_of::()) + .map_err(|()| "cannot read text relocation table")?; 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 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(), + image_sz: text_sz, strtab: strtab, symtab: symtab, - pltrel: pltrel, - hash_bucket: &hash[..nbucket], - hash_chain: &hash[nbucket..nbucket + nchain], + textrel: textrel, }; // If a borrow exists anywhere, the borrowed memory cannot be mutated except @@ -343,8 +302,7 @@ impl<'a> Library<'a> { // 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 pltrel { library.resolve_rela(r, resolve)? } + for r in textrel { library.resolve_rela(r, resolve)? } Ok(library) }