Rust · 96629 bytes Raw Blame History
1 use std::collections::HashMap;
2 use std::fmt;
3 use std::path::PathBuf;
4 use std::thread;
5
6 use crate::atom::{Atom, AtomSection, AtomTable};
7 use crate::input::ObjectFile;
8 use crate::layout::{ExtraOutputSection, ExtraSectionAnchor, Layout, LayoutInput};
9 use crate::macho::writer::LinkEditPlan;
10 use crate::reloc::{ParsedRelocCache, Referent, Reloc, RelocKind, RelocLength};
11 use crate::resolve::{InputId, Symbol, SymbolId, SymbolTable};
12 use crate::section::{OutputAtom, OutputSection, SectionKind};
13 use crate::symbol::{InputSymbol, SymKind};
14 use crate::synth::stubs::{STUB_HELPER_ENTRY_SIZE, STUB_HELPER_HEADER_SIZE, STUB_SIZE};
15 use crate::synth::tlv::THREAD_VARIABLE_DESCRIPTOR_SIZE;
16 use crate::synth::SyntheticPlan;
17 use crate::{LinkOptions, ThunkMode};
18
19 #[derive(Debug, Clone, PartialEq, Eq)]
20 pub struct RelocError {
21 pub input: PathBuf,
22 pub atom: crate::resolve::AtomId,
23 pub atom_offset: u32,
24 pub kind: RelocKind,
25 pub referent: String,
26 pub detail: String,
27 }
28
29 impl fmt::Display for RelocError {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 write!(
32 f,
33 "{}: relocation {:?} at atom {:?}+0x{:x} against {}: {}",
34 self.input.display(),
35 self.kind,
36 self.atom,
37 self.atom_offset,
38 self.referent,
39 self.detail
40 )
41 }
42 }
43
44 impl std::error::Error for RelocError {}
45
46 struct ResolveView<'a> {
47 sym_table: &'a SymbolTable,
48 symbol_name_index: &'a HashMap<String, SymbolId>,
49 atom_table: &'a AtomTable,
50 atom_addrs: &'a HashMap<crate::resolve::AtomId, u64>,
51 atoms_by_input_section: &'a HashMap<(InputId, u8), Vec<crate::resolve::AtomId>>,
52 section_addrs: &'a HashMap<(InputId, u8), u64>,
53 stub_addrs: &'a HashMap<SymbolId, u64>,
54 got_addrs: &'a HashMap<SymbolId, u64>,
55 lazy_pointer_addrs: &'a HashMap<SymbolId, u64>,
56 stub_helper_entry_addrs: &'a HashMap<SymbolId, u64>,
57 stub_helper_header_addr: Option<u64>,
58 dyld_private_addr: Option<u64>,
59 icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
60 }
61
62 struct SyntheticAddressMaps {
63 stub_addrs: HashMap<SymbolId, u64>,
64 got_addrs: HashMap<SymbolId, u64>,
65 lazy_pointer_addrs: HashMap<SymbolId, u64>,
66 stub_helper_entry_addrs: HashMap<SymbolId, u64>,
67 stub_helper_header_addr: Option<u64>,
68 dyld_private_addr: Option<u64>,
69 }
70
71 pub struct ApplyLayoutPlan<'a> {
72 pub synthetic_plan: Option<&'a SyntheticPlan>,
73 pub thunk_plan: Option<&'a ThunkPlan>,
74 pub linkedit: &'a LinkEditPlan,
75 pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
76 pub parsed_relocs: &'a ParsedRelocCache,
77 pub parallel_jobs: usize,
78 }
79
80 struct InputSectionResolveCtx<'a> {
81 obj: &'a ObjectFile,
82 atom: &'a Atom,
83 kind: RelocKind,
84 referent: &'a str,
85 }
86
87 struct RegularRelocContext<'a> {
88 input_map: &'a HashMap<InputId, &'a ObjectFile>,
89 atoms: &'a AtomTable,
90 resolve: &'a ResolveView<'a>,
91 thunk_plan: Option<&'a ThunkPlan>,
92 thunk_addrs: Option<&'a HashMap<usize, u64>>,
93 parsed_relocs: &'a ParsedRelocCache,
94 }
95
96 const THUNK_SIZE: u64 = 12;
97 const BR_X16: u32 = 0xd61f_0200;
98 const BRANCH26_MAX_FORWARD_DELTA_BYTES: u64 = ((1u64 << 25) - 1) * 4;
99
100 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101 enum BranchTargetKey {
102 Symbol(SymbolId),
103 Stub(SymbolId),
104 InputSectionOffset {
105 origin: InputId,
106 input_section: u8,
107 input_offset: u32,
108 },
109 }
110
111 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
112 struct ThunkBucketKey {
113 island: usize,
114 target: BranchTargetKey,
115 }
116
117 #[derive(Debug, Clone, PartialEq, Eq)]
118 struct ThunkIsland {
119 segment: String,
120 after_atom: crate::resolve::AtomId,
121 }
122
123 #[derive(Debug, Clone, PartialEq, Eq)]
124 struct ThunkEntry {
125 island: usize,
126 slot_in_island: usize,
127 target: BranchTargetKey,
128 }
129
130 #[derive(Debug, Clone, PartialEq, Eq, Default)]
131 pub struct ThunkPlan {
132 redirects: HashMap<(crate::resolve::AtomId, u32), usize>,
133 islands: Vec<ThunkIsland>,
134 entries: Vec<ThunkEntry>,
135 }
136
137 impl ThunkPlan {
138 pub fn split_after_atoms(&self) -> Vec<crate::resolve::AtomId> {
139 self.islands
140 .iter()
141 .map(|island| island.after_atom)
142 .collect()
143 }
144
145 pub fn output_sections(&self) -> Vec<ExtraOutputSection> {
146 if self.entries.is_empty() {
147 return Vec::new();
148 }
149 let mut counts = vec![0usize; self.islands.len()];
150 for entry in &self.entries {
151 counts[entry.island] += 1;
152 }
153 let mut sections = Vec::new();
154 for (island, island_desc) in self.islands.iter().enumerate() {
155 let count = counts[island];
156 if count == 0 {
157 continue;
158 }
159 sections.push(ExtraOutputSection {
160 after_section: Some(ExtraSectionAnchor::AfterAtom(island_desc.after_atom)),
161 section: OutputSection {
162 segment: island_desc.segment.clone(),
163 name: "__thunks".into(),
164 kind: SectionKind::Text,
165 align_pow2: 2,
166 flags: crate::macho::constants::S_REGULAR
167 | crate::macho::constants::S_ATTR_PURE_INSTRUCTIONS
168 | crate::macho::constants::S_ATTR_SOME_INSTRUCTIONS,
169 reserved1: 0,
170 reserved2: 0,
171 reserved3: 0,
172 atoms: Vec::new(),
173 synthetic_offset: 0,
174 synthetic_data: vec![0; count * THUNK_SIZE as usize],
175 addr: 0,
176 size: (count as u64) * THUNK_SIZE,
177 file_off: 0,
178 },
179 });
180 }
181 sections
182 }
183
184 fn redirect_for(&self, atom: crate::resolve::AtomId, atom_offset: u32) -> Option<usize> {
185 self.redirects.get(&(atom, atom_offset)).copied()
186 }
187
188 fn thunk_addrs(&self, layout: &Layout) -> HashMap<usize, u64> {
189 let bases: HashMap<_, _> = self
190 .islands
191 .iter()
192 .enumerate()
193 .filter_map(|(island_idx, island)| {
194 find_thunk_section_index(layout, island)
195 .map(|section_idx| (island_idx, layout.sections[section_idx].addr))
196 })
197 .collect();
198 self.entries
199 .iter()
200 .enumerate()
201 .filter_map(|(index, entry)| {
202 bases
203 .get(&entry.island)
204 .copied()
205 .map(|base| (index, base + (entry.slot_in_island as u64) * THUNK_SIZE))
206 })
207 .collect()
208 }
209 }
210
211 pub fn apply_layout(
212 layout: &mut Layout,
213 inputs: &[LayoutInput<'_>],
214 atoms: &AtomTable,
215 sym_table: &SymbolTable,
216 plan: ApplyLayoutPlan<'_>,
217 ) -> Result<(), RelocError> {
218 let input_map: HashMap<InputId, &ObjectFile> = inputs
219 .iter()
220 .map(|input| (input.id, input.object))
221 .collect();
222 let atom_addrs = atom_address_map(layout);
223 let atoms_by_input_section = atoms.by_input_section();
224 let section_addrs = input_section_address_map(layout, atoms);
225 let synth_addrs = synthetic_address_maps(layout, plan.synthetic_plan);
226 let symbol_name_index = build_symbol_name_index(sym_table);
227 let resolve = ResolveView {
228 sym_table,
229 symbol_name_index: &symbol_name_index,
230 atom_table: atoms,
231 atom_addrs: &atom_addrs,
232 atoms_by_input_section: &atoms_by_input_section,
233 section_addrs: &section_addrs,
234 stub_addrs: &synth_addrs.stub_addrs,
235 got_addrs: &synth_addrs.got_addrs,
236 lazy_pointer_addrs: &synth_addrs.lazy_pointer_addrs,
237 stub_helper_entry_addrs: &synth_addrs.stub_helper_entry_addrs,
238 stub_helper_header_addr: synth_addrs.stub_helper_header_addr,
239 dyld_private_addr: synth_addrs.dyld_private_addr,
240 icf_redirects: plan.icf_redirects,
241 };
242 let thunk_addrs = plan
243 .thunk_plan
244 .map(|thunk_plan| thunk_plan.thunk_addrs(layout));
245
246 let regular_ctx = RegularRelocContext {
247 input_map: &input_map,
248 atoms,
249 resolve: &resolve,
250 thunk_plan: plan.thunk_plan,
251 thunk_addrs: thunk_addrs.as_ref(),
252 parsed_relocs: plan.parsed_relocs,
253 };
254 apply_regular_relocs(layout, &regular_ctx, plan.parallel_jobs)?;
255
256 if let Some(thunk_plan) = plan.thunk_plan {
257 synthesize_thunk_section(layout, thunk_plan, &resolve)?;
258 }
259
260 if let Some(synthetic_plan) = plan.synthetic_plan {
261 synthesize_thread_variable_section(
262 layout,
263 synthetic_plan,
264 atoms,
265 &input_map,
266 plan.parsed_relocs,
267 &resolve,
268 )?;
269 synthesize_got_section(layout, synthetic_plan, &resolve)?;
270 synthesize_stub_section(layout, synthetic_plan, &resolve)?;
271 synthesize_lazy_pointer_section(layout, synthetic_plan, &resolve)?;
272 synthesize_stub_helper_section(layout, synthetic_plan, &resolve, plan.linkedit)?;
273 }
274
275 Ok(())
276 }
277
278 fn apply_regular_relocs(
279 layout: &mut Layout,
280 ctx: &RegularRelocContext<'_>,
281 parallel_jobs: usize,
282 ) -> Result<(), RelocError> {
283 let parallel_jobs = parallel_jobs.max(1);
284 for out_section in &mut layout.sections {
285 let atom_count = out_section.atoms.len();
286 if parallel_jobs == 1 || atom_count < 2 {
287 apply_regular_atom_chunk(&mut out_section.atoms, ctx)?;
288 continue;
289 }
290
291 let job_count = parallel_jobs.min(atom_count).max(1);
292 let chunk_size = atom_count.div_ceil(job_count);
293 thread::scope(|scope| {
294 let mut handles = Vec::new();
295 for chunk in out_section.atoms.chunks_mut(chunk_size) {
296 handles.push(scope.spawn(move || apply_regular_atom_chunk(chunk, ctx)));
297 }
298 for handle in handles {
299 handle.join().expect("relocation worker panicked")?;
300 }
301 Ok::<(), RelocError>(())
302 })?;
303 }
304 Ok(())
305 }
306
307 fn apply_regular_atom_chunk(
308 placed_atoms: &mut [OutputAtom],
309 ctx: &RegularRelocContext<'_>,
310 ) -> Result<(), RelocError> {
311 for placed in placed_atoms {
312 let atom = ctx.atoms.get(placed.atom);
313 if atom.size == 0 || placed.data.is_empty() {
314 continue;
315 }
316 let obj = ctx.input_map.get(&atom.origin).ok_or_else(|| {
317 reloc_error(
318 atom,
319 &PathBuf::from("<missing object>"),
320 0,
321 RelocKind::Unsigned,
322 "object",
323 "missing parsed object".to_string(),
324 )
325 })?;
326 patch_eh_frame_cie_pointer(&mut placed.data, atom, ctx.resolve)?;
327 let relocs = ctx
328 .parsed_relocs
329 .get(&(atom.origin, atom.input_section))
330 .map(Vec::as_slice)
331 .unwrap_or(&[]);
332 for reloc in relocs_for_atom(relocs, atom) {
333 apply_one(
334 &mut placed.data,
335 atom,
336 obj,
337 reloc,
338 ctx.resolve,
339 ctx.thunk_plan,
340 ctx.thunk_addrs,
341 )?;
342 }
343 }
344 Ok(())
345 }
346
347 fn patch_eh_frame_cie_pointer(
348 bytes: &mut [u8],
349 atom: &Atom,
350 resolve: &ResolveView<'_>,
351 ) -> Result<(), RelocError> {
352 if atom.section != AtomSection::EhFrame || bytes.len() < 8 {
353 return Ok(());
354 }
355 let mut buf = [0u8; 4];
356 buf.copy_from_slice(&bytes[4..8]);
357 let cie_delta = u32::from_le_bytes(buf);
358 if cie_delta == 0 {
359 return Ok(());
360 }
361
362 let cie_offset = atom
363 .input_offset
364 .checked_add(4)
365 .and_then(|value| value.checked_sub(cie_delta))
366 .ok_or_else(|| {
367 reloc_error(
368 atom,
369 &PathBuf::from("<eh_frame>"),
370 4,
371 RelocKind::Unsigned,
372 "__eh_frame CIE pointer",
373 "invalid CIE back-pointer".to_string(),
374 )
375 })?;
376 let cie_atom = resolve
377 .atoms_by_input_section
378 .get(&(atom.origin, atom.input_section))
379 .and_then(|atom_ids| {
380 atom_ids.iter().find_map(|atom_id| {
381 let candidate = resolve.atom_table.get(*atom_id);
382 let start = candidate.input_offset;
383 let end = candidate.input_offset.saturating_add(candidate.size);
384 (start <= cie_offset && cie_offset < end).then_some(*atom_id)
385 })
386 })
387 .and_then(|atom_id| resolve.atom_addrs.get(&atom_id).copied())
388 .ok_or_else(|| {
389 reloc_error(
390 atom,
391 &PathBuf::from("<eh_frame>"),
392 4,
393 RelocKind::Unsigned,
394 "__eh_frame CIE pointer",
395 "eh_frame CIE atom is missing from the final layout".to_string(),
396 )
397 })?;
398 let fde_field = resolve.atom_addrs.get(&atom.id).copied().ok_or_else(|| {
399 reloc_error(
400 atom,
401 &PathBuf::from("<eh_frame>"),
402 4,
403 RelocKind::Unsigned,
404 "__eh_frame CIE pointer",
405 "eh_frame atom is missing a final address".to_string(),
406 )
407 })? + 4;
408 let rewritten = fde_field.wrapping_sub(cie_atom) as u32;
409 bytes[4..8].copy_from_slice(&rewritten.to_le_bytes());
410 Ok(())
411 }
412
413 fn relocs_for_atom<'a>(relocs: &'a [Reloc], atom: &Atom) -> impl Iterator<Item = Reloc> + 'a {
414 let start = atom.input_offset;
415 let end = atom.input_offset + atom.size;
416 relocs.iter().copied().filter(move |reloc| {
417 let reloc_end = reloc.offset + reloc.length.byte_width() as u32;
418 reloc.offset >= start && reloc_end <= end
419 })
420 }
421
422 fn atom_address_map(layout: &Layout) -> HashMap<crate::resolve::AtomId, u64> {
423 let mut out = HashMap::new();
424 for section in &layout.sections {
425 for placed in &section.atoms {
426 out.insert(placed.atom, section.addr + placed.offset);
427 }
428 }
429 out
430 }
431
432 fn input_section_address_map(layout: &Layout, atoms: &AtomTable) -> HashMap<(InputId, u8), u64> {
433 let mut out = HashMap::new();
434 for section in &layout.sections {
435 for placed in &section.atoms {
436 let atom = atoms.get(placed.atom);
437 out.entry((atom.origin, atom.input_section))
438 .or_insert(section.addr);
439 }
440 }
441 out
442 }
443
444 fn atom_output_segment_map(layout: &Layout) -> HashMap<crate::resolve::AtomId, String> {
445 let mut out = HashMap::new();
446 for section in &layout.sections {
447 for placed in &section.atoms {
448 out.insert(placed.atom, section.segment.clone());
449 }
450 }
451 out
452 }
453
454 fn synthetic_address_maps(
455 layout: &Layout,
456 synthetic_plan: Option<&SyntheticPlan>,
457 ) -> SyntheticAddressMaps {
458 let Some(plan) = synthetic_plan else {
459 return SyntheticAddressMaps {
460 stub_addrs: HashMap::new(),
461 got_addrs: HashMap::new(),
462 lazy_pointer_addrs: HashMap::new(),
463 stub_helper_entry_addrs: HashMap::new(),
464 stub_helper_header_addr: None,
465 dyld_private_addr: None,
466 };
467 };
468
469 let mut stub_addrs = HashMap::new();
470 if let Some(section) = layout
471 .sections
472 .iter()
473 .find(|section| section.segment == "__TEXT" && section.name == "__stubs")
474 {
475 for (idx, entry) in plan.stubs.entries.iter().enumerate() {
476 stub_addrs.insert(entry.symbol, section.addr + (idx as u64) * STUB_SIZE as u64);
477 }
478 }
479
480 let mut got_addrs = HashMap::new();
481 if let Some(section) = layout
482 .sections
483 .iter()
484 .find(|section| section.segment == "__DATA_CONST" && section.name == "__got")
485 {
486 for (idx, entry) in plan.got.entries.iter().enumerate() {
487 got_addrs.insert(entry.symbol, section.addr + (idx as u64) * 8);
488 }
489 }
490
491 let mut lazy_pointer_addrs = HashMap::new();
492 if let Some(section) = layout
493 .sections
494 .iter()
495 .find(|section| section.segment == "__DATA" && section.name == "__la_symbol_ptr")
496 {
497 for (idx, entry) in plan.lazy_pointers.entries.iter().enumerate() {
498 lazy_pointer_addrs.insert(entry.symbol, section.addr + (idx as u64) * 8);
499 }
500 }
501
502 let mut stub_helper_entry_addrs = HashMap::new();
503 let mut stub_helper_header_addr = None;
504 if let Some(section) = layout
505 .sections
506 .iter()
507 .find(|section| section.segment == "__TEXT" && section.name == "__stub_helper")
508 {
509 stub_helper_header_addr = Some(section.addr);
510 for (idx, entry) in plan.lazy_pointers.entries.iter().enumerate() {
511 stub_helper_entry_addrs.insert(
512 entry.symbol,
513 section.addr
514 + STUB_HELPER_HEADER_SIZE as u64
515 + (idx as u64) * STUB_HELPER_ENTRY_SIZE as u64,
516 );
517 }
518 }
519
520 let dyld_private_addr = layout
521 .sections
522 .iter()
523 .find(|section| {
524 section.segment == "__DATA"
525 && section.name == "__data"
526 && section.synthetic_data.len() >= crate::synth::stubs::DYLD_PRIVATE_SIZE as usize
527 })
528 .map(|section| section.addr + section.synthetic_offset);
529
530 SyntheticAddressMaps {
531 stub_addrs,
532 got_addrs,
533 lazy_pointer_addrs,
534 stub_helper_entry_addrs,
535 stub_helper_header_addr,
536 dyld_private_addr,
537 }
538 }
539
540 pub struct ThunkPlanningContext<'a> {
541 pub layout: &'a Layout,
542 pub inputs: &'a [LayoutInput<'a>],
543 pub atoms: &'a AtomTable,
544 pub sym_table: &'a SymbolTable,
545 pub synthetic_plan: Option<&'a SyntheticPlan>,
546 pub icf_redirects: Option<&'a HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
547 pub parsed_relocs: &'a ParsedRelocCache,
548 }
549
550 pub fn plan_thunks(
551 opts: &LinkOptions,
552 ctx: ThunkPlanningContext<'_>,
553 ) -> Result<Option<ThunkPlan>, RelocError> {
554 if opts.thunks == ThunkMode::None {
555 return Ok(None);
556 }
557
558 let ThunkPlanningContext {
559 layout,
560 inputs,
561 atoms,
562 sym_table,
563 synthetic_plan,
564 icf_redirects,
565 parsed_relocs,
566 } = ctx;
567
568 if opts.thunks == ThunkMode::Safe && layout_fits_branch26_span(layout) {
569 return Ok(None);
570 }
571
572 let input_map: HashMap<InputId, &ObjectFile> = inputs
573 .iter()
574 .map(|input| (input.id, input.object))
575 .collect();
576 let atom_addrs = atom_address_map(layout);
577 let atom_segments = atom_output_segment_map(layout);
578 let atoms_by_input_section = atoms.by_input_section();
579 let section_addrs = input_section_address_map(layout, atoms);
580 let synth_addrs = synthetic_address_maps(layout, synthetic_plan);
581 let symbol_name_index = build_symbol_name_index(sym_table);
582 let resolve = ResolveView {
583 sym_table,
584 symbol_name_index: &symbol_name_index,
585 atom_table: atoms,
586 atom_addrs: &atom_addrs,
587 atoms_by_input_section: &atoms_by_input_section,
588 section_addrs: &section_addrs,
589 stub_addrs: &synth_addrs.stub_addrs,
590 got_addrs: &synth_addrs.got_addrs,
591 lazy_pointer_addrs: &synth_addrs.lazy_pointer_addrs,
592 stub_helper_entry_addrs: &synth_addrs.stub_helper_entry_addrs,
593 stub_helper_header_addr: synth_addrs.stub_helper_header_addr,
594 dyld_private_addr: synth_addrs.dyld_private_addr,
595 icf_redirects,
596 };
597
598 let mut redirects = HashMap::new();
599 let mut island_index: HashMap<crate::resolve::AtomId, usize> = HashMap::new();
600 let mut index: HashMap<ThunkBucketKey, usize> = HashMap::new();
601 let mut islands: Vec<ThunkIsland> = Vec::new();
602 let mut entries: Vec<ThunkEntry> = Vec::new();
603 for (atom_id, atom) in atoms.iter() {
604 let Some(obj) = input_map.get(&atom.origin) else {
605 continue;
606 };
607 let relocs = parsed_relocs
608 .get(&(atom.origin, atom.input_section))
609 .map(Vec::as_slice)
610 .unwrap_or(&[]);
611 for reloc in relocs_for_atom(relocs, atom) {
612 if reloc.kind != RelocKind::Branch26 {
613 continue;
614 }
615 let local_offset = reloc.offset.saturating_sub(atom.input_offset);
616 let Some(place) = resolve.atom_addrs.get(&atom.id).copied() else {
617 continue;
618 };
619 let Some(caller_segment) = atom_segments.get(&atom.id).cloned() else {
620 continue;
621 };
622 let place = place + local_offset as u64;
623 let target_key = resolve_branch_target_key(obj, atom, reloc, &resolve)?;
624 let target = resolve_branch_target_from_key(obj, atom, reloc, target_key, &resolve)?;
625 let needs_thunk = match opts.thunks {
626 ThunkMode::None => false,
627 ThunkMode::Safe => !branch26_in_range(place, target),
628 ThunkMode::All => true,
629 };
630 if !needs_thunk {
631 continue;
632 }
633 let island = if let Some(&existing) = island_index.get(&atom_id) {
634 existing
635 } else {
636 let next = islands.len();
637 islands.push(ThunkIsland {
638 segment: caller_segment.clone(),
639 after_atom: atom_id,
640 });
641 island_index.insert(atom_id, next);
642 next
643 };
644 let bucket_key = ThunkBucketKey {
645 island,
646 target: target_key,
647 };
648 let thunk_index = if let Some(&existing) = index.get(&bucket_key) {
649 existing
650 } else {
651 let next = entries.len();
652 let slot_in_island = entries
653 .iter()
654 .filter(|entry| entry.island == island)
655 .count();
656 entries.push(ThunkEntry {
657 island,
658 slot_in_island,
659 target: target_key,
660 });
661 index.insert(bucket_key, next);
662 next
663 };
664 redirects.insert((atom_id, local_offset), thunk_index);
665 }
666 }
667
668 if entries.is_empty() {
669 Ok(None)
670 } else {
671 Ok(Some(ThunkPlan {
672 redirects,
673 islands,
674 entries,
675 }))
676 }
677 }
678
679 fn apply_one(
680 bytes: &mut [u8],
681 atom: &Atom,
682 obj: &ObjectFile,
683 reloc: Reloc,
684 resolve: &ResolveView<'_>,
685 thunk_plan: Option<&ThunkPlan>,
686 thunk_addrs: Option<&HashMap<usize, u64>>,
687 ) -> Result<(), RelocError> {
688 let local_offset = reloc.offset.checked_sub(atom.input_offset).ok_or_else(|| {
689 reloc_error(
690 atom,
691 &obj.path,
692 reloc.offset,
693 reloc.kind,
694 &describe_referent(obj, reloc.referent),
695 "relocation lands before atom start".to_string(),
696 )
697 })?;
698 let place = resolve.atom_addrs.get(&atom.id).copied().ok_or_else(|| {
699 reloc_error(
700 atom,
701 &obj.path,
702 local_offset,
703 reloc.kind,
704 &describe_referent(obj, reloc.referent),
705 "atom missing final address".to_string(),
706 )
707 })? + local_offset as u64;
708 match reloc.kind {
709 RelocKind::Unsigned => {
710 if dylib_import_symbol_id(obj, reloc.referent, resolve).is_some() {
711 if direct_import_bind_supported(reloc) {
712 clear_direct_import_slot(bytes, atom, obj, local_offset, reloc)
713 } else {
714 Err(reloc_error(
715 atom,
716 &obj.path,
717 local_offset,
718 reloc.kind,
719 &describe_referent(obj, reloc.referent),
720 "direct dylib imports currently require a 64-bit UNSIGNED pointer slot"
721 .to_string(),
722 ))
723 }
724 } else {
725 patch_unsigned(
726 bytes,
727 atom,
728 obj,
729 local_offset,
730 reloc,
731 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?,
732 )
733 }
734 }
735 RelocKind::Subtractor => patch_subtractor(
736 bytes,
737 atom,
738 obj,
739 local_offset,
740 reloc,
741 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?,
742 resolve,
743 ),
744 RelocKind::Branch26 => {
745 let target = if let Some(plan) = thunk_plan {
746 if let Some(index) = plan.redirect_for(atom.id, local_offset) {
747 thunk_addrs
748 .and_then(|addrs| addrs.get(&index).copied())
749 .ok_or_else(|| {
750 reloc_error(
751 atom,
752 &obj.path,
753 local_offset,
754 reloc.kind,
755 &describe_referent(obj, reloc.referent),
756 "thunk section missing final address".to_string(),
757 )
758 })?
759 } else {
760 resolve_branch_target(obj, atom, reloc, resolve)?
761 }
762 } else {
763 resolve_branch_target(obj, atom, reloc, resolve)?
764 };
765 patch_branch26(bytes, atom, obj, local_offset, reloc, place, target)
766 }
767 RelocKind::Page21 => patch_page21(
768 bytes,
769 atom,
770 obj,
771 local_offset,
772 reloc,
773 place,
774 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?,
775 ),
776 RelocKind::PageOff12 => patch_pageoff12(
777 bytes,
778 atom,
779 obj,
780 local_offset,
781 reloc,
782 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?,
783 ),
784 RelocKind::GotLoadPage21 => patch_page21(
785 bytes,
786 atom,
787 obj,
788 local_offset,
789 reloc,
790 place,
791 if got_reloc_relaxes_locally(obj, reloc, resolve) {
792 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?
793 } else {
794 resolve_got_target(obj, atom, reloc, resolve)?
795 },
796 ),
797 RelocKind::GotLoadPageOff12 => {
798 let target = if got_reloc_relaxes_locally(obj, reloc, resolve) {
799 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)?
800 } else {
801 resolve_got_target(obj, atom, reloc, resolve)?
802 };
803 if got_reloc_relaxes_locally(obj, reloc, resolve) {
804 patch_got_pageoff12_relaxed(bytes, atom, obj, local_offset, reloc, target)
805 } else {
806 patch_pageoff12(bytes, atom, obj, local_offset, reloc, target)
807 }
808 }
809 RelocKind::PointerToGot => patch_unsigned(
810 bytes,
811 atom,
812 obj,
813 local_offset,
814 reloc,
815 resolve_got_target(obj, atom, reloc, resolve)?,
816 ),
817 RelocKind::TlvpLoadPage21 => patch_page21(
818 bytes,
819 atom,
820 obj,
821 local_offset,
822 reloc,
823 place,
824 resolve_tlvp_target(obj, atom, reloc, resolve)?,
825 ),
826 RelocKind::TlvpLoadPageOff12 => {
827 let target = resolve_tlvp_pageoff_target(obj, atom, reloc, resolve)?;
828 if dylib_import_symbol_id(obj, reloc.referent, resolve).is_some() {
829 patch_pageoff12(bytes, atom, obj, local_offset, reloc, target)
830 } else {
831 patch_tlvp_pageoff12(bytes, atom, obj, local_offset, reloc, target)
832 }
833 }
834 }
835 }
836
837 fn resolve_branch_target(
838 obj: &ObjectFile,
839 atom: &Atom,
840 reloc: Reloc,
841 resolve: &ResolveView<'_>,
842 ) -> Result<u64, RelocError> {
843 let key = resolve_branch_target_key(obj, atom, reloc, resolve)?;
844 resolve_branch_target_from_key(obj, atom, reloc, key, resolve)
845 }
846
847 fn resolve_branch_target_key(
848 obj: &ObjectFile,
849 atom: &Atom,
850 reloc: Reloc,
851 resolve: &ResolveView<'_>,
852 ) -> Result<BranchTargetKey, RelocError> {
853 if let Some(symbol_id) = dylib_import_symbol_id(obj, reloc.referent, resolve) {
854 return Ok(BranchTargetKey::Stub(symbol_id));
855 }
856 match reloc.referent {
857 Referent::Section(section_idx) => Ok(BranchTargetKey::InputSectionOffset {
858 origin: atom.origin,
859 input_section: section_idx,
860 input_offset: 0,
861 }),
862 Referent::Symbol(sym_idx) => {
863 let input_sym = obj.symbols.get(sym_idx as usize).ok_or_else(|| {
864 reloc_error(
865 atom,
866 &obj.path,
867 0,
868 reloc.kind,
869 &format!("symbol #{sym_idx}"),
870 "symbol index is out of range".to_string(),
871 )
872 })?;
873 if let Ok(name) = obj.symbol_name(input_sym) {
874 if let Some(symbol_id) = resolve.symbol_name_index.get(name).copied() {
875 return Ok(BranchTargetKey::Symbol(symbol_id));
876 }
877 }
878 match input_sym.kind() {
879 SymKind::Sect => {
880 let section = obj.section_for_symbol(input_sym).ok_or_else(|| {
881 reloc_error(
882 atom,
883 &obj.path,
884 0,
885 reloc.kind,
886 &describe_input_symbol(obj, input_sym),
887 "section-backed symbol did not resolve to an input section".to_string(),
888 )
889 })?;
890 Ok(BranchTargetKey::InputSectionOffset {
891 origin: atom.origin,
892 input_section: input_sym.sect_idx(),
893 input_offset: input_sym.value().saturating_sub(section.addr) as u32,
894 })
895 }
896 SymKind::Abs => Err(reloc_error(
897 atom,
898 &obj.path,
899 0,
900 reloc.kind,
901 &describe_input_symbol(obj, input_sym),
902 "absolute BRANCH26 targets are not supported".to_string(),
903 )),
904 SymKind::Undef => Err(reloc_error(
905 atom,
906 &obj.path,
907 0,
908 reloc.kind,
909 &describe_input_symbol(obj, input_sym),
910 "symbol remained undefined at relocation time".to_string(),
911 )),
912 SymKind::Indirect => Err(reloc_error(
913 atom,
914 &obj.path,
915 0,
916 reloc.kind,
917 &describe_input_symbol(obj, input_sym),
918 "indirect BRANCH26 targets are not yet supported".to_string(),
919 )),
920 }
921 }
922 }
923 }
924
925 fn resolve_branch_target_from_key(
926 obj: &ObjectFile,
927 atom: &Atom,
928 reloc: Reloc,
929 key: BranchTargetKey,
930 resolve: &ResolveView<'_>,
931 ) -> Result<u64, RelocError> {
932 match key {
933 BranchTargetKey::Symbol(symbol_id) => match resolve.sym_table.get(symbol_id) {
934 Symbol::Defined {
935 atom: target_atom,
936 value,
937 ..
938 } => resolve
939 .atom_addrs
940 .get(&canonical_atom(*target_atom, resolve.icf_redirects))
941 .copied()
942 .map(|addr| addr + *value)
943 .ok_or_else(|| {
944 reloc_error(
945 atom,
946 &obj.path,
947 reloc.offset.saturating_sub(atom.input_offset),
948 reloc.kind,
949 &describe_referent(obj, reloc.referent),
950 "target atom missing final address".to_string(),
951 )
952 }),
953 Symbol::DylibImport { .. } => Err(reloc_error(
954 atom,
955 &obj.path,
956 reloc.offset.saturating_sub(atom.input_offset),
957 reloc.kind,
958 &describe_referent(obj, reloc.referent),
959 "dylib import is missing synthetic stub".to_string(),
960 )),
961 other => Err(reloc_error(
962 atom,
963 &obj.path,
964 reloc.offset.saturating_sub(atom.input_offset),
965 reloc.kind,
966 &describe_referent(obj, reloc.referent),
967 format!("symbol resolved to unsupported state {:?}", other.kind()),
968 )),
969 },
970 BranchTargetKey::Stub(symbol_id) => {
971 resolve.stub_addrs.get(&symbol_id).copied().ok_or_else(|| {
972 reloc_error(
973 atom,
974 &obj.path,
975 reloc.offset.saturating_sub(atom.input_offset),
976 reloc.kind,
977 &describe_referent(obj, reloc.referent),
978 "dylib import is missing synthetic stub".to_string(),
979 )
980 })
981 }
982 BranchTargetKey::InputSectionOffset {
983 origin,
984 input_section,
985 input_offset,
986 } => resolve_input_section_offset(
987 origin,
988 input_section,
989 input_offset,
990 InputSectionResolveCtx {
991 obj,
992 atom,
993 kind: reloc.kind,
994 referent: &describe_referent(obj, reloc.referent),
995 },
996 resolve,
997 ),
998 }
999 }
1000
1001 fn branch26_in_range(place: u64, target: u64) -> bool {
1002 let delta = target.wrapping_sub(place) as i64;
1003 delta & 0b11 == 0 && fits_signed(delta >> 2, 26)
1004 }
1005
1006 fn layout_fits_branch26_span(layout: &Layout) -> bool {
1007 let mut min_addr = u64::MAX;
1008 let mut max_addr = 0u64;
1009 for section in &layout.sections {
1010 if section.segment == "__LINKEDIT" || section.size == 0 {
1011 continue;
1012 }
1013 min_addr = min_addr.min(section.addr);
1014 max_addr = max_addr.max(section.addr.saturating_add(section.size));
1015 }
1016 min_addr == u64::MAX || max_addr.saturating_sub(min_addr) <= BRANCH26_MAX_FORWARD_DELTA_BYTES
1017 }
1018
1019 fn synthesize_thunk_section(
1020 layout: &mut Layout,
1021 plan: &ThunkPlan,
1022 resolve: &ResolveView<'_>,
1023 ) -> Result<(), RelocError> {
1024 let mut counts: HashMap<usize, usize> = HashMap::new();
1025 for entry in &plan.entries {
1026 *counts.entry(entry.island).or_insert(0usize) += 1;
1027 }
1028 for (island_idx, island) in plan.islands.iter().enumerate() {
1029 let expected_len = counts.get(&island_idx).copied().unwrap_or(0) * THUNK_SIZE as usize;
1030 let section_idx = find_thunk_section_index(layout, island).ok_or_else(|| RelocError {
1031 input: PathBuf::from("<synthetic thunks>"),
1032 atom: crate::resolve::AtomId(0),
1033 atom_offset: (island_idx as u32) * THUNK_SIZE as u32,
1034 kind: RelocKind::Branch26,
1035 referent: "thunk section".to_string(),
1036 detail: format!(
1037 "missing thunk section for island after {},{}",
1038 island.segment, island.after_atom.0
1039 ),
1040 })?;
1041 let section = &mut layout.sections[section_idx];
1042 if section.synthetic_data.len() != expected_len {
1043 section.synthetic_data.resize(expected_len, 0);
1044 }
1045 }
1046 for (idx, entry) in plan.entries.iter().enumerate() {
1047 let island = &plan.islands[entry.island];
1048 let section_idx = find_thunk_section_index(layout, island).ok_or_else(|| RelocError {
1049 input: PathBuf::from("<synthetic thunks>"),
1050 atom: crate::resolve::AtomId(0),
1051 atom_offset: (idx as u32) * THUNK_SIZE as u32,
1052 kind: RelocKind::Branch26,
1053 referent: "thunk section".to_string(),
1054 detail: format!(
1055 "missing thunk section for island after {},{}",
1056 island.segment, island.after_atom.0
1057 ),
1058 })?;
1059 let section = &mut layout.sections[section_idx];
1060 let thunk_addr = section.addr + (entry.slot_in_island as u64) * THUNK_SIZE;
1061 let target = match entry.target {
1062 BranchTargetKey::Symbol(symbol_id) => match resolve.sym_table.get(symbol_id) {
1063 Symbol::Defined { atom, value, .. } => resolve
1064 .atom_addrs
1065 .get(&canonical_atom(*atom, resolve.icf_redirects))
1066 .copied()
1067 .map(|addr| addr + *value),
1068 _ => None,
1069 },
1070 BranchTargetKey::Stub(symbol_id) => resolve.stub_addrs.get(&symbol_id).copied(),
1071 BranchTargetKey::InputSectionOffset {
1072 origin,
1073 input_section,
1074 input_offset,
1075 } => resolve_input_section_offset_simple(origin, input_section, input_offset, resolve),
1076 }
1077 .ok_or_else(|| RelocError {
1078 input: PathBuf::from("<synthetic thunks>"),
1079 atom: crate::resolve::AtomId(0),
1080 atom_offset: (idx as u32) * THUNK_SIZE as u32,
1081 kind: RelocKind::Branch26,
1082 referent: "thunk target".to_string(),
1083 detail: "missing final target address".to_string(),
1084 })?;
1085 let adrp = encode_adrp_reg(16, thunk_addr, target, "thunk target")?;
1086 let add = encode_add_x_reg_pageoff(16, target, "thunk target")?;
1087 let start = entry.slot_in_island * THUNK_SIZE as usize;
1088 section.synthetic_data[start..start + 4].copy_from_slice(&adrp.to_le_bytes());
1089 section.synthetic_data[start + 4..start + 8].copy_from_slice(&add.to_le_bytes());
1090 section.synthetic_data[start + 8..start + 12].copy_from_slice(&BR_X16.to_le_bytes());
1091 }
1092 Ok(())
1093 }
1094
1095 fn find_thunk_section_index(layout: &Layout, island: &ThunkIsland) -> Option<usize> {
1096 layout
1097 .sections
1098 .iter()
1099 .enumerate()
1100 .skip(1)
1101 .find_map(|(idx, section)| {
1102 let prev = &layout.sections[idx - 1];
1103 (prev.segment == island.segment
1104 && prev
1105 .atoms
1106 .last()
1107 .map(|placed| placed.atom == island.after_atom)
1108 .unwrap_or(false)
1109 && section.segment == island.segment
1110 && section.name == "__thunks")
1111 .then_some(idx)
1112 })
1113 }
1114
1115 fn resolve_got_target(
1116 obj: &ObjectFile,
1117 atom: &Atom,
1118 reloc: Reloc,
1119 resolve: &ResolveView<'_>,
1120 ) -> Result<u64, RelocError> {
1121 let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, resolve) else {
1122 return Err(reloc_error(
1123 atom,
1124 &obj.path,
1125 reloc.offset.saturating_sub(atom.input_offset),
1126 reloc.kind,
1127 &describe_referent(obj, reloc.referent),
1128 "GOT relocations require a symbol target".to_string(),
1129 ));
1130 };
1131 resolve.got_addrs.get(&symbol_id).copied().ok_or_else(|| {
1132 reloc_error(
1133 atom,
1134 &obj.path,
1135 reloc.offset.saturating_sub(atom.input_offset),
1136 reloc.kind,
1137 &describe_referent(obj, reloc.referent),
1138 "symbol is missing synthetic GOT slot".to_string(),
1139 )
1140 })
1141 }
1142
1143 fn got_reloc_relaxes_locally(obj: &ObjectFile, reloc: Reloc, resolve: &ResolveView<'_>) -> bool {
1144 match symbol_referent_id(obj, reloc.referent, resolve) {
1145 Some(symbol_id) => match resolve.sym_table.get(symbol_id) {
1146 Symbol::DylibImport { .. } => false,
1147 Symbol::Defined { .. } => true,
1148 _ => true,
1149 },
1150 None => true,
1151 }
1152 }
1153
1154 fn resolve_tlvp_target(
1155 obj: &ObjectFile,
1156 atom: &Atom,
1157 reloc: Reloc,
1158 resolve: &ResolveView<'_>,
1159 ) -> Result<u64, RelocError> {
1160 if dylib_import_symbol_id(obj, reloc.referent, resolve).is_some() {
1161 return resolve_got_target(obj, atom, reloc, resolve);
1162 }
1163 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)
1164 }
1165
1166 fn resolve_tlvp_pageoff_target(
1167 obj: &ObjectFile,
1168 atom: &Atom,
1169 reloc: Reloc,
1170 resolve: &ResolveView<'_>,
1171 ) -> Result<u64, RelocError> {
1172 if dylib_import_symbol_id(obj, reloc.referent, resolve).is_some() {
1173 return resolve_got_target(obj, atom, reloc, resolve);
1174 }
1175 resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)
1176 }
1177
1178 fn resolve_referent(
1179 obj: &ObjectFile,
1180 atom: &Atom,
1181 kind: RelocKind,
1182 referent: Referent,
1183 resolve: &ResolveView<'_>,
1184 ) -> Result<u64, RelocError> {
1185 match referent {
1186 Referent::Section(section_idx) => resolve
1187 .section_addrs
1188 .get(&(atom.origin, section_idx))
1189 .copied()
1190 .ok_or_else(|| {
1191 reloc_error(
1192 atom,
1193 &obj.path,
1194 0,
1195 kind,
1196 &format!("section #{section_idx}"),
1197 "referenced input section was not laid out".to_string(),
1198 )
1199 }),
1200 Referent::Symbol(sym_idx) => {
1201 resolve_symbol_referent(obj, atom, kind, sym_idx as usize, resolve)
1202 }
1203 }
1204 }
1205
1206 fn resolve_symbol_referent(
1207 obj: &ObjectFile,
1208 atom: &Atom,
1209 kind: RelocKind,
1210 sym_idx: usize,
1211 resolve: &ResolveView<'_>,
1212 ) -> Result<u64, RelocError> {
1213 let input_sym = obj.symbols.get(sym_idx).ok_or_else(|| {
1214 reloc_error(
1215 atom,
1216 &obj.path,
1217 0,
1218 kind,
1219 &format!("symbol #{sym_idx}"),
1220 "symbol index is out of range".to_string(),
1221 )
1222 })?;
1223
1224 if let Ok(name) = obj.symbol_name(input_sym) {
1225 if let Some(symbol_id) = resolve.symbol_name_index.get(name).copied() {
1226 return resolve_global_symbol(
1227 obj,
1228 atom,
1229 kind,
1230 name,
1231 resolve.sym_table.get(symbol_id),
1232 resolve,
1233 );
1234 }
1235 }
1236
1237 resolve_input_symbol(obj, atom, kind, input_sym, resolve)
1238 }
1239
1240 fn dylib_import_symbol_id(
1241 obj: &ObjectFile,
1242 referent: Referent,
1243 resolve: &ResolveView<'_>,
1244 ) -> Option<SymbolId> {
1245 let symbol_id = symbol_referent_id(obj, referent, resolve)?;
1246 matches!(resolve.sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id)
1247 }
1248
1249 fn symbol_referent_id(
1250 obj: &ObjectFile,
1251 referent: Referent,
1252 resolve: &ResolveView<'_>,
1253 ) -> Option<SymbolId> {
1254 let Referent::Symbol(sym_idx) = referent else {
1255 return None;
1256 };
1257 let input_sym = obj.symbols.get(sym_idx as usize)?;
1258 let name = obj.symbol_name(input_sym).ok()?;
1259 resolve.symbol_name_index.get(name).copied()
1260 }
1261
1262 fn build_symbol_name_index(sym_table: &SymbolTable) -> HashMap<String, SymbolId> {
1263 sym_table
1264 .iter()
1265 .map(|(symbol_id, symbol)| {
1266 (
1267 sym_table.interner.resolve(symbol.name()).to_string(),
1268 symbol_id,
1269 )
1270 })
1271 .collect()
1272 }
1273
1274 fn resolve_global_symbol(
1275 obj: &ObjectFile,
1276 atom: &Atom,
1277 kind: RelocKind,
1278 name: &str,
1279 symbol: &Symbol,
1280 resolve: &ResolveView<'_>,
1281 ) -> Result<u64, RelocError> {
1282 match symbol {
1283 Symbol::Defined {
1284 atom: target_atom,
1285 value,
1286 ..
1287 } => resolve
1288 .atom_addrs
1289 .get(target_atom)
1290 .copied()
1291 .map(|addr| addr + *value)
1292 .ok_or_else(|| {
1293 reloc_error(
1294 atom,
1295 &obj.path,
1296 0,
1297 kind,
1298 name,
1299 "target atom missing final address".to_string(),
1300 )
1301 }),
1302 Symbol::DylibImport { .. } => Err(reloc_error(
1303 atom,
1304 &obj.path,
1305 0,
1306 kind,
1307 name,
1308 "direct dylib import reference must use GOT/stub/TLV machinery or a bound pointer slot"
1309 .to_string(),
1310 )),
1311 other => Err(reloc_error(
1312 atom,
1313 &obj.path,
1314 0,
1315 kind,
1316 name,
1317 format!("symbol resolved to unsupported state {:?}", other.kind()),
1318 )),
1319 }
1320 }
1321
1322 fn resolve_input_symbol(
1323 obj: &ObjectFile,
1324 atom: &Atom,
1325 kind: RelocKind,
1326 input_sym: &InputSymbol,
1327 resolve: &ResolveView<'_>,
1328 ) -> Result<u64, RelocError> {
1329 resolve_input_symbol_at_origin(atom.origin, obj, atom, kind, input_sym, resolve)
1330 }
1331
1332 fn resolve_input_symbol_at_origin(
1333 origin: InputId,
1334 obj: &ObjectFile,
1335 atom: &Atom,
1336 kind: RelocKind,
1337 input_sym: &InputSymbol,
1338 resolve: &ResolveView<'_>,
1339 ) -> Result<u64, RelocError> {
1340 match input_sym.kind() {
1341 SymKind::Abs => Ok(input_sym.value()),
1342 SymKind::Sect => {
1343 let section = obj.section_for_symbol(input_sym).ok_or_else(|| {
1344 reloc_error(
1345 atom,
1346 &obj.path,
1347 0,
1348 kind,
1349 &describe_input_symbol(obj, input_sym),
1350 "section-backed symbol did not resolve to an input section".to_string(),
1351 )
1352 })?;
1353 let section_offset = input_sym.value().saturating_sub(section.addr) as u32;
1354 resolve_input_section_offset(
1355 origin,
1356 input_sym.sect_idx(),
1357 section_offset,
1358 InputSectionResolveCtx {
1359 obj,
1360 atom,
1361 kind,
1362 referent: &describe_input_symbol(obj, input_sym),
1363 },
1364 resolve,
1365 )
1366 }
1367 SymKind::Undef => Err(reloc_error(
1368 atom,
1369 &obj.path,
1370 0,
1371 kind,
1372 &describe_input_symbol(obj, input_sym),
1373 "symbol remained undefined at relocation time".to_string(),
1374 )),
1375 SymKind::Indirect => Err(reloc_error(
1376 atom,
1377 &obj.path,
1378 0,
1379 kind,
1380 &describe_input_symbol(obj, input_sym),
1381 "indirect symbol relocations are not yet implemented".to_string(),
1382 )),
1383 }
1384 }
1385
1386 fn resolve_input_section_offset(
1387 origin: InputId,
1388 input_section: u8,
1389 input_offset: u32,
1390 ctx: InputSectionResolveCtx<'_>,
1391 resolve: &ResolveView<'_>,
1392 ) -> Result<u64, RelocError> {
1393 if let Some(atom_ids) = resolve.atoms_by_input_section.get(&(origin, input_section)) {
1394 if let Some((target_atom, delta)) = atom_ids.iter().find_map(|atom_id| {
1395 let candidate = resolve.atom_table.get(*atom_id);
1396 let start = candidate.input_offset;
1397 let end = candidate.input_offset.saturating_add(candidate.size);
1398 if start <= input_offset && input_offset < end {
1399 Some((*atom_id, input_offset - start))
1400 } else if input_offset == end {
1401 Some((*atom_id, candidate.size))
1402 } else {
1403 None
1404 }
1405 }) {
1406 let target_atom = canonical_atom(target_atom, resolve.icf_redirects);
1407 let atom_addr = resolve
1408 .atom_addrs
1409 .get(&target_atom)
1410 .copied()
1411 .ok_or_else(|| {
1412 reloc_error(
1413 ctx.atom,
1414 &ctx.obj.path,
1415 0,
1416 ctx.kind,
1417 ctx.referent,
1418 "section-backed symbol's containing atom is missing a final address"
1419 .to_string(),
1420 )
1421 })?;
1422 return Ok(atom_addr + delta as u64);
1423 }
1424 }
1425
1426 let section_addr = resolve
1427 .section_addrs
1428 .get(&(origin, input_section))
1429 .copied()
1430 .ok_or_else(|| {
1431 reloc_error(
1432 ctx.atom,
1433 &ctx.obj.path,
1434 0,
1435 ctx.kind,
1436 ctx.referent,
1437 "section-backed symbol's output section is missing".to_string(),
1438 )
1439 })?;
1440 Ok(section_addr + input_offset as u64)
1441 }
1442
1443 fn resolve_input_section_offset_simple(
1444 origin: InputId,
1445 input_section: u8,
1446 input_offset: u32,
1447 resolve: &ResolveView<'_>,
1448 ) -> Option<u64> {
1449 if let Some(atom_ids) = resolve.atoms_by_input_section.get(&(origin, input_section)) {
1450 if let Some((target_atom, delta)) = atom_ids.iter().find_map(|atom_id| {
1451 let candidate = resolve.atom_table.get(*atom_id);
1452 let start = candidate.input_offset;
1453 let end = candidate.input_offset.saturating_add(candidate.size);
1454 if start <= input_offset && input_offset < end {
1455 Some((*atom_id, input_offset - start))
1456 } else if input_offset == end {
1457 Some((*atom_id, candidate.size))
1458 } else {
1459 None
1460 }
1461 }) {
1462 let target_atom = canonical_atom(target_atom, resolve.icf_redirects);
1463 return resolve
1464 .atom_addrs
1465 .get(&target_atom)
1466 .copied()
1467 .map(|addr| addr + delta as u64);
1468 }
1469 }
1470 resolve
1471 .section_addrs
1472 .get(&(origin, input_section))
1473 .copied()
1474 .map(|section_addr| section_addr + input_offset as u64)
1475 }
1476
1477 fn canonical_atom(
1478 atom_id: crate::resolve::AtomId,
1479 redirects: Option<&HashMap<crate::resolve::AtomId, crate::resolve::AtomId>>,
1480 ) -> crate::resolve::AtomId {
1481 let Some(redirects) = redirects else {
1482 return atom_id;
1483 };
1484 let mut current = atom_id;
1485 while let Some(&next) = redirects.get(&current) {
1486 if next == current {
1487 break;
1488 }
1489 current = next;
1490 }
1491 current
1492 }
1493
1494 fn patch_unsigned(
1495 bytes: &mut [u8],
1496 atom: &Atom,
1497 obj: &ObjectFile,
1498 local_offset: u32,
1499 reloc: Reloc,
1500 target: u64,
1501 ) -> Result<(), RelocError> {
1502 let implicit_addend = read_implicit_addend(
1503 bytes,
1504 local_offset,
1505 reloc.length,
1506 atom,
1507 obj,
1508 reloc.kind,
1509 reloc.referent,
1510 )?;
1511 let value = target
1512 .wrapping_add_signed(reloc.addend)
1513 .wrapping_add_signed(implicit_addend);
1514 match reloc.length {
1515 RelocLength::Word => write_u32(
1516 bytes,
1517 local_offset,
1518 value as u32,
1519 atom,
1520 obj,
1521 reloc.kind,
1522 &describe_referent(obj, reloc.referent),
1523 ),
1524 RelocLength::Quad => write_u64(
1525 bytes,
1526 local_offset,
1527 value,
1528 atom,
1529 obj,
1530 reloc.kind,
1531 &describe_referent(obj, reloc.referent),
1532 ),
1533 other => Err(reloc_error(
1534 atom,
1535 &obj.path,
1536 local_offset,
1537 reloc.kind,
1538 &describe_referent(obj, reloc.referent),
1539 format!("unsupported UNSIGNED width {:?}", other),
1540 )),
1541 }
1542 }
1543
1544 fn direct_import_bind_supported(reloc: Reloc) -> bool {
1545 matches!(reloc.length, RelocLength::Quad) && !reloc.pcrel && reloc.subtrahend.is_none()
1546 }
1547
1548 fn clear_direct_import_slot(
1549 bytes: &mut [u8],
1550 atom: &Atom,
1551 obj: &ObjectFile,
1552 local_offset: u32,
1553 reloc: Reloc,
1554 ) -> Result<(), RelocError> {
1555 write_u64(
1556 bytes,
1557 local_offset,
1558 0,
1559 atom,
1560 obj,
1561 reloc.kind,
1562 &describe_referent(obj, reloc.referent),
1563 )
1564 }
1565
1566 fn patch_subtractor(
1567 bytes: &mut [u8],
1568 atom: &Atom,
1569 obj: &ObjectFile,
1570 local_offset: u32,
1571 reloc: Reloc,
1572 minuend: u64,
1573 resolve: &ResolveView<'_>,
1574 ) -> Result<(), RelocError> {
1575 let subtrahend = resolve_referent(
1576 obj,
1577 atom,
1578 reloc.kind,
1579 reloc.subtrahend.ok_or_else(|| {
1580 reloc_error(
1581 atom,
1582 &obj.path,
1583 local_offset,
1584 reloc.kind,
1585 &describe_referent(obj, reloc.referent),
1586 "SUBTRACTOR reloc missing subtrahend pair".to_string(),
1587 )
1588 })?,
1589 resolve,
1590 )?;
1591 let implicit_addend = read_implicit_addend(
1592 bytes,
1593 local_offset,
1594 reloc.length,
1595 atom,
1596 obj,
1597 reloc.kind,
1598 reloc.referent,
1599 )?;
1600 let value = minuend
1601 .wrapping_sub(subtrahend)
1602 .wrapping_add_signed(reloc.addend);
1603 let value = value.wrapping_add_signed(implicit_addend);
1604 match reloc.length {
1605 RelocLength::Word => write_u32(
1606 bytes,
1607 local_offset,
1608 value as u32,
1609 atom,
1610 obj,
1611 reloc.kind,
1612 &describe_referent(obj, reloc.referent),
1613 ),
1614 RelocLength::Quad => write_u64(
1615 bytes,
1616 local_offset,
1617 value,
1618 atom,
1619 obj,
1620 reloc.kind,
1621 &describe_referent(obj, reloc.referent),
1622 ),
1623 other => Err(reloc_error(
1624 atom,
1625 &obj.path,
1626 local_offset,
1627 reloc.kind,
1628 &describe_referent(obj, reloc.referent),
1629 format!("unsupported SUBTRACTOR width {:?}", other),
1630 )),
1631 }
1632 }
1633
1634 fn patch_branch26(
1635 bytes: &mut [u8],
1636 atom: &Atom,
1637 obj: &ObjectFile,
1638 local_offset: u32,
1639 reloc: Reloc,
1640 place: u64,
1641 target: u64,
1642 ) -> Result<(), RelocError> {
1643 let delta = target.wrapping_add_signed(reloc.addend).wrapping_sub(place) as i64;
1644 if delta & 0b11 != 0 {
1645 return Err(reloc_error(
1646 atom,
1647 &obj.path,
1648 local_offset,
1649 reloc.kind,
1650 &describe_referent(obj, reloc.referent),
1651 format!("branch target delta 0x{delta:x} is not 4-byte aligned"),
1652 ));
1653 }
1654 let imm = delta >> 2;
1655 if !fits_signed(imm, 26) {
1656 return Err(reloc_error(
1657 atom,
1658 &obj.path,
1659 local_offset,
1660 reloc.kind,
1661 &describe_referent(obj, reloc.referent),
1662 format!("branch target is out of BRANCH26 range (delta {delta:#x})"),
1663 ));
1664 }
1665 let insn = read_u32(
1666 bytes,
1667 local_offset,
1668 atom,
1669 obj,
1670 reloc.kind,
1671 &describe_referent(obj, reloc.referent),
1672 )?;
1673 let imm26 = (imm as u32) & 0x03ff_ffff;
1674 write_u32(
1675 bytes,
1676 local_offset,
1677 (insn & !0x03ff_ffff) | imm26,
1678 atom,
1679 obj,
1680 reloc.kind,
1681 &describe_referent(obj, reloc.referent),
1682 )
1683 }
1684
1685 fn patch_page21(
1686 bytes: &mut [u8],
1687 atom: &Atom,
1688 obj: &ObjectFile,
1689 local_offset: u32,
1690 reloc: Reloc,
1691 place: u64,
1692 target: u64,
1693 ) -> Result<(), RelocError> {
1694 let delta = page(target.wrapping_add_signed(reloc.addend)).wrapping_sub(page(place)) as i64;
1695 let imm = delta >> 12;
1696 if !fits_signed(imm, 21) {
1697 return Err(reloc_error(
1698 atom,
1699 &obj.path,
1700 local_offset,
1701 reloc.kind,
1702 &describe_referent(obj, reloc.referent),
1703 format!("page delta is out of PAGE21 range ({delta:#x})"),
1704 ));
1705 }
1706 let insn = read_u32(
1707 bytes,
1708 local_offset,
1709 atom,
1710 obj,
1711 reloc.kind,
1712 &describe_referent(obj, reloc.referent),
1713 )?;
1714 let encoded = (imm as u32) & 0x1f_ffff;
1715 let immlo = encoded & 0x3;
1716 let immhi = (encoded >> 2) & 0x7ffff;
1717 let patched = (insn & !((0x3 << 29) | (0x7ffff << 5))) | (immlo << 29) | (immhi << 5);
1718 write_u32(
1719 bytes,
1720 local_offset,
1721 patched,
1722 atom,
1723 obj,
1724 reloc.kind,
1725 &describe_referent(obj, reloc.referent),
1726 )
1727 }
1728
1729 fn patch_pageoff12(
1730 bytes: &mut [u8],
1731 atom: &Atom,
1732 obj: &ObjectFile,
1733 local_offset: u32,
1734 reloc: Reloc,
1735 target: u64,
1736 ) -> Result<(), RelocError> {
1737 let pageoff = target.wrapping_add_signed(reloc.addend) & 0xfff;
1738 let insn = read_u32(
1739 bytes,
1740 local_offset,
1741 atom,
1742 obj,
1743 reloc.kind,
1744 &describe_referent(obj, reloc.referent),
1745 )?;
1746 let imm = if is_add_immediate(insn) {
1747 pageoff
1748 } else {
1749 let shift = pageoff_load_store_shift(insn);
1750 let scale = 1u64 << shift;
1751 if !pageoff.is_multiple_of(scale) {
1752 return Err(reloc_error(
1753 atom,
1754 &obj.path,
1755 local_offset,
1756 reloc.kind,
1757 &describe_referent(obj, reloc.referent),
1758 format!("page offset 0x{pageoff:x} is not aligned for scaled load/store"),
1759 ));
1760 }
1761 pageoff >> shift
1762 };
1763 if imm > 0xfff {
1764 return Err(reloc_error(
1765 atom,
1766 &obj.path,
1767 local_offset,
1768 reloc.kind,
1769 &describe_referent(obj, reloc.referent),
1770 format!("pageoff immediate 0x{imm:x} exceeds 12 bits"),
1771 ));
1772 }
1773 let patched = (insn & !(0xfff << 10)) | ((imm as u32) << 10);
1774 write_u32(
1775 bytes,
1776 local_offset,
1777 patched,
1778 atom,
1779 obj,
1780 reloc.kind,
1781 &describe_referent(obj, reloc.referent),
1782 )
1783 }
1784
1785 fn pageoff_load_store_shift(insn: u32) -> u64 {
1786 if is_simd_fp_pageoff(insn) {
1787 simd_fp_pageoff_shift(insn)
1788 } else {
1789 ((insn >> 30) & 0b11) as u64
1790 }
1791 }
1792
1793 fn is_simd_fp_pageoff(insn: u32) -> bool {
1794 ((insn >> 24) & 0b111) == 0b101
1795 }
1796
1797 fn simd_fp_pageoff_shift(insn: u32) -> u64 {
1798 let size = ((insn >> 30) & 0b11) as u64;
1799 let opc = ((insn >> 22) & 0b11) as u64;
1800 if size == 0 && (opc & 0b10) != 0 {
1801 4
1802 } else {
1803 size
1804 }
1805 }
1806
1807 fn read_implicit_addend(
1808 bytes: &[u8],
1809 local_offset: u32,
1810 length: RelocLength,
1811 atom: &Atom,
1812 obj: &ObjectFile,
1813 kind: RelocKind,
1814 referent: Referent,
1815 ) -> Result<i64, RelocError> {
1816 match length {
1817 RelocLength::Word => Ok(read_u32(
1818 bytes,
1819 local_offset,
1820 atom,
1821 obj,
1822 kind,
1823 &describe_referent(obj, referent),
1824 )? as i32 as i64),
1825 RelocLength::Quad => Ok(read_u64(
1826 bytes,
1827 local_offset,
1828 atom,
1829 obj,
1830 kind,
1831 &describe_referent(obj, referent),
1832 )? as i64),
1833 other => Err(reloc_error(
1834 atom,
1835 &obj.path,
1836 local_offset,
1837 kind,
1838 &describe_referent(obj, referent),
1839 format!("unsupported implicit addend width {:?}", other),
1840 )),
1841 }
1842 }
1843
1844 fn patch_tlvp_pageoff12(
1845 bytes: &mut [u8],
1846 atom: &Atom,
1847 obj: &ObjectFile,
1848 local_offset: u32,
1849 reloc: Reloc,
1850 target: u64,
1851 ) -> Result<(), RelocError> {
1852 let pageoff = target.wrapping_add_signed(reloc.addend) & 0xfff;
1853 if pageoff > 0xfff {
1854 return Err(reloc_error(
1855 atom,
1856 &obj.path,
1857 local_offset,
1858 reloc.kind,
1859 &describe_referent(obj, reloc.referent),
1860 format!("pageoff immediate 0x{pageoff:x} exceeds 12 bits"),
1861 ));
1862 }
1863
1864 let insn = read_u32(
1865 bytes,
1866 local_offset,
1867 atom,
1868 obj,
1869 reloc.kind,
1870 &describe_referent(obj, reloc.referent),
1871 )?;
1872 let rd = insn & 0x1f;
1873 let rn = (insn >> 5) & 0x1f;
1874 let patched = 0x9100_0000 | ((pageoff as u32) << 10) | (rn << 5) | rd;
1875 write_u32(
1876 bytes,
1877 local_offset,
1878 patched,
1879 atom,
1880 obj,
1881 reloc.kind,
1882 &describe_referent(obj, reloc.referent),
1883 )
1884 }
1885
1886 fn patch_got_pageoff12_relaxed(
1887 bytes: &mut [u8],
1888 atom: &Atom,
1889 obj: &ObjectFile,
1890 local_offset: u32,
1891 reloc: Reloc,
1892 target: u64,
1893 ) -> Result<(), RelocError> {
1894 let pageoff = target.wrapping_add_signed(reloc.addend) & 0xfff;
1895 if pageoff > 0xfff {
1896 return Err(reloc_error(
1897 atom,
1898 &obj.path,
1899 local_offset,
1900 reloc.kind,
1901 &describe_referent(obj, reloc.referent),
1902 format!("pageoff immediate 0x{pageoff:x} exceeds 12 bits"),
1903 ));
1904 }
1905
1906 let insn = read_u32(
1907 bytes,
1908 local_offset,
1909 atom,
1910 obj,
1911 reloc.kind,
1912 &describe_referent(obj, reloc.referent),
1913 )?;
1914 let rd = insn & 0x1f;
1915 let rn = (insn >> 5) & 0x1f;
1916 let patched = 0x9100_0000 | ((pageoff as u32) << 10) | (rn << 5) | rd;
1917 write_u32(
1918 bytes,
1919 local_offset,
1920 patched,
1921 atom,
1922 obj,
1923 reloc.kind,
1924 &describe_referent(obj, reloc.referent),
1925 )
1926 }
1927
1928 fn synthesize_thread_variable_section(
1929 layout: &mut Layout,
1930 plan: &SyntheticPlan,
1931 atoms: &AtomTable,
1932 input_map: &HashMap<InputId, &ObjectFile>,
1933 reloc_cache: &HashMap<(InputId, u8), Vec<Reloc>>,
1934 resolve: &ResolveView<'_>,
1935 ) -> Result<(), RelocError> {
1936 let Some(_bootstrap_symbol) = plan.tlv_bootstrap_symbol else {
1937 return Ok(());
1938 };
1939 let Some(template_base) = layout
1940 .sections
1941 .iter()
1942 .find(|section| section.segment == "__DATA" && section.name == "__thread_data")
1943 .map(|section| section.addr)
1944 .or_else(|| {
1945 layout
1946 .sections
1947 .iter()
1948 .find(|section| section.segment == "__DATA" && section.name == "__thread_bss")
1949 .map(|section| section.addr)
1950 })
1951 else {
1952 return Ok(());
1953 };
1954 let Some(section) = layout
1955 .sections
1956 .iter_mut()
1957 .find(|section| section.segment == "__DATA" && section.name == "__thread_vars")
1958 else {
1959 return Ok(());
1960 };
1961
1962 for placed in &mut section.atoms {
1963 let atom = atoms.get(placed.atom);
1964 let obj = input_map.get(&atom.origin).ok_or_else(|| RelocError {
1965 input: PathBuf::from("<missing object>"),
1966 atom: placed.atom,
1967 atom_offset: 0,
1968 kind: RelocKind::Unsigned,
1969 referent: "__thread_vars".to_string(),
1970 detail: "missing parsed object for TLV descriptor atom".to_string(),
1971 })?;
1972 let relocs = reloc_cache
1973 .get(&(atom.origin, atom.input_section))
1974 .map(Vec::as_slice)
1975 .unwrap_or(&[]);
1976 if placed.size % THREAD_VARIABLE_DESCRIPTOR_SIZE as u64 != 0 {
1977 return Err(RelocError {
1978 input: PathBuf::from("<synthetic tlv>"),
1979 atom: placed.atom,
1980 atom_offset: 0,
1981 kind: RelocKind::Unsigned,
1982 referent: "__thread_vars".to_string(),
1983 detail: format!(
1984 "TLV descriptor atom has unexpected size 0x{:x}",
1985 placed.size
1986 ),
1987 });
1988 }
1989
1990 for descriptor_offset in
1991 (0..placed.size as usize).step_by(THREAD_VARIABLE_DESCRIPTOR_SIZE as usize)
1992 {
1993 let descriptor_offset_u32 = descriptor_offset as u32;
1994 let start = descriptor_offset;
1995 let end = start + THREAD_VARIABLE_DESCRIPTOR_SIZE as usize;
1996 let descriptor = placed.data.get_mut(start..end).ok_or_else(|| RelocError {
1997 input: PathBuf::from("<synthetic tlv>"),
1998 atom: placed.atom,
1999 atom_offset: descriptor_offset as u32,
2000 kind: RelocKind::Unsigned,
2001 referent: "__thread_vars".to_string(),
2002 detail: "TLV descriptor lands outside atom bytes".to_string(),
2003 })?;
2004
2005 descriptor[0..8].fill(0);
2006 let init_addr = resolve_tlv_init_address(
2007 descriptor,
2008 atom,
2009 obj,
2010 relocs,
2011 descriptor_offset_u32,
2012 input_map,
2013 resolve,
2014 )?;
2015 if init_addr < template_base {
2016 return Err(RelocError {
2017 input: PathBuf::from("<synthetic tlv>"),
2018 atom: placed.atom,
2019 atom_offset: descriptor_offset as u32 + 16,
2020 kind: RelocKind::Unsigned,
2021 referent: "__thread_vars".to_string(),
2022 detail: format!(
2023 "TLV init address 0x{init_addr:x} lands before TLS template base 0x{template_base:x}"
2024 ),
2025 });
2026 }
2027 let init_offset = init_addr - template_base;
2028 descriptor[16..24].copy_from_slice(&init_offset.to_le_bytes());
2029 }
2030 }
2031
2032 Ok(())
2033 }
2034
2035 fn resolve_tlv_init_address(
2036 descriptor: &[u8],
2037 atom: &Atom,
2038 obj: &ObjectFile,
2039 relocs: &[Reloc],
2040 descriptor_offset: u32,
2041 input_map: &HashMap<InputId, &ObjectFile>,
2042 resolve: &ResolveView<'_>,
2043 ) -> Result<u64, RelocError> {
2044 for owner in descriptor_owner_symbols(obj, atom, descriptor_offset) {
2045 if let Some(init_addr) = resolve_named_tlv_init(owner, atom, input_map, resolve)? {
2046 return Ok(init_addr);
2047 }
2048 }
2049
2050 let field_offset = atom.input_offset + descriptor_offset + 16;
2051 if let Some(reloc) = relocs_for_atom(relocs, atom).find(|reloc| reloc.offset == field_offset) {
2052 let target = resolve_tlv_descriptor_referent(obj, atom, reloc, resolve)?;
2053 return Ok(target.wrapping_add_signed(reloc.addend));
2054 }
2055
2056 Ok(u64::from_le_bytes(
2057 descriptor[16..24]
2058 .try_into()
2059 .expect("8-byte descriptor tail"),
2060 ))
2061 }
2062
2063 fn descriptor_owner_symbols<'a>(
2064 obj: &'a ObjectFile,
2065 atom: &'a Atom,
2066 descriptor_offset: u32,
2067 ) -> impl Iterator<Item = &'a InputSymbol> + 'a {
2068 let descriptor_start = atom.input_offset as u64 + descriptor_offset as u64;
2069 obj.symbols.iter().filter(move |input_sym| {
2070 input_sym.kind() == SymKind::Sect
2071 && input_sym.sect_idx() == atom.input_section
2072 && obj.section_for_symbol(input_sym).is_some_and(|section| {
2073 input_sym.value().saturating_sub(section.addr) == descriptor_start
2074 })
2075 })
2076 }
2077
2078 fn matching_tlv_init_symbol<'a>(
2079 obj: &'a ObjectFile,
2080 owner: &InputSymbol,
2081 ) -> Result<Option<&'a InputSymbol>, RelocError> {
2082 let owner_name = match obj.symbol_name(owner) {
2083 Ok(name) => name,
2084 Err(_) => return Ok(None),
2085 };
2086 let init_name = format!("{owner_name}$tlv$init");
2087 Ok(obj.symbols.iter().find(|input_sym| {
2088 obj.symbol_name(input_sym)
2089 .is_ok_and(|name| name == init_name)
2090 }))
2091 }
2092
2093 fn resolve_named_tlv_init(
2094 owner: &InputSymbol,
2095 atom: &Atom,
2096 input_map: &HashMap<InputId, &ObjectFile>,
2097 resolve: &ResolveView<'_>,
2098 ) -> Result<Option<u64>, RelocError> {
2099 for (&origin, obj) in input_map {
2100 let Some(init_symbol) = matching_tlv_init_symbol(obj, owner)? else {
2101 continue;
2102 };
2103 return Ok(Some(resolve_input_symbol_at_origin(
2104 origin,
2105 obj,
2106 atom,
2107 RelocKind::Unsigned,
2108 init_symbol,
2109 resolve,
2110 )?));
2111 }
2112 Ok(None)
2113 }
2114
2115 fn resolve_tlv_descriptor_referent(
2116 obj: &ObjectFile,
2117 atom: &Atom,
2118 reloc: Reloc,
2119 resolve: &ResolveView<'_>,
2120 ) -> Result<u64, RelocError> {
2121 match reloc.referent {
2122 Referent::Section(section_idx) => resolve
2123 .section_addrs
2124 .get(&(atom.origin, section_idx))
2125 .copied()
2126 .ok_or_else(|| {
2127 reloc_error(
2128 atom,
2129 &obj.path,
2130 reloc.offset.saturating_sub(atom.input_offset),
2131 reloc.kind,
2132 &format!("section #{section_idx}"),
2133 "TLV descriptor referent section was not laid out".to_string(),
2134 )
2135 }),
2136 Referent::Symbol(sym_idx) => {
2137 let input_sym = obj.symbols.get(sym_idx as usize).ok_or_else(|| {
2138 reloc_error(
2139 atom,
2140 &obj.path,
2141 reloc.offset.saturating_sub(atom.input_offset),
2142 reloc.kind,
2143 &format!("symbol #{sym_idx}"),
2144 "TLV descriptor symbol index is out of range".to_string(),
2145 )
2146 })?;
2147 resolve_input_symbol_at_origin(atom.origin, obj, atom, reloc.kind, input_sym, resolve)
2148 }
2149 }
2150 }
2151
2152 fn synthesize_got_section(
2153 layout: &mut Layout,
2154 plan: &SyntheticPlan,
2155 resolve: &ResolveView<'_>,
2156 ) -> Result<(), RelocError> {
2157 let Some(section) = layout
2158 .sections
2159 .iter_mut()
2160 .find(|section| section.segment == "__DATA_CONST" && section.name == "__got")
2161 else {
2162 return Ok(());
2163 };
2164
2165 for (idx, entry) in plan.got.entries.iter().enumerate() {
2166 let start = idx * 8;
2167 let end = start + 8;
2168 let value = match resolve.sym_table.get(entry.symbol) {
2169 Symbol::DylibImport { .. } => 0,
2170 Symbol::Defined {
2171 atom: target_atom,
2172 value,
2173 ..
2174 } => {
2175 resolve
2176 .atom_addrs
2177 .get(target_atom)
2178 .copied()
2179 .ok_or_else(|| RelocError {
2180 input: PathBuf::from("<synthetic got>"),
2181 atom: crate::resolve::AtomId(0),
2182 atom_offset: start as u32,
2183 kind: RelocKind::PointerToGot,
2184 referent: format!("symbol {:?}", entry.symbol),
2185 detail: "defined GOT target is missing final address".to_string(),
2186 })?
2187 + *value
2188 }
2189 other => {
2190 return Err(RelocError {
2191 input: PathBuf::from("<synthetic got>"),
2192 atom: crate::resolve::AtomId(0),
2193 atom_offset: start as u32,
2194 kind: RelocKind::PointerToGot,
2195 referent: format!("symbol {:?}", entry.symbol),
2196 detail: format!(
2197 "synthetic GOT currently does not support symbol kind {:?}",
2198 other.kind()
2199 ),
2200 });
2201 }
2202 };
2203 section.synthetic_data[start..end].copy_from_slice(&value.to_le_bytes());
2204 }
2205
2206 Ok(())
2207 }
2208
2209 fn synthesize_stub_section(
2210 layout: &mut Layout,
2211 plan: &SyntheticPlan,
2212 resolve: &ResolveView<'_>,
2213 ) -> Result<(), RelocError> {
2214 let Some(section) = layout
2215 .sections
2216 .iter_mut()
2217 .find(|section| section.segment == "__TEXT" && section.name == "__stubs")
2218 else {
2219 return Ok(());
2220 };
2221
2222 for (idx, entry) in plan.stubs.entries.iter().enumerate() {
2223 let start = idx * STUB_SIZE as usize;
2224 let end = start + STUB_SIZE as usize;
2225 let stub_addr = section.addr + (idx as u64) * STUB_SIZE as u64;
2226 let lazy_addr = resolve
2227 .lazy_pointer_addrs
2228 .get(&entry.symbol)
2229 .copied()
2230 .ok_or_else(|| RelocError {
2231 input: PathBuf::from("<synthetic stubs>"),
2232 atom: crate::resolve::AtomId(0),
2233 atom_offset: start as u32,
2234 kind: RelocKind::Branch26,
2235 referent: format!("symbol {:?}", entry.symbol),
2236 detail: "synthetic stub is missing lazy pointer target".to_string(),
2237 })?;
2238 let bytes = encode_stub(stub_addr, lazy_addr)?;
2239 section.synthetic_data[start..end].copy_from_slice(&bytes);
2240 }
2241
2242 Ok(())
2243 }
2244
2245 fn synthesize_lazy_pointer_section(
2246 layout: &mut Layout,
2247 plan: &SyntheticPlan,
2248 resolve: &ResolveView<'_>,
2249 ) -> Result<(), RelocError> {
2250 let Some(section) = layout
2251 .sections
2252 .iter_mut()
2253 .find(|section| section.segment == "__DATA" && section.name == "__la_symbol_ptr")
2254 else {
2255 return Ok(());
2256 };
2257
2258 for (idx, entry) in plan.lazy_pointers.entries.iter().enumerate() {
2259 let start = idx * 8;
2260 let end = start + 8;
2261 let helper_addr = resolve
2262 .stub_helper_entry_addrs
2263 .get(&entry.symbol)
2264 .copied()
2265 .ok_or_else(|| RelocError {
2266 input: PathBuf::from("<synthetic lazy pointers>"),
2267 atom: crate::resolve::AtomId(0),
2268 atom_offset: start as u32,
2269 kind: RelocKind::Unsigned,
2270 referent: format!("symbol {:?}", entry.symbol),
2271 detail: "lazy pointer is missing stub helper target".to_string(),
2272 })?;
2273 section.synthetic_data[start..end].copy_from_slice(&helper_addr.to_le_bytes());
2274 }
2275
2276 Ok(())
2277 }
2278
2279 fn synthesize_stub_helper_section(
2280 layout: &mut Layout,
2281 plan: &SyntheticPlan,
2282 resolve: &ResolveView<'_>,
2283 linkedit: &LinkEditPlan,
2284 ) -> Result<(), RelocError> {
2285 let Some(binder_symbol) = plan.binder_symbol else {
2286 return Ok(());
2287 };
2288 let Some(section) = layout
2289 .sections
2290 .iter_mut()
2291 .find(|section| section.segment == "__TEXT" && section.name == "__stub_helper")
2292 else {
2293 return Ok(());
2294 };
2295
2296 let header_addr = resolve.stub_helper_header_addr.ok_or_else(|| RelocError {
2297 input: PathBuf::from("<synthetic stub helper>"),
2298 atom: crate::resolve::AtomId(0),
2299 atom_offset: 0,
2300 kind: RelocKind::Branch26,
2301 referent: "__stub_helper".to_string(),
2302 detail: "stub helper section missing final address".to_string(),
2303 })?;
2304 let dyld_private_addr = resolve.dyld_private_addr.ok_or_else(|| RelocError {
2305 input: PathBuf::from("<synthetic stub helper>"),
2306 atom: crate::resolve::AtomId(0),
2307 atom_offset: 0,
2308 kind: RelocKind::Unsigned,
2309 referent: "__dyld_private".to_string(),
2310 detail: "dyld-private slot missing final address".to_string(),
2311 })?;
2312 let binder_got_addr = resolve
2313 .got_addrs
2314 .get(&binder_symbol)
2315 .copied()
2316 .ok_or_else(|| RelocError {
2317 input: PathBuf::from("<synthetic stub helper>"),
2318 atom: crate::resolve::AtomId(0),
2319 atom_offset: 0,
2320 kind: RelocKind::GotLoadPage21,
2321 referent: "dyld_stub_binder".to_string(),
2322 detail: "binder GOT slot missing final address".to_string(),
2323 })?;
2324
2325 let header = encode_stub_helper_header(header_addr, dyld_private_addr, binder_got_addr)?;
2326 section.synthetic_data[..STUB_HELPER_HEADER_SIZE as usize].copy_from_slice(&header);
2327
2328 for (idx, entry) in plan.lazy_pointers.entries.iter().enumerate() {
2329 let start = STUB_HELPER_HEADER_SIZE as usize + idx * STUB_HELPER_ENTRY_SIZE as usize;
2330 let end = start + STUB_HELPER_ENTRY_SIZE as usize;
2331 let entry_addr = resolve
2332 .stub_helper_entry_addrs
2333 .get(&entry.symbol)
2334 .copied()
2335 .ok_or_else(|| RelocError {
2336 input: PathBuf::from("<synthetic stub helper>"),
2337 atom: crate::resolve::AtomId(0),
2338 atom_offset: start as u32,
2339 kind: RelocKind::Branch26,
2340 referent: format!("symbol {:?}", entry.symbol),
2341 detail: "stub helper entry missing final address".to_string(),
2342 })?;
2343 let lazy_bind_offset =
2344 linkedit
2345 .lazy_bind_offset(entry.symbol)
2346 .ok_or_else(|| RelocError {
2347 input: PathBuf::from("<synthetic stub helper>"),
2348 atom: crate::resolve::AtomId(0),
2349 atom_offset: start as u32,
2350 kind: RelocKind::Unsigned,
2351 referent: format!("symbol {:?}", entry.symbol),
2352 detail: "lazy bind offset missing for stub helper entry".to_string(),
2353 })?;
2354 let bytes = encode_stub_helper_entry(entry_addr, header_addr, lazy_bind_offset)?;
2355 section.synthetic_data[start..end].copy_from_slice(&bytes);
2356 }
2357
2358 Ok(())
2359 }
2360
2361 fn encode_stub(
2362 stub_addr: u64,
2363 lazy_pointer_addr: u64,
2364 ) -> Result<[u8; STUB_SIZE as usize], RelocError> {
2365 let adrp = encode_adrp_reg(16, stub_addr, lazy_pointer_addr, "lazy pointer")?;
2366 let ldr = encode_ldr_x_reg_pageoff(16, lazy_pointer_addr, "lazy pointer")?;
2367 let br = 0xd61f0200u32;
2368
2369 let mut out = [0u8; STUB_SIZE as usize];
2370 out[0..4].copy_from_slice(&adrp.to_le_bytes());
2371 out[4..8].copy_from_slice(&ldr.to_le_bytes());
2372 out[8..12].copy_from_slice(&br.to_le_bytes());
2373 Ok(out)
2374 }
2375
2376 fn encode_stub_helper_header(
2377 header_addr: u64,
2378 dyld_private_addr: u64,
2379 binder_got_addr: u64,
2380 ) -> Result<[u8; STUB_HELPER_HEADER_SIZE as usize], RelocError> {
2381 let mut out = [0u8; STUB_HELPER_HEADER_SIZE as usize];
2382 let words = [
2383 encode_adrp_reg(17, header_addr, dyld_private_addr, "__dyld_private")?,
2384 encode_add_x_reg_pageoff(17, dyld_private_addr, "__dyld_private")?,
2385 encode_stp_x16_x17_sp_preindex(),
2386 encode_adrp_reg(
2387 16,
2388 header_addr + 12,
2389 binder_got_addr,
2390 "dyld_stub_binder@GOT",
2391 )?,
2392 encode_ldr_x_reg_pageoff(16, binder_got_addr, "dyld_stub_binder@GOT")?,
2393 0xd61f0200u32,
2394 ];
2395 for (idx, word) in words.iter().enumerate() {
2396 let start = idx * 4;
2397 out[start..start + 4].copy_from_slice(&word.to_le_bytes());
2398 }
2399 Ok(out)
2400 }
2401
2402 fn encode_stub_helper_entry(
2403 entry_addr: u64,
2404 header_addr: u64,
2405 lazy_bind_offset: u32,
2406 ) -> Result<[u8; STUB_HELPER_ENTRY_SIZE as usize], RelocError> {
2407 let mut out = [0u8; STUB_HELPER_ENTRY_SIZE as usize];
2408 let ldr = encode_ldr_w16_literal_plus8();
2409 let branch = encode_branch26(entry_addr + 4, header_addr, "__stub_helper header")?;
2410 out[0..4].copy_from_slice(&ldr.to_le_bytes());
2411 out[4..8].copy_from_slice(&branch.to_le_bytes());
2412 out[8..12].copy_from_slice(&lazy_bind_offset.to_le_bytes());
2413 Ok(out)
2414 }
2415
2416 fn encode_adrp_reg(reg: u8, place: u64, target: u64, referent: &str) -> Result<u32, RelocError> {
2417 let delta = page(target).wrapping_sub(page(place)) as i64;
2418 let imm = delta >> 12;
2419 if !fits_signed(imm, 21) {
2420 return Err(RelocError {
2421 input: PathBuf::from("<synthetic stubs>"),
2422 atom: crate::resolve::AtomId(0),
2423 atom_offset: 0,
2424 kind: RelocKind::Page21,
2425 referent: format!("{referent} @ {target:#x}"),
2426 detail: format!("page delta is out of PAGE21 range ({delta:#x})"),
2427 });
2428 }
2429
2430 let encoded = (imm as u32) & 0x1f_ffff;
2431 let immlo = encoded & 0x3;
2432 let immhi = (encoded >> 2) & 0x7ffff;
2433 Ok(0x9000_0000 | (immlo << 29) | (immhi << 5) | reg as u32)
2434 }
2435
2436 fn encode_ldr_x_reg_pageoff(reg: u8, target: u64, referent: &str) -> Result<u32, RelocError> {
2437 let low = target & 0xfff;
2438 if low & 0b111 != 0 {
2439 return Err(RelocError {
2440 input: PathBuf::from("<synthetic stubs>"),
2441 atom: crate::resolve::AtomId(0),
2442 atom_offset: 4,
2443 kind: RelocKind::PageOff12,
2444 referent: format!("{referent} @ {target:#x}"),
2445 detail: format!("lazy pointer page offset {low:#x} is not 8-byte aligned"),
2446 });
2447 }
2448 let imm12 = ((low >> 3) as u32) & 0xfff;
2449 Ok(0xf940_0000 | (imm12 << 10) | ((reg as u32) << 5) | reg as u32)
2450 }
2451
2452 fn encode_add_x_reg_pageoff(reg: u8, target: u64, referent: &str) -> Result<u32, RelocError> {
2453 let low = target & 0xfff;
2454 if low > 0xfff {
2455 return Err(RelocError {
2456 input: PathBuf::from("<synthetic stubs>"),
2457 atom: crate::resolve::AtomId(0),
2458 atom_offset: 4,
2459 kind: RelocKind::PageOff12,
2460 referent: format!("{referent} @ {target:#x}"),
2461 detail: format!("pageoff immediate 0x{low:x} exceeds 12 bits"),
2462 });
2463 }
2464 Ok(0x9100_0000 | ((low as u32) << 10) | ((reg as u32) << 5) | reg as u32)
2465 }
2466
2467 fn encode_stp_x16_x17_sp_preindex() -> u32 {
2468 let imm7 = ((-2i8 as u8) & 0x7f) as u32;
2469 0xa980_0000 | (imm7 << 15) | (17 << 10) | (31 << 5) | 16
2470 }
2471
2472 fn encode_ldr_w16_literal_plus8() -> u32 {
2473 0x1800_0050
2474 }
2475
2476 fn encode_branch26(place: u64, target: u64, referent: &str) -> Result<u32, RelocError> {
2477 let delta = target.wrapping_sub(place) as i64;
2478 if delta & 0b11 != 0 {
2479 return Err(RelocError {
2480 input: PathBuf::from("<synthetic stubs>"),
2481 atom: crate::resolve::AtomId(0),
2482 atom_offset: 0,
2483 kind: RelocKind::Branch26,
2484 referent: referent.to_string(),
2485 detail: format!("branch target delta 0x{delta:x} is not 4-byte aligned"),
2486 });
2487 }
2488 let imm = delta >> 2;
2489 if !fits_signed(imm, 26) {
2490 return Err(RelocError {
2491 input: PathBuf::from("<synthetic stubs>"),
2492 atom: crate::resolve::AtomId(0),
2493 atom_offset: 0,
2494 kind: RelocKind::Branch26,
2495 referent: referent.to_string(),
2496 detail: format!("branch target is out of BRANCH26 range (delta {delta:#x})"),
2497 });
2498 }
2499 Ok(0x1400_0000 | ((imm as u32) & 0x03ff_ffff))
2500 }
2501
2502 fn read_u32(
2503 bytes: &[u8],
2504 offset: u32,
2505 atom: &Atom,
2506 obj: &ObjectFile,
2507 kind: RelocKind,
2508 referent: &str,
2509 ) -> Result<u32, RelocError> {
2510 let start = offset as usize;
2511 let end = start + 4;
2512 let slice = bytes.get(start..end).ok_or_else(|| {
2513 reloc_error(
2514 atom,
2515 &obj.path,
2516 offset,
2517 kind,
2518 referent,
2519 "relocation write would run past the atom bytes".to_string(),
2520 )
2521 })?;
2522 Ok(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]))
2523 }
2524
2525 fn read_u64(
2526 bytes: &[u8],
2527 offset: u32,
2528 atom: &Atom,
2529 obj: &ObjectFile,
2530 kind: RelocKind,
2531 referent: &str,
2532 ) -> Result<u64, RelocError> {
2533 let start = offset as usize;
2534 let end = start + 8;
2535 let slice = bytes.get(start..end).ok_or_else(|| {
2536 reloc_error(
2537 atom,
2538 &obj.path,
2539 offset,
2540 kind,
2541 referent,
2542 "relocation write would run past the atom bytes".to_string(),
2543 )
2544 })?;
2545 Ok(u64::from_le_bytes([
2546 slice[0], slice[1], slice[2], slice[3], slice[4], slice[5], slice[6], slice[7],
2547 ]))
2548 }
2549
2550 fn write_u32(
2551 bytes: &mut [u8],
2552 offset: u32,
2553 value: u32,
2554 atom: &Atom,
2555 obj: &ObjectFile,
2556 kind: RelocKind,
2557 referent: &str,
2558 ) -> Result<(), RelocError> {
2559 let start = offset as usize;
2560 let end = start + 4;
2561 let slice = bytes.get_mut(start..end).ok_or_else(|| {
2562 reloc_error(
2563 atom,
2564 &obj.path,
2565 offset,
2566 kind,
2567 referent,
2568 "relocation write would run past the atom bytes".to_string(),
2569 )
2570 })?;
2571 slice.copy_from_slice(&value.to_le_bytes());
2572 Ok(())
2573 }
2574
2575 fn write_u64(
2576 bytes: &mut [u8],
2577 offset: u32,
2578 value: u64,
2579 atom: &Atom,
2580 obj: &ObjectFile,
2581 kind: RelocKind,
2582 referent: &str,
2583 ) -> Result<(), RelocError> {
2584 let start = offset as usize;
2585 let end = start + 8;
2586 let slice = bytes.get_mut(start..end).ok_or_else(|| {
2587 reloc_error(
2588 atom,
2589 &obj.path,
2590 offset,
2591 kind,
2592 referent,
2593 "relocation write would run past the atom bytes".to_string(),
2594 )
2595 })?;
2596 slice.copy_from_slice(&value.to_le_bytes());
2597 Ok(())
2598 }
2599
2600 fn is_add_immediate(insn: u32) -> bool {
2601 (insn & 0x1f00_0000) == 0x1100_0000
2602 }
2603
2604 fn page(value: u64) -> u64 {
2605 value & !0xfff
2606 }
2607
2608 fn fits_signed(value: i64, bits: u32) -> bool {
2609 let min = -(1i64 << (bits - 1));
2610 let max = (1i64 << (bits - 1)) - 1;
2611 value >= min && value <= max
2612 }
2613
2614 fn describe_referent(obj: &ObjectFile, referent: Referent) -> String {
2615 match referent {
2616 Referent::Section(idx) => format!("section #{idx}"),
2617 Referent::Symbol(sym_idx) => obj
2618 .symbols
2619 .get(sym_idx as usize)
2620 .map(|sym| describe_input_symbol(obj, sym))
2621 .unwrap_or_else(|| format!("symbol #{sym_idx}")),
2622 }
2623 }
2624
2625 fn describe_input_symbol(obj: &ObjectFile, input_sym: &InputSymbol) -> String {
2626 obj.symbol_name(input_sym)
2627 .map(str::to_string)
2628 .unwrap_or_else(|_| format!("symbol@strx{}", input_sym.strx()))
2629 }
2630
2631 fn reloc_error(
2632 atom: &Atom,
2633 path: &std::path::Path,
2634 atom_offset: u32,
2635 kind: RelocKind,
2636 referent: &str,
2637 detail: String,
2638 ) -> RelocError {
2639 RelocError {
2640 input: path.to_path_buf(),
2641 atom: atom.id,
2642 atom_offset,
2643 kind,
2644 referent: referent.to_string(),
2645 detail,
2646 }
2647 }
2648
2649 #[cfg(test)]
2650 mod tests {
2651 use super::*;
2652 use std::path::PathBuf;
2653
2654 use crate::atom::{AtomFlags, AtomSection};
2655 use crate::input::ObjectFile;
2656 use crate::macho::constants::{
2657 CPU_SUBTYPE_ARM64_ALL, CPU_TYPE_ARM64, MH_MAGIC_64, MH_OBJECT, N_EXT, N_SECT,
2658 S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_REGULAR,
2659 };
2660 use crate::macho::reader::MachHeader64;
2661 use crate::reloc::{write_raw_relocs, write_relocs};
2662 use crate::resolve::{InsertOutcome, Symbol, SymbolTable};
2663 use crate::section::InputSection;
2664 use crate::string_table::StringTable;
2665 use crate::symbol::{InputSymbol, RawNlist};
2666 use crate::OutputKind;
2667
2668 #[test]
2669 fn branch26_patches_low_bits() {
2670 let insn = 0x9400_0000u32;
2671 let delta = 0x40i64;
2672 let imm = ((delta >> 2) as u32) & 0x03ff_ffff;
2673 let patched = (insn & !0x03ff_ffff) | imm;
2674 assert_eq!(patched, 0x9400_0010);
2675 }
2676
2677 #[test]
2678 fn page21_encodes_immhi_and_immlo() {
2679 let insn = 0x9000_0000u32;
2680 let imm = 0x12345u32;
2681 let immlo = imm & 0x3;
2682 let immhi = (imm >> 2) & 0x7ffff;
2683 let patched = (insn & !((0x3 << 29) | (0x7ffff << 5))) | (immlo << 29) | (immhi << 5);
2684 assert_eq!(patched & (0x3 << 29), immlo << 29);
2685 assert_eq!(patched & (0x7ffff << 5), immhi << 5);
2686 }
2687
2688 #[test]
2689 fn add_immediate_pageoff_is_unscaled() {
2690 let insn = 0x9100_0000u32;
2691 assert!(is_add_immediate(insn));
2692 let patched = (insn & !(0xfff << 10)) | (0xabc << 10);
2693 assert_eq!((patched >> 10) & 0xfff, 0xabc);
2694 }
2695
2696 #[test]
2697 fn load_store_pageoff_uses_size_scaling() {
2698 let insn = 0xf940_0000u32;
2699 assert!(!is_add_immediate(insn));
2700 let shift = pageoff_load_store_shift(insn);
2701 assert_eq!(shift, 0b11);
2702 let pageoff = 0x3f8u64;
2703 let imm = pageoff >> shift;
2704 let patched = (insn & !(0xfff << 10)) | ((imm as u32) << 10);
2705 assert_eq!((patched >> 10) & 0xfff, 0x7f);
2706 }
2707
2708 #[test]
2709 fn simd_q_pageoff_uses_16_byte_scaling() {
2710 let insn = 0x3dc0_0100u32;
2711 assert!(!is_add_immediate(insn));
2712 assert!(is_simd_fp_pageoff(insn));
2713 let shift = pageoff_load_store_shift(insn);
2714 assert_eq!(shift, 4);
2715 let pageoff = 0x690u64;
2716 let imm = pageoff >> shift;
2717 let patched = (insn & !(0xfff << 10)) | ((imm as u32) << 10);
2718 assert_eq!((patched >> 10) & 0xfff, 0x69);
2719 assert_eq!(patched, 0x3dc1_a500);
2720 }
2721
2722 #[test]
2723 fn signed_fit_helper_matches_branch_range() {
2724 assert!(fits_signed((1 << 25) - 1, 26));
2725 assert!(fits_signed(-(1 << 25), 26));
2726 assert!(!fits_signed(1 << 25, 26));
2727 assert!(!fits_signed(-(1 << 25) - 1, 26));
2728 }
2729
2730 #[test]
2731 fn branch26_span_fast_path_rejects_only_large_non_linkedit_images() {
2732 let small = Layout {
2733 kind: OutputKind::Executable,
2734 segments: Vec::new(),
2735 sections: vec![
2736 output_section("__TEXT", "__text", 0x1_0000_0000, 0x100),
2737 output_section("__DATA", "__data", 0x1_0001_0000, 0x100),
2738 output_section("__LINKEDIT", "__linkedit", 0x1_8000_0000, 0x1000),
2739 ],
2740 };
2741 assert!(layout_fits_branch26_span(&small));
2742
2743 let large = Layout {
2744 kind: OutputKind::Executable,
2745 segments: Vec::new(),
2746 sections: vec![
2747 output_section("__TEXT", "__text", 0x1_0000_0000, 0x100),
2748 output_section(
2749 "__DATA",
2750 "__data",
2751 0x1_0000_0000 + BRANCH26_MAX_FORWARD_DELTA_BYTES + 1,
2752 0x100,
2753 ),
2754 ],
2755 };
2756 assert!(!layout_fits_branch26_span(&large));
2757 }
2758
2759 #[test]
2760 fn thunk_plan_splits_monolithic_text_section_into_multiple_islands() {
2761 let gap = 0x0900_0000u32;
2762 let caller2_offset = 4 + gap;
2763 let target_offset = 8 + gap * 2;
2764 let raw_relocs = branch26_raw_relocs(&[0, caller2_offset]);
2765 let object = thunk_test_object(raw_relocs, target_offset as u64, target_offset as u64 + 4);
2766
2767 let mut atoms = AtomTable::new();
2768 let caller1 = atoms.push(test_atom(0, 4));
2769 atoms.push(test_atom(4, gap));
2770 let caller2 = atoms.push(test_atom(caller2_offset, 4));
2771 atoms.push(test_atom(caller2_offset + 4, gap));
2772 let target = atoms.push(test_atom(target_offset, 4));
2773
2774 let mut sym_table = SymbolTable::new();
2775 let target_name = sym_table.intern("_target");
2776 let insert = sym_table
2777 .insert(Symbol::Defined {
2778 name: target_name,
2779 origin: crate::resolve::InputId(0),
2780 atom: target,
2781 value: 0,
2782 weak: false,
2783 private_extern: false,
2784 no_dead_strip: false,
2785 })
2786 .unwrap();
2787 assert!(matches!(insert, InsertOutcome::Inserted(_)));
2788
2789 let inputs = [LayoutInput {
2790 id: crate::resolve::InputId(0),
2791 object: &object,
2792 load_order: 0,
2793 archive_member_offset: None,
2794 }];
2795 let opts = LinkOptions {
2796 kind: OutputKind::Executable,
2797 ..LinkOptions::default()
2798 };
2799 let base_layout = Layout::build(OutputKind::Executable, &inputs, &atoms, 0);
2800 let parsed_relocs = crate::macho::writer::build_parsed_reloc_cache(&inputs).unwrap();
2801 let plan = plan_thunks(
2802 &opts,
2803 ThunkPlanningContext {
2804 layout: &base_layout,
2805 inputs: &inputs,
2806 atoms: &atoms,
2807 sym_table: &sym_table,
2808 synthetic_plan: None,
2809 icf_redirects: None,
2810 parsed_relocs: &parsed_relocs,
2811 },
2812 )
2813 .unwrap()
2814 .unwrap();
2815
2816 assert_eq!(
2817 plan.redirect_for(caller1, 0),
2818 Some(0),
2819 "expected first caller to use its own thunk"
2820 );
2821 assert_eq!(
2822 plan.redirect_for(caller2, 0),
2823 Some(1),
2824 "expected second caller to use its own thunk"
2825 );
2826
2827 let rebuilt = Layout::build_with_synthetics_and_extra_filtered(
2828 OutputKind::Executable,
2829 &inputs,
2830 &atoms,
2831 0,
2832 None,
2833 None,
2834 crate::layout::ExtraLayoutSections {
2835 extra_sections: &plan.output_sections(),
2836 split_after_atoms: &plan.split_after_atoms(),
2837 },
2838 );
2839 let text_section_count = rebuilt
2840 .sections
2841 .iter()
2842 .filter(|section| section.segment == "__TEXT" && section.name == "__text")
2843 .count();
2844 let thunk_section_count = rebuilt
2845 .sections
2846 .iter()
2847 .filter(|section| section.segment == "__TEXT" && section.name == "__thunks")
2848 .count();
2849 assert_eq!(
2850 text_section_count, 3,
2851 "expected the monolithic text section to split around the two caller atoms"
2852 );
2853 assert_eq!(
2854 thunk_section_count, 2,
2855 "expected one thunk island per far caller atom"
2856 );
2857 let text_sequence: Vec<_> = rebuilt
2858 .sections
2859 .iter()
2860 .filter(|section| section.segment == "__TEXT")
2861 .map(|section| section.name.as_str())
2862 .collect();
2863 assert_eq!(
2864 text_sequence,
2865 vec!["__text", "__thunks", "__text", "__thunks", "__text"]
2866 );
2867
2868 let replan = plan_thunks(
2869 &opts,
2870 ThunkPlanningContext {
2871 layout: &rebuilt,
2872 inputs: &inputs,
2873 atoms: &atoms,
2874 sym_table: &sym_table,
2875 synthetic_plan: None,
2876 icf_redirects: None,
2877 parsed_relocs: &parsed_relocs,
2878 },
2879 )
2880 .unwrap()
2881 .unwrap();
2882 assert_eq!(
2883 replan, plan,
2884 "expected thunk planning to converge once the intra-section islands exist"
2885 );
2886 }
2887
2888 fn branch26_raw_relocs(offsets: &[u32]) -> Vec<u8> {
2889 let relocs: Vec<_> = offsets
2890 .iter()
2891 .copied()
2892 .map(|offset| crate::reloc::Reloc {
2893 offset,
2894 kind: RelocKind::Branch26,
2895 length: RelocLength::Word,
2896 pcrel: true,
2897 referent: Referent::Symbol(0),
2898 addend: 0,
2899 subtrahend: None,
2900 })
2901 .collect();
2902 let raws = write_relocs(&relocs).unwrap();
2903 let mut out = Vec::new();
2904 write_raw_relocs(&raws, &mut out);
2905 out
2906 }
2907
2908 fn output_section(segment: &str, name: &str, addr: u64, size: u64) -> OutputSection {
2909 OutputSection {
2910 segment: segment.into(),
2911 name: name.into(),
2912 kind: SectionKind::Text,
2913 align_pow2: 2,
2914 flags: 0,
2915 reserved1: 0,
2916 reserved2: 0,
2917 reserved3: 0,
2918 atoms: Vec::new(),
2919 synthetic_offset: 0,
2920 synthetic_data: Vec::new(),
2921 addr,
2922 size,
2923 file_off: 0,
2924 }
2925 }
2926
2927 fn thunk_test_object(raw_relocs: Vec<u8>, target_offset: u64, section_size: u64) -> ObjectFile {
2928 let strings = b"\0_target\0".to_vec();
2929 ObjectFile {
2930 path: PathBuf::from("/tmp/thunk-plan.o"),
2931 header: MachHeader64 {
2932 magic: MH_MAGIC_64,
2933 cputype: CPU_TYPE_ARM64,
2934 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
2935 filetype: MH_OBJECT,
2936 ncmds: 0,
2937 sizeofcmds: 0,
2938 flags: 0,
2939 reserved: 0,
2940 },
2941 commands: Vec::new(),
2942 sections: vec![InputSection {
2943 segname: "__TEXT".into(),
2944 sectname: "__text".into(),
2945 kind: crate::section::SectionKind::Text,
2946 addr: 0,
2947 size: section_size,
2948 align_pow2: 2,
2949 flags: S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
2950 offset: 0,
2951 reloff: 0,
2952 nreloc: (raw_relocs.len() / 8) as u32,
2953 reserved1: 0,
2954 reserved2: 0,
2955 reserved3: 0,
2956 data: Vec::new(),
2957 raw_relocs,
2958 }],
2959 symbols: vec![InputSymbol::from_raw(RawNlist {
2960 strx: 1,
2961 n_type: N_SECT | N_EXT,
2962 n_sect: 1,
2963 n_desc: 0,
2964 n_value: target_offset,
2965 })],
2966 strings: StringTable::from_bytes(strings),
2967 symtab: None,
2968 dysymtab: None,
2969 loh: Vec::new(),
2970 data_in_code: Vec::new(),
2971 }
2972 }
2973
2974 fn test_atom(input_offset: u32, size: u32) -> Atom {
2975 Atom {
2976 id: crate::resolve::AtomId(0),
2977 origin: crate::resolve::InputId(0),
2978 input_section: 1,
2979 section: AtomSection::Text,
2980 input_offset,
2981 size,
2982 align_pow2: 2,
2983 owner: None,
2984 alt_entries: Vec::new(),
2985 data: Vec::new(),
2986 flags: AtomFlags::NONE,
2987 parent_of: None,
2988 }
2989 }
2990 }
2991