#![deny( future_incompatible, let_underscore, nonstandard_style, clippy::all )] #![warn(rust_2024_compatibility)] #![warn(clippy::pedantic)] #![allow( clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::doc_markdown, clippy::enum_glob_use, clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::module_name_repetitions, clippy::similar_names, clippy::struct_field_names, clippy::too_many_lines, clippy::wildcard_imports )] use std::{collections::HashMap, mem, ptr, slice, str}; use byteorder::{ByteOrder, LittleEndian}; use dwarf::*; use elf::*; mod dwarf; mod elf; #[derive(PartialEq, Clone, Copy)] pub enum Isa { CortexA9, RiscV32, } #[derive(Debug)] pub enum Error { Parsing(&'static str), Lookup(&'static str), } impl From<&'static str> for Error { fn from(desc: &'static str) -> Error { Error::Parsing(desc) } } pub trait Relocatable { fn offset(&self) -> Elf32_Addr; fn type_info(&self) -> u8; fn sym_info(&self) -> Elf32_Word; fn addend(&self, sec_image: &[u8]) -> Elf32_Sword; } impl Relocatable for Elf32_Rel { fn offset(&self) -> Elf32_Addr { self.r_offset } fn type_info(&self) -> u8 { ELF32_R_TYPE(self.r_info) } fn sym_info(&self) -> Elf32_Word { ELF32_R_SYM(self.r_info) } fn addend(&self, sec_image: &[u8]) -> Elf32_Sword { LittleEndian::read_i32(&sec_image[self.offset() as usize..]) } } impl Relocatable for Elf32_Rela { fn offset(&self) -> Elf32_Addr { self.r_offset } fn type_info(&self) -> u8 { ELF32_R_TYPE(self.r_info) } fn sym_info(&self) -> Elf32_Word { ELF32_R_SYM(self.r_info) } fn addend(&self, _: &[u8]) -> Elf32_Sword { self.r_addend } } struct SectionRecord<'a> { shdr: Elf32_Shdr, name: &'a str, data: Vec, } fn read_unaligned(data: &[u8], offset: usize) -> Option { if data.len() < offset + mem::size_of::() { None } else { let ptr = data.as_ptr().wrapping_add(offset).cast(); Some(unsafe { ptr::read_unaligned(ptr) }) } } #[must_use] pub fn get_ref_slice(data: &[u8], offset: usize, len: usize) -> Option<&[T]> { if data.len() < offset + mem::size_of::() * len { None } else { let ptr = data.as_ptr().wrapping_add(offset).cast(); Some(unsafe { slice::from_raw_parts(ptr, len) }) } } fn from_struct_slice(struct_vec: &[T]) -> Vec { let ptr = struct_vec.as_ptr(); unsafe { slice::from_raw_parts(ptr.cast(), mem::size_of_val(struct_vec)) }.to_vec() } fn to_struct_slice(bytes: &[u8]) -> &[T] { unsafe { slice::from_raw_parts(bytes.as_ptr().cast(), bytes.len() / mem::size_of::()) } } fn to_struct_mut_slice(bytes: &mut [u8]) -> &mut [T] { unsafe { slice::from_raw_parts_mut(bytes.as_mut_ptr().cast(), bytes.len() / mem::size_of::()) } } fn elf_hash(name: &[u8]) -> u32 { let mut h: u32 = 0; for c in name { h = (h << 4) + u32::from(*c); let g = h & 0xf000_0000; 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")?) } macro_rules! get_section_by_name { ($linker: ident, $sec_name: expr) => { $linker.elf_shdrs.iter().find(|rec| rec.name == $sec_name) }; } macro_rules! get_mut_section_by_name { ($linker: ident, $sec_name: expr) => { $linker.elf_shdrs.iter_mut().find(|rec| rec.name == $sec_name) }; } struct SymbolTableReader<'a> { symtab: &'a [Elf32_Sym], strtab: &'a [u8], } impl<'a> SymbolTableReader<'a> { pub fn find_index_by_name(&self, sym_name: &[u8]) -> Option { self.symtab.iter().position(|sym| { if let Ok(dynsym_name) = name_starting_at_slice(self.strtab, sym.st_name as usize) { sym_name == dynsym_name } else { false } }) } } pub struct Linker<'a> { isa: Isa, symtab: &'a [Elf32_Sym], strtab: &'a [u8], elf_shdrs: Vec>, section_map: HashMap, image: Vec, load_offset: u32, rela_dyn_relas: Vec, } impl<'a> Linker<'a> { fn get_dynamic_symbol_table(&self) -> Result { let dynsym_rec = get_section_by_name!(self, ".dynsym") .ok_or("cannot make SymbolTableReader using .dynsym")?; Ok(SymbolTableReader { symtab: to_struct_slice::(dynsym_rec.data.as_slice()), strtab: self.elf_shdrs[dynsym_rec.shdr.sh_link as usize].data.as_slice(), }) } fn load_section(&mut self, shdr: &Elf32_Shdr, sh_name_str: &'a str, data: Vec) -> usize { let mut elf_shdr = *shdr; // Maintain alignment requirement specified in sh_addralign let align = shdr.sh_addralign; let padding = (align - (self.load_offset % align)) % align; self.load_offset += padding; elf_shdr.sh_addr = if (shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC { self.load_offset } else { 0 }; elf_shdr.sh_offset = self.load_offset; self.elf_shdrs.push(SectionRecord { shdr: elf_shdr, name: sh_name_str, data }); self.load_offset += shdr.sh_size; self.elf_shdrs.len() - 1 } // Perform relocation according to the relocation entries // Only symbols that support relative addressing would be resolved // This is because the loading address is not known yet fn resolve_relocatables( &mut self, relocs: &[R], target_section: Elf32_Word, ) -> Result<(), Error> { type RelocateFn = dyn Fn(&mut [u8], Elf32_Word); struct RelocInfo<'a, R> { pub defined_val: bool, pub indirect_reloc: Option<&'a R>, pub pc_relative: bool, pub relocate: Option>, } for reloc in relocs { let sym = match reloc.sym_info() as usize { STN_UNDEF => None, sym_index => { Some(self.symtab.get(sym_index).ok_or("symbol out of bounds of symbol table")?) } }; let resolve_symbol_addr = |sym_option: Option<&Elf32_Sym>| -> Result { let Some(sym) = sym_option else { return Ok(0) }; match sym.st_shndx { SHN_UNDEF => Err(Error::Lookup("undefined symbol")), SHN_ABS => Ok(sym.st_value), sec_ind => self .section_map .get(&(sec_ind as usize)) .map(|&elf_sec_ind: &usize| { // Unlike the code in artiq libdyld, the image offset value is // irrelevant in this case. // The .elf dynamic library can be linked to an arbitrary address // within the kernel address space self.elf_shdrs[elf_sec_ind].shdr.sh_offset as Elf32_Word + sym.st_value }) .ok_or(Error::Parsing("section not mapped to the ELF file")), } }; let get_target_section_index = || -> Result { self.section_map .get(&(target_section as usize)) .copied() .ok_or(Error::Parsing("Cannot find section with matching sh_index")) }; let classify = |reloc: &R, sym_option: Option<&Elf32_Sym>| -> Option> { let defined_val = sym_option.map_or(true, |sym| { sym.st_shndx != SHN_UNDEF || ELF32_ST_BIND(sym.st_info) == STB_LOCAL }); match self.isa { Isa::CortexA9 => match reloc.type_info() { R_ARM_REL32 | R_ARM_TARGET2 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: true, relocate: Some(Box::new(|target_word, value| { LittleEndian::write_u32(target_word, value); })), }), R_ARM_PREL31 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: true, relocate: Some(Box::new(|target_word, value| { LittleEndian::write_u32( target_word, (LittleEndian::read_u32(target_word) & 0x8000_0000) | value & 0x7FFF_FFFF, ); })), }), R_ARM_ABS32 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: None, }), _ => None, }, Isa::RiscV32 => match reloc.type_info() { R_RISCV_CALL_PLT | R_RISCV_GOT_HI20 | R_RISCV_PCREL_HI20 => { Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: true, relocate: Some(Box::new(|target_word, value| { let auipc_raw = LittleEndian::read_u32(target_word); let auipc_insn = (auipc_raw & 0xFFF) | ((value + 0x800) & 0xFFFF_F000); LittleEndian::write_u32(target_word, auipc_insn); })), }) } R_RISCV_32_PCREL => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: true, relocate: Some(Box::new(|target_word, value| { LittleEndian::write_u32(target_word, value); })), }), R_RISCV_PCREL_LO12_I => { let expected_offset = sym_option.map_or(0, |sym| sym.st_value); let indirect_reloc = relocs.iter().find(|reloc| reloc.offset() == expected_offset)?; Some(RelocInfo { defined_val: { let indirect_sym = self.symtab[indirect_reloc.sym_info() as usize]; indirect_sym.st_shndx != SHN_UNDEF || ELF32_ST_BIND(indirect_sym.st_info) == STB_LOCAL }, indirect_reloc: Some(indirect_reloc), pc_relative: true, relocate: Some(Box::new(|target_word, value| { // Here, we convert to direct addressing // GOT reloc (indirect) -> lw + addi // PCREL reloc (direct) -> addi let (lo_opcode, lo_funct3) = (0b001_0011, 0b000); let addi_lw_raw = LittleEndian::read_u32(target_word); let addi_insn = lo_opcode | (addi_lw_raw & 0xF8F80) | (lo_funct3 << 12) | ((value & 0xFFF) << 20); LittleEndian::write_u32(target_word, addi_insn); })), }) } R_RISCV_32 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: None, }), R_RISCV_SET32 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { LittleEndian::write_u32(target_word, value); })), }), R_RISCV_ADD32 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { let old_value = LittleEndian::read_u32(target_word); LittleEndian::write_u32(target_word, old_value.wrapping_add(value)); })), }), R_RISCV_SUB32 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { let old_value = LittleEndian::read_u32(target_word); LittleEndian::write_u32(target_word, old_value.wrapping_sub(value)); })), }), R_RISCV_SET16 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { LittleEndian::write_u16(target_word, value as u16); })), }), R_RISCV_ADD16 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { let old_value = LittleEndian::read_u16(target_word); LittleEndian::write_u16( target_word, old_value.wrapping_add(value as u16), ); })), }), R_RISCV_SUB16 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { let old_value = LittleEndian::read_u16(target_word); LittleEndian::write_u16( target_word, old_value.wrapping_sub(value as u16), ); })), }), R_RISCV_SET8 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { target_word[0] = value as u8; })), }), R_RISCV_ADD8 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { target_word[0] = target_word[0].wrapping_add(value as u8); })), }), R_RISCV_SUB8 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { target_word[0] = target_word[0].wrapping_sub(value as u8); })), }), R_RISCV_SET6 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { target_word[0] = (target_word[0] & 0xC0) | ((value & 0x3F) as u8); })), }), R_RISCV_SUB6 => Some(RelocInfo { defined_val, indirect_reloc: None, pc_relative: false, relocate: Some(Box::new(|target_word, value| { let new_value = (target_word[0].wrapping_sub(value as u8)) & 0x3F; target_word[0] = (target_word[0] & 0xC0) | new_value; })), }), _ => None, }, } }; let reloc_info = classify(reloc, sym).ok_or(Error::Parsing("unsupported relocation"))?; let target_index = get_target_section_index()?; let target_sec_off = self.elf_shdrs[target_index].shdr.sh_offset; if reloc_info.defined_val { let (sym_addr, rela_off) = { let (refed_sym, refed_reloc) = if let Some(indirect_reloc) = reloc_info.indirect_reloc { (Some(&self.symtab[indirect_reloc.sym_info() as usize]), indirect_reloc) } else { (sym, reloc) }; (resolve_symbol_addr(refed_sym)?, target_sec_off + refed_reloc.offset()) }; let target_sec_image = &mut self.elf_shdrs[target_index].data; let value = if reloc_info.pc_relative { sym_addr .wrapping_sub(rela_off) .wrapping_add(reloc.addend(target_sec_image) as Elf32_Word) } else { sym_addr.wrapping_add(reloc.addend(target_sec_image) as Elf32_Word) }; if let Some(relocate) = reloc_info.relocate { let target_word = &mut target_sec_image[reloc.offset() as usize..]; relocate(target_word, value); } else { self.rela_dyn_relas.push(Elf32_Rela { r_offset: rela_off, r_info: ELF32_R_INFO( 0, // R_ARM_RELATIVE does not have associated symbol match self.isa { Isa::CortexA9 => R_ARM_RELATIVE, Isa::RiscV32 => R_RISCV_RELATIVE, }, ), r_addend: value as Elf32_Sword, }); } } else { let target_sec_image = &self.elf_shdrs[target_index].data; let sym_name = name_starting_at_slice(self.strtab, sym.unwrap().st_name as usize) .map_err(|_| "cannot read symbol name from original .strtab")?; let dynsymtab_index = self .get_dynamic_symbol_table()? .find_index_by_name(sym_name) .ok_or("UNDEF relative symbol: cannot find symbol in .dynsym")?; self.rela_dyn_relas.push(Elf32_Rela { r_offset: target_sec_off as Elf32_Addr + reloc.offset(), r_info: ELF32_R_INFO(dynsymtab_index as Elf32_Word, reloc.type_info()), r_addend: reloc.addend(target_sec_image), }); } } Ok(()) } // Fill in the .eh_frame_hdr section // Technically it can be done before relocation, but the FDE entries in the // eh_frame_hdr section should be sorted. There are no guarantees that those in // .eh_frame would be sorted. fn implement_eh_frame_hdr(&mut self) -> Result<(), Error> { // Fetch .eh_frame & .eh_frame_hdr from the custom section table let eh_frame_rec = get_section_by_name!(self, ".eh_frame").ok_or("cannot find .eh_frame from .elf")?; let eh_frame_hdr_rec = get_section_by_name!(self, ".eh_frame_hdr") .ok_or("cannot find .eh_frame_hdr from .elf")?; let eh_frame_slice = eh_frame_rec.data.as_slice(); // Prepare a new buffer to dodge borrow check let mut eh_frame_hdr_vec: Vec = vec![0; eh_frame_hdr_rec.shdr.sh_size as usize]; let eh_frame = EH_Frame::new(eh_frame_slice, eh_frame_rec.shdr.sh_offset); let mut eh_frame_hdr = EH_Frame_Hdr::new( eh_frame_hdr_vec.as_mut_slice(), eh_frame_hdr_rec.shdr.sh_offset, eh_frame_rec.shdr.sh_offset, ); eh_frame.cfi_records().flat_map(|cfi| cfi.fde_records()).for_each(&mut |( init_pos, virt_addr, )| { eh_frame_hdr.add_fde(init_pos, virt_addr); }); // Sort FDE entries in .eh_frame_hdr eh_frame_hdr.finalize_fde(); // Replace the data buffer in the record get_mut_section_by_name!(self, ".eh_frame_hdr") .ok_or("cannot find .eh_frame_hdr from .elf")? .data = eh_frame_hdr_vec; Ok(()) } pub fn ld(data: &'a [u8]) -> Result, Error> { fn allocate_rela_dyn( linker: &Linker, relocs: &[R], ) -> Result<(usize, Vec), Error> { let mut alloc_size = 0; let mut rela_dyn_sym_indices = Vec::new(); for reloc in relocs { if reloc.sym_info() as usize == STN_UNDEF { continue; } let sym: &Elf32_Sym = linker .symtab .get(reloc.sym_info() as usize) .ok_or("symbol out of bounds of symbol table")?; match (linker.isa, reloc.type_info()) { // Absolute address relocations // A runtime relocation is needed to find the loading address (Isa::CortexA9, R_ARM_ABS32) | (Isa::RiscV32, R_RISCV_32) => { alloc_size += mem::size_of::(); // FIXME: RELA vs REL if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF { rela_dyn_sym_indices.push(reloc.sym_info()); } } // Relative address relocations // Relay the relocation to the runtime linker only if the symbol is not defined (Isa::CortexA9, R_ARM_REL32 | R_ARM_PREL31 | R_ARM_TARGET2) | ( Isa::RiscV32, R_RISCV_CALL_PLT | R_RISCV_PCREL_HI20 | R_RISCV_GOT_HI20 | R_RISCV_32_PCREL | R_RISCV_SET32 | R_RISCV_ADD32 | R_RISCV_SUB32 | R_RISCV_SET16 | R_RISCV_ADD16 | R_RISCV_SUB16 | R_RISCV_SET8 | R_RISCV_ADD8 | R_RISCV_SUB8 | R_RISCV_SET6 | R_RISCV_SUB6, ) => { if ELF32_ST_BIND(sym.st_info) == STB_GLOBAL && sym.st_shndx == SHN_UNDEF { alloc_size += mem::size_of::(); // FIXME: RELA vs REL rela_dyn_sym_indices.push(reloc.sym_info()); } } // RISC-V: Lower 12-bits relocations // If the upper 20-bits relocation cannot be resolved, // this relocation will be relayed to the runtime linker. (Isa::RiscV32, R_RISCV_PCREL_LO12_I) => { // Find the HI20 relocation let indirect_reloc = relocs .iter() .find(|reloc| reloc.offset() == sym.st_value) .ok_or("malformatted LO12 relocation")?; let indirect_sym = linker.symtab[indirect_reloc.sym_info() as usize]; if ELF32_ST_BIND(indirect_sym.st_info) == STB_GLOBAL && indirect_sym.st_shndx == SHN_UNDEF { alloc_size += mem::size_of::(); // FIXME: RELA vs REL rela_dyn_sym_indices.push(reloc.sym_info()); } } _ => { println!("Relocation type 0x{:X?} is not supported", reloc.type_info()); unimplemented!() } } } Ok((alloc_size, rela_dyn_sym_indices)) } let Some(ehdr) = read_unaligned::(data, 0) else { Err("cannot read ELF header")? }; let isa = match ehdr.e_machine { EM_ARM => Isa::CortexA9, EM_RISCV => Isa::RiscV32, _ => return Err(Error::Parsing("unsupported architecture")), }; let Some(shdrs) = get_ref_slice::(data, ehdr.e_shoff as usize, ehdr.e_shnum as usize) else { Err("cannot read section header table")? }; // Read .strtab let strtab_shdr = shdrs[ehdr.e_shstrndx as usize]; let Some(strtab) = get_ref_slice::(data, strtab_shdr.sh_offset as usize, strtab_shdr.sh_size as usize) else { Err("cannot read the string table from data")? }; // Read .symtab let symtab_shdr = shdrs .iter() .find(|shdr| shdr.sh_type as usize == SHT_SYMTAB) .ok_or(Error::Parsing("cannot find the symbol table"))?; let Some(symtab) = get_ref_slice::( data, symtab_shdr.sh_offset as usize, symtab_shdr.sh_size as usize / mem::size_of::(), ) else { Err("cannot read the symbol table from data")? }; // Section table for the .elf paired with the section name // To be formalized incrementally // Very hashmap-like structure, but the order matters, so it is a vector let elf_shdrs = vec![SectionRecord { shdr: Elf32_Shdr { sh_name: 0, sh_type: 0, sh_flags: 0, sh_addr: 0, sh_offset: 0, sh_size: 0, sh_link: 0, sh_info: 0, sh_addralign: 0, sh_entsize: 0, }, name: "", data: vec![0; 0], }]; let elf_sh_data_off = mem::size_of::() + mem::size_of::() * 5; // Image of the linked dynamic library, to be formalized incrementally // just as the section table eventually does let image: Vec = vec![0; elf_sh_data_off]; // Section relocation table // A map of the original index of copied sections to the new sections let section_map = HashMap::new(); // Vector of relocation entries in .rela.dyn let rela_dyn_relas = Vec::new(); let mut linker = Linker { isa, symtab, strtab, elf_shdrs, section_map, image, load_offset: elf_sh_data_off as u32, rela_dyn_relas, }; // Generate .text, keep the section index to find .rela.text let is_text_shdr = |shdr: &Elf32_Shdr| { shdr.sh_flags as usize & (SHF_ALLOC | SHF_EXECINSTR) == (SHF_ALLOC | SHF_EXECINSTR) }; let is_progbits = |shdr: &Elf32_Shdr| shdr.sh_type as usize == SHT_PROGBITS; let text_shdr_index = shdrs .iter() .position(|shdr| is_text_shdr(shdr) && is_progbits(shdr)) .ok_or(Error::Parsing("cannot find the .text section"))?; let text_shdr = shdrs[text_shdr_index]; linker.load_section( &text_shdr, ".text", data[text_shdr.sh_offset as usize ..text_shdr.sh_offset as usize + text_shdr.sh_size as usize] .to_vec(), ); linker.section_map.insert(text_shdr_index, 1); // ARM: Prioritize the transfer of EXIDX before EXTAB // It is to ensure that EXIDX is within a LOAD program header // Otherwise, the runtime linker will not copy the index table if linker.isa == Isa::CortexA9 { let arm_exidx_shdr_index = shdrs .iter() .position(|shdr| shdr.sh_type as usize == SHT_ARM_EXIDX) .ok_or(Error::Parsing("cannot find the .ARM.exidx section"))?; let arm_exidx_shdr = shdrs[arm_exidx_shdr_index]; let loaded_index = linker.load_section( &arm_exidx_shdr, ".ARM.exidx", data[arm_exidx_shdr.sh_offset as usize ..arm_exidx_shdr.sh_offset as usize + arm_exidx_shdr.sh_size as usize] .to_vec(), ); linker.section_map.insert(arm_exidx_shdr_index, loaded_index); } // Prepare all read-only progbits except .eh_frame // The executable section is already loaded as .text for (i, shdr) in shdrs.iter().enumerate() { if shdr.sh_type as usize != SHT_PROGBITS || shdr.sh_flags as usize & (SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR) != SHF_ALLOC { continue; } let section_name = name_starting_at_slice(strtab, shdr.sh_name as usize) .map_err(|_| "cannot read section name")?; let elf_shdrs_index = linker.load_section( shdr, str::from_utf8(section_name).unwrap(), data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize].to_vec(), ); linker.section_map.insert(i, elf_shdrs_index); } // Non-ARM targets use .eh_frame with an additional .eh_frame_hdr to perform // exception handling. ARM targets use .ARM.exidx, indicated by the ARM_EXIDX type // But the exception handling section would have been loaded beforehand. // Therefore, there is nothing to do for CortexA9 target. if linker.isa == Isa::RiscV32 { // Prepare .eh_frame and give a dummy .eh_frame_hdr // The header will be implemented later let eh_frame_shdr = shdrs .iter() .find(|shdr| { name_starting_at_slice(strtab, shdr.sh_name as usize).unwrap() == b".eh_frame" }) .ok_or("cannot find .eh_frame from object")?; // For some reason ld.lld would add an zero-entry of CIE at the end of the .eh_frame, // which obviously has no FDEs associated to it. That entry should be skippable. let eh_frame = &data[eh_frame_shdr.sh_offset as usize ..(eh_frame_shdr.sh_offset + eh_frame_shdr.sh_size) as usize]; // Allocate memory for .eh_frame_hdr // Calculate the size by parsing .eh_frame at coarse as possible let eh_frame_hdr_size = EH_Frame_Hdr::size_from_eh_frame(eh_frame); // Describe the .eh_frame_hdr with a dummy shdr. let eh_frame_hdr_shdr = Elf32_Shdr { sh_name: 0, sh_type: SHT_PROGBITS as Elf32_Word, sh_flags: SHF_ALLOC as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: eh_frame_hdr_size as Elf32_Word, sh_link: 0, sh_info: 0, sh_addralign: 4, sh_entsize: 0, }; linker.load_section(&eh_frame_hdr_shdr, ".eh_frame_hdr", vec![0; eh_frame_hdr_size]); } // Allocate memory for both .rela.dyn // The number of entries in .rela.dyn is found by counting relocations that either // - use global undefined symbols; or // - need the loading address let mut rela_dyn_size = 0; let mut rela_dyn_sym_indices = Vec::::new(); // There are 2 types of relocation entries, RELA & REL. // There are essentially no difference in processing their fields. macro_rules! reloc_invariant { ($shdr: expr, $stmt: expr) => { match $shdr.sh_type as usize { SHT_RELA => { let Some(relocs) = get_ref_slice::( data, $shdr.sh_offset as usize, $shdr.sh_size as usize / mem::size_of::(), ) else { Err("cannot parse relocations")? }; #[allow(clippy::redundant_closure_call)] $stmt(relocs) } SHT_REL => { let Some(relocs) = get_ref_slice::( data, $shdr.sh_offset as usize, $shdr.sh_size as usize / mem::size_of::(), ) else { Err("cannot parse relocations")? }; #[allow(clippy::redundant_closure_call)] $stmt(relocs) } _ => unreachable!(), } }; } for shdr in shdrs .iter() .filter(|shdr| shdr.sh_type as usize == SHT_REL || shdr.sh_type as usize == SHT_RELA) { // If the reloction refers to a section that will not be loaded, // do not allocate space for the resulting relocations, it will not be processed let referred_shdr = shdrs .get(shdr.sh_info as usize) .ok_or("relocation is not specified to a valid section number")?; if (referred_shdr.sh_flags as usize & SHF_ALLOC) != SHF_ALLOC { continue; } reloc_invariant!(shdr, |relocs| { match allocate_rela_dyn(&linker, relocs) { Ok((alloc_size, additional_indices)) => { rela_dyn_size += alloc_size; rela_dyn_sym_indices.extend(additional_indices); Ok(()) } Err(e) => Err(e), } })?; } // Avoid symbol duplication rela_dyn_sym_indices.sort_unstable(); rela_dyn_sym_indices.dedup(); if rela_dyn_size != 0 { let rela_dyn_shdr = Elf32_Shdr { sh_name: 0, sh_type: SHT_RELA as Elf32_Word, sh_flags: SHF_ALLOC as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: rela_dyn_size as Elf32_Word, sh_link: 0, sh_info: 0, sh_addralign: 4, sh_entsize: mem::size_of::() as Elf32_Word, }; linker.load_section(&rela_dyn_shdr, ".rela.dyn", vec![0; rela_dyn_size]); } // Construct the .dynsym & .dynstr sections // .dynsym section should only contain the symbols needed for .rela.dyn let mut dynsym = Vec::new(); let mut dynstr = Vec::new(); let mut dynsym_names = Vec::new(); dynsym.push(Elf32_Sym { st_name: 0, st_value: 0, st_size: 0, st_info: 0, st_other: 0, st_shndx: 0, }); dynstr.push(0); dynsym_names.push((0, 0)); for rela_dyn_sym_index in rela_dyn_sym_indices { let mut sym = linker.symtab[rela_dyn_sym_index as usize]; let sym_name = name_starting_at_slice(strtab, sym.st_name as usize) .map_err(|_| "cannot read symbol name from the original .strtab")?; let dynstr_start_index = dynstr.len(); sym.st_name = dynstr_start_index as Elf32_Word; if sym.st_shndx != SHN_UNDEF { let elf_shdr_index = linker .section_map .get(&(sym.st_shndx as usize)) .copied() .ok_or(Error::Parsing("Cannot find section with matching sh_index"))?; let elf_shdr_offset = linker.elf_shdrs[elf_shdr_index].shdr.sh_offset; sym.st_value += elf_shdr_offset; // Convert scope of symbols to global // All relocation symbols must be visible to the dynamic linker sym.st_info = ELF32_ST_INFO(STB_GLOBAL, ELF32_ST_TYPE(sym.st_info)); sym.st_shndx = elf_shdr_index as Elf32_Section; } dynsym.push(sym); dynstr.extend(sym_name); dynstr.push(0); dynsym_names.push((dynstr_start_index, dynstr_start_index + sym_name.len())); } // Copy __modinit__ symbol from object file let modinit_sym = symtab .iter() .find(|sym| { let sym_name = name_starting_at_slice(strtab, sym.st_name as usize).unwrap(); sym_name == b"__modinit__" }) .ok_or("__modinit__ symbol cannot be found")?; let modinit_shdr_index = linker .section_map .get(&(modinit_sym.st_shndx as usize)) .copied() .ok_or(Error::Parsing("Cannot find section with matching sh_index"))?; let modinit_shdr = linker.elf_shdrs[modinit_shdr_index].shdr; let dynstr_start_index = dynstr.len(); dynsym.push(Elf32_Sym { st_name: dynstr_start_index as Elf32_Word, st_value: modinit_shdr.sh_offset + modinit_sym.st_value, st_size: modinit_sym.st_value, st_info: modinit_sym.st_info, st_other: modinit_sym.st_other, st_shndx: modinit_shdr_index as Elf32_Section, }); let sym_slice = b"__modinit__"; dynsym_names.push((dynstr.len(), dynstr.len() + sym_slice.len())); dynstr.extend(sym_slice); dynstr.push(0); // Additional symbols // st_name will be defined when synthesizing .dynstr // st_value & st_shndx will be finalized when .bss sections are processed let mut extra_sym_vec = vec![ Elf32_Sym { st_name: 0, st_value: 0, st_size: 0, st_info: ELF32_ST_INFO(STB_GLOBAL, STT_NOTYPE), st_other: STV_DEFAULT as u8, st_shndx: 0, }; 3 ]; let sym_slice = b"__bss_start"; dynsym_names.push((dynstr.len(), dynstr.len() + sym_slice.len())); extra_sym_vec[0].st_name = dynstr.len() as Elf32_Word; dynstr.extend(b"__bss_start"); dynstr.push(0); let sym_slice = b"_end"; dynsym_names.push((dynstr.len(), dynstr.len() + sym_slice.len())); extra_sym_vec[1].st_name = dynstr.len() as Elf32_Word; dynstr.extend(b"_end"); dynstr.push(0); let sym_slice = b"_sstack_guard"; dynsym_names.push((dynstr.len(), dynstr.len() + sym_slice.len())); extra_sym_vec[2].st_name = dynstr.len() as Elf32_Word; dynstr.extend(b"_sstack_guard"); dynstr.push(0); dynsym.extend(extra_sym_vec); // There should be dynsym.len() buckets & chains // No entries could be skipped, even symbols like __modinit__ will be looked up let mut hash_bucket: Vec = vec![0; dynsym.len()]; let mut hash_chain: Vec = vec![0; dynsym.len()]; for (sym_index, (str_start, str_end)) in dynsym_names.iter().enumerate().take(dynsym.len()).skip(1) { let hash = elf_hash(&dynstr[*str_start..*str_end]); let mut hash_index = hash as usize % hash_bucket.len(); if hash_bucket[hash_index] == 0 { hash_bucket[hash_index] = sym_index as u32; } else { hash_index = hash_bucket[hash_index] as usize; while hash_chain[hash_index] != 0 { hash_index = hash_chain[hash_index] as usize; } hash_chain[hash_index] = sym_index as u32; } } let mut hash: Vec = Vec::new(); hash.push(hash_bucket.len() as u32); hash.push(hash_chain.len() as u32); hash.extend(hash_bucket); hash.extend(hash_chain); // Add .dynsym, .dynstr, .hash to the linker let dynstr_elf_index = linker.load_section( &Elf32_Shdr { sh_name: 0, sh_type: SHT_STRTAB as Elf32_Word, sh_flags: SHF_ALLOC as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: dynstr.len() as Elf32_Word, sh_link: 0, sh_info: 0, sh_addralign: 1, sh_entsize: 0, }, ".dynstr", dynstr, ); let dynsym_elf_index = linker.load_section( &Elf32_Shdr { sh_name: 0, sh_type: SHT_DYNSYM as Elf32_Word, sh_flags: SHF_ALLOC as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: (dynsym.len() * mem::size_of::()) as Elf32_Word, sh_link: dynstr_elf_index as Elf32_Word, // Index of the .dynstr section, to be inserted sh_info: 1, // Last local symbol is at index 0 (NOTYPE) sh_addralign: mem::size_of::() as Elf32_Word, sh_entsize: mem::size_of::() as Elf32_Word, }, ".dynsym", from_struct_slice(&dynsym), ); let hash_elf_index = linker.load_section( &Elf32_Shdr { sh_name: 0, sh_type: SHT_HASH as Elf32_Word, sh_flags: SHF_ALLOC as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: (hash.len() * 4) as Elf32_Word, sh_link: dynsym_elf_index as Elf32_Word, // Index of the .dynsym section sh_info: 0, sh_addralign: 4, sh_entsize: 4, }, ".hash", from_struct_slice(&hash), ); // Link .rela.dyn header to the .dynsym header get_mut_section_by_name!(linker, ".rela.dyn") .ok_or(".dynsym not initialized before .dynstr")? .shdr .sh_link = dynsym_elf_index as Elf32_Word; let first_writable_sec_elf_index = linker.elf_shdrs.len(); // Load writable PROGBITS sections for (i, shdr) in shdrs.iter().enumerate() { if shdr.sh_type as usize == SHT_PROGBITS && shdr.sh_flags as usize & (SHF_WRITE | SHF_ALLOC | SHF_EXECINSTR) == (SHF_WRITE | SHF_ALLOC) { let section_name = name_starting_at_slice(strtab, shdr.sh_name as usize) .map_err(|_| "failed to load section name")?; let elf_shdrs_index = linker.load_section( shdr, str::from_utf8(section_name).unwrap(), data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] .to_vec(), ); linker.section_map.insert(i, elf_shdrs_index); } } // Load the .dynamic section // Initialize with mandatory dyn entries let mut dyn_entries = vec![ Elf32_Dyn { d_tag: DT_HASH, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: linker.elf_shdrs[hash_elf_index].shdr.sh_offset, }, }, Elf32_Dyn { d_tag: DT_STRTAB, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: linker.elf_shdrs[dynstr_elf_index].shdr.sh_offset, }, }, Elf32_Dyn { d_tag: DT_SYMTAB, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: linker.elf_shdrs[dynsym_elf_index].shdr.sh_offset, }, }, Elf32_Dyn { d_tag: DT_STRSZ, d_un: Elf32_Dyn__bindgen_ty_1 { d_val: linker.elf_shdrs[dynstr_elf_index].shdr.sh_size, }, }, Elf32_Dyn { d_tag: DT_SYMENT, d_un: Elf32_Dyn__bindgen_ty_1 { d_val: linker.elf_shdrs[dynsym_elf_index].shdr.sh_entsize, }, }, ]; if rela_dyn_size != 0 { let rela_dyn_shdr = get_section_by_name!(linker, ".rela.dyn") .ok_or(".rela.dyn header not properly initialised")? .shdr; dyn_entries.push(Elf32_Dyn { d_tag: DT_RELA, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: rela_dyn_shdr.sh_offset }, }); dyn_entries.push(Elf32_Dyn { d_tag: DT_RELASZ, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: rela_dyn_shdr.sh_size }, }); dyn_entries.push(Elf32_Dyn { d_tag: DT_RELAENT, d_un: Elf32_Dyn__bindgen_ty_1 { d_ptr: rela_dyn_shdr.sh_entsize }, }); } // Termination entry in .dynamic dyn_entries.push(Elf32_Dyn { d_tag: DT_NULL, d_un: Elf32_Dyn__bindgen_ty_1 { d_val: 0 } }); let dynamic_shdr = Elf32_Shdr { sh_name: 0, sh_type: SHT_DYNAMIC as Elf32_Word, sh_flags: (SHF_WRITE | SHF_ALLOC) as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: (dyn_entries.len() * mem::size_of::()) as Elf32_Word, sh_link: dynstr_elf_index as Elf32_Word, sh_info: 0, sh_addralign: 4, sh_entsize: mem::size_of::() as Elf32_Word, }; let dynamic_elf_index = linker.load_section(&dynamic_shdr, ".dynamic", from_struct_slice(&dyn_entries)); let last_w_sec_elf_index = linker.elf_shdrs.len() - 1; // Load all other A-flag non-PROGBITS sections (ARM: non-ARM_EXIDX as well) // .bss sections (i.e. .sbss, .sbss.*, .bss & .bss.*) will be loaded later let mut bss_index_vec = Vec::new(); for (i, shdr) in shdrs.iter().enumerate() { if (shdr.sh_type as usize != SHT_PROGBITS) && (shdr.sh_type as usize != SHT_ARM_EXIDX) && ((shdr.sh_flags as usize & SHF_ALLOC) == SHF_ALLOC) { let section_name_slice = name_starting_at_slice(strtab, shdr.sh_name as usize) .map_err(|_| "failed to load section name")?; let section_name = str::from_utf8(section_name_slice).map_err(|_| "cannot parse section name")?; if section_name == ".bss" || section_name == ".sbss" || section_name.starts_with(".bss.") || section_name.starts_with(".sbss.") { bss_index_vec.push((i, section_name)); } else { let elf_shdrs_index = linker.load_section( shdr, section_name, data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] .to_vec(), ); linker.section_map.insert(i, elf_shdrs_index); } } } macro_rules! update_dynsym_record { ($sym_name: expr, $st_value: expr, $st_shndx: expr) => { let symbol_table = linker.get_dynamic_symbol_table()?; let bss_start_sym_index = symbol_table .find_index_by_name($sym_name) .ok_or(stringify!($sym_name symbol not initialized))?; let dynsyms = to_struct_mut_slice::( get_mut_section_by_name!(linker, ".dynsym") .ok_or("cannot make retrieve .dynsym")? .data .as_mut_slice(), ); dynsyms[bss_start_sym_index].st_value = $st_value; dynsyms[bss_start_sym_index].st_shndx = $st_shndx; } } // Load the .bss sections, finalize the .bss symbols if bss_index_vec.is_empty() { // Insert a zero-size .bss section if there aren't any let bss_elf_index = linker.load_section( &Elf32_Shdr { sh_name: 0, sh_type: SHT_NOBITS as Elf32_Word, sh_flags: (SHF_ALLOC | SHF_WRITE) as Elf32_Word, sh_addr: 0, sh_offset: 0, sh_size: 0, sh_link: 0, sh_info: 0, sh_addralign: 4, sh_entsize: 0, }, ".bss", vec![0; 0], ); let bss_offset = linker.elf_shdrs[bss_elf_index].shdr.sh_offset; update_dynsym_record!(b"__bss_start", bss_offset, bss_elf_index as Elf32_Section); update_dynsym_record!(b"_end", bss_offset, bss_elf_index as Elf32_Section); } else { for (bss_iter_index, &(bss_section_index, section_name)) in bss_index_vec.iter().enumerate() { let shdr = &shdrs[bss_section_index]; let bss_elf_index = linker.load_section( shdr, section_name, data[shdr.sh_offset as usize..(shdr.sh_offset + shdr.sh_size) as usize] .to_vec(), ); linker.section_map.insert(bss_section_index, bss_elf_index); let loaded_shdr = linker.elf_shdrs[bss_elf_index].shdr; if bss_iter_index == 0 { update_dynsym_record!( b"__bss_start", loaded_shdr.sh_offset, bss_elf_index as Elf32_Section ); } if bss_iter_index == bss_index_vec.len() - 1 { update_dynsym_record!( b"_end", loaded_shdr.sh_offset + loaded_shdr.sh_size, bss_elf_index as Elf32_Section ); } } } // All sections that should be allocated memory are loaded // The stack guard address can be determined let last_elf_shdr_index = linker.elf_shdrs.len() - 1; let last_load_shdr = linker.elf_shdrs[last_elf_shdr_index].shdr; let end_load_addr = last_load_shdr.sh_offset + last_load_shdr.sh_size; let stack_guard_addr = end_load_addr + ((0x1000 - (end_load_addr % 0x1000)) % 0x1000); update_dynsym_record!( b"_sstack_guard", stack_guard_addr, last_elf_shdr_index as Elf32_Section ); for shdr in shdrs .iter() .filter(|shdr| shdr.sh_type as usize == SHT_RELA || shdr.sh_type as usize == SHT_REL) { // If the reloction refers to a section that will not be loaded, // do not process the relocations. The section will not be loaded let referred_shdr = shdrs .get(shdr.sh_info as usize) .ok_or("relocation is not specified to a valid section number")?; if (referred_shdr.sh_flags as usize & SHF_ALLOC) != SHF_ALLOC { continue; } reloc_invariant!(shdr, |relocs| linker.resolve_relocatables(relocs, shdr.sh_info))?; } // Load .rela.dyn symbols generated during relocation if rela_dyn_size != 0 { let rela_dyn_rec = get_mut_section_by_name!(linker, ".rela.dyn") .ok_or(".rela.dyn not initialized in the ELF file")?; let rela_dyn_slice = to_struct_mut_slice::(rela_dyn_rec.data.as_mut_slice()); for (i, &rela) in linker.rela_dyn_relas.iter().enumerate() { rela_dyn_slice[i] = rela; } } // Prepare a STRTAB to hold the names of section headers // Fix the sh_name field of the section headers let mut shstrtab = Vec::new(); for shdr_rec in &mut linker.elf_shdrs { let shstrtab_index = shstrtab.len(); shstrtab.extend(shdr_rec.name.as_bytes()); shstrtab.push(0); shdr_rec.shdr.sh_name = shstrtab_index as Elf32_Word; } // Add en entry for .shstrtab let shstrtab_shdr_sh_name = shstrtab.len(); shstrtab.extend(b".shstrtab"); shstrtab.push(0); let shstrtab_shdr = Elf32_Shdr { sh_name: shstrtab_shdr_sh_name as Elf32_Word, sh_type: SHT_STRTAB as Elf32_Word, sh_flags: 0, sh_addr: 0, sh_offset: 0, sh_size: shstrtab.len() as Elf32_Word, sh_link: 0, sh_info: 0, sh_addralign: 1, sh_entsize: 0, }; let shstrtab_elf_index = linker.load_section(&shstrtab_shdr, ".shstrtab", shstrtab); // Edit .eh_frame_hdr content if linker.isa == Isa::RiscV32 { linker.implement_eh_frame_hdr()?; } // Load all section data into the image for rec in &linker.elf_shdrs[1..] { linker.image.extend(vec![0; (rec.shdr.sh_offset as usize) - linker.image.len()]); linker.image.extend(&rec.data); } // Load all section headers to the image let alignment = (4 - (linker.image.len() % 4)) % 4; let sec_headers_offset = linker.image.len() + alignment; linker.image.extend(vec![0; alignment]); for rec in &linker.elf_shdrs { let shdr = rec.shdr; linker.image.extend(unsafe { slice::from_raw_parts(ptr::addr_of!(shdr).cast(), mem::size_of::()) }); } // Update the PHDRs let phdr_offset = mem::size_of::(); unsafe { let phdr_ptr = linker.image.as_mut_ptr().add(phdr_offset).cast(); let phdr_slice = slice::from_raw_parts_mut(phdr_ptr, 5); // List of program headers: // 1. ELF headers & program headers // 2. Read-only sections // 3. All other A-flag sections // 4. Dynamic // 5. EH frame & its header let header_size = mem::size_of::() + mem::size_of::() * 5; phdr_slice[0] = Elf32_Phdr { p_type: PT_LOAD, p_offset: 0, p_vaddr: 0, p_paddr: 0, p_filesz: header_size as Elf32_Word, p_memsz: header_size as Elf32_Word, p_flags: PF_R as Elf32_Word, p_align: 0x1000, }; let last_ro_shdr = linker.elf_shdrs[first_writable_sec_elf_index - 1].shdr; let last_ro_addr = last_ro_shdr.sh_offset + last_ro_shdr.sh_size; let ro_load_size = last_ro_addr - header_size as Elf32_Word; phdr_slice[1] = Elf32_Phdr { p_type: PT_LOAD, p_offset: header_size as Elf32_Off, p_vaddr: header_size as Elf32_Addr, p_paddr: header_size as Elf32_Addr, p_filesz: ro_load_size, p_memsz: ro_load_size, p_flags: (PF_R | PF_X) as Elf32_Word, p_align: 0x1000, }; let first_w_shdr = linker.elf_shdrs[first_writable_sec_elf_index].shdr; let first_w_addr = first_w_shdr.sh_offset; let last_w_shdr = linker.elf_shdrs[last_w_sec_elf_index].shdr; let w_size = last_w_shdr.sh_offset + last_w_shdr.sh_size - first_w_addr; phdr_slice[2] = Elf32_Phdr { p_type: PT_LOAD, p_offset: first_w_addr as Elf32_Off, p_vaddr: first_w_addr as Elf32_Addr, p_paddr: first_w_addr as Elf32_Addr, p_filesz: w_size, p_memsz: w_size, p_flags: (PF_R | PF_W) as Elf32_Word, p_align: 0x1000, }; let dynamic_shdr = linker.elf_shdrs[dynamic_elf_index].shdr; phdr_slice[3] = Elf32_Phdr { p_type: PT_DYNAMIC, p_offset: dynamic_shdr.sh_offset, p_vaddr: dynamic_shdr.sh_offset, p_paddr: dynamic_shdr.sh_offset, p_filesz: dynamic_shdr.sh_size, p_memsz: dynamic_shdr.sh_size, p_flags: (PF_R | PF_W) as Elf32_Word, p_align: 4, }; let (eh_type, eh_shdr_name) = match linker.isa { Isa::CortexA9 => (PT_ARM_EXIDX, ".ARM.exidx"), Isa::RiscV32 => (PT_GNU_EH_FRAME, ".eh_frame_hdr"), }; let eh_shdr = get_section_by_name!(linker, eh_shdr_name) .ok_or("cannot read error handling section when finalizing phdrs")? .shdr; phdr_slice[4] = Elf32_Phdr { p_type: eh_type, p_offset: eh_shdr.sh_offset, p_vaddr: eh_shdr.sh_offset, p_paddr: eh_shdr.sh_offset, p_filesz: eh_shdr.sh_size, p_memsz: eh_shdr.sh_size, p_flags: PF_R as Elf32_Word, p_align: 4, }; } // Update the EHDR let ehdr_ptr = linker.image.as_mut_ptr().cast(); unsafe { *ehdr_ptr = Elf32_Ehdr { e_ident: ehdr.e_ident, e_type: ET_DYN, e_machine: ehdr.e_machine, e_version: ehdr.e_version, e_entry: elf_sh_data_off as Elf32_Addr, e_phoff: phdr_offset as Elf32_Off, e_shoff: sec_headers_offset as Elf32_Off, e_flags: match linker.isa { Isa::RiscV32 => ehdr.e_flags, Isa::CortexA9 => ehdr.e_flags | EF_ARM_ABI_FLOAT_HARD as Elf32_Word, }, e_ehsize: mem::size_of::() as Elf32_Half, e_phentsize: mem::size_of::() as Elf32_Half, e_phnum: 5, e_shentsize: mem::size_of::() as Elf32_Half, e_shnum: linker.elf_shdrs.len() as Elf32_Half, e_shstrndx: shstrtab_elf_index as Elf32_Half, } } Ok(linker.image) } }