1use 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
157fn 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, §ions, 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
332fn 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
444fn 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}