1use std::borrow::Cow;
8use std::mem;
9
10use object::read::coff::{CoffHeader, ImageSymbol as _};
11use object::read::elf::{SectionHeader as _, Sym as _};
12use object::read::macho::Nlist;
13use object::{Endianness, elf, macho, pe};
14use rustc_data_structures::fx::{FxHashMap, FxHashSet};
15use rustc_middle::middle::exported_symbols::SymbolExportKind;
16
17use super::symbol_export::undecorate_coff_symbol;
18
19struct Patch {
20 offset: usize,
21 value: u8,
22}
23
24struct RenameEntry {
25 name_field_offset: usize,
26 name: String,
27}
28
29pub(super) fn apply_edits<'a>(
30 data: &'a [u8],
31 exported: &FxHashSet<String>,
32 hide: bool,
33 rename: Option<&(FxHashSet<String>, &str)>,
34) -> Cow<'a, [u8]> {
35 let result = match object::File::parse(data).ok() {
36 Some(object::File::Elf64(_)) => elf_edit_impl::<elf::FileHeader64<Endianness>>(
37 data,
38 exported,
39 hide,
40 rename,
41 const { builtin # offset_of(elf::Sym64<Endianness>, st_other) }mem::offset_of!(elf::Sym64<Endianness>, st_other),
42 ),
43 Some(object::File::Elf32(_)) => elf_edit_impl::<elf::FileHeader32<Endianness>>(
44 data,
45 exported,
46 hide,
47 rename,
48 const { builtin # offset_of(elf::Sym32<Endianness>, st_other) }mem::offset_of!(elf::Sym32<Endianness>, st_other),
49 ),
50 Some(object::File::MachO64(_)) => macho_edit_impl::<macho::MachHeader64<Endianness>>(
51 data,
52 exported,
53 hide,
54 rename,
55 const { builtin # offset_of(macho::Nlist64<Endianness>, n_type) }mem::offset_of!(macho::Nlist64<Endianness>, n_type),
56 ),
57 Some(object::File::MachO32(_)) => macho_edit_impl::<macho::MachHeader32<Endianness>>(
58 data,
59 exported,
60 hide,
61 rename,
62 const { builtin # offset_of(macho::Nlist32<Endianness>, n_type) }mem::offset_of!(macho::Nlist32<Endianness>, n_type),
63 ),
64 Some(object::File::Coff(f)) => coff_edit_impl(data, f.coff_header(), hide, rename),
65 Some(object::File::CoffBig(f)) => coff_edit_impl(data, f.coff_header(), hide, rename),
66 _ => None,
67 };
68 match result {
69 Some(v) => Cow::Owned(v),
70 None => Cow::Borrowed(data),
71 }
72}
73
74pub(super) fn collect_internal_names(
75 data: &[u8],
76 exported: &FxHashSet<String>,
77 out: &mut FxHashSet<String>,
78) {
79 let Ok(file) = object::File::parse(data) else { return };
80 match file {
81 object::File::Elf64(_) => {
82 elf_collect_impl::<elf::FileHeader64<Endianness>>(data, exported, out)
83 }
84 object::File::Elf32(_) => {
85 elf_collect_impl::<elf::FileHeader32<Endianness>>(data, exported, out)
86 }
87 object::File::MachO64(_) => {
88 macho_collect_impl::<macho::MachHeader64<Endianness>>(data, exported, out)
89 }
90 object::File::MachO32(_) => {
91 macho_collect_impl::<macho::MachHeader32<Endianness>>(data, exported, out)
92 }
93 object::File::Coff(f) => coff_collect_impl(data, f.coff_header(), exported, out),
94 object::File::CoffBig(f) => coff_collect_impl(data, f.coff_header(), exported, out),
95 _ => {}
96 }
97}
98
99fn coff_symbol_kind(sym: &impl object::read::coff::ImageSymbol) -> SymbolExportKind {
102 if sym.derived_type() == pe::IMAGE_SYM_DTYPE_FUNCTION {
103 SymbolExportKind::Text
104 } else {
105 SymbolExportKind::Data
106 }
107}
108
109fn elf_collect_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
110 data: &[u8],
111 exported: &FxHashSet<String>,
112 out: &mut FxHashSet<String>,
113) where
114 u64: From<Elf::Word>,
115{
116 let Ok(header) = Elf::parse(data) else { return };
117 let Ok(endian) = header.endian() else { return };
118 let Ok(sections) = header.sections(endian, data) else { return };
119 let Ok(symtab) = sections.symbols(endian, data, elf::SHT_SYMTAB) else { return };
120 let strings = symtab.strings();
121
122 for sym in symtab.iter() {
123 let binding = sym.st_bind();
124 if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
125 continue;
126 }
127 if sym.is_undefined(endian) {
128 continue;
129 }
130 let Ok(name_bytes) = sym.name(endian, strings) else { continue };
131 let Ok(name) = str::from_utf8(name_bytes) else { continue };
132 if !exported.contains(name) {
133 out.insert(name.to_string());
134 }
135 }
136}
137
138fn macho_collect_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>(
139 data: &[u8],
140 exported: &FxHashSet<String>,
141 out: &mut FxHashSet<String>,
142) {
143 let Ok(header) = Mach::parse(data, 0) else { return };
144 let Ok(endian) = header.endian() else { return };
145 let Ok(mut commands) = header.load_commands(endian, data, 0) else { return };
146
147 let symtab_cmd = loop {
148 let Ok(Some(cmd)) = commands.next() else { return };
149 if let Ok(Some(st)) = cmd.symtab() {
150 break st;
151 }
152 };
153 let Ok(symtab) = symtab_cmd.symbols::<Mach, _>(endian, data) else { return };
154 let strings = symtab.strings();
155
156 for nlist in symtab.iter() {
157 if nlist.is_stab() {
158 continue;
159 }
160 if nlist.is_undefined() {
161 continue;
162 }
163 if nlist.n_type() & macho::N_EXT == 0 {
164 continue;
165 }
166 let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
167 let Ok(name) = str::from_utf8(name_bytes) else { continue };
168 let name = name.strip_prefix('_').unwrap_or(name);
169 if !exported.contains(name) {
170 out.insert(name.to_string());
171 }
172 }
173}
174
175fn elf_edit_impl<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
180 data: &[u8],
181 exported: &FxHashSet<String>,
182 hide: bool,
183 rename: Option<&(FxHashSet<String>, &str)>,
184 st_other_offset: usize,
185) -> Option<Vec<u8>>
186where
187 u64: From<Elf::Word>,
188{
189 let header = Elf::parse(data).ok()?;
190 let endian = header.endian().ok()?;
191 let sections = header.sections(endian, data).ok()?;
192 let symtab = sections.symbols(endian, data, elf::SHT_SYMTAB).ok()?;
193 let data_ptr = data.as_ptr() as usize;
194 let strings = symtab.strings();
195
196 let mut patches = Vec::new();
197 let mut renames = Vec::new();
198
199 for sym in symtab.iter() {
200 let binding = sym.st_bind();
201 if binding != elf::STB_GLOBAL && binding != elf::STB_WEAK {
202 continue;
203 }
204 let Ok(name_bytes) = sym.name(endian, strings) else { continue };
205 let Ok(name) = str::from_utf8(name_bytes) else { continue };
206
207 let sym_addr = sym as *const Elf::Sym as usize;
208
209 if hide && !sym.is_undefined(endian) && !exported.contains(name) {
210 let offset = sym_addr - data_ptr + st_other_offset;
211 let new_vis = (sym.st_other() & !0x03) | elf::STV_HIDDEN;
212 patches.push(Patch { offset, value: new_vis });
213 }
214 if let Some((rename_set, _)) = rename {
215 if rename_set.contains(name) {
216 renames.push(RenameEntry {
217 name_field_offset: sym_addr - data_ptr,
218 name: name.to_string(),
219 });
220 }
221 }
222 }
223
224 if patches.is_empty() && renames.is_empty() {
225 return None;
226 }
227
228 let mut result = data.to_vec();
229 for p in &patches {
230 result[p.offset] = p.value;
231 }
232
233 if !renames.is_empty() {
234 let suffix = rename.unwrap().1;
235 if let Some(renamed) =
236 elf_rebuild_strtab::<Elf>(&result, &renames, suffix, §ions, header, endian)
237 {
238 result = renamed;
239 }
240 }
241
242 Some(result)
243}
244
245fn elf_rebuild_strtab<Elf: object::read::elf::FileHeader<Endian = Endianness>>(
246 data: &[u8],
247 renames: &[RenameEntry],
248 suffix: &str,
249 sections: &object::read::elf::SectionTable<'_, Elf>,
250 header: &Elf,
251 endian: Endianness,
252) -> Option<Vec<u8>>
253where
254 u64: From<Elf::Word>,
255{
256 let mut strtab_si: Option<usize> = None;
257 for section in sections.iter() {
258 if section.sh_type(endian) == elf::SHT_SYMTAB {
259 strtab_si = Some(section.sh_link(endian) as usize);
260 break;
261 }
262 }
263 let strtab_si = strtab_si?;
264
265 let e_shoff = u64::from(header.e_shoff(endian)) as usize;
266 let e_shentsize = mem::size_of::<Elf::SectionHeader>();
267 let e_shnum = sections.len();
268
269 let strtab_section = sections.section(object::SectionIndex(strtab_si)).ok()?;
270 let old_strtab_offset = u64::from(strtab_section.sh_offset(endian)) as usize;
271 let old_strtab_size = u64::from(strtab_section.sh_size(endian)) as usize;
272 let old_strtab = data.get(old_strtab_offset..old_strtab_offset + old_strtab_size)?;
273
274 let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix);
275
276 let is_64 = mem::size_of::<Elf::Word>() == 8;
277 let new_strtab_file_off = data.len();
278 let new_strtab_size = new_strtab.len();
279 let new_e_shoff_raw = new_strtab_file_off + new_strtab_size;
280 let new_e_shoff = (new_e_shoff_raw + 7) & !7;
281 let padding = new_e_shoff - new_e_shoff_raw;
282 let section_headers_size = e_shentsize * e_shnum;
283
284 let result_size = new_e_shoff + section_headers_size;
285 let mut result = Vec::with_capacity(result_size);
286 result.extend_from_slice(data);
287 result.extend_from_slice(&new_strtab);
288 result.resize(result.len() + padding, 0);
289 let sh_data = data.get(e_shoff..e_shoff + section_headers_size)?;
290 result.extend_from_slice(sh_data);
291
292 if is_64 {
293 write_u64_at(
294 &mut result,
295 const { builtin # offset_of(elf::FileHeader64<Endianness>, e_shoff) }mem::offset_of!(elf::FileHeader64<Endianness>, e_shoff),
296 new_e_shoff as u64,
297 endian,
298 );
299 } else {
300 write_u32_at(
301 &mut result,
302 const { builtin # offset_of(elf::FileHeader32<Endianness>, e_shoff) }mem::offset_of!(elf::FileHeader32<Endianness>, e_shoff),
303 new_e_shoff as u32,
304 endian,
305 );
306 }
307
308 let new_strtab_shdr_offset = new_e_shoff + strtab_si * e_shentsize;
309
310 if is_64 {
311 let sh_offset_field = const { builtin # offset_of(elf::SectionHeader64<Endianness>, sh_offset) }mem::offset_of!(elf::SectionHeader64<Endianness>, sh_offset);
312 let sh_size_field = const { builtin # offset_of(elf::SectionHeader64<Endianness>, sh_size) }mem::offset_of!(elf::SectionHeader64<Endianness>, sh_size);
313 write_u64_at(
314 &mut result,
315 new_strtab_shdr_offset + sh_offset_field,
316 new_strtab_file_off as u64,
317 endian,
318 );
319 write_u64_at(
320 &mut result,
321 new_strtab_shdr_offset + sh_size_field,
322 new_strtab_size as u64,
323 endian,
324 );
325 } else {
326 let sh_offset_field = const { builtin # offset_of(elf::SectionHeader32<Endianness>, sh_offset) }mem::offset_of!(elf::SectionHeader32<Endianness>, sh_offset);
327 let sh_size_field = const { builtin # offset_of(elf::SectionHeader32<Endianness>, sh_size) }mem::offset_of!(elf::SectionHeader32<Endianness>, sh_size);
328 write_u32_at(
329 &mut result,
330 new_strtab_shdr_offset + sh_offset_field,
331 new_strtab_file_off as u32,
332 endian,
333 );
334 write_u32_at(
335 &mut result,
336 new_strtab_shdr_offset + sh_size_field,
337 new_strtab_size as u32,
338 endian,
339 );
340 }
341
342 for entry in renames {
343 let new_st_name = rename_map[&entry.name];
344 write_u32_at(&mut result, entry.name_field_offset, new_st_name, endian);
345 }
346
347 Some(result)
348}
349
350fn macho_edit_impl<Mach: object::read::macho::MachHeader<Endian = Endianness>>(
355 data: &[u8],
356 exported: &FxHashSet<String>,
357 hide: bool,
358 rename: Option<&(FxHashSet<String>, &str)>,
359 n_type_offset: usize,
360) -> Option<Vec<u8>> {
361 let header = Mach::parse(data, 0).ok()?;
362 let endian = header.endian().ok()?;
363 let mut commands = header.load_commands(endian, data, 0).ok()?;
364
365 let (symtab_cmd, symtab_cmd_offset) = loop {
366 let cmd = commands.next().ok()??;
367 if let Some(st) = cmd.symtab().ok().flatten() {
368 break (st, cmd.raw_data().as_ptr() as usize - data.as_ptr() as usize);
369 }
370 };
371
372 let symtab: object::read::macho::SymbolTable<'_, Mach, &_> =
373 symtab_cmd.symbols(endian, data).ok()?;
374 let data_ptr = data.as_ptr() as usize;
375 let strings = symtab.strings();
376
377 let mut patches = Vec::new();
378 let mut renames = Vec::new();
379
380 for nlist in symtab.iter() {
381 if nlist.is_stab() {
382 continue;
383 }
384 if nlist.n_type() & macho::N_EXT == 0 {
385 continue;
386 }
387 let Ok(name_bytes) = nlist.name(endian, strings) else { continue };
388 let Ok(raw_name) = str::from_utf8(name_bytes) else { continue };
389 let name = raw_name.strip_prefix('_').unwrap_or(raw_name);
390
391 let nlist_addr = nlist as *const Mach::Nlist as usize;
392
393 if hide && !nlist.is_undefined() && !exported.contains(name) {
394 let offset = nlist_addr - data_ptr + n_type_offset;
395 patches.push(Patch { offset, value: nlist.n_type() | macho::N_PEXT });
396 }
397 if let Some((rename_set, _)) = rename {
398 if rename_set.contains(name) {
399 renames.push(RenameEntry {
400 name_field_offset: nlist_addr - data_ptr,
401 name: raw_name.to_string(),
402 });
403 }
404 }
405 }
406
407 if patches.is_empty() && renames.is_empty() {
408 return None;
409 }
410
411 let mut result = data.to_vec();
412 for p in &patches {
413 result[p.offset] = p.value;
414 }
415
416 if !renames.is_empty() {
417 let suffix = rename.unwrap().1;
418 if let Some(renamed) =
419 macho_rebuild_strtab(&result, &renames, suffix, &symtab_cmd, symtab_cmd_offset, endian)
420 {
421 result = renamed;
422 }
423 }
424
425 Some(result)
426}
427
428fn macho_rebuild_strtab(
429 data: &[u8],
430 renames: &[RenameEntry],
431 suffix: &str,
432 symtab_cmd: &macho::SymtabCommand<Endianness>,
433 symtab_cmd_offset: usize,
434 endian: Endianness,
435) -> Option<Vec<u8>> {
436 let old_stroff = symtab_cmd.stroff.get(endian) as usize;
437 let old_strsize = symtab_cmd.strsize.get(endian) as usize;
438 let old_strtab = data.get(old_stroff..old_stroff + old_strsize)?;
439
440 let (new_strtab, rename_map) = build_renamed_strtab(old_strtab, renames, suffix);
441
442 let new_strtab_file_off = data.len();
443 let new_strtab_size = new_strtab.len();
444
445 let mut result = Vec::with_capacity(data.len() + new_strtab_size);
446 result.extend_from_slice(data);
447 result.extend_from_slice(&new_strtab);
448
449 let stroff_off = const { builtin # offset_of(macho::SymtabCommand<Endianness>, stroff) }mem::offset_of!(macho::SymtabCommand<Endianness>, stroff);
450 let strsize_off = const { builtin # offset_of(macho::SymtabCommand<Endianness>, strsize) }mem::offset_of!(macho::SymtabCommand<Endianness>, strsize);
451 write_u32_at(&mut result, symtab_cmd_offset + stroff_off, new_strtab_file_off as u32, endian);
452 write_u32_at(&mut result, symtab_cmd_offset + strsize_off, new_strtab_size as u32, endian);
453
454 for entry in renames {
455 let new_strx = rename_map[&entry.name];
456 write_u32_at(&mut result, entry.name_field_offset, new_strx, endian);
457 }
458
459 Some(result)
460}
461
462fn coff_collect_impl<'data, Coff: CoffHeader>(
467 data: &'data [u8],
468 header: &'data Coff,
469 exported: &FxHashSet<String>,
470 out: &mut FxHashSet<String>,
471) {
472 let Ok(symbols) = header.symbols(data) else { return };
473 let strings = symbols.strings();
474 let machine = header.machine();
475
476 for (_index, sym) in symbols.iter() {
477 let sclass = sym.storage_class();
478 if sclass != pe::IMAGE_SYM_CLASS_EXTERNAL && sclass != pe::IMAGE_SYM_CLASS_WEAK_EXTERNAL {
479 continue;
480 }
481 if sym.section_number() <= 0 {
482 continue;
483 }
484 let Ok(name_bytes) = sym.name(strings) else { continue };
485 let Ok(name) = str::from_utf8(name_bytes) else { continue };
486 let export_name = undecorate_coff_symbol(name, machine, coff_symbol_kind(sym));
487 if !exported.contains(export_name) {
488 out.insert(name.to_string());
489 }
490 }
491}
492
493fn coff_edit_impl<'data, Coff: CoffHeader>(
494 data: &'data [u8],
495 header: &'data Coff,
496 hide: bool,
497 rename: Option<&(FxHashSet<String>, &str)>,
498) -> Option<Vec<u8>> {
499 if !!hide { ::core::panicking::panic("assertion failed: !hide") };assert!(!hide);
502
503 let pointer_to_symbol_table = header.pointer_to_symbol_table() as usize;
504 let number_of_symbols = header.number_of_symbols() as usize;
505 let sym_size = mem::size_of::<Coff::ImageSymbolBytes>();
507
508 let symbol_bytes_offset = pointer_to_symbol_table;
509 let strtab_base = pointer_to_symbol_table + number_of_symbols * sym_size;
510 if strtab_base > data.len() {
511 return None;
512 }
513
514 if data.len() < strtab_base + 4 {
515 return None;
516 }
517 let old_len =
518 u32::from_le_bytes(data[strtab_base..strtab_base + 4].try_into().unwrap()) as usize;
519 if strtab_base + old_len > data.len() {
520 return None;
521 }
522
523 let Ok(symbols) = header.symbols(data) else { return None };
524 let strings = symbols.strings();
525
526 let mut renames = Vec::new();
527 for (index, sym) in symbols.iter() {
528 let sclass = sym.storage_class();
529 if sclass != pe::IMAGE_SYM_CLASS_EXTERNAL && sclass != pe::IMAGE_SYM_CLASS_WEAK_EXTERNAL {
530 continue;
531 }
532 let Ok(name_bytes) = sym.name(strings) else { continue };
533 let Ok(name) = str::from_utf8(name_bytes) else { continue };
534 if rename.is_some_and(|(rename_set, _)| rename_set.contains(name)) {
535 renames.push(RenameEntry {
536 name_field_offset: symbol_bytes_offset + index.0 * sym_size,
537 name: name.to_string(),
538 });
539 }
540 }
541 if renames.is_empty() {
542 return None;
543 }
544 let suffix = rename.unwrap().1;
545
546 let mut new_strtab = Vec::new();
547 let mut map: FxHashMap<String, u32> = FxHashMap::default();
548 let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect();
549 sorted_names.sort();
550 sorted_names.dedup();
551 for name in &sorted_names {
552 let rel_offset = (old_len + new_strtab.len()) as u32;
553 new_strtab.extend_from_slice(name.as_bytes());
554 new_strtab.extend_from_slice(suffix.as_bytes());
555 new_strtab.push(0);
556 map.insert(name.to_string(), rel_offset);
557 }
558
559 let mut result = data.to_vec();
560 let new_len = (old_len + new_strtab.len()) as u32;
561 result[strtab_base..strtab_base + 4].copy_from_slice(&new_len.to_le_bytes());
562 result.extend_from_slice(&new_strtab);
563
564 for r in &renames {
567 let rel = map[&r.name];
568 let field = &mut result[r.name_field_offset..r.name_field_offset + 8];
569 field[..4].fill(0);
570 field[4..].copy_from_slice(&rel.to_le_bytes());
571 }
572
573 Some(result)
574}
575
576fn build_renamed_strtab(
581 old_strtab: &[u8],
582 renames: &[RenameEntry],
583 suffix: &str,
584) -> (Vec<u8>, FxHashMap<String, u32>) {
585 let mut new_strtab = old_strtab.to_vec();
586 let mut rename_map: FxHashMap<String, u32> = FxHashMap::default();
587
588 let mut sorted_names: Vec<&str> = renames.iter().map(|r| r.name.as_str()).collect();
589 sorted_names.sort();
590 sorted_names.dedup();
591
592 for name in &sorted_names {
593 let new_offset = new_strtab.len() as u32;
594 new_strtab.extend_from_slice(name.as_bytes());
595 new_strtab.extend_from_slice(suffix.as_bytes());
596 new_strtab.push(0);
597 rename_map.insert(name.to_string(), new_offset);
598 }
599
600 (new_strtab, rename_map)
601}
602
603fn write_u32_at(buf: &mut [u8], offset: usize, value: u32, endian: Endianness) {
604 let bytes = match endian {
605 Endianness::Little => value.to_le_bytes(),
606 Endianness::Big => value.to_be_bytes(),
607 };
608 buf[offset..offset + 4].copy_from_slice(&bytes);
609}
610
611fn write_u64_at(buf: &mut [u8], offset: usize, value: u64, endian: Endianness) {
612 let bytes = match endian {
613 Endianness::Little => value.to_le_bytes(),
614 Endianness::Big => value.to_be_bytes(),
615 };
616 buf[offset..offset + 8].copy_from_slice(&bytes);
617}