use anyhow::Result; use std::{ cmp::Ordering, collections::{HashMap, HashSet}, fs::{self, File}, io::{self, BufWriter, Cursor, Seek, SeekFrom, Write}, path::Path, process::Command, }; use binrw::{binrw, BinRead, BinResult, BinWrite, Endian}; use enum_map::EnumMap; use temp_dir::TempDir; use crate::{ConvertStatics, Encoding, Function, OutputSection}; const EI_NIDENT: usize = 16; const EI_CLASS: usize = 4; const EI_DATA: usize = 5; const SHN_UNDEF: usize = 0; const SHN_ABS: usize = 0xfff1; const SHN_XINDEX: usize = 0xffff; const STT_OBJECT: u8 = 1; const STT_FUNC: u8 = 2; const STB_LOCAL: u8 = 0; const STB_GLOBAL: u8 = 1; const STV_DEFAULT: u8 = 0; const SHT_NULL: u32 = 0; const SHT_SYMTAB: u32 = 2; const SHT_STRTAB: u32 = 3; const SHT_RELA: u32 = 4; const SHT_NOBITS: u32 = 8; const SHT_REL: u32 = 9; const SHT_MIPS_GPTAB: u32 = 0x70000003; const SHT_MIPS_DEBUG: u32 = 0x70000005; const SHF_LINK_ORDER: u32 = 0x80; const MIPS_DEBUG_ST_STATIC: usize = 2; const MIPS_DEBUG_ST_PROC: usize = 6; const MIPS_DEBUG_ST_BLOCK: usize = 7; const MIPS_DEBUG_ST_END: usize = 8; const MIPS_DEBUG_ST_FILE: usize = 11; const MIPS_DEBUG_ST_STATIC_PROC: usize = 14; const MIPS_DEBUG_ST_STRUCT: usize = 26; const MIPS_DEBUG_ST_UNION: usize = 27; const MIPS_DEBUG_ST_ENUM: usize = 28; #[binrw] struct ElfHeader { e_ident: [u8; EI_NIDENT], e_type: u16, e_machine: u16, e_version: u32, e_entry: u32, e_phoff: u32, e_shoff: u32, e_flags: u32, e_ehsize: u16, e_phentsize: u16, e_phnum: u16, e_shentsize: u16, e_shnum: u16, e_shstrndx: u16, } impl ElfHeader { const SIZE: usize = 52; fn new(data: &[u8], endian: Endian) -> BinResult { let mut cursor = Cursor::new(data); let header = Self::read_options(&mut cursor, endian, ())?; assert_eq!(header.e_ident[EI_CLASS], 1, "ELF must be 32-bit"); assert_eq!(header.e_type, 1, "ELF must be relocatable"); assert_eq!(header.e_machine, 8, "ELF must be MIPS 1"); assert_eq!(header.e_phoff, 0, "ELF must not have program headers"); assert_ne!(header.e_shoff, 0, "ELF must have section headers"); assert_ne!( header.e_shstrndx, SHN_UNDEF as u16, "ELF must have a section header string table" ); Ok(header) } fn to_bin(&self, endian: Endian) -> [u8; Self::SIZE] { let mut rv = [0; Self::SIZE]; let mut cursor = Cursor::new(rv.as_mut_slice()); self.write_options(&mut cursor, endian, ()).unwrap(); rv } } #[binrw] struct SymbolData { st_name: u32, st_value: u32, st_size: u32, st_info: u8, st_other: u8, st_shndx: u16, } #[derive(Clone)] struct Symbol { st_name: usize, st_value: usize, st_size: usize, st_shndx: usize, st_type: u8, st_bind: u8, st_visibility: u8, name: Vec, } impl Symbol { fn new(data: &[u8], strtab: &Section, endian: Endian) -> BinResult { let mut cursor = Cursor::new(data); let data = SymbolData::read_options(&mut cursor, endian, ())?; if data.st_shndx == SHN_XINDEX as u16 { panic!("too many sections (SHN_XINDEX not supported)"); } let st_type = data.st_info & 0xf; let st_bind = data.st_info >> 4; let st_visibility = data.st_other & 0x3; let name = strtab.lookup_str(data.st_name as usize); Ok(Self { st_name: data.st_name as usize, st_value: data.st_value as usize, st_size: data.st_size as usize, st_shndx: data.st_shndx as usize, st_type, st_bind, st_visibility, name, }) } fn to_bin(&self) -> Vec { let mut rv = vec![]; let mut cursor = Cursor::new(&mut rv); SymbolData { st_name: self.st_name as u32, st_value: self.st_value as u32, st_size: self.st_size as u32, st_info: self.st_bind << 4 | self.st_type, st_other: self.st_visibility, st_shndx: self.st_shndx as u16, } .write_options(&mut cursor, Endian::Big, ()) .unwrap(); rv } } #[derive(Clone)] struct Relocation { r_offset: usize, sym_index: usize, rel_type: u32, r_addend: Option, } impl Relocation { fn new(data: &[u8], sh_type: u32, endian: Endian) -> BinResult { let mut cursor = Cursor::new(data); let r_offset = u32::read_options(&mut cursor, endian, ())? as usize; let r_info = u32::read_options(&mut cursor, endian, ())?; let r_addend = if sh_type == SHT_REL { None } else { Some(u32::read_options(&mut cursor, endian, ())?) }; let sym_index = (r_info >> 8) as usize; let rel_type = r_info & 0xff; Ok(Self { r_offset, sym_index, rel_type, r_addend, }) } fn to_bin(&self, endian: Endian) -> Vec { let mut rv = vec![]; let mut cursor = Cursor::new(&mut rv); let r_offset = self.r_offset as u32; let r_info = ((self.sym_index as u32) << 8) | self.rel_type; r_offset.write_options(&mut cursor, endian, ()).unwrap(); r_info.write_options(&mut cursor, endian, ()).unwrap(); self.r_addend .write_options(&mut cursor, endian, ()) .unwrap(); rv } } #[binrw] struct Hdrr { magic: u16, vstamp: u16, iline_max: u32, cb_line: u32, cb_line_offset: u32, idn_max: u32, cb_dn_offset: u32, ipd_max: u32, cb_pd_offset: u32, isym_max: u32, cb_sym_offset: u32, iopt_max: u32, cb_opt_offset: u32, iaux_max: u32, cb_aux_offset: u32, iss_max: u32, cb_ss_offset: u32, iss_ext_max: u32, cb_ss_ext_offset: u32, ifd_max: u32, cb_fd_offset: u32, crfd: u32, cb_rfd_offset: u32, iext_max: u32, cb_ext_offset: u32, } impl Hdrr { const SIZE: usize = 96; } #[binrw] #[derive(Clone)] struct SectionHeader { sh_name: u32, sh_type: u32, sh_flags: u32, sh_addr: u32, sh_offset: u32, sh_size: u32, sh_link: u32, sh_info: u32, sh_addralign: u32, sh_entsize: u32, } impl SectionHeader { const SIZE: usize = 40; } #[derive(Clone)] struct Section { header: SectionHeader, data: Vec, index: usize, relocated_by: Vec, relocations: Vec, name: String, } impl Section { fn new(data: &[u8], other_data: &[u8], index: usize, endian: Endian) -> BinResult { let mut cursor = Cursor::new(data); let header = SectionHeader::read_options(&mut cursor, endian, ())?; assert!(header.sh_flags & SHF_LINK_ORDER == 0); if header.sh_entsize != 0 { assert_eq!(header.sh_size % header.sh_entsize, 0); } let data = if header.sh_type == SHT_NOBITS { vec![] } else { other_data[header.sh_offset as usize..(header.sh_offset + header.sh_size) as usize] .to_vec() }; Ok(Self { header, data, index, relocated_by: vec![], relocations: vec![], name: "".into(), }) } fn from_parts( sh_name: u32, fields: &HeaderFields, data: &[u8], index: usize, endian: Endian, ) -> Self { let header = SectionHeader { sh_name, sh_type: fields.sh_type, sh_flags: fields.sh_flags, sh_addr: 0, sh_offset: 0, sh_size: data.len() as u32, sh_link: fields.sh_link, sh_info: fields.sh_info, sh_addralign: fields.sh_addralign, sh_entsize: fields.sh_entsize, }; let mut rv = [0; SectionHeader::SIZE]; let mut cursor = Cursor::new(rv.as_mut_slice()); header.write_options(&mut cursor, endian, ()).unwrap(); Self::new(&rv, data, index, endian).unwrap() } fn lookup_str(&self, index: usize) -> Vec { assert_eq!(self.header.sh_type, SHT_STRTAB); let to = self.data[index..] .iter() .position(|&x| x == 0) .expect("bad strtab index") + index; self.data[index..to].to_owned() } fn add_str(&mut self, string: &[u8]) -> u32 { assert_eq!(self.header.sh_type, SHT_STRTAB); let index = self.data.len() as u32; self.data.extend_from_slice(string); self.data.push(0); index } fn is_rel(&self) -> bool { self.header.sh_type == SHT_REL || self.header.sh_type == SHT_RELA } fn header_to_bin(&mut self, endian: Endian) -> [u8; SectionHeader::SIZE] { if self.header.sh_type != SHT_NOBITS { self.header.sh_size = self.data.len() as u32; } let mut rv = [0; SectionHeader::SIZE]; let mut cursor = Cursor::new(rv.as_mut_slice()); self.header.write_options(&mut cursor, endian, ()).unwrap(); rv } fn init_relocs(&mut self, endian: Endian) { assert!(self.is_rel()); let mut entries = vec![]; for i in (0..self.header.sh_size).step_by(self.header.sh_entsize as usize) { entries.push( Relocation::new( &self.data[i as usize..(i + self.header.sh_entsize) as usize], self.header.sh_type, endian, ) .unwrap(), ); } self.relocations = entries; } fn relocate_mdebug(&mut self, original_offset: u32, endian: Endian) { assert_eq!(self.header.sh_type, SHT_MIPS_DEBUG); let shift_by = self.header.sh_offset.wrapping_sub(original_offset); let mut hdrr = Hdrr::read_options(&mut Cursor::new(&self.data), endian, ()).unwrap(); assert_eq!(hdrr.magic, 0x7009); let relocate = |a, b: &mut u32| { if a != 0 { *b = b.wrapping_add(shift_by); } }; relocate(hdrr.cb_line, &mut hdrr.cb_line_offset); relocate(hdrr.idn_max, &mut hdrr.cb_dn_offset); relocate(hdrr.ipd_max, &mut hdrr.cb_pd_offset); relocate(hdrr.isym_max, &mut hdrr.cb_sym_offset); relocate(hdrr.iopt_max, &mut hdrr.cb_opt_offset); relocate(hdrr.iaux_max, &mut hdrr.cb_aux_offset); relocate(hdrr.iss_max, &mut hdrr.cb_ss_offset); relocate(hdrr.iss_ext_max, &mut hdrr.cb_ss_ext_offset); relocate(hdrr.ifd_max, &mut hdrr.cb_fd_offset); relocate(hdrr.crfd, &mut hdrr.cb_rfd_offset); relocate(hdrr.iext_max, &mut hdrr.cb_ext_offset); let mut new_data = [0; Hdrr::SIZE]; let mut cursor = Cursor::new(new_data.as_mut_slice()); hdrr.write_options(&mut cursor, endian, ()).unwrap(); self.data = new_data.to_vec(); } } struct ElfFile { data: Vec, endian: Endian, header: ElfHeader, sections: Vec
, symbol_entries: Vec, symtab: usize, sym_strtab: usize, } struct HeaderFields { sh_type: u32, sh_flags: u32, sh_link: u32, sh_info: u32, sh_addralign: u32, sh_entsize: u32, } impl ElfFile { fn new(data: &[u8]) -> BinResult { let data = data.to_vec(); assert_eq!(data[..4], [0x7f, b'E', b'L', b'F']); let endian: Endian = if data[EI_DATA] == 1 { Endian::Little } else if data[5] == 2 { Endian::Big } else { panic!("Invalid ELF endianness"); }; let header = ElfHeader::new(&data[..ElfHeader::SIZE], endian).unwrap(); let offset = header.e_shoff as usize; let size = header.e_shentsize as usize; let null_section = Section::new(&data[offset..offset + size], &data, 0, endian).unwrap(); let num_sections = if header.e_shnum == 0 { null_section.header.sh_size as usize } else { header.e_shnum as usize }; let mut sections = vec![null_section]; for i in 1..num_sections { let ind = offset + i * size; let section = Section::new(&data[ind..ind + size], &data, i, endian).unwrap(); sections.push(section); } let symtab_index = sections .iter() .position(|s| s.header.sh_type == SHT_SYMTAB) .expect("missing symtab"); let sym_strtab_index = sections[symtab_index].header.sh_link as usize; let symtab = §ions[symtab_index]; let sym_strtab = §ions[sym_strtab_index]; let symbol_entries = ElfFile::init_symbols(symtab, sym_strtab, endian); let shstr = sections[header.e_shstrndx as usize].clone(); for i in 0..sections.len() { let s = &mut sections[i]; s.name = String::from_utf8(shstr.lookup_str(s.header.sh_name as usize)).unwrap(); assert!(s.name.is_ascii()); if s.is_rel() { let target_index = s.header.sh_info as usize; s.init_relocs(endian); sections[target_index].relocated_by.push(i); } } Ok(ElfFile { data, endian, header, sections, symbol_entries, symtab: symtab_index, sym_strtab: sym_strtab_index, }) } fn find_section(&self, name: &str) -> Option<&Section> { self.sections.iter().find(|s| s.name == name) } fn find_section_mut(&mut self, name: &str) -> Option<&mut Section> { self.sections.iter_mut().find(|s| s.name == name) } fn symtab(&self) -> &Section { &self.sections[self.symtab] } fn symtab_mut(&mut self) -> &mut Section { &mut self.sections[self.symtab] } fn sym_strtab(&self) -> &Section { &self.sections[self.sym_strtab] } fn sym_strtab_mut(&mut self) -> &mut Section { &mut self.sections[self.sym_strtab] } fn init_symbols(symtab: &Section, strtab: &Section, endian: Endian) -> Vec { assert_eq!(symtab.header.sh_type, SHT_SYMTAB); assert_eq!(symtab.header.sh_entsize, 16); let mut syms = Vec::new(); for i in 0..(symtab.data.len() / 16) { syms.push(Symbol::new(&symtab.data[i * 16..(i + 1) * 16], strtab, endian).unwrap()); } syms } fn find_symbol(&self, name: &[u8]) -> Option<(usize, usize)> { for s in &self.symbol_entries { if s.name == name { return Some((s.st_shndx, s.st_value)); } } None } fn find_symbol_in_section(&self, name: &[u8], section: &Section) -> usize { let Some((st_shndx, st_value)) = self.find_symbol(name) else { panic!("failed to find symbol: {}", String::from_utf8_lossy(name)); }; if st_shndx != section.index { panic!( "symbol {} is in wrong section", String::from_utf8_lossy(name) ); } st_value } fn add_section(&mut self, name: &str, fields: &HeaderFields, data: &[u8], endian: Endian) { let shstr = self .sections .get_mut(self.header.e_shstrndx as usize) .expect("bad e_shstrndx"); let sh_name = shstr.add_str(name.as_bytes()); let mut s = Section::from_parts(sh_name, fields, data, self.sections.len(), endian); s.name = name.to_string(); self.sections.push(s); } fn drop_mdebug_gptab(&mut self) { // We can only drop sections at the end, since otherwise section // references might be wrong. Luckily, these sections typically are. while let Some(s) = self.sections.last() { if s.header.sh_type != SHT_MIPS_DEBUG && s.header.sh_type != SHT_MIPS_GPTAB { break; } self.sections.pop(); } } fn pad_out(writer: &mut BufWriter<&mut File>, align: usize) -> io::Result<()> { let pos = writer.stream_position()? as usize; if align > 0 && pos % align != 0 { let pad = align - (pos % align); for _ in 0..pad { writer.write_all(&[0])?; } } Ok(()) } fn write(&mut self, writer: &mut BufWriter<&mut File>) -> io::Result<()> { self.header.e_shnum = self.sections.len() as u16; writer.write_all(&self.header.to_bin(self.endian))?; for s in &mut self.sections { if s.header.sh_type != SHT_NOBITS && s.header.sh_type != SHT_NULL { Self::pad_out(writer, s.header.sh_addralign as usize)?; let old_offset = s.header.sh_offset; s.header.sh_offset = writer.stream_position()? as u32; if s.header.sh_type == SHT_MIPS_DEBUG && s.header.sh_offset != old_offset { // The .mdebug section has moved, relocate offsets s.relocate_mdebug(old_offset, self.endian); } writer.write_all(&s.data)?; } } Self::pad_out(writer, 4)?; self.header.e_shoff = writer.stream_position()? as u32; for s in &mut self.sections { writer.write_all(&s.header_to_bin(self.endian))?; } writer.seek(SeekFrom::Start(0))?; writer.write_all(&self.header.to_bin(self.endian))?; writer.flush()?; Ok(()) } } pub(crate) fn fixup_objfile( objfile_path: &Path, functions: &[Function], asm_prelude: &str, assembler: &str, output_enc: &Encoding, drop_mdebug_gptab: bool, convert_statics: ConvertStatics, ) -> Result<()> { const OUTPUT_SECTIONS: [OutputSection; 4] = [ OutputSection::Data, OutputSection::Text, OutputSection::Rodata, OutputSection::Bss, ]; const INPUT_SECTION_NAMES: [&str; 5] = [".data", ".text", ".rodata", ".bss", ".late_rodata"]; let objfile_data = fs::read(objfile_path)?; let mut objfile = ElfFile::new(&objfile_data)?; let endian = objfile.endian; let mut prev_locs: EnumMap = EnumMap::default(); struct ToCopyData { loc: usize, size: usize, temp_name: String, fn_desc: String, } let mut to_copy: EnumMap> = EnumMap::default(); let mut asm: Vec = vec![]; let mut all_late_rodata_dummy_bytes: Vec> = vec![]; let mut all_jtbl_rodata_size: Vec = vec![]; let mut late_rodata_asm: Vec = vec![]; let late_rodata_source_name_start = "_asmpp_late_rodata_start"; let late_rodata_source_name_end = "_asmpp_late_rodata_end"; // Generate an assembly file with all the assembly we need to fill in. For // simplicity we pad with nops/.space so that addresses match exactly, so we // don't have to fix up relocations/symbol references. let mut all_text_glabels: HashSet> = HashSet::new(); let mut func_sizes: HashMap, usize> = HashMap::new(); for function in functions.iter() { let text_glabels = function .text_glabels .iter() .map(|x| output_enc.encode(x)) .collect::>>()?; let mut ifdefed = false; for (sectype, &(ref temp_name, size)) in function.data.iter() { let Some(temp_name) = temp_name else { continue }; if size == 0 { panic!("Size of section {} is 0", sectype.as_str()); } let Some((_, loc)) = objfile.find_symbol(temp_name.as_bytes()) else { ifdefed = true; break; }; let prev_loc = prev_locs[sectype]; if loc < prev_loc { // If the dummy C generates too little asm, and we have two // consecutive GLOBAL_ASM blocks, we detect that error here. // On the other hand, if it generates too much, we don't have // a good way of discovering that error: it's indistinguishable // from a static symbol occurring after the GLOBAL_ASM block. panic!( "Wrongly computed size for section {} (diff {}). This is an asm-processor bug!", sectype, prev_loc - loc ); } if loc != prev_loc { asm.push(format!(".section {}", sectype)); if sectype == OutputSection::Text { for _ in 0..((loc - prev_loc) / 4) { asm.push("nop".to_owned()); } } else { asm.push(format!(".space {}", loc - prev_loc)); } } to_copy[sectype].push(ToCopyData { loc, size, temp_name: temp_name.clone(), fn_desc: function.fn_desc.clone(), }); if !text_glabels.is_empty() && sectype == OutputSection::Text { func_sizes.insert(text_glabels[0].to_vec(), size); } prev_locs[sectype] = loc + size; } if !ifdefed { all_text_glabels.extend(text_glabels.iter().map(|x| x.to_vec())); all_late_rodata_dummy_bytes.push(function.late_rodata_dummy_bytes.clone()); all_jtbl_rodata_size.push(function.jtbl_rodata_size); late_rodata_asm.extend(function.late_rodata_asm_conts.iter().cloned()); for (sectype, (temp_name, _)) in function.data.iter() { if let Some(temp_name) = temp_name { asm.push(format!(".section {}", sectype)); asm.push(format!("glabel {}_asm_start", temp_name)); } } asm.push(".text".to_owned()); asm.extend(function.asm_conts.iter().cloned()); for (sectype, (temp_name, _)) in function.data.iter() { if let Some(temp_name) = temp_name { asm.push(format!(".section {}", sectype)); asm.push(format!("glabel {}_asm_end", temp_name)); } } } } if !late_rodata_asm.is_empty() { asm.push(".section .late_rodata".to_string()); // Put some padding at the start to avoid conflating symbols with // references to the whole section. asm.push(".word 0, 0".to_string()); asm.push(format!("glabel {}", late_rodata_source_name_start)); asm.extend(late_rodata_asm.iter().cloned()); asm.push(format!("glabel {}", late_rodata_source_name_end)); } let temp_dir = TempDir::with_prefix("asm_processor")?; let obj_stem = objfile_path.file_stem().unwrap().to_str().unwrap(); let o_file_path = temp_dir .path() .join(format!("asm_processor_{}.o", obj_stem)); let s_file_path = temp_dir .path() .join(format!("asm_processor_{}.s", obj_stem)); { let mut s_file = File::create(&s_file_path)?; s_file.write_all(&output_enc.encode(asm_prelude)?)?; s_file.write_all(&output_enc.encode("\n")?)?; for line in asm { s_file.write_all(&output_enc.encode(&line)?)?; s_file.write_all(&output_enc.encode("\n")?)?; } } let status = Command::new("sh") .arg("-c") .arg(format!( "{} {} -o {}", assembler, shlex::try_quote(s_file_path.to_str().unwrap()).unwrap(), shlex::try_quote(o_file_path.to_str().unwrap()).unwrap(), )) .status() .expect("Failed to run shell"); if !status.success() { return Err(anyhow::anyhow!("Failed to assemble")); } let asm_objfile = ElfFile::new(&fs::read(&o_file_path)?)?; // Remove clutter from objdump output for tests, and make the tests // portable by avoiding absolute paths. Outside of tests .mdebug is // useful for showing source together with asm, though. let mdebug_section = objfile.find_section(".mdebug").cloned(); if drop_mdebug_gptab { objfile.drop_mdebug_gptab(); } // Unify reginfo sections if let Some(target_reginfo) = objfile.find_section_mut(".reginfo") { let source_reginfo = &asm_objfile .find_section(".reginfo") .expect("couldn't find source .reginfo"); for (s, t) in source_reginfo .data .iter() .zip(target_reginfo.data.iter_mut()) { *t |= *s; } } // Move over section contents let mut modified_text_positions = HashSet::new(); let mut jtbl_rodata_positions: HashSet = HashSet::new(); let mut last_rodata_pos = 0; for sectype in OUTPUT_SECTIONS { if to_copy[sectype].is_empty() { continue; } let Some(source) = asm_objfile.find_section(sectype.as_str()) else { panic!("didn't find source section: {}", sectype); }; for &ToCopyData { loc, size, ref temp_name, ref fn_desc, } in to_copy[sectype].iter() { let loc1 = asm_objfile .find_symbol_in_section(format!("{}_asm_start", &temp_name).as_bytes(), source); let loc2 = asm_objfile .find_symbol_in_section(format!("{}_asm_end", &temp_name).as_bytes(), source); if loc1 != loc { panic!( "assembly and C files don't line up for section {}, {}", sectype, fn_desc ); } if loc2 - loc1 != size { return Err(anyhow::anyhow!( "incorrectly computed size for section {}, {}. If using .double, make sure to provide explicit alignment padding.", sectype, fn_desc )); } } if sectype == OutputSection::Bss { continue; } let Some(target) = objfile.find_section_mut(sectype.as_str()) else { panic!("didn't find target section: {}", sectype); }; for &ToCopyData { loc, size, .. } in to_copy[sectype].iter() { target.data[loc..loc + size].copy_from_slice(&source.data[loc..loc + size]); if sectype == OutputSection::Text { assert_eq!(size % 4, 0); assert_eq!(loc % 4, 0); for j in 0..size / 4 { modified_text_positions.insert(loc + 4 * j); } } else if sectype == OutputSection::Rodata { last_rodata_pos = loc + size; } } } // Move over late rodata. This is heuristic, sadly, since I can't think // of another way of doing it. let mut moved_late_rodata: HashMap = HashMap::new(); if all_late_rodata_dummy_bytes.iter().any(|b| !b.is_empty()) || all_jtbl_rodata_size.iter().any(|&s| s > 0) { let source = asm_objfile .find_section(".late_rodata") .expect(".late_rodata source section should exist"); let target = objfile .find_section_mut(".rodata") .expect(".rodata target section should exist"); let mut source_pos = asm_objfile.find_symbol_in_section(late_rodata_source_name_start.as_bytes(), source); let source_end = asm_objfile.find_symbol_in_section(late_rodata_source_name_end.as_bytes(), source); let num_dummies: usize = all_late_rodata_dummy_bytes.iter().map(|x| x.len()).sum(); let expected_size = num_dummies * 4 + all_jtbl_rodata_size.iter().sum::(); if source_end - source_pos != expected_size { return Err(anyhow::anyhow!("computed wrong size of .late_rodata")); } let mut new_data = target.data.clone(); for (dummy_bytes_list, &jtbl_rodata_size) in all_late_rodata_dummy_bytes .iter_mut() .zip(all_jtbl_rodata_size.iter()) { let dummy_bytes_list_len = dummy_bytes_list.len(); for (index, dummy_bytes) in dummy_bytes_list.iter_mut().enumerate() { if endian == Endian::Little { dummy_bytes.reverse(); } let mut pos = target.data[last_rodata_pos..] .windows(4) .position(|x| x == dummy_bytes) .expect("failed to find dummy .late_rodata bytes") + last_rodata_pos; if index == 0 && dummy_bytes_list_len > 1 && target.data[pos + 4..pos + 8] == *b"\0\0\0\0" { // Ugly hack to handle double alignment for non-matching builds. // We were told by .late_rodata_alignment (or deduced from a .double) // that a function's late_rodata started out 4 (mod 8), and emitted // a float and then a double. But it was actually 0 (mod 8), so our // double was moved by 4 bytes. To make them adjacent to keep jump // tables correct, move the float by 4 bytes as well. new_data[pos..pos + 4].copy_from_slice(b"\0\0\0\0"); pos += 4; } new_data[pos..pos + 4].copy_from_slice(&source.data[source_pos..source_pos + 4]); moved_late_rodata.insert(source_pos, pos); last_rodata_pos = pos + 4; source_pos += 4; } if jtbl_rodata_size > 0 { assert!(!dummy_bytes_list.is_empty()); let pos = last_rodata_pos; new_data[pos..pos + jtbl_rodata_size] .copy_from_slice(&source.data[source_pos..source_pos + jtbl_rodata_size]); for i in (0..jtbl_rodata_size).step_by(4) { moved_late_rodata.insert(source_pos + i, pos + i); jtbl_rodata_positions.insert(pos + i); } last_rodata_pos += jtbl_rodata_size; source_pos += jtbl_rodata_size; } } target.data = new_data; } // Merge strtab data. let strtab = objfile.sym_strtab_mut(); let strtab_adj = strtab.data.len(); strtab.data.extend(&asm_objfile.sym_strtab().data); // Find relocated symbols in asm_objfile let mut relocated_symbols = HashSet::new(); for sectype in INPUT_SECTION_NAMES.iter() { if let Some(sec) = asm_objfile.find_section(sectype) { for reltab_idx in &sec.relocated_by { let reltab = &asm_objfile.sections[*reltab_idx]; for rel in &reltab.relocations { relocated_symbols.insert(rel.sym_index); } } } } enum SymInd { Obj(usize), Asm(usize), } // Move over symbols, deleting the temporary function labels. // Skip over new local symbols that aren't relocated against, to // avoid conflicts. let empty_symbol = objfile.symbol_entries[0].clone(); let mut new_syms: Vec<(Symbol, Vec)> = objfile .symbol_entries .iter() .enumerate() .map(|(i, x)| (x, vec![SymInd::Obj(i)])) .skip(1) .filter(|(x, _)| !x.name.starts_with(b"_asmpp_")) .map(|(x, inds)| (x.clone(), inds)) .collect(); for (i, s) in asm_objfile.symbol_entries.iter().enumerate() { let is_local = i < asm_objfile.symtab().header.sh_info as usize; if is_local && !relocated_symbols.contains(&i) { continue; } if s.name.starts_with(b"_asmpp_") { assert!(!relocated_symbols.contains(&i)); continue; } let mut s = s.clone(); if s.st_shndx != SHN_UNDEF && s.st_shndx != SHN_ABS { let section_name = asm_objfile.sections[s.st_shndx].name.clone(); let mut target_section_name = section_name.clone(); if section_name == ".late_rodata" { target_section_name = ".rodata".to_string(); } else if !INPUT_SECTION_NAMES.contains(§ion_name.as_str()) { return Err(anyhow::anyhow!( "generated assembly .o must only have symbols for .text, .data, .rodata, .late_rodata, ABS and UNDEF, but found {}", section_name )); } let Some(objfile_section) = objfile.find_section(&target_section_name) else { return Err(anyhow::anyhow!( "generated assembly .o has section that real objfile lacks: {}", target_section_name )); }; s.st_shndx = objfile_section.index; // glabels aren't marked as functions, making objdump output confusing. Fix that. if all_text_glabels.contains(&s.name) { s.st_type = STT_FUNC; if let Some(&size) = func_sizes.get(&s.name) { s.st_size = size; } } if section_name == ".late_rodata" { if s.st_value == 0 { // This must be a symbol corresponding to the whole .late_rodata // section, being referred to from a relocation. // Moving local symbols is tricky, because it requires fixing up // lo16/hi16 relocation references to .late_rodata+. // Just disallow it for now. return Err(anyhow::anyhow!( "local symbols in .late_rodata are not allowed" )); } s.st_value = moved_late_rodata[&s.st_value]; } } s.st_name += strtab_adj; new_syms.push((s, vec![SymInd::Asm(i)])); } // Add static symbols from .mdebug, so they can be referred to from GLOBAL_ASM if mdebug_section.is_some() && convert_statics != ConvertStatics::No { let mdebug_section = mdebug_section.unwrap(); let mut static_name_count: HashMap, usize> = HashMap::new(); let mut strtab_index = objfile.sym_strtab().data.len(); let mut new_strtab_data = vec![]; let read_u32 = |data: &[u8], offset| { u32::from_be_bytes(data[offset..offset + 4].try_into().unwrap()) as usize }; let ifd_max = read_u32(&mdebug_section.data, 18 * 4); let cb_fd_offset = read_u32(&mdebug_section.data, 19 * 4); let cb_sym_offset = read_u32(&mdebug_section.data, 9 * 4); let cb_ss_offset = read_u32(&mdebug_section.data, 15 * 4); for i in 0..ifd_max { let offset = cb_fd_offset + 18 * 4 * i; let iss_base = read_u32(&objfile.data, offset + 2 * 4); let isym_base = read_u32(&objfile.data, offset + 4 * 4); let csym = read_u32(&objfile.data, offset + 5 * 4); let mut scope_level = 0; for j in 0..csym { let offset2 = cb_sym_offset + 12 * (isym_base + j); let iss = read_u32(&objfile.data, offset2); let value = read_u32(&objfile.data, offset2 + 4); let st_sc_index = read_u32(&objfile.data, offset2 + 8); let st = st_sc_index >> 26; let sc = (st_sc_index >> 21) & 0x1F; if st == MIPS_DEBUG_ST_STATIC || st == MIPS_DEBUG_ST_STATIC_PROC { let symbol_name_offset = cb_ss_offset + iss_base + iss; let symbol_name_offset_end = objfile_data[symbol_name_offset..] .iter() .position(|x| *x == 0) .expect("bad .mdebug strtab reference") + symbol_name_offset; let mut symbol_name = objfile_data[symbol_name_offset..symbol_name_offset_end].to_owned(); if scope_level > 1 { // For in-function statics, append an increasing counter to // the name, to avoid duplicate conflicting symbols. let count = static_name_count.get(&symbol_name).unwrap_or(&0) + 1; static_name_count.insert(symbol_name.clone(), count); symbol_name.extend(format!(":{}", count).as_bytes()); } let mut emitted_symbol_name = symbol_name.clone(); if convert_statics == ConvertStatics::GlobalWithFilename { // Change the emitted symbol name to include the filename, // but don't let that affect deduplication logic (we still // want to be able to reference statics from GLOBAL_ASM). let mut new_name = objfile_path.to_string_lossy().into_owned().into_bytes(); new_name.push(b':'); new_name.extend(emitted_symbol_name); emitted_symbol_name = new_name; }; let section_name = match sc { 1 => ".text", 2 => ".data", 3 => ".bss", 15 => ".rodata", _ => { return Err(anyhow::anyhow!("unsupported MIPS_DEBUG_SC value: {}", sc)); } }; let Some(section) = objfile.find_section(section_name) else { panic!( "couldn't find section referenced from .mdebug: {}", section_name ); }; let symtype = if sc == 1 { STT_FUNC } else { STT_OBJECT }; let binding = match convert_statics { ConvertStatics::Global | ConvertStatics::GlobalWithFilename => STB_GLOBAL, _ => STB_LOCAL, }; let sym = Symbol { st_name: strtab_index, st_value: value, st_size: 0, st_bind: binding, st_type: symtype, st_visibility: STV_DEFAULT, st_shndx: section.index, name: symbol_name, }; strtab_index += emitted_symbol_name.len() + 1; new_strtab_data.extend(&emitted_symbol_name); new_strtab_data.push(b'\0'); new_syms.push((sym, vec![])); } match st { MIPS_DEBUG_ST_FILE | MIPS_DEBUG_ST_STRUCT | MIPS_DEBUG_ST_UNION | MIPS_DEBUG_ST_ENUM | MIPS_DEBUG_ST_BLOCK | MIPS_DEBUG_ST_PROC | MIPS_DEBUG_ST_STATIC_PROC => { scope_level += 1; } MIPS_DEBUG_ST_END => { scope_level -= 1; } _ => {} } } assert_eq!(scope_level, 0); } objfile.sym_strtab_mut().data.extend(new_strtab_data); } // Get rid of duplicate symbols, favoring ones that are not UNDEF. // Skip this for unnamed local symbols though. new_syms.sort_by(|(a, _), (b, _)| { if a.st_shndx != SHN_UNDEF && b.st_shndx == SHN_UNDEF { Ordering::Less } else { Ordering::Greater } }); let new_syms_prev = new_syms; let mut new_syms = vec![]; let mut name_to_sym = HashMap::new(); for (mut s, inds) in new_syms_prev { if s.name == b"_gp_disp" { s.st_type = STT_OBJECT; } if s.st_bind == STB_LOCAL && s.st_shndx == SHN_UNDEF { return Err(anyhow::anyhow!( "local symbol \"{}\" is undefined", String::from_utf8_lossy(&s.name) )); } if s.name.is_empty() { if s.st_bind != STB_LOCAL { return Err(anyhow::anyhow!("global symbol with no name")); } new_syms.push((s.clone(), inds)); } else { match name_to_sym.get(&s.name) { None => { name_to_sym.insert(s.name.clone(), new_syms.len()); new_syms.push((s.clone(), inds)); } Some(&existing) => { let (s2, inds2) = &mut new_syms[existing]; if s.st_shndx != SHN_UNDEF && !(s2.st_shndx == s.st_shndx && s2.st_value == s.st_value) { return Err(anyhow::anyhow!( "symbol \"{}\" defined twice", String::from_utf8_lossy(&s.name) )); } inds2.extend(inds); } } } } // Put local symbols in front, with the initial dummy entry first, and // _gp_disp at the end if it exists. new_syms.insert(0, (empty_symbol.clone(), vec![])); new_syms.sort_by_key(|(a, _)| (a.st_bind != STB_LOCAL, a.name == b"_gp_disp")); let mut obj_new_index: HashMap = HashMap::new(); let mut asm_new_index: HashMap = HashMap::new(); for (new_index, (_, inds)) in new_syms.iter().enumerate() { for i in inds { match i { SymInd::Obj(i) => obj_new_index.insert(*i, new_index), SymInd::Asm(i) => asm_new_index.insert(*i, new_index), }; } } let new_syms: Vec<_> = new_syms.iter().map(|(s, _)| s).collect(); let num_local_syms = new_syms.iter().filter(|s| s.st_bind == STB_LOCAL).count(); let new_sym_data: Vec = new_syms.iter().flat_map(|s| s.to_bin()).collect(); objfile.symtab_mut().data = new_sym_data; objfile.symtab_mut().header.sh_info = num_local_syms as u32; // Fix up relocation symbol references for sectype in OUTPUT_SECTIONS { let target = objfile.find_section(sectype.as_str()).cloned(); if let Some(target) = target { // fixup relocation symbol indices, since we butchered them above for reltab in target.relocated_by.iter() { let reltab = &mut objfile.sections[*reltab]; let mut nrels = vec![]; for rel in reltab.relocations.iter() { let mut rel = rel.clone(); if (sectype == OutputSection::Text && modified_text_positions.contains(&rel.r_offset)) || (sectype == OutputSection::Rodata && jtbl_rodata_positions.contains(&rel.r_offset)) { // don't include relocations for late_rodata dummy code continue; } rel.sym_index = obj_new_index[&rel.sym_index]; nrels.push(rel); } reltab.data = nrels.iter().flat_map(|x| x.to_bin(endian)).collect(); reltab.relocations = nrels; } } } // Move over relocations for sectype in INPUT_SECTION_NAMES.iter() { if let Some(source) = asm_objfile.find_section(sectype) { if source.data.is_empty() { continue; } let target_sectype = if *sectype == ".late_rodata" { ".rodata" } else { sectype }; let target_index = objfile .find_section(target_sectype) .expect("didn't find target section") .index; for reltab in &source.relocated_by { let reltab = &mut asm_objfile.sections[*reltab].clone(); for rel in &mut reltab.relocations { rel.sym_index = asm_new_index[&rel.sym_index]; if *sectype == ".late_rodata" { rel.r_offset = moved_late_rodata[&rel.r_offset]; } } let new_data: Vec = reltab .relocations .iter() .flat_map(|x| x.to_bin(endian)) .collect(); let (prefix, sh_entsize) = if reltab.header.sh_type == SHT_REL { (".rel", 8) } else { (".rela", 12) }; let rel_section_name = format!("{}{}", prefix, target_sectype); if let Some(target_reltab) = objfile.find_section_mut(&rel_section_name) { target_reltab.data.extend(new_data); } else { objfile.add_section( &rel_section_name, &HeaderFields { sh_type: reltab.header.sh_type, sh_flags: 0, sh_link: objfile.symtab().index as u32, sh_info: target_index as u32, sh_addralign: 4, sh_entsize, }, &new_data, endian, ); } } } } let mut file = std::fs::File::create(objfile_path).expect("unable to write to .o file"); let mut writer = BufWriter::new(&mut file); objfile.write(&mut writer)?; fs::remove_file(s_file_path)?; fs::remove_file(o_file_path)?; Ok(()) }