Rust · 27211 bytes Raw Blame History
1 use std::collections::{HashMap, HashSet, VecDeque};
2 use std::fmt::Write as _;
3
4 use crate::atom::{Atom, AtomSection, AtomTable};
5 use crate::icf::FoldedSymbol;
6 use crate::input::ObjectFile;
7 use crate::layout::LayoutInput;
8 use crate::reloc::{parse_raw_relocs, parse_relocs, Referent};
9 use crate::resolve::{AtomId, InputId, Symbol, SymbolId, SymbolTable};
10 use crate::{LinkOptions, OutputKind};
11
12 #[derive(Debug, Clone)]
13 enum RootReason {
14 Entry(String),
15 NoDeadStrip,
16 ExportedDylib,
17 }
18
19 impl RootReason {
20 fn describe(&self, symbol: &str) -> String {
21 match self {
22 RootReason::Entry(entry) => format!("{symbol} is in -e {entry} (GC root)"),
23 RootReason::NoDeadStrip => format!("{symbol} is marked N_NO_DEAD_STRIP (GC root)"),
24 RootReason::ExportedDylib => format!("{symbol} is exported from the dylib (GC root)"),
25 }
26 }
27 }
28
29 #[derive(Debug, Clone)]
30 enum LiveCause {
31 Root(RootReason),
32 ReferencedBy(AtomId),
33 ParentOf(AtomId),
34 }
35
36 #[derive(Debug, Clone, PartialEq, Eq)]
37 pub struct DeadStrippedSymbol {
38 pub name: String,
39 pub file_index: usize,
40 }
41
42 #[derive(Debug, Clone)]
43 pub struct DeadStripAnalysis {
44 live_atoms: HashSet<AtomId>,
45 causes: HashMap<AtomId, LiveCause>,
46 resolved_by_name: HashMap<String, SymbolId>,
47 atom_symbols: HashMap<AtomId, Vec<SymbolId>>,
48 }
49
50 impl DeadStripAnalysis {
51 pub fn build(
52 opts: &LinkOptions,
53 layout_inputs: &[LayoutInput<'_>],
54 atom_table: &AtomTable,
55 sym_table: &SymbolTable,
56 entry_symbol: Option<SymbolId>,
57 ) -> Self {
58 let resolved_by_name = resolved_symbol_map(sym_table);
59 let atom_symbols = atom_symbol_sets(atom_table);
60 let roots = root_atoms(opts, atom_table, sym_table, entry_symbol);
61 let forward_edges =
62 build_forward_edges(layout_inputs, atom_table, sym_table, &resolved_by_name);
63 let parent_edges = parent_edges(atom_table);
64
65 let mut live_atoms = HashSet::new();
66 let mut causes = HashMap::new();
67 let mut worklist = VecDeque::new();
68 for (atom_id, reason) in roots {
69 if live_atoms.insert(atom_id) {
70 causes.insert(atom_id, LiveCause::Root(reason));
71 worklist.push_back(atom_id);
72 }
73 }
74
75 while let Some(atom_id) = worklist.pop_front() {
76 if let Some(children) = parent_edges.get(&atom_id) {
77 for &child in children {
78 if live_atoms.insert(child) {
79 causes.insert(child, LiveCause::ParentOf(atom_id));
80 worklist.push_back(child);
81 }
82 }
83 }
84 if let Some(targets) = forward_edges.get(&atom_id) {
85 for &target in targets {
86 if live_atoms.insert(target) {
87 causes.insert(target, LiveCause::ReferencedBy(atom_id));
88 worklist.push_back(target);
89 }
90 }
91 }
92 }
93
94 Self {
95 live_atoms,
96 causes,
97 resolved_by_name,
98 atom_symbols,
99 }
100 }
101
102 pub fn live_atoms(&self) -> &HashSet<AtomId> {
103 &self.live_atoms
104 }
105
106 pub fn dead_stripped_symbols(
107 &self,
108 atom_table: &AtomTable,
109 sym_table: &SymbolTable,
110 layout_inputs: &[LayoutInput<'_>],
111 ) -> Vec<DeadStrippedSymbol> {
112 let file_index_by_input: HashMap<InputId, usize> = layout_inputs
113 .iter()
114 .enumerate()
115 .map(|(idx, input)| (input.id, idx + 1))
116 .collect();
117 let mut out = Vec::new();
118 for (atom_id, _atom) in atom_table.iter() {
119 if self.live_atoms.contains(&atom_id) {
120 continue;
121 }
122 let Some(symbols) = self.atom_symbols.get(&atom_id) else {
123 continue;
124 };
125 for &symbol_id in symbols {
126 let Symbol::Defined { origin, .. } = sym_table.get(symbol_id) else {
127 continue;
128 };
129 out.push(DeadStrippedSymbol {
130 name: self.symbol_name(sym_table, symbol_id),
131 file_index: file_index_by_input.get(origin).copied().unwrap_or(0),
132 });
133 }
134 }
135 out.sort_by(|lhs, rhs| {
136 lhs.name
137 .cmp(&rhs.name)
138 .then(lhs.file_index.cmp(&rhs.file_index))
139 });
140 out.dedup_by(|lhs, rhs| lhs.name == rhs.name && lhs.file_index == rhs.file_index);
141 out
142 }
143
144 fn symbol_name(&self, sym_table: &SymbolTable, symbol_id: SymbolId) -> String {
145 sym_table
146 .interner
147 .resolve(sym_table.get(symbol_id).name())
148 .to_string()
149 }
150
151 fn atom_name(&self, sym_table: &SymbolTable, atom_id: AtomId) -> String {
152 self.atom_symbols
153 .get(&atom_id)
154 .and_then(|symbols| symbols.first().copied())
155 .map(|symbol_id| self.symbol_name(sym_table, symbol_id))
156 .unwrap_or_else(|| format!("<atom {:?}>", atom_id))
157 }
158
159 fn is_live_symbol(&self, sym_table: &SymbolTable, symbol_id: SymbolId) -> bool {
160 match sym_table.get(symbol_id) {
161 Symbol::Defined { atom, .. } if atom.0 != 0 => self.live_atoms.contains(atom),
162 _ => false,
163 }
164 }
165
166 fn format_symbol_explanation(
167 &self,
168 sym_table: &SymbolTable,
169 requested_symbol: SymbolId,
170 ) -> String {
171 let requested_name = self.symbol_name(sym_table, requested_symbol);
172 if !self.is_live_symbol(sym_table, requested_symbol) {
173 return format!("{requested_name} is not live (dead-stripped)\n");
174 }
175
176 let Symbol::Defined { atom, .. } = sym_table.get(requested_symbol) else {
177 return format!("{requested_name} is not backed by a dead-strip eligible input atom\n");
178 };
179 let mut out = String::new();
180 writeln!(&mut out, "{requested_name} is live because:").unwrap();
181
182 let mut cursor = *atom;
183 let mut first = true;
184 loop {
185 let Some(cause) = self.causes.get(&cursor).cloned() else {
186 writeln!(
187 &mut out,
188 " no reachability chain from a known GC root was found"
189 )
190 .unwrap();
191 break;
192 };
193 match cause {
194 LiveCause::Root(reason) => {
195 let name = if first {
196 requested_name.as_str()
197 } else {
198 &self.atom_name(sym_table, cursor)
199 };
200 writeln!(&mut out, " {}", reason.describe(name)).unwrap();
201 break;
202 }
203 LiveCause::ReferencedBy(parent) => {
204 let child_name = if first {
205 requested_name.as_str()
206 } else {
207 &self.atom_name(sym_table, cursor)
208 };
209 writeln!(
210 &mut out,
211 " {child_name} is reachable from {}",
212 self.atom_name(sym_table, parent)
213 )
214 .unwrap();
215 cursor = parent;
216 }
217 LiveCause::ParentOf(parent) => {
218 let child_name = if first {
219 requested_name.as_str()
220 } else {
221 &self.atom_name(sym_table, cursor)
222 };
223 writeln!(
224 &mut out,
225 " {child_name} is reachable via unwind parent from {}",
226 self.atom_name(sym_table, parent)
227 )
228 .unwrap();
229 cursor = parent;
230 }
231 }
232 first = false;
233 }
234
235 out
236 }
237 }
238
239 pub fn format_explanations(
240 opts: &LinkOptions,
241 layout_inputs: &[LayoutInput<'_>],
242 atom_table: &AtomTable,
243 sym_table: &SymbolTable,
244 entry_symbol: Option<SymbolId>,
245 dead_strip: Option<&DeadStripAnalysis>,
246 folded_symbols: &[FoldedSymbol],
247 ) -> Result<Option<String>, String> {
248 if opts.why_live.is_empty() {
249 return Ok(None);
250 }
251
252 let folded_by_name: HashMap<&str, &str> = folded_symbols
253 .iter()
254 .map(|symbol| (symbol.name.as_str(), symbol.winner.as_str()))
255 .collect();
256
257 if let Some(dead_strip) = dead_strip {
258 let mut out = String::new();
259 for (idx, requested) in opts.why_live.iter().enumerate() {
260 let winner = folded_by_name
261 .get(requested.as_str())
262 .copied()
263 .unwrap_or(requested.as_str());
264 let Some(&target) = dead_strip.resolved_by_name.get(winner) else {
265 return Err(format!("`-why_live` symbol `{requested}` was not found"));
266 };
267 if idx > 0 {
268 out.push('\n');
269 }
270 if winner != requested {
271 writeln!(&mut out, "{requested} was folded to {winner} by -icf=safe").unwrap();
272 }
273 out.push_str(&dead_strip.format_symbol_explanation(sym_table, target));
274 }
275 return Ok(Some(out));
276 }
277
278 let graph = WhyLiveGraph::build(opts, layout_inputs, atom_table, sym_table, entry_symbol);
279 let mut out = String::new();
280 for (idx, requested) in opts.why_live.iter().enumerate() {
281 let winner = folded_by_name
282 .get(requested.as_str())
283 .copied()
284 .unwrap_or(requested.as_str());
285 let Some(&target) = graph.resolved_by_name.get(winner) else {
286 return Err(format!("`-why_live` symbol `{requested}` was not found"));
287 };
288 if idx > 0 {
289 out.push('\n');
290 }
291 if winner != requested {
292 writeln!(&mut out, "{requested} was folded to {winner} by -icf=safe").unwrap();
293 }
294 let target_name = graph.symbol_name(target);
295 writeln!(&mut out, "{target_name} is live because:").unwrap();
296 writeln!(
297 &mut out,
298 " note: -dead_strip was not requested; showing the current reachability roots"
299 )
300 .unwrap();
301 if let Some(reason) = graph.roots.get(&target) {
302 writeln!(&mut out, " {}", reason.describe(&target_name)).unwrap();
303 continue;
304 }
305 if let Some(path) = graph.find_chain(target) {
306 for pair in path.windows(2).rev() {
307 let parent = graph.symbol_name(pair[0]);
308 let child = graph.symbol_name(pair[1]);
309 writeln!(&mut out, " {child} is reachable from {parent}").unwrap();
310 }
311 let root = path[0];
312 let root_name = graph.symbol_name(root);
313 writeln!(
314 &mut out,
315 " {}",
316 graph
317 .roots
318 .get(&root)
319 .expect("root path starts from a root")
320 .describe(&root_name)
321 )
322 .unwrap();
323 } else {
324 writeln!(
325 &mut out,
326 " no reachability chain from a known GC root was found; linked inputs are currently retained as loaded"
327 )
328 .unwrap();
329 }
330 }
331 Ok(Some(out))
332 }
333
334 struct WhyLiveGraph<'a> {
335 sym_table: &'a SymbolTable,
336 resolved_by_name: HashMap<String, SymbolId>,
337 roots: HashMap<SymbolId, RootReason>,
338 reverse_edges: HashMap<SymbolId, Vec<SymbolId>>,
339 }
340
341 impl<'a> WhyLiveGraph<'a> {
342 fn build(
343 opts: &LinkOptions,
344 layout_inputs: &[LayoutInput<'_>],
345 atom_table: &AtomTable,
346 sym_table: &'a SymbolTable,
347 entry_symbol: Option<SymbolId>,
348 ) -> Self {
349 let resolved_by_name = resolved_symbol_map(sym_table);
350 let roots = root_symbols(opts, sym_table, entry_symbol);
351 let reverse_edges = build_reverse_edges(layout_inputs, atom_table, &resolved_by_name);
352 Self {
353 sym_table,
354 resolved_by_name,
355 roots,
356 reverse_edges,
357 }
358 }
359
360 fn symbol_name(&self, symbol_id: SymbolId) -> String {
361 self.sym_table
362 .interner
363 .resolve(self.sym_table.get(symbol_id).name())
364 .to_string()
365 }
366
367 fn find_chain(&self, target: SymbolId) -> Option<Vec<SymbolId>> {
368 let mut queue = VecDeque::from([target]);
369 let mut seen = HashSet::from([target]);
370 let mut next_toward_target = HashMap::<SymbolId, SymbolId>::new();
371
372 while let Some(current) = queue.pop_front() {
373 let Some(predecessors) = self.reverse_edges.get(&current) else {
374 continue;
375 };
376 for &pred in predecessors {
377 if !seen.insert(pred) {
378 continue;
379 }
380 next_toward_target.insert(pred, current);
381 if self.roots.contains_key(&pred) {
382 let mut path = vec![pred];
383 let mut cursor = pred;
384 while let Some(&next) = next_toward_target.get(&cursor) {
385 path.push(next);
386 if next == target {
387 break;
388 }
389 cursor = next;
390 }
391 return Some(path);
392 }
393 queue.push_back(pred);
394 }
395 }
396
397 None
398 }
399 }
400
401 fn resolved_symbol_map(sym_table: &SymbolTable) -> HashMap<String, SymbolId> {
402 let mut out = HashMap::new();
403 for (symbol_id, symbol) in sym_table.iter() {
404 let name = sym_table.interner.resolve(symbol.name()).to_string();
405 let resolved = match symbol {
406 Symbol::Alias { name, .. } => sym_table
407 .resolve_chain(*name)
408 .map(|(resolved_id, _)| resolved_id)
409 .unwrap_or(symbol_id),
410 _ => symbol_id,
411 };
412 out.insert(name, resolved);
413 }
414 out
415 }
416
417 fn root_symbols(
418 opts: &LinkOptions,
419 sym_table: &SymbolTable,
420 entry_symbol: Option<SymbolId>,
421 ) -> HashMap<SymbolId, RootReason> {
422 let mut roots = HashMap::new();
423 if let Some(entry_symbol) = entry_symbol {
424 roots.insert(
425 entry_symbol,
426 RootReason::Entry(symbol_name(sym_table, entry_symbol)),
427 );
428 }
429 for (symbol_id, symbol) in sym_table.iter() {
430 match symbol {
431 Symbol::Defined {
432 no_dead_strip: true,
433 ..
434 } => {
435 roots.entry(symbol_id).or_insert(RootReason::NoDeadStrip);
436 }
437 Symbol::Defined {
438 private_extern: false,
439 ..
440 } if opts.kind == OutputKind::Dylib => {
441 roots.entry(symbol_id).or_insert(RootReason::ExportedDylib);
442 }
443 _ => {}
444 }
445 }
446 roots
447 }
448
449 fn root_atoms(
450 opts: &LinkOptions,
451 atom_table: &AtomTable,
452 sym_table: &SymbolTable,
453 entry_symbol: Option<SymbolId>,
454 ) -> HashMap<AtomId, RootReason> {
455 let mut roots = HashMap::new();
456 if let Some(entry_symbol) = entry_symbol {
457 if let Symbol::Defined { atom, .. } = sym_table.get(entry_symbol) {
458 if atom.0 != 0 {
459 roots.insert(
460 *atom,
461 RootReason::Entry(symbol_name(sym_table, entry_symbol)),
462 );
463 }
464 }
465 }
466
467 for (atom_id, atom) in atom_table.iter() {
468 if atom.flags.has(crate::atom::AtomFlags::NO_DEAD_STRIP) {
469 roots.entry(atom_id).or_insert(RootReason::NoDeadStrip);
470 }
471 }
472
473 for (_, symbol) in sym_table.iter() {
474 match symbol {
475 Symbol::Defined {
476 atom,
477 no_dead_strip: true,
478 ..
479 } if atom.0 != 0 => {
480 roots.entry(*atom).or_insert(RootReason::NoDeadStrip);
481 }
482 Symbol::Defined {
483 atom,
484 private_extern: false,
485 ..
486 } if opts.kind == OutputKind::Dylib && atom.0 != 0 => {
487 roots.entry(*atom).or_insert(RootReason::ExportedDylib);
488 }
489 _ => {}
490 }
491 }
492
493 roots
494 }
495
496 fn build_reverse_edges(
497 layout_inputs: &[LayoutInput<'_>],
498 atom_table: &AtomTable,
499 resolved_by_name: &HashMap<String, SymbolId>,
500 ) -> HashMap<SymbolId, Vec<SymbolId>> {
501 let atoms_by_input_section = atom_table.by_input_section();
502 let atom_symbols = atom_symbol_sets(atom_table);
503 let mut edge_set = HashSet::<(SymbolId, SymbolId)>::new();
504
505 for input in layout_inputs {
506 for (section_idx_zero, section) in input.object.sections.iter().enumerate() {
507 if section.raw_relocs.is_empty() {
508 continue;
509 }
510 let Ok(raws) = parse_raw_relocs(&section.raw_relocs, 0, section.nreloc) else {
511 continue;
512 };
513 let Ok(relocs) = parse_relocs(&raws) else {
514 continue;
515 };
516 let input_section = (section_idx_zero + 1) as u8;
517 for reloc in relocs {
518 let Some(source_atom) = find_atom_for_offset(
519 atom_table,
520 &atoms_by_input_section,
521 input.id,
522 input_section,
523 reloc.offset,
524 ) else {
525 continue;
526 };
527 let Some(source_symbols) = atom_symbols.get(&source_atom) else {
528 continue;
529 };
530 let Some(target_symbols) =
531 target_symbols_for_reloc(input.object, reloc.referent, resolved_by_name)
532 else {
533 continue;
534 };
535 for &source in source_symbols {
536 for &target in &target_symbols {
537 if source != target {
538 edge_set.insert((target, source));
539 }
540 }
541 }
542 }
543 }
544 }
545
546 let mut reverse_edges = HashMap::<SymbolId, Vec<SymbolId>>::new();
547 for (target, source) in edge_set {
548 reverse_edges.entry(target).or_default().push(source);
549 }
550 for predecessors in reverse_edges.values_mut() {
551 predecessors.sort_by_key(|sid| sid.0);
552 }
553 reverse_edges
554 }
555
556 fn build_forward_edges(
557 layout_inputs: &[LayoutInput<'_>],
558 atom_table: &AtomTable,
559 sym_table: &SymbolTable,
560 resolved_by_name: &HashMap<String, SymbolId>,
561 ) -> HashMap<AtomId, Vec<AtomId>> {
562 let atoms_by_input_section = atom_table.by_input_section();
563 let mut edge_set = HashSet::<(AtomId, AtomId)>::new();
564
565 for input in layout_inputs {
566 for (section_idx_zero, section) in input.object.sections.iter().enumerate() {
567 if section.raw_relocs.is_empty() {
568 continue;
569 }
570 let Ok(raws) = parse_raw_relocs(&section.raw_relocs, 0, section.nreloc) else {
571 continue;
572 };
573 let Ok(relocs) = parse_relocs(&raws) else {
574 continue;
575 };
576 let input_section = (section_idx_zero + 1) as u8;
577 for reloc in relocs {
578 let Some(source_atom) = find_atom_for_offset(
579 atom_table,
580 &atoms_by_input_section,
581 input.id,
582 input_section,
583 reloc.offset,
584 ) else {
585 continue;
586 };
587 for target_atom in target_atoms_for_reloc(
588 input.id,
589 input.object,
590 atom_table.get(source_atom),
591 reloc,
592 reloc.referent,
593 reloc.subtrahend,
594 atom_table,
595 sym_table,
596 resolved_by_name,
597 &atoms_by_input_section,
598 ) {
599 if source_atom != target_atom {
600 edge_set.insert((source_atom, target_atom));
601 }
602 }
603 }
604 }
605 }
606
607 for (atom_id, atom) in atom_table.iter() {
608 if atom.section != AtomSection::EhFrame {
609 continue;
610 }
611 let Some(cie_atom) = eh_frame_cie_atom(atom_table, &atoms_by_input_section, atom) else {
612 continue;
613 };
614 if atom_id != cie_atom {
615 edge_set.insert((atom_id, cie_atom));
616 }
617 }
618
619 let mut forward_edges = HashMap::<AtomId, Vec<AtomId>>::new();
620 for (source, target) in edge_set {
621 forward_edges.entry(source).or_default().push(target);
622 }
623 for targets in forward_edges.values_mut() {
624 targets.sort_by_key(|aid| aid.0);
625 }
626 forward_edges
627 }
628
629 fn parent_edges(atom_table: &AtomTable) -> HashMap<AtomId, Vec<AtomId>> {
630 let mut out = HashMap::<AtomId, Vec<AtomId>>::new();
631 for (atom_id, atom) in atom_table.iter() {
632 if let Some(parent) = atom.parent_of {
633 out.entry(parent).or_default().push(atom_id);
634 }
635 }
636 for children in out.values_mut() {
637 children.sort_by_key(|aid| aid.0);
638 }
639 out
640 }
641
642 fn atom_symbol_sets(atom_table: &AtomTable) -> HashMap<crate::resolve::AtomId, Vec<SymbolId>> {
643 let mut out = HashMap::new();
644 for (atom_id, atom) in atom_table.iter() {
645 let mut symbols = Vec::new();
646 if let Some(owner) = atom.owner {
647 symbols.push(owner);
648 }
649 for alt in &atom.alt_entries {
650 symbols.push(alt.symbol);
651 }
652 symbols.sort_by_key(|sid| sid.0);
653 symbols.dedup();
654 if !symbols.is_empty() {
655 out.insert(atom_id, symbols);
656 }
657 }
658 out
659 }
660
661 fn find_atom_for_offset(
662 atom_table: &AtomTable,
663 atoms_by_input_section: &HashMap<(InputId, u8), Vec<AtomId>>,
664 input_id: InputId,
665 input_section: u8,
666 offset: u32,
667 ) -> Option<AtomId> {
668 atoms_by_input_section
669 .get(&(input_id, input_section))
670 .and_then(|ids| {
671 ids.iter().find_map(|atom_id| {
672 let atom = atom_table.get(*atom_id);
673 let start = atom.input_offset;
674 let end = atom.input_offset.saturating_add(atom.size);
675 (start <= offset && offset < end).then_some(*atom_id)
676 })
677 })
678 }
679
680 #[allow(clippy::too_many_arguments)]
681 fn target_atoms_for_reloc(
682 input_id: InputId,
683 object: &ObjectFile,
684 source_atom: &Atom,
685 reloc: crate::reloc::Reloc,
686 referent: Referent,
687 subtrahend: Option<Referent>,
688 atom_table: &AtomTable,
689 sym_table: &SymbolTable,
690 resolved_by_name: &HashMap<String, SymbolId>,
691 atoms_by_input_section: &HashMap<(InputId, u8), Vec<AtomId>>,
692 ) -> Vec<AtomId> {
693 let mut out = referent_atoms(
694 input_id,
695 object,
696 source_atom,
697 reloc,
698 referent,
699 atom_table,
700 sym_table,
701 resolved_by_name,
702 atoms_by_input_section,
703 );
704 if let Some(subtrahend) = subtrahend {
705 out.extend(referent_atoms(
706 input_id,
707 object,
708 source_atom,
709 reloc,
710 subtrahend,
711 atom_table,
712 sym_table,
713 resolved_by_name,
714 atoms_by_input_section,
715 ));
716 }
717 out.sort_by_key(|aid| aid.0);
718 out.dedup();
719 out
720 }
721
722 #[allow(clippy::too_many_arguments)]
723 fn referent_atoms(
724 input_id: InputId,
725 object: &ObjectFile,
726 source_atom: &Atom,
727 reloc: crate::reloc::Reloc,
728 referent: Referent,
729 atom_table: &AtomTable,
730 sym_table: &SymbolTable,
731 resolved_by_name: &HashMap<String, SymbolId>,
732 atoms_by_input_section: &HashMap<(InputId, u8), Vec<AtomId>>,
733 ) -> Vec<AtomId> {
734 match referent {
735 Referent::Symbol(symbol_index) => {
736 let Some(input_sym) = object.symbols.get(symbol_index as usize) else {
737 return Vec::new();
738 };
739 let Some(name) = object.symbol_name(input_sym).ok() else {
740 return Vec::new();
741 };
742 let Some(&symbol_id) = resolved_by_name.get(name) else {
743 return Vec::new();
744 };
745 match sym_table.get(symbol_id) {
746 Symbol::Defined { atom, .. } if atom.0 != 0 => vec![*atom],
747 _ => Vec::new(),
748 }
749 }
750 Referent::Section(section_index) => {
751 if let Some(atom_id) = section_referent_atom(
752 input_id,
753 source_atom,
754 reloc,
755 section_index,
756 atom_table,
757 atoms_by_input_section,
758 ) {
759 vec![atom_id]
760 } else {
761 atoms_by_input_section
762 .get(&(input_id, section_index))
763 .cloned()
764 .unwrap_or_default()
765 }
766 }
767 }
768 }
769
770 fn section_referent_atom(
771 input_id: InputId,
772 source_atom: &Atom,
773 reloc: crate::reloc::Reloc,
774 section_index: u8,
775 atom_table: &AtomTable,
776 atoms_by_input_section: &HashMap<(InputId, u8), Vec<AtomId>>,
777 ) -> Option<AtomId> {
778 if source_atom.section == AtomSection::CompactUnwind
779 && reloc.offset == source_atom.input_offset
780 && source_atom.data.len() >= 8
781 {
782 let mut buf = [0u8; 8];
783 buf.copy_from_slice(&source_atom.data[..8]);
784 let target_offset = u64::from_le_bytes(buf) as u32;
785 return find_atom_for_offset(
786 atom_table,
787 atoms_by_input_section,
788 input_id,
789 section_index,
790 target_offset,
791 );
792 }
793 None
794 }
795
796 fn eh_frame_cie_atom(
797 atom_table: &AtomTable,
798 atoms_by_input_section: &HashMap<(InputId, u8), Vec<AtomId>>,
799 atom: &Atom,
800 ) -> Option<AtomId> {
801 if atom.section != AtomSection::EhFrame || atom.data.len() < 8 {
802 return None;
803 }
804 let mut buf = [0u8; 4];
805 buf.copy_from_slice(&atom.data[4..8]);
806 let cie_delta = u32::from_le_bytes(buf);
807 if cie_delta == 0 {
808 return None;
809 }
810 let cie_offset = atom.input_offset.checked_add(4)?.checked_sub(cie_delta)?;
811 find_atom_for_offset(
812 atom_table,
813 atoms_by_input_section,
814 atom.origin,
815 atom.input_section,
816 cie_offset,
817 )
818 }
819
820 fn target_symbols_for_reloc(
821 object: &ObjectFile,
822 referent: Referent,
823 resolved_by_name: &HashMap<String, SymbolId>,
824 ) -> Option<Vec<SymbolId>> {
825 let Referent::Symbol(symbol_index) = referent else {
826 return None;
827 };
828 let input_sym = object.symbols.get(symbol_index as usize)?;
829 let name = object.symbol_name(input_sym).ok()?;
830 resolved_by_name.get(name).copied().map(|sid| vec![sid])
831 }
832
833 fn symbol_name(sym_table: &SymbolTable, symbol_id: SymbolId) -> String {
834 sym_table
835 .interner
836 .resolve(sym_table.get(symbol_id).name())
837 .to_string()
838 }
839