diff options
Diffstat (limited to 'tools/asm-processor/rust/src/postprocess.rs')
| -rw-r--r-- | tools/asm-processor/rust/src/postprocess.rs | 1312 |
1 files changed, 1312 insertions, 0 deletions
diff --git a/tools/asm-processor/rust/src/postprocess.rs b/tools/asm-processor/rust/src/postprocess.rs new file mode 100644 index 0000000..60b782b --- /dev/null +++ b/tools/asm-processor/rust/src/postprocess.rs @@ -0,0 +1,1312 @@ +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<Self> { + 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<u8>, +} + +impl Symbol { + fn new(data: &[u8], strtab: &Section, endian: Endian) -> BinResult<Self> { + 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<u8> { + 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<u32>, +} + +impl Relocation { + fn new(data: &[u8], sh_type: u32, endian: Endian) -> BinResult<Self> { + 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<u8> { + 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<u8>, + index: usize, + relocated_by: Vec<usize>, + relocations: Vec<Relocation>, + name: String, +} + +impl Section { + fn new(data: &[u8], other_data: &[u8], index: usize, endian: Endian) -> BinResult<Self> { + 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<u8> { + 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<u8>, + endian: Endian, + header: ElfHeader, + sections: Vec<Section>, + symbol_entries: Vec<Symbol>, + 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<Self> { + 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<Symbol> { + 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<OutputSection, usize> = EnumMap::default(); + + struct ToCopyData { + loc: usize, + size: usize, + temp_name: String, + fn_desc: String, + } + + let mut to_copy: EnumMap<OutputSection, Vec<ToCopyData>> = EnumMap::default(); + + let mut asm: Vec<String> = vec![]; + let mut all_late_rodata_dummy_bytes: Vec<Vec<[u8; 4]>> = vec![]; + let mut all_jtbl_rodata_size: Vec<usize> = vec![]; + let mut late_rodata_asm: Vec<String> = 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<Vec<u8>> = HashSet::new(); + let mut func_sizes: HashMap<Vec<u8>, usize> = HashMap::new(); + + for function in functions.iter() { + let text_glabels = function + .text_glabels + .iter() + .map(|x| output_enc.encode(x)) + .collect::<Result<Vec<_>>>()?; + 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<usize> = 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<usize, usize> = 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::<usize>(); + + 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<SymInd>)> = 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+<offset>. + // 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<Vec<u8>, 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<usize, usize> = HashMap::new(); + let mut asm_new_index: HashMap<usize, usize> = 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<u8> = 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<u8> = 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(()) +} |
