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