| //! Binary-level symbol editing for staticlib post-processing. | |
| //! | |
| //! - **Hide**: sets STV_HIDDEN (ELF) or N_PEXT (Mach-O) on non-exported symbols. | |
| //! - **Rename**: appends a vendor-specific suffix to non-exported symbol names by | |
| //! rebuilding the string table. | |
| use std::borrow::Cow; | |
| use std::mem; | |
| use object::read::elf::{SectionHeader as _, Sym as _}; | |
| use object::read::macho::Nlist; | |
| use object::{Endianness, elf, macho}; | |
| use rustc_data_structures::fx::{FxHashMap, FxHashSet}; | |
| struct Patch { | |
| offset: usize, | |
| value: u8, | |
| } | |
| struct RenameEntry { | |
| name_field_offset: usize, | |
| name: String, | |
| } | |
| pub(super) fn apply_edits<'a>( | |
| data: &'a [u8], | |
| exported: &FxHashSet<String>, | |
| hide: bool, | |
| rename: Option<&(FxHashSet<String>, &str)>, | |
| ) -> Cow<'a, [u8]> { | |
| let result = match object::File::parse(data).ok() { | |
| Some(object::File::Elf64(_)) => elf_edit_impl::<elf::FileHeader64<Endianness>>( | |
| data, | |
| exported, | |
| hide, | |
| rename, | |
| mem::offset_of!(elf::Sym64<Endianness>, st_other), | |
| ), | |
| Some(object::File::Elf32(_)) => elf_edit_impl::<elf::FileHeader32<Endianness>>( | |
| data, | |
| exported, | |
| hide, | |
| rename, | |
| mem::offset_of!(elf::Sym32<Endianness>, st_other), | |
| ), | |
| Some(object::File::MachO64(_)) => macho_edit_impl::<macho::MachHeader64<Endianness>>( | |
| data, | |
| exported, | |
| hide, | |
| rename, | |
| mem::offset_of!(macho::Nlist64<Endianness>, n_type), | |
| ), | |
| Some(object::File::MachO32(_)) => macho_edit_impl::<macho::MachHeader32<Endianness>>( | |
| data, | |
| exported, | |
| hide, | |
| rename, | |
| mem::offset_of!(macho::Nlist32<Endianness>, n_type), | |
| ), | |
| _ => None, | |
| }; | |
| match result { | |
| Some(v) => Cow::Owned(v), | |
| None => Cow::Borrowed(data), | |
| } | |
| } | |
| pub(super) fn collect_internal_names( | |
| data: &[u8], | |
| exported: &FxHashSet<String>, | |
| out: &mut FxHashSet<String>, | |
| ) { | |
| let Ok(file) = object::File::parse(data) else { return }; | |
| match file { | |
| object::File::Elf64(_) => { | |
| elf_collect_impl::<elf::FileHeader64<Endianness>>(data, exported, out) | |
| } | |
| object::File::Elf32(_) => { | |
| elf_collect_impl::<elf::FileHeader32<Endianness>>(data, exported, out) | |
| } | |
| object::File::MachO64(_) => { | |
| macho_collect_impl::<macho::MachHeader64<Endianness>>(data, exported, out) | |
| } | |
| object::File::MachO32(_) => { | |
| macho_collect_impl::<macho::MachHeader32<Endianness>>(data, exported, out) | |
| } | |
| _ => {} | |
| } | |
| } | |
| fn elf_collect_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>( | |
| data: &[u8], | |
| exported: &FxHashSet<String>, | |
| out: &mut FxHashSet<String>, | |
| ) where | |
| u64: From<Elf::Word>, | |
| { | |
| let Ok(header) = Elf::parse(data) else { return }; | |
| let Ok(endian) = header.endian() else { return }; | |
| let Ok(sections) = header.sections(endian, data) else { return }; | |
| let Ok(symtab) = sections.symbols(endian, data, elf::SHT_SYMTAB) else { return }; | |
| let strings = symtab.strings(); | |
| for sym in symtab.iter() { | |
| let binding = sym.st_bind(); | |
| if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK { | |
| continue; | |
| } | |
| if sym.is_undefined(endian) { | |
| continue; | |
| } | |
| let Ok(name_bytes) = sym.name(endian, strings) else { continue }; | |
| let Ok(name) = str::from_utf8(name_bytes) else { continue }; | |
| if !exported.contains(name) { | |
| out.insert(name.to_string()); | |
| } | |
| } | |
| } | |
| fn macho_collect_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>( | |
| data: &[u8], | |
| exported: &FxHashSet<String>, | |
| out: &mut FxHashSet<String>, | |
| ) { | |
| let Ok(header) = Mach::parse(data, 0) else { return }; | |
| let Ok(endian) = header.endian() else { return }; | |
| let Ok(mut commands) = header.load_commands(endian, data, 0) else { return }; | |
| let symtab_cmd = loop { | |
| let Ok(Some(cmd)) = commands.next() else { return }; | |
| if let Ok(Some(st)) = cmd.symtab() { | |
| break st; | |
| } | |
| }; | |
| let Ok(symtab) = symtab_cmd.symbols::<Mach, _>(endian, data) else { return }; | |
| let strings = symtab.strings(); | |
| for nlist in symtab.iter() { | |
| if nlist.is_stab() { | |
| continue; | |
| } | |
| if nlist.is_undefined() { | |
| continue; | |
| } | |
| if nlist.n_type() & macho::N_EXT == 0 { | |
| continue; | |
| } | |
| let Ok(name_bytes) = nlist.name(endian, strings) else { continue }; | |
| let Ok(name) = str::from_utf8(name_bytes) else { continue }; | |
| let name = name.strip_prefix('_').unwrap_or(name); | |
| if !exported.contains(name) { | |
| out.insert(name.to_string()); | |
| } | |
| } | |
| } | |
| // --------------------------------------------------------------------------- | |
| // ELF: single-pass collection + apply | |
| // --------------------------------------------------------------------------- | |
| fn elf_edit_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>( | |
| data: &[u8], | |
| exported: &FxHashSet<String>, | |
| hide: bool, | |
| rename: Option<&(FxHashSet<String>, &str)>, | |
| st_other_offset: usize, | |
| ) -> Option<Vec<u8>> | |
| where | |
| u64: From<Elf::Word>, | |
| { | |
| let header = Elf::parse(data).ok()?; | |
| let endian = header.endian().ok()?; | |
| let sections = header.sections(endian, data).ok()?; | |
| let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?; | |
| let data_ptr = data.as_ptr() as usize; | |
| let strings = symtab.strings(); | |
| let mut patches = Vec::new(); | |
| let mut renames = Vec::new(); | |
| for sym in symtab.iter() { | |
| let binding = sym.st_bind(); | |
| if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK { | |
| continue; | |
| } | |
| let Ok(name_bytes) = sym.name(endian, strings) else { continue }; | |
| let Ok(name) = str::from_utf8(name_bytes) else { continue }; | |
| let sym_addr = sym as *const Elf::Sym as usize; | |
| if hide && !sym.is_undefined(endian) && !exported.contains(name) { | |
| let offset = sym_addr - data_ptr + st_other_offset; | |
| let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN; | |
| patches.push(Patch { offset, value: new_vis }); | |
| } | |
| if let Some((rename_set, _)) = rename { | |
| if rename_set.contains(name) { | |
| renames.push(RenameEntry { | |
| name_field_offset: sym_addr - data_ptr, | |
| name: name.to_string(), | |
| }); | |
| } | |
| } | |
| } | |
| if patches.is_empty() && renames.is_empty() { | |
| return None; | |
| } | |
| let mut result = data.to_vec(); | |
| for p in &patches { | |
| result[p.offset] = p.value; | |
| } | |
| if !renames.is_empty() { | |
| let suffix = rename.unwrap().1; | |
| if let Some(renamed) = | |
| elf_rebuild_strtab::<Elf>(&result, &renames, suffix, §ions, header, endian) | |
| { | |
| result = renamed; | |
| } | |
| } | |
| Some(result) | |
| } | |
| fn elf_rebuild_strtab<Elf: object::read::elf::FileHeader<Endian = Endianness>>( | |
| data: &[u8], | |
| renames: &[RenameEntry], | |
| suffix: &str, | |
| sections: &object::read::elf::SectionTable<'_, Elf>, | |
| header: &Elf, | |
| endian: Endianness, | |
| ) -> Option<Vec<u8>> | |
| where | |
| u64: From<Elf::Word>, | |
| { | |
| let mut strtab_si: Option<usize> = None; | |
| for section in sections.iter() { | |
| if section.sh_type(endian) == elf::SHT_SYMTAB { | |
| strtab_si = Some(section.sh_link(endian) as usize); | |
| break; | |
| } | |
| } | |
| let strtab_si = strtab_si?; | |
| let e_shoff = u64::from(header.e_shoff(endian)) as usize; | |
| let e_shentsize = mem::size_of::<Elf::SectionHeader>(); | |
| let e_shnum = sections.len(); | |
| let strtab_section = sections.section(object::SectionIndex(strtab_si)).ok()?; | |
| let old_strtab_offset = u64::from(strtab_section.sh_offset(endian)) as usize; | |
| let old_strtab_size = u64::from(strtab_section.sh_size(endian)) as usize; | |
| let old_strtab = data.get(old_strtab_offset..old_strtab_offset + old_strtab_size)?; | |
| let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix); | |
| let is_64 = mem::size_of::<Elf::Word>() == 8; | |
| let new_strtab_file_off = data.len(); | |
| let new_strtab_size = new_strtab.len(); | |
| let new_e_shoff_raw = new_strtab_file_off + new_strtab_size; | |
| let new_e_shoff = (new_e_shoff_raw + 7) & !7; | |
| let padding = new_e_shoff - new_e_shoff_raw; | |
| let section_headers_size = e_shentsize * e_shnum; | |
| let result_size = new_e_shoff + section_headers_size; | |
| let mut result = Vec::with_capacity(result_size); | |
| result.extend_from_slice(data); | |
| result.extend_from_slice(&new_strtab); | |
| result.resize(result.len() + padding, 0); | |
| let sh_data = data.get(e_shoff..e_shoff + section_headers_size)?; | |
| result.extend_from_slice(sh_data); | |
| if is_64 { | |
| write_u64_at( | |
| &mut result, | |
| mem::offset_of!(elf::FileHeader64<Endianness>, e_shoff), | |
| new_e_shoff as u64, | |
| endian, | |
| ); | |
| } else { | |
| write_u32_at( | |
| &mut result, | |
| mem::offset_of!(elf::FileHeader32<Endianness>, e_shoff), | |
| new_e_shoff as u32, | |
| endian, | |
| ); | |
| } | |
| let new_strtab_shdr_offset = new_e_shoff + strtab_si * e_shentsize; | |
| if is_64 { | |
| let sh_offset_field = mem::offset_of!(elf::SectionHeader64<Endianness>, sh_offset); | |
| let sh_size_field = mem::offset_of!(elf::SectionHeader64<Endianness>, sh_size); | |
| write_u64_at( | |
| &mut result, | |
| new_strtab_shdr_offset + sh_offset_field, | |
| new_strtab_file_off as u64, | |
| endian, | |
| ); | |
| write_u64_at( | |
| &mut result, | |
| new_strtab_shdr_offset + sh_size_field, | |
| new_strtab_size as u64, | |
| endian, | |
| ); | |
| } else { | |
| let sh_offset_field = mem::offset_of!(elf::SectionHeader32<Endianness>, sh_offset); | |
| let sh_size_field = mem::offset_of!(elf::SectionHeader32<Endianness>, sh_size); | |
| write_u32_at( | |
| &mut result, | |
| new_strtab_shdr_offset + sh_offset_field, | |
| new_strtab_file_off as u32, | |
| endian, | |
| ); | |
| write_u32_at( | |
| &mut result, | |
| new_strtab_shdr_offset + sh_size_field, | |
| new_strtab_size as u32, | |
| endian, | |
| ); | |
| } | |
| for entry in renames { | |
| let new_st_name = rename_map[&entry.name]; | |
| write_u32_at(&mut result, entry.name_field_offset, new_st_name, endian); | |
| } | |
| Some(result) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Mach-O: single-pass collection + apply | |
| // --------------------------------------------------------------------------- | |
| fn macho_edit_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>( | |
| data: &[u8], | |
| exported: &FxHashSet<String>, | |
| hide: bool, | |
| rename: Option<&(FxHashSet<String>, &str)>, | |
| n_type_offset: usize, | |
| ) -> Option<Vec<u8>> { | |
| let header = Mach::parse(data, 0).ok()?; | |
| let endian = header.endian().ok()?; | |
| let mut commands = header.load_commands(endian, data, 0).ok()?; | |
| let (symtab_cmd, symtab_cmd_offset) = loop { | |
| let cmd = commands.next().ok()??; | |
| if let Some(st) = cmd.symtab().ok().flatten() { | |
| break (st, cmd.raw_data().as_ptr() as usize - data.as_ptr() as usize); | |
| } | |
| }; | |
| let symtab: object::read::macho::SymbolTable<'_, Mach, &_> = | |
| symtab_cmd.symbols(endian, data).ok()?; | |
| let data_ptr = data.as_ptr() as usize; | |
| let strings = symtab.strings(); | |
| let mut patches = Vec::new(); | |
| let mut renames = Vec::new(); | |
| for nlist in symtab.iter() { | |
| if nlist.is_stab() { | |
| continue; | |
| } | |
| if nlist.n_type() & macho::N_EXT == 0 { | |
| continue; | |
| } | |
| let Ok(name_bytes) = nlist.name(endian, strings) else { continue }; | |
| let Ok(raw_name) = str::from_utf8(name_bytes) else { continue }; | |
| let name = raw_name.strip_prefix('_').unwrap_or(raw_name); | |
| let nlist_addr = nlist as *const Mach::Nlist as usize; | |
| if hide && !nlist.is_undefined() && !exported.contains(name) { | |
| let offset = nlist_addr - data_ptr + n_type_offset; | |
| patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT }); | |
| } | |
| if let Some((rename_set, _)) = rename { | |
| if rename_set.contains(name) { | |
| renames.push(RenameEntry { | |
| name_field_offset: nlist_addr - data_ptr, | |
| name: raw_name.to_string(), | |
| }); | |
| } | |
| } | |
| } | |
| if patches.is_empty() && renames.is_empty() { | |
| return None; | |
| } | |
| let mut result = data.to_vec(); | |
| for p in &patches { | |
| result[p.offset] = p.value; | |
| } | |
| if !renames.is_empty() { | |
| let suffix = rename.unwrap().1; | |
| if let Some(renamed) = | |
| macho_rebuild_strtab(&result, &renames, suffix, &symtab_cmd, symtab_cmd_offset, endian) | |
| { | |
| result = renamed; | |
| } | |
| } | |
| Some(result) | |
| } | |
| fn macho_rebuild_strtab( | |
| data: &[u8], | |
| renames: &[RenameEntry], | |
| suffix: &str, | |
| symtab_cmd: &macho::SymtabCommand<Endianness>, | |
| symtab_cmd_offset: usize, | |
| endian: Endianness, | |
| ) -> Option<Vec<u8>> { | |
| let old_stroff = symtab_cmd.stroff.get(endian) as usize; | |
| let old_strsize = symtab_cmd.strsize.get(endian) as usize; | |
| let old_strtab = data.get(old_stroff..old_stroff + old_strsize)?; | |
| let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix); | |
| let new_strtab_file_off = data.len(); | |
| let new_strtab_size = new_strtab.len(); | |
| let mut result = Vec::with_capacity(data.len() + new_strtab_size); | |
| result.extend_from_slice(data); | |
| result.extend_from_slice(&new_strtab); | |
| let stroff_off = mem::offset_of!(macho::SymtabCommand<Endianness>, stroff); | |
| let strsize_off = mem::offset_of!(macho::SymtabCommand<Endianness>, strsize); | |
| write_u32_at(&mut result, symtab_cmd_offset + stroff_off, new_strtab_file_off as u32, endian); | |
| write_u32_at(&mut result, symtab_cmd_offset + strsize_off, new_strtab_size as u32, endian); | |
| for entry in renames { | |
| let new_strx = rename_map[&entry.name]; | |
| write_u32_at(&mut result, entry.name_field_offset, new_strx, endian); | |
| } | |
| Some(result) | |
| } | |
| // --------------------------------------------------------------------------- | |
| // Shared helpers | |
| // --------------------------------------------------------------------------- | |
| fn build_renamed_strtab( | |
| old_strtab: &[u8], | |
| renames: &[RenameEntry], | |
| suffix: &str, | |
| ) -> (Vec<u8>, FxHashMap<String, u32>) { | |
| let mut new_strtab = old_strtab.to_vec(); | |
| let mut rename_map: FxHashMap<String, u32> = FxHashMap::default(); | |
| let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect(); | |
| sorted_names.sort(); | |
| sorted_names.dedup(); | |
| for name in &sorted_names { | |
| let new_offset = new_strtab.len() as u32; | |
| new_strtab.extend_from_slice(name.as_bytes()); | |
| new_strtab.extend_from_slice(suffix.as_bytes()); | |
| new_strtab.push(0); | |
| rename_map.insert(name.to_string(), new_offset); | |
| } | |
| (new_strtab, rename_map) | |
| } | |
| fn write_u32_at(buf: &mut [u8], offset: usize, value: u32, endian: Endianness) { | |
| let bytes = match endian { | |
| Endianness::Little => value.to_le_bytes(), | |
| Endianness::Big => value.to_be_bytes(), | |
| }; | |
| buf[offset..offset + 4].copy_from_slice(&bytes); | |
| } | |
| fn write_u64_at(buf: &mut [u8], offset: usize, value: u64, endian: Endianness) { | |
| let bytes = match endian { | |
| Endianness::Little => value.to_le_bytes(), | |
| Endianness::Big => value.to_be_bytes(), | |
| }; | |
| buf[offset..offset + 8].copy_from_slice(&bytes); | |
| } |