Skip to main content

rustc_codegen_ssa/back/
symbol_edit.rs

1//! Binary-level symbol editing for staticlib post-processing.
2//!
3//! - **Hide**: sets STV_HIDDEN (ELF) or N_PEXT (Mach-O) on non-exported symbols.
4//! - **Rename**: appends a vendor-specific suffix to non-exported symbol names by
5//!   rebuilding the string table.
6
7use std::borrow::Cow;
8use std::mem;
9
10use object::read::elf::{SectionHeader as _, Sym as _};
11use object::read::macho::Nlist;
12use object::{Endianness, elf, macho};
13use rustc_data_structures::fx::{FxHashMap, FxHashSet};
14
15struct Patch {
16    offset: usize,
17    value: u8,
18}
19
20struct RenameEntry {
21    name_field_offset: usize,
22    name: String,
23}
24
25pub(super) fn apply_edits<'a>(
26    data: &'a [u8],
27    exported: &FxHashSet<String>,
28    hide: bool,
29    rename: Option<&(FxHashSet<String>, &str)>,
30) -> Cow<'a, [u8]> {
31    let result = match object::File::parse(data).ok() {
32        Some(object::File::Elf64(_)) => elf_edit_impl::<elf::FileHeader64<Endianness>>(
33            data,
34            exported,
35            hide,
36            rename,
37            const { builtin # offset_of(elf::Sym64<Endianness>, st_other) }mem::offset_of!(elf::Sym64<Endianness>, st_other),
38        ),
39        Some(object::File::Elf32(_)) => elf_edit_impl::<elf::FileHeader32<Endianness>>(
40            data,
41            exported,
42            hide,
43            rename,
44            const { builtin # offset_of(elf::Sym32<Endianness>, st_other) }mem::offset_of!(elf::Sym32<Endianness>, st_other),
45        ),
46        Some(object::File::MachO64(_)) => macho_edit_impl::<macho::MachHeader64<Endianness>>(
47            data,
48            exported,
49            hide,
50            rename,
51            const { builtin # offset_of(macho::Nlist64<Endianness>, n_type) }mem::offset_of!(macho::Nlist64<Endianness>, n_type),
52        ),
53        Some(object::File::MachO32(_)) => macho_edit_impl::<macho::MachHeader32<Endianness>>(
54            data,
55            exported,
56            hide,
57            rename,
58            const { builtin # offset_of(macho::Nlist32<Endianness>, n_type) }mem::offset_of!(macho::Nlist32<Endianness>, n_type),
59        ),
60        _ => None,
61    };
62    match result {
63        Some(v) => Cow::Owned(v),
64        None => Cow::Borrowed(data),
65    }
66}
67
68pub(super) fn collect_internal_names(
69    data: &[u8],
70    exported: &FxHashSet<String>,
71    out: &mut FxHashSet<String>,
72) {
73    let Ok(file) = object::File::parse(data) else { return };
74    match file {
75        object::File::Elf64(_) => {
76            elf_collect_impl::<elf::FileHeader64<Endianness>>(data, exported, out)
77        }
78        object::File::Elf32(_) => {
79            elf_collect_impl::<elf::FileHeader32<Endianness>>(data, exported, out)
80        }
81        object::File::MachO64(_) => {
82            macho_collect_impl::<macho::MachHeader64<Endianness>>(data, exported, out)
83        }
84        object::File::MachO32(_) => {
85            macho_collect_impl::<macho::MachHeader32<Endianness>>(data, exported, out)
86        }
87        _ => {}
88    }
89}
90
91fn elf_collect_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
92    data: &[u8],
93    exported: &FxHashSet<String>,
94    out: &mut FxHashSet<String>,
95) where
96    u64: From<Elf::Word>,
97{
98    let Ok(header) = Elf::parse(data) else { return };
99    let Ok(endian) = header.endian() else { return };
100    let Ok(sections) = header.sections(endian, data) else { return };
101    let Ok(symtab) = sections.symbols(endian, data, elf::SHT_SYMTAB) else { return };
102    let strings = symtab.strings();
103
104    for sym in symtab.iter() {
105        let binding = sym.st_bind();
106        if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
107            continue;
108        }
109        if sym.is_undefined(endian) {
110            continue;
111        }
112        let Ok(name_bytes) = sym.name(endian, strings) else { continue };
113        let Ok(name) = str::from_utf8(name_bytes) else { continue };
114        if !exported.contains(name) {
115            out.insert(name.to_string());
116        }
117    }
118}
119
120fn macho_collect_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>(
121    data: &[u8],
122    exported: &FxHashSet<String>,
123    out: &mut FxHashSet<String>,
124) {
125    let Ok(header) = Mach::parse(data, 0) else { return };
126    let Ok(endian) = header.endian() else { return };
127    let Ok(mut commands) = header.load_commands(endian, data, 0) else { return };
128
129    let symtab_cmd = loop {
130        let Ok(Some(cmd)) = commands.next() else { return };
131        if let Ok(Some(st)) = cmd.symtab() {
132            break st;
133        }
134    };
135    let Ok(symtab) = symtab_cmd.symbols::<Mach, _>(endian, data) else { return };
136    let strings = symtab.strings();
137
138    for nlist in symtab.iter() {
139        if nlist.is_stab() {
140            continue;
141        }
142        if nlist.is_undefined() {
143            continue;
144        }
145        if nlist.n_type() & macho::N_EXT == 0 {
146            continue;
147        }
148        let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
149        let Ok(name) = str::from_utf8(name_bytes) else { continue };
150        let name = name.strip_prefix('_').unwrap_or(name);
151        if !exported.contains(name) {
152            out.insert(name.to_string());
153        }
154    }
155}
156
157// ---------------------------------------------------------------------------
158// ELF: single-pass collection + apply
159// ---------------------------------------------------------------------------
160
161fn elf_edit_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
162    data: &[u8],
163    exported: &FxHashSet<String>,
164    hide: bool,
165    rename: Option<&(FxHashSet<String>, &str)>,
166    st_other_offset: usize,
167) -> Option<Vec<u8>>
168where
169    u64: From<Elf::Word>,
170{
171    let header = Elf::parse(data).ok()?;
172    let endian = header.endian().ok()?;
173    let sections = header.sections(endian, data).ok()?;
174    let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
175    let data_ptr = data.as_ptr() as usize;
176    let strings = symtab.strings();
177
178    let mut patches = Vec::new();
179    let mut renames = Vec::new();
180
181    for sym in symtab.iter() {
182        let binding = sym.st_bind();
183        if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
184            continue;
185        }
186        let Ok(name_bytes) = sym.name(endian, strings) else { continue };
187        let Ok(name) = str::from_utf8(name_bytes) else { continue };
188
189        let sym_addr = sym as *const Elf::Sym as usize;
190
191        if hide && !sym.is_undefined(endian) && !exported.contains(name) {
192            let offset = sym_addr - data_ptr + st_other_offset;
193            let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
194            patches.push(Patch { offset, value: new_vis });
195        }
196        if let Some((rename_set, _)) = rename {
197            if rename_set.contains(name) {
198                renames.push(RenameEntry {
199                    name_field_offset: sym_addr - data_ptr,
200                    name: name.to_string(),
201                });
202            }
203        }
204    }
205
206    if patches.is_empty() && renames.is_empty() {
207        return None;
208    }
209
210    let mut result = data.to_vec();
211    for p in &patches {
212        result[p.offset] = p.value;
213    }
214
215    if !renames.is_empty() {
216        let suffix = rename.unwrap().1;
217        if let Some(renamed) =
218            elf_rebuild_strtab::<Elf>(&result, &renames, suffix, &sections, header, endian)
219        {
220            result = renamed;
221        }
222    }
223
224    Some(result)
225}
226
227fn elf_rebuild_strtab<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
228    data: &[u8],
229    renames: &[RenameEntry],
230    suffix: &str,
231    sections: &object::read::elf::SectionTable<'_, Elf>,
232    header: &Elf,
233    endian: Endianness,
234) -> Option<Vec<u8>>
235where
236    u64: From<Elf::Word>,
237{
238    let mut strtab_si: Option<usize> = None;
239    for section in sections.iter() {
240        if section.sh_type(endian) == elf::SHT_SYMTAB {
241            strtab_si = Some(section.sh_link(endian) as usize);
242            break;
243        }
244    }
245    let strtab_si = strtab_si?;
246
247    let e_shoff = u64::from(header.e_shoff(endian)) as usize;
248    let e_shentsize = mem::size_of::<Elf::SectionHeader>();
249    let e_shnum = sections.len();
250
251    let strtab_section = sections.section(object::SectionIndex(strtab_si)).ok()?;
252    let old_strtab_offset = u64::from(strtab_section.sh_offset(endian)) as usize;
253    let old_strtab_size = u64::from(strtab_section.sh_size(endian)) as usize;
254    let old_strtab = data.get(old_strtab_offset..old_strtab_offset + old_strtab_size)?;
255
256    let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix);
257
258    let is_64 = mem::size_of::<Elf::Word>() == 8;
259    let new_strtab_file_off = data.len();
260    let new_strtab_size = new_strtab.len();
261    let new_e_shoff_raw = new_strtab_file_off + new_strtab_size;
262    let new_e_shoff = (new_e_shoff_raw + 7) & !7;
263    let padding = new_e_shoff - new_e_shoff_raw;
264    let section_headers_size = e_shentsize * e_shnum;
265
266    let result_size = new_e_shoff + section_headers_size;
267    let mut result = Vec::with_capacity(result_size);
268    result.extend_from_slice(data);
269    result.extend_from_slice(&new_strtab);
270    result.resize(result.len() + padding, 0);
271    let sh_data = data.get(e_shoff..e_shoff + section_headers_size)?;
272    result.extend_from_slice(sh_data);
273
274    if is_64 {
275        write_u64_at(
276            &mut result,
277            const { builtin # offset_of(elf::FileHeader64<Endianness>, e_shoff) }mem::offset_of!(elf::FileHeader64<Endianness>, e_shoff),
278            new_e_shoff as u64,
279            endian,
280        );
281    } else {
282        write_u32_at(
283            &mut result,
284            const { builtin # offset_of(elf::FileHeader32<Endianness>, e_shoff) }mem::offset_of!(elf::FileHeader32<Endianness>, e_shoff),
285            new_e_shoff as u32,
286            endian,
287        );
288    }
289
290    let new_strtab_shdr_offset = new_e_shoff + strtab_si * e_shentsize;
291
292    if is_64 {
293        let sh_offset_field = const { builtin # offset_of(elf::SectionHeader64<Endianness>, sh_offset) }mem::offset_of!(elf::SectionHeader64<Endianness>, sh_offset);
294        let sh_size_field = const { builtin # offset_of(elf::SectionHeader64<Endianness>, sh_size) }mem::offset_of!(elf::SectionHeader64<Endianness>, sh_size);
295        write_u64_at(
296            &mut result,
297            new_strtab_shdr_offset + sh_offset_field,
298            new_strtab_file_off as u64,
299            endian,
300        );
301        write_u64_at(
302            &mut result,
303            new_strtab_shdr_offset + sh_size_field,
304            new_strtab_size as u64,
305            endian,
306        );
307    } else {
308        let sh_offset_field = const { builtin # offset_of(elf::SectionHeader32<Endianness>, sh_offset) }mem::offset_of!(elf::SectionHeader32<Endianness>, sh_offset);
309        let sh_size_field = const { builtin # offset_of(elf::SectionHeader32<Endianness>, sh_size) }mem::offset_of!(elf::SectionHeader32<Endianness>, sh_size);
310        write_u32_at(
311            &mut result,
312            new_strtab_shdr_offset + sh_offset_field,
313            new_strtab_file_off as u32,
314            endian,
315        );
316        write_u32_at(
317            &mut result,
318            new_strtab_shdr_offset + sh_size_field,
319            new_strtab_size as u32,
320            endian,
321        );
322    }
323
324    for entry in renames {
325        let new_st_name = rename_map[&entry.name];
326        write_u32_at(&mut result, entry.name_field_offset, new_st_name, endian);
327    }
328
329    Some(result)
330}
331
332// ---------------------------------------------------------------------------
333// Mach-O: single-pass collection + apply
334// ---------------------------------------------------------------------------
335
336fn macho_edit_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>(
337    data: &[u8],
338    exported: &FxHashSet<String>,
339    hide: bool,
340    rename: Option<&(FxHashSet<String>, &str)>,
341    n_type_offset: usize,
342) -> Option<Vec<u8>> {
343    let header = Mach::parse(data, 0).ok()?;
344    let endian = header.endian().ok()?;
345    let mut commands = header.load_commands(endian, data, 0).ok()?;
346
347    let (symtab_cmd, symtab_cmd_offset) = loop {
348        let cmd = commands.next().ok()??;
349        if let Some(st) = cmd.symtab().ok().flatten() {
350            break (st, cmd.raw_data().as_ptr() as usize - data.as_ptr() as usize);
351        }
352    };
353
354    let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
355        symtab_cmd.symbols(endian, data).ok()?;
356    let data_ptr = data.as_ptr() as usize;
357    let strings = symtab.strings();
358
359    let mut patches = Vec::new();
360    let mut renames = Vec::new();
361
362    for nlist in symtab.iter() {
363        if nlist.is_stab() {
364            continue;
365        }
366        if nlist.n_type() & macho::N_EXT == 0 {
367            continue;
368        }
369        let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
370        let Ok(raw_name) = str::from_utf8(name_bytes) else { continue };
371        let name = raw_name.strip_prefix('_').unwrap_or(raw_name);
372
373        let nlist_addr = nlist as *const Mach::Nlist as usize;
374
375        if hide && !nlist.is_undefined() && !exported.contains(name) {
376            let offset = nlist_addr - data_ptr + n_type_offset;
377            patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
378        }
379        if let Some((rename_set, _)) = rename {
380            if rename_set.contains(name) {
381                renames.push(RenameEntry {
382                    name_field_offset: nlist_addr - data_ptr,
383                    name: raw_name.to_string(),
384                });
385            }
386        }
387    }
388
389    if patches.is_empty() && renames.is_empty() {
390        return None;
391    }
392
393    let mut result = data.to_vec();
394    for p in &patches {
395        result[p.offset] = p.value;
396    }
397
398    if !renames.is_empty() {
399        let suffix = rename.unwrap().1;
400        if let Some(renamed) =
401            macho_rebuild_strtab(&result, &renames, suffix, &symtab_cmd, symtab_cmd_offset, endian)
402        {
403            result = renamed;
404        }
405    }
406
407    Some(result)
408}
409
410fn macho_rebuild_strtab(
411    data: &[u8],
412    renames: &[RenameEntry],
413    suffix: &str,
414    symtab_cmd: &macho::SymtabCommand<Endianness>,
415    symtab_cmd_offset: usize,
416    endian: Endianness,
417) -> Option<Vec<u8>> {
418    let old_stroff = symtab_cmd.stroff.get(endian) as usize;
419    let old_strsize = symtab_cmd.strsize.get(endian) as usize;
420    let old_strtab = data.get(old_stroff..old_stroff + old_strsize)?;
421
422    let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix);
423
424    let new_strtab_file_off = data.len();
425    let new_strtab_size = new_strtab.len();
426
427    let mut result = Vec::with_capacity(data.len() + new_strtab_size);
428    result.extend_from_slice(data);
429    result.extend_from_slice(&new_strtab);
430
431    let stroff_off = const { builtin # offset_of(macho::SymtabCommand<Endianness>, stroff) }mem::offset_of!(macho::SymtabCommand<Endianness>, stroff);
432    let strsize_off = const { builtin # offset_of(macho::SymtabCommand<Endianness>, strsize) }mem::offset_of!(macho::SymtabCommand<Endianness>, strsize);
433    write_u32_at(&mut result, symtab_cmd_offset + stroff_off, new_strtab_file_off as u32, endian);
434    write_u32_at(&mut result, symtab_cmd_offset + strsize_off, new_strtab_size as u32, endian);
435
436    for entry in renames {
437        let new_strx = rename_map[&entry.name];
438        write_u32_at(&mut result, entry.name_field_offset, new_strx, endian);
439    }
440
441    Some(result)
442}
443
444// ---------------------------------------------------------------------------
445// Shared helpers
446// ---------------------------------------------------------------------------
447
448fn build_renamed_strtab(
449    old_strtab: &[u8],
450    renames: &[RenameEntry],
451    suffix: &str,
452) -> (Vec<u8>, FxHashMap<String, u32>) {
453    let mut new_strtab = old_strtab.to_vec();
454    let mut rename_map: FxHashMap<String, u32> = FxHashMap::default();
455
456    let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect();
457    sorted_names.sort();
458    sorted_names.dedup();
459
460    for name in &sorted_names {
461        let new_offset = new_strtab.len() as u32;
462        new_strtab.extend_from_slice(name.as_bytes());
463        new_strtab.extend_from_slice(suffix.as_bytes());
464        new_strtab.push(0);
465        rename_map.insert(name.to_string(), new_offset);
466    }
467
468    (new_strtab, rename_map)
469}
470
471fn write_u32_at(buf: &mut [u8], offset: usize, value: u32, endian: Endianness) {
472    let bytes = match endian {
473        Endianness::Little => value.to_le_bytes(),
474        Endianness::Big => value.to_be_bytes(),
475    };
476    buf[offset..offset + 4].copy_from_slice(&bytes);
477}
478
479fn write_u64_at(buf: &mut [u8], offset: usize, value: u64, endian: Endianness) {
480    let bytes = match endian {
481        Endianness::Little => value.to_le_bytes(),
482        Endianness::Big => value.to_be_bytes(),
483    };
484    buf[offset..offset + 8].copy_from_slice(&bytes);
485}