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