Skip to main content

rustc_codegen_ssa/back/
symbol_edit.rs

1// We use the `object` crate for the read-only pass over ELF/Mach-O object files
2// because its `Sym`/`Nlist` traits provide clean access to symbol properties without
3// manual byte parsing. However, `object` does not expose mutable views into the data,
4// so we cannot use it to modify symbol fields in place. Instead, the read-only pass
5// collects byte-level patches (offset + new value), and the write pass
6// (`apply_patches`) applies them to a copy of the byte buffer without any ELF/Mach-O
7// parsing — similar to how linker relocations work.
8
9use std::mem;
10
11use object::read::elf::Sym as _;
12use object::read::macho::Nlist;
13use object::{Endianness, elf, macho};
14use rustc_data_structures::fx::FxHashSet;
15
16/// A byte-level patch collected in the read-only pass and applied in the write pass.
17struct Patch {
18    offset: usize,
19    value: u8,
20}
21
22/// Apply a list of byte patches to `data`, returning the (possibly modified) bytes.
23fn apply_patches(data: &[u8], patches: &[Patch]) -> Vec<u8> {
24    let mut buf = data.to_vec();
25    for p in patches {
26        buf[p.offset] = p.value;
27    }
28    buf
29}
30
31// ---------------------------------------------------------------------------
32// ELF hide – read-only pass uses `object` crate, write pass uses `Patch` list
33// ---------------------------------------------------------------------------
34
35fn elf_hide_patches_impl<'data, Elf: object::read::elf::FileHeader<Endian = Endianness>>(
36    data: &'data [u8],
37    st_other_offset: usize,
38    exported: &FxHashSet<String>,
39) -> Option<Vec<Patch>>
40where
41    u64: From<Elf::Word>,
42{
43    let header = Elf::parse(data).ok()?;
44    let endian = header.endian().ok()?;
45    let sections = header.sections(endian, data).ok()?;
46    let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
47
48    let data_ptr = data.as_ptr() as usize;
49    let strings = symtab.strings();
50    let mut patches = Vec::new();
51
52    for sym in symtab.iter() {
53        let binding = sym.st_bind();
54        if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
55            continue;
56        }
57        if sym.is_undefined(endian) {
58            continue;
59        }
60        let Ok(name_bytes) = sym.name(endian, strings) else { continue };
61        let Ok(name) = str::from_utf8(name_bytes) else { continue };
62        if !exported.contains(name) {
63            let sym_addr = sym as *const Elf::Sym as usize;
64            let offset = sym_addr - data_ptr + st_other_offset;
65            let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
66            patches.push(Patch { offset, value: new_vis });
67        }
68    }
69
70    Some(patches)
71}
72
73// ---------------------------------------------------------------------------
74// Mach-O hide – same architecture: read-only pass via `object`, write via patches
75// ---------------------------------------------------------------------------
76
77fn macho_hide_patches_impl<'data, Mach: object::read::macho::MachHeader<Endian = Endianness>>(
78    data: &'data [u8],
79    n_type_offset: usize,
80    exported: &FxHashSet<String>,
81) -> Option<Vec<Patch>> {
82    let header = Mach::parse(data, 0).ok()?;
83    let endian = header.endian().ok()?;
84    let mut commands = header.load_commands(endian, data, 0).ok()?;
85
86    let symtab_cmd = loop {
87        let cmd = commands.next().ok()??;
88        if let Some(st) = cmd.symtab().ok().flatten() {
89            break st;
90        }
91    };
92    let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
93        symtab_cmd.symbols(endian, data).ok()?;
94
95    let data_ptr = data.as_ptr() as usize;
96    let strings = symtab.strings();
97    let mut patches = Vec::new();
98
99    for nlist in symtab.iter() {
100        if nlist.is_stab() {
101            continue;
102        }
103        if nlist.is_undefined() {
104            continue;
105        }
106        if nlist.n_type() & macho::N_EXT == 0 {
107            continue;
108        }
109        let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
110        let Ok(name) = str::from_utf8(name_bytes) else { continue };
111        let name = name.strip_prefix('_').unwrap_or(name);
112        if !exported.contains(name) {
113            let nlist_addr = nlist as *const Mach::Nlist as usize;
114            let offset = nlist_addr - data_ptr + n_type_offset;
115            patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
116        }
117    }
118
119    Some(patches)
120}
121
122// ---------------------------------------------------------------------------
123// Unified dispatch: top-level detection via `object::File::parse`
124// ---------------------------------------------------------------------------
125
126fn hide_patches(data: &[u8], exported: &FxHashSet<String>) -> Option<Vec<Patch>> {
127    let file = object::File::parse(data).ok()?;
128    match file {
129        object::File::Elf64(_) => elf_hide_patches_impl::<elf::FileHeader64<Endianness>>(
130            data,
131            const { builtin # offset_of(elf::Sym64<Endianness>, st_other) }mem::offset_of!(elf::Sym64<Endianness>, st_other),
132            exported,
133        ),
134        object::File::Elf32(_) => elf_hide_patches_impl::<elf::FileHeader32<Endianness>>(
135            data,
136            const { builtin # offset_of(elf::Sym32<Endianness>, st_other) }mem::offset_of!(elf::Sym32<Endianness>, st_other),
137            exported,
138        ),
139        object::File::MachO64(_) => macho_hide_patches_impl::<macho::MachHeader64<Endianness>>(
140            data,
141            const { builtin # offset_of(macho::Nlist64<Endianness>, n_type) }mem::offset_of!(macho::Nlist64<Endianness>, n_type),
142            exported,
143        ),
144        object::File::MachO32(_) => macho_hide_patches_impl::<macho::MachHeader32<Endianness>>(
145            data,
146            const { builtin # offset_of(macho::Nlist32<Endianness>, n_type) }mem::offset_of!(macho::Nlist32<Endianness>, n_type),
147            exported,
148        ),
149        _ => None,
150    }
151}
152
153pub(super) fn apply_hide(data: &[u8], exported: &FxHashSet<String>) -> Vec<u8> {
154    let patches = hide_patches(data, exported).unwrap_or_default();
155    apply_patches(data, &patches)
156}