Rust · 45528 bytes Raw Blame History
1 pub mod code_sig;
2 pub mod dyld_info;
3 pub mod got;
4 pub mod stubs;
5 pub mod tlv;
6 pub mod unwind;
7
8 use std::collections::HashMap;
9 use std::fmt;
10 use std::path::PathBuf;
11
12 use crate::atom::{Atom, AtomSection, AtomTable};
13 use crate::input::ObjectFile;
14 use crate::layout::LayoutInput;
15 use crate::macho::constants::{
16 S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_LAZY_SYMBOL_POINTERS,
17 S_NON_LAZY_SYMBOL_POINTERS, S_REGULAR, S_SYMBOL_STUBS, S_THREAD_LOCAL_VARIABLE_POINTERS,
18 };
19 use crate::reloc::{parse_raw_relocs, parse_relocs, Referent, Reloc, RelocKind, RelocLength};
20 use crate::resolve::{
21 AtomId, DylibId, DylibInput, InputId, InsertOutcome, Symbol, SymbolId, SymbolTable,
22 };
23 use crate::section::{OutputSection, SectionKind};
24
25 use self::got::GotSection;
26 use self::stubs::{
27 LazyPointerSection, StubsSection, DYLD_PRIVATE_SIZE, STUB_HELPER_ENTRY_SIZE,
28 STUB_HELPER_HEADER_SIZE,
29 };
30 use self::tlv::{ThreadPointerSection, THREAD_POINTER_SIZE};
31
32 const COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET: u32 = 16;
33
34 #[derive(Debug, Clone, PartialEq, Eq)]
35 pub struct SyntheticPlan {
36 pub got: GotSection,
37 pub stubs: StubsSection,
38 pub lazy_pointers: LazyPointerSection,
39 pub thread_pointers: ThreadPointerSection,
40 pub direct_binds: Vec<DirectBind>,
41 pub binder_symbol: Option<SymbolId>,
42 pub tlv_bootstrap_symbol: Option<SymbolId>,
43 pub needs_dyld_private: bool,
44 }
45
46 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
47 pub struct DirectBind {
48 pub atom: AtomId,
49 pub atom_offset: u32,
50 pub symbol: SymbolId,
51 pub addend: i64,
52 }
53
54 #[derive(Debug, Clone, PartialEq, Eq)]
55 pub struct SynthError {
56 pub input: PathBuf,
57 pub atom: crate::resolve::AtomId,
58 pub reloc_offset: u32,
59 pub kind: RelocKind,
60 pub detail: String,
61 }
62
63 impl fmt::Display for SynthError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 write!(
66 f,
67 "{}: synthetic planning for {:?} at atom {:?}+0x{:x}: {}",
68 self.input.display(),
69 self.kind,
70 self.atom,
71 self.reloc_offset,
72 self.detail
73 )
74 }
75 }
76
77 impl std::error::Error for SynthError {}
78
79 impl SyntheticPlan {
80 pub fn build(
81 inputs: &[LayoutInput<'_>],
82 atoms: &AtomTable,
83 sym_table: &mut SymbolTable,
84 dylibs: &[DylibInput],
85 ) -> Result<Self, SynthError> {
86 let input_map: HashMap<InputId, &ObjectFile> = inputs
87 .iter()
88 .map(|input| (input.id, input.object))
89 .collect();
90 let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new();
91 for input in inputs {
92 for (sect_idx, section) in input.object.sections.iter().enumerate() {
93 let relocs = if section.nreloc == 0 {
94 Vec::new()
95 } else {
96 let raws = parse_raw_relocs(&section.raw_relocs, 0, section.nreloc).map_err(
97 |err| SynthError {
98 input: input.object.path.clone(),
99 atom: crate::resolve::AtomId(0),
100 reloc_offset: 0,
101 kind: RelocKind::Unsigned,
102 detail: err.to_string(),
103 },
104 )?;
105 parse_relocs(&raws).map_err(|err| SynthError {
106 input: input.object.path.clone(),
107 atom: crate::resolve::AtomId(0),
108 reloc_offset: 0,
109 kind: RelocKind::Unsigned,
110 detail: err.to_string(),
111 })?
112 };
113 reloc_cache.insert((input.id, (sect_idx + 1) as u8), relocs);
114 }
115 }
116
117 let mut got = GotSection::default();
118 let mut stubs = StubsSection::default();
119 let mut lazy_pointers = LazyPointerSection::default();
120 let mut thread_pointers = ThreadPointerSection::default();
121 let mut direct_binds = Vec::new();
122
123 for (atom_id, atom) in atoms.iter() {
124 let obj = input_map.get(&atom.origin).ok_or_else(|| SynthError {
125 input: PathBuf::from("<missing object>"),
126 atom: atom_id,
127 reloc_offset: 0,
128 kind: RelocKind::Unsigned,
129 detail: "missing parsed object".to_string(),
130 })?;
131 let relocs = reloc_cache
132 .get(&(atom.origin, atom.input_section))
133 .map(Vec::as_slice)
134 .unwrap_or(&[]);
135 for reloc in relocs_for_atom(relocs, atom) {
136 if atom.section == AtomSection::CompactUnwind
137 && reloc.kind == RelocKind::Unsigned
138 && reloc.offset == atom.input_offset + COMPACT_UNWIND_PERSONALITY_FIELD_OFFSET
139 {
140 if let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table) {
141 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
142 }
143 continue;
144 }
145 match reloc.kind {
146 RelocKind::Unsigned => {
147 // `__thread_vars` descriptors carry their own dedicated
148 // bind encoding later in the writer; don't double-count
149 // those dylib imports as generic bound pointer slots.
150 if matches!(atom.section, AtomSection::ThreadLocalVariables) {
151 continue;
152 }
153 let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table)
154 else {
155 continue;
156 };
157 if direct_import_bind_supported(reloc) {
158 direct_binds.push(DirectBind {
159 atom: atom_id,
160 atom_offset: reloc.offset.saturating_sub(atom.input_offset),
161 symbol: symbol_id,
162 addend: reloc.addend,
163 });
164 }
165 }
166 RelocKind::GotLoadPage21
167 | RelocKind::GotLoadPageOff12
168 | RelocKind::PointerToGot => {
169 if matches!(
170 reloc.kind,
171 RelocKind::GotLoadPage21 | RelocKind::GotLoadPageOff12
172 ) {
173 let Some(symbol_id) =
174 dylib_import_referent(obj, reloc.referent, sym_table)
175 else {
176 continue;
177 };
178 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
179 continue;
180 }
181 let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, sym_table)
182 else {
183 continue;
184 };
185 if matches!(reloc.kind, RelocKind::PointerToGot)
186 && matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. })
187 {
188 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
189 continue;
190 }
191 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
192 }
193 RelocKind::Branch26 => {
194 let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table)
195 else {
196 continue;
197 };
198 let dylib = match sym_table.get(symbol_id) {
199 Symbol::DylibImport { dylib, .. } => *dylib,
200 _ => continue,
201 };
202 stubs.intern(symbol_id, dylib, dylib_import_is_weak(sym_table, symbol_id));
203 lazy_pointers.intern(
204 symbol_id,
205 dylib,
206 dylib_import_is_weak(sym_table, symbol_id),
207 );
208 }
209 RelocKind::TlvpLoadPage21 | RelocKind::TlvpLoadPageOff12 => {
210 if let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, sym_table)
211 {
212 if tlv_symbol_needs_got(sym_table, symbol_id) {
213 got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id));
214 } else if tlv_symbol_needs_thread_pointer(sym_table, symbol_id) {
215 thread_pointers.intern(symbol_id);
216 }
217 }
218 }
219 _ => {}
220 }
221 }
222 }
223
224 sort_symbol_indexed_entries(
225 &mut got.entries,
226 &mut got.index,
227 |entry| entry.symbol,
228 sym_table,
229 );
230 sort_symbol_indexed_entries(
231 &mut stubs.entries,
232 &mut stubs.index,
233 |entry| entry.symbol,
234 sym_table,
235 );
236 sort_symbol_indexed_entries(
237 &mut lazy_pointers.entries,
238 &mut lazy_pointers.index,
239 |entry| entry.symbol,
240 sym_table,
241 );
242 sort_symbol_indexed_entries(
243 &mut thread_pointers.entries,
244 &mut thread_pointers.index,
245 |entry| entry.symbol,
246 sym_table,
247 );
248
249 let mut binder_symbol = None;
250 let mut tlv_bootstrap_symbol = None;
251 let mut needs_dyld_private = false;
252 if !stubs.entries.is_empty() {
253 let binder = ensure_stub_helper_support(sym_table, dylibs, &mut got)?;
254 binder_symbol = Some(binder);
255 needs_dyld_private = true;
256 }
257 if !thread_pointers.entries.is_empty() || inputs_have_tlv_descriptors(inputs) {
258 tlv_bootstrap_symbol = ensure_tlv_support(sym_table, dylibs)?;
259 }
260
261 Ok(SyntheticPlan {
262 got,
263 stubs,
264 lazy_pointers,
265 thread_pointers,
266 direct_binds,
267 binder_symbol,
268 tlv_bootstrap_symbol,
269 needs_dyld_private,
270 })
271 }
272
273 pub fn output_sections(&self) -> Vec<OutputSection> {
274 let mut out = Vec::new();
275 if !self.stubs.entries.is_empty() {
276 out.push(OutputSection {
277 segment: "__TEXT".into(),
278 name: "__stubs".into(),
279 kind: SectionKind::SymbolStubs,
280 align_pow2: 2,
281 flags: S_SYMBOL_STUBS | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
282 reserved1: 0,
283 reserved2: stubs::STUB_SIZE,
284 reserved3: 0,
285 atoms: Vec::new(),
286 synthetic_offset: 0,
287 synthetic_data: vec![0; self.stubs.entries.len() * stubs::STUB_SIZE as usize],
288 addr: 0,
289 size: (self.stubs.entries.len() as u64) * stubs::STUB_SIZE as u64,
290 file_off: 0,
291 });
292 }
293 if self.binder_symbol.is_some() {
294 out.push(OutputSection {
295 segment: "__TEXT".into(),
296 name: "__stub_helper".into(),
297 kind: SectionKind::Text,
298 align_pow2: 2,
299 flags: S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
300 reserved1: 0,
301 reserved2: 0,
302 reserved3: 0,
303 atoms: Vec::new(),
304 synthetic_offset: 0,
305 synthetic_data: vec![
306 0;
307 STUB_HELPER_HEADER_SIZE as usize
308 + self.lazy_pointers.entries.len()
309 * STUB_HELPER_ENTRY_SIZE as usize
310 ],
311 addr: 0,
312 size: STUB_HELPER_HEADER_SIZE as u64
313 + (self.lazy_pointers.entries.len() as u64) * STUB_HELPER_ENTRY_SIZE as u64,
314 file_off: 0,
315 });
316 }
317 if !self.got.entries.is_empty() {
318 out.push(OutputSection {
319 segment: "__DATA_CONST".into(),
320 name: "__got".into(),
321 kind: SectionKind::NonLazySymbolPointers,
322 align_pow2: 3,
323 flags: S_NON_LAZY_SYMBOL_POINTERS,
324 reserved1: 0,
325 reserved2: 0,
326 reserved3: 0,
327 atoms: Vec::new(),
328 synthetic_offset: 0,
329 synthetic_data: vec![0; self.got.entries.len() * 8],
330 addr: 0,
331 size: (self.got.entries.len() as u64) * 8,
332 file_off: 0,
333 });
334 }
335 if self.needs_dyld_private {
336 out.push(OutputSection {
337 segment: "__DATA".into(),
338 name: "__data".into(),
339 kind: SectionKind::Data,
340 align_pow2: 3,
341 flags: S_REGULAR,
342 reserved1: 0,
343 reserved2: 0,
344 reserved3: 0,
345 atoms: Vec::new(),
346 synthetic_offset: 0,
347 synthetic_data: vec![0; DYLD_PRIVATE_SIZE as usize],
348 addr: 0,
349 size: DYLD_PRIVATE_SIZE as u64,
350 file_off: 0,
351 });
352 }
353 if !self.lazy_pointers.entries.is_empty() {
354 out.push(OutputSection {
355 segment: "__DATA".into(),
356 name: "__la_symbol_ptr".into(),
357 kind: SectionKind::LazySymbolPointers,
358 align_pow2: 3,
359 flags: S_LAZY_SYMBOL_POINTERS,
360 reserved1: 0,
361 reserved2: 0,
362 reserved3: 0,
363 atoms: Vec::new(),
364 synthetic_offset: 0,
365 synthetic_data: vec![0; self.lazy_pointers.entries.len() * 8],
366 addr: 0,
367 size: (self.lazy_pointers.entries.len() as u64) * 8,
368 file_off: 0,
369 });
370 }
371 if !self.thread_pointers.entries.is_empty() {
372 out.push(OutputSection {
373 segment: "__DATA".into(),
374 name: "__thread_ptrs".into(),
375 kind: SectionKind::ThreadLocalVariablePointers,
376 align_pow2: 3,
377 flags: S_THREAD_LOCAL_VARIABLE_POINTERS,
378 reserved1: 0,
379 reserved2: 0,
380 reserved3: 0,
381 atoms: Vec::new(),
382 synthetic_offset: 0,
383 synthetic_data: vec![
384 0;
385 self.thread_pointers.entries.len()
386 * THREAD_POINTER_SIZE as usize
387 ],
388 addr: 0,
389 size: (self.thread_pointers.entries.len() as u64) * THREAD_POINTER_SIZE as u64,
390 file_off: 0,
391 });
392 }
393 out
394 }
395 }
396
397 fn sort_symbol_indexed_entries<T, F>(
398 entries: &mut [T],
399 index: &mut HashMap<SymbolId, usize>,
400 symbol_of: F,
401 sym_table: &SymbolTable,
402 ) where
403 F: Fn(&T) -> SymbolId,
404 {
405 entries.sort_by(|lhs, rhs| {
406 let lhs_name = sym_table
407 .interner
408 .resolve(sym_table.get(symbol_of(lhs)).name());
409 let rhs_name = sym_table
410 .interner
411 .resolve(sym_table.get(symbol_of(rhs)).name());
412 lhs_name.cmp(rhs_name)
413 });
414 index.clear();
415 for (idx, entry) in entries.iter().enumerate() {
416 index.insert(symbol_of(entry), idx);
417 }
418 }
419
420 fn relocs_for_atom<'a>(relocs: &'a [Reloc], atom: &Atom) -> impl Iterator<Item = Reloc> + 'a {
421 let start = atom.input_offset;
422 let end = atom.input_offset + atom.size;
423 relocs.iter().copied().filter(move |reloc| {
424 let reloc_end = reloc.offset + reloc.width_for_planning();
425 reloc.offset >= start && reloc_end <= end
426 })
427 }
428
429 trait RelocPlanningWidth {
430 fn width_for_planning(self) -> u32;
431 }
432
433 impl RelocPlanningWidth for Reloc {
434 fn width_for_planning(self) -> u32 {
435 match self.kind {
436 RelocKind::Subtractor => 8,
437 _ => match self.length {
438 crate::reloc::RelocLength::Byte => 1,
439 crate::reloc::RelocLength::Half => 2,
440 crate::reloc::RelocLength::Word => 4,
441 crate::reloc::RelocLength::Quad => 8,
442 },
443 }
444 }
445 }
446
447 fn dylib_import_referent(
448 obj: &ObjectFile,
449 referent: Referent,
450 sym_table: &SymbolTable,
451 ) -> Option<SymbolId> {
452 let symbol_id = symbol_referent_id(obj, referent, sym_table)?;
453 matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id)
454 }
455
456 fn symbol_referent_id(
457 obj: &ObjectFile,
458 referent: Referent,
459 sym_table: &SymbolTable,
460 ) -> Option<SymbolId> {
461 let Referent::Symbol(sym_idx) = referent else {
462 return None;
463 };
464 let input_sym = obj.symbols.get(sym_idx as usize)?;
465 let name = obj.symbol_name(input_sym).ok()?;
466 let (symbol_id, _) = sym_table
467 .iter()
468 .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name)?;
469 Some(symbol_id)
470 }
471
472 fn dylib_import_is_weak(sym_table: &SymbolTable, symbol_id: SymbolId) -> bool {
473 matches!(
474 sym_table.get(symbol_id),
475 Symbol::DylibImport {
476 weak_import: true,
477 ..
478 }
479 )
480 }
481
482 fn tlv_symbol_needs_thread_pointer(sym_table: &SymbolTable, symbol_id: SymbolId) -> bool {
483 matches!(
484 sym_table.get(symbol_id),
485 Symbol::LazyArchive { .. } | Symbol::LazyObject { .. }
486 )
487 }
488
489 fn tlv_symbol_needs_got(sym_table: &SymbolTable, symbol_id: SymbolId) -> bool {
490 matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. })
491 }
492
493 fn direct_import_bind_supported(reloc: Reloc) -> bool {
494 matches!(reloc.length, RelocLength::Quad) && !reloc.pcrel && reloc.subtrahend.is_none()
495 }
496
497 fn inputs_have_tlv_descriptors(inputs: &[LayoutInput<'_>]) -> bool {
498 inputs.iter().any(|input| {
499 input
500 .object
501 .sections
502 .iter()
503 .any(|section| section.kind == SectionKind::ThreadLocalVariables)
504 })
505 }
506
507 fn ensure_stub_helper_support(
508 sym_table: &mut SymbolTable,
509 dylibs: &[DylibInput],
510 got: &mut GotSection,
511 ) -> Result<SymbolId, SynthError> {
512 let (libsystem_id, libsystem) = find_libsystem_input(dylibs).ok_or_else(|| SynthError {
513 input: PathBuf::from("<synthetic stubs>"),
514 atom: crate::resolve::AtomId(0),
515 reloc_offset: 0,
516 kind: RelocKind::Branch26,
517 detail: "stub helper requires a libSystem dylib/TBD input for `dyld_stub_binder`"
518 .to_string(),
519 })?;
520
521 let name = sym_table.intern("dyld_stub_binder");
522 let symbol_id = if let Some(id) = sym_table.lookup(name) {
523 match sym_table.get(id) {
524 Symbol::DylibImport { .. } => id,
525 other => {
526 return Err(SynthError {
527 input: PathBuf::from("<synthetic stubs>"),
528 atom: crate::resolve::AtomId(0),
529 reloc_offset: 0,
530 kind: RelocKind::Branch26,
531 detail: format!(
532 "`dyld_stub_binder` already exists as unsupported symbol kind {:?}",
533 other.kind()
534 ),
535 });
536 }
537 }
538 } else {
539 match sym_table
540 .insert(Symbol::DylibImport {
541 name,
542 dylib: DylibId(libsystem_id as u32),
543 ordinal: libsystem.ordinal,
544 weak_import: false,
545 })
546 .map_err(|err| SynthError {
547 input: PathBuf::from("<synthetic stubs>"),
548 atom: crate::resolve::AtomId(0),
549 reloc_offset: 0,
550 kind: RelocKind::Branch26,
551 detail: format!("{err:?}"),
552 })? {
553 InsertOutcome::Inserted(id)
554 | InsertOutcome::Kept(id)
555 | InsertOutcome::PendingArchiveFetch { id, .. }
556 | InsertOutcome::PendingObjectLoad { id, .. }
557 | InsertOutcome::CommonCoalesced { id }
558 | InsertOutcome::Replaced { id, .. } => id,
559 }
560 };
561
562 got.intern(symbol_id, false);
563 Ok(symbol_id)
564 }
565
566 fn ensure_tlv_support(
567 sym_table: &mut SymbolTable,
568 dylibs: &[DylibInput],
569 ) -> Result<Option<SymbolId>, SynthError> {
570 let Some((libsystem_id, libsystem)) = find_libsystem_input(dylibs) else {
571 return Ok(None);
572 };
573
574 let name = sym_table.intern("__tlv_bootstrap");
575 let symbol_id = if let Some(id) = sym_table.lookup(name) {
576 match sym_table.get(id) {
577 Symbol::DylibImport { .. } => id,
578 other => {
579 return Err(SynthError {
580 input: PathBuf::from("<synthetic tlv>"),
581 atom: crate::resolve::AtomId(0),
582 reloc_offset: 0,
583 kind: RelocKind::TlvpLoadPage21,
584 detail: format!(
585 "`__tlv_bootstrap` already exists as unsupported symbol kind {:?}",
586 other.kind()
587 ),
588 });
589 }
590 }
591 } else {
592 match sym_table
593 .insert(Symbol::DylibImport {
594 name,
595 dylib: DylibId(libsystem_id as u32),
596 ordinal: libsystem.ordinal,
597 weak_import: false,
598 })
599 .map_err(|err| SynthError {
600 input: PathBuf::from("<synthetic tlv>"),
601 atom: crate::resolve::AtomId(0),
602 reloc_offset: 0,
603 kind: RelocKind::TlvpLoadPage21,
604 detail: format!("{err:?}"),
605 })? {
606 InsertOutcome::Inserted(id)
607 | InsertOutcome::Kept(id)
608 | InsertOutcome::PendingArchiveFetch { id, .. }
609 | InsertOutcome::PendingObjectLoad { id, .. }
610 | InsertOutcome::CommonCoalesced { id }
611 | InsertOutcome::Replaced { id, .. } => id,
612 }
613 };
614
615 Ok(Some(symbol_id))
616 }
617
618 fn find_libsystem_input(dylibs: &[DylibInput]) -> Option<(usize, &DylibInput)> {
619 dylibs
620 .iter()
621 .enumerate()
622 .find(|(_, dylib)| dylib.load_install_name == "/usr/lib/libSystem.B.dylib")
623 .or_else(|| {
624 dylibs
625 .iter()
626 .enumerate()
627 .find(|(_, dylib)| dylib.load_install_name.contains("libSystem"))
628 })
629 .or_else(|| {
630 dylibs
631 .iter()
632 .enumerate()
633 .find(|(_, dylib)| dylib.path.to_string_lossy().contains("libSystem.tbd"))
634 })
635 }
636
637 #[cfg(test)]
638 mod tests {
639 use std::path::PathBuf;
640
641 use crate::atom::{Atom, AtomFlags, AtomSection, AtomTable};
642 use crate::input::ObjectFile;
643 use crate::layout::LayoutInput;
644 use crate::macho::constants::{
645 CPU_SUBTYPE_ARM64_ALL, CPU_TYPE_ARM64, MH_DYLIB, MH_MAGIC_64, MH_OBJECT, N_EXT, N_UNDF,
646 S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS, S_REGULAR,
647 };
648 use crate::macho::dylib::DylibFile;
649 use crate::macho::exports::Exports;
650 use crate::macho::reader::{LoadCommand, MachHeader64};
651 use crate::reloc::{write_raw_relocs, write_relocs, Referent, Reloc, RelocKind, RelocLength};
652 use crate::resolve::{DylibId, DylibInput, InputId, Symbol, SymbolTable};
653 use crate::section::{InputSection, SectionKind};
654 use crate::string_table::StringTable;
655 use crate::symbol::{InputSymbol, RawNlist};
656
657 use super::*;
658
659 #[test]
660 fn synthetic_plan_collects_got_and_stub_needs_for_imports() {
661 let mut sym_table = SymbolTable::new();
662 let name = sym_table.intern("_printf");
663 let input_id = InputId(0);
664 sym_table
665 .insert(Symbol::DylibImport {
666 name,
667 dylib: DylibId(2),
668 ordinal: 3,
669 weak_import: false,
670 })
671 .unwrap();
672
673 let relocs = vec![
674 Reloc {
675 offset: 0,
676 kind: RelocKind::Branch26,
677 length: RelocLength::Word,
678 pcrel: true,
679 referent: Referent::Symbol(0),
680 addend: 0,
681 subtrahend: None,
682 },
683 Reloc {
684 offset: 4,
685 kind: RelocKind::GotLoadPage21,
686 length: RelocLength::Word,
687 pcrel: true,
688 referent: Referent::Symbol(0),
689 addend: 0,
690 subtrahend: None,
691 },
692 Reloc {
693 offset: 8,
694 kind: RelocKind::GotLoadPageOff12,
695 length: RelocLength::Word,
696 pcrel: false,
697 referent: Referent::Symbol(0),
698 addend: 0,
699 subtrahend: None,
700 },
701 ];
702 let raw_relocs = encode_raw_relocs(&relocs);
703 let object = synth_object("_printf", raw_relocs);
704
705 let mut atoms = AtomTable::new();
706 atoms.push(Atom {
707 id: crate::resolve::AtomId(0),
708 origin: input_id,
709 input_section: 1,
710 section: AtomSection::Text,
711 input_offset: 0,
712 size: 12,
713 align_pow2: 2,
714 owner: None,
715 alt_entries: Vec::new(),
716 data: vec![0; 12],
717 flags: AtomFlags::default().with(AtomFlags::PURE_INSTRUCTIONS),
718 parent_of: None,
719 });
720 let dylibs = vec![libsystem_input()];
721
722 let plan = SyntheticPlan::build(
723 &[LayoutInput {
724 id: input_id,
725 object: &object,
726 load_order: 0,
727 archive_member_offset: None,
728 }],
729 &atoms,
730 &mut sym_table,
731 &dylibs,
732 )
733 .unwrap();
734
735 assert_eq!(plan.got.entries.len(), 2);
736 assert_eq!(plan.stubs.entries.len(), 1);
737 assert_eq!(plan.lazy_pointers.entries.len(), 1);
738 assert!(plan.thread_pointers.entries.is_empty());
739 assert!(plan.direct_binds.is_empty());
740 assert!(plan.binder_symbol.is_some());
741 assert!(plan.tlv_bootstrap_symbol.is_none());
742 assert!(plan.needs_dyld_private);
743 assert_eq!(plan.stubs.entries[0].dylib, DylibId(2));
744 assert_eq!(
745 plan.lazy_pointers.entries[0].symbol,
746 plan.stubs.entries[0].symbol
747 );
748 }
749
750 #[test]
751 fn synthetic_plan_ignores_defined_targets() {
752 let mut sym_table = SymbolTable::new();
753 let name = sym_table.intern("_local");
754 let input_id = InputId(0);
755 sym_table
756 .insert(Symbol::Defined {
757 name,
758 origin: input_id,
759 atom: crate::resolve::AtomId(1),
760 value: 0,
761 weak: false,
762 private_extern: false,
763 no_dead_strip: false,
764 })
765 .unwrap();
766
767 let relocs = vec![Reloc {
768 offset: 0,
769 kind: RelocKind::Branch26,
770 length: RelocLength::Word,
771 pcrel: true,
772 referent: Referent::Symbol(0),
773 addend: 0,
774 subtrahend: None,
775 }];
776 let object = synth_object("_local", encode_raw_relocs(&relocs));
777
778 let mut atoms = AtomTable::new();
779 atoms.push(Atom {
780 id: crate::resolve::AtomId(0),
781 origin: input_id,
782 input_section: 1,
783 section: AtomSection::Text,
784 input_offset: 0,
785 size: 4,
786 align_pow2: 2,
787 owner: None,
788 alt_entries: Vec::new(),
789 data: vec![0; 4],
790 flags: AtomFlags::default().with(AtomFlags::PURE_INSTRUCTIONS),
791 parent_of: None,
792 });
793
794 let plan = SyntheticPlan::build(
795 &[LayoutInput {
796 id: input_id,
797 object: &object,
798 load_order: 0,
799 archive_member_offset: None,
800 }],
801 &atoms,
802 &mut sym_table,
803 &[],
804 )
805 .unwrap();
806 assert!(plan.got.entries.is_empty());
807 assert!(plan.stubs.entries.is_empty());
808 assert!(plan.lazy_pointers.entries.is_empty());
809 assert!(plan.thread_pointers.entries.is_empty());
810 assert!(plan.binder_symbol.is_none());
811 assert!(plan.tlv_bootstrap_symbol.is_none());
812 assert!(!plan.needs_dyld_private);
813 }
814
815 #[test]
816 fn synthetic_plan_keeps_local_tlvp_on_descriptors() {
817 let mut sym_table = SymbolTable::new();
818 let name = sym_table.intern("_tlsvar");
819 let input_id = InputId(0);
820 sym_table
821 .insert(Symbol::Defined {
822 name,
823 origin: input_id,
824 atom: crate::resolve::AtomId(1),
825 value: 0,
826 weak: false,
827 private_extern: false,
828 no_dead_strip: false,
829 })
830 .unwrap();
831
832 let relocs = vec![
833 Reloc {
834 offset: 0,
835 kind: RelocKind::TlvpLoadPage21,
836 length: RelocLength::Word,
837 pcrel: true,
838 referent: Referent::Symbol(0),
839 addend: 0,
840 subtrahend: None,
841 },
842 Reloc {
843 offset: 4,
844 kind: RelocKind::TlvpLoadPageOff12,
845 length: RelocLength::Word,
846 pcrel: false,
847 referent: Referent::Symbol(0),
848 addend: 0,
849 subtrahend: None,
850 },
851 ];
852 let object = tlvp_object("_tlsvar", encode_raw_relocs(&relocs));
853
854 let mut atoms = AtomTable::new();
855 atoms.push(Atom {
856 id: crate::resolve::AtomId(0),
857 origin: input_id,
858 input_section: 1,
859 section: AtomSection::Text,
860 input_offset: 0,
861 size: 8,
862 align_pow2: 2,
863 owner: None,
864 alt_entries: Vec::new(),
865 data: vec![0; 8],
866 flags: AtomFlags::default().with(AtomFlags::PURE_INSTRUCTIONS),
867 parent_of: None,
868 });
869
870 let plan = SyntheticPlan::build(
871 &[LayoutInput {
872 id: input_id,
873 object: &object,
874 load_order: 0,
875 archive_member_offset: None,
876 }],
877 &atoms,
878 &mut sym_table,
879 &[libsystem_input()],
880 )
881 .unwrap();
882
883 assert!(plan.thread_pointers.entries.is_empty());
884 assert!(plan.binder_symbol.is_none());
885 assert!(plan.tlv_bootstrap_symbol.is_some());
886
887 let sections = plan.output_sections();
888 assert!(sections
889 .iter()
890 .all(|section| !(section.segment == "__DATA" && section.name == "__thread_ptrs")));
891 }
892
893 #[test]
894 fn synthetic_plan_routes_imported_tlvp_through_got() {
895 let mut sym_table = SymbolTable::new();
896 let name = sym_table.intern("_ext_tls");
897 let input_id = InputId(0);
898 let import = match sym_table
899 .insert(Symbol::DylibImport {
900 name,
901 dylib: DylibId(0),
902 ordinal: 2,
903 weak_import: false,
904 })
905 .unwrap()
906 {
907 crate::resolve::InsertOutcome::Inserted(id) => id,
908 other => panic!("unexpected insert outcome: {other:?}"),
909 };
910
911 let relocs = vec![
912 Reloc {
913 offset: 0,
914 kind: RelocKind::TlvpLoadPage21,
915 length: RelocLength::Word,
916 pcrel: true,
917 referent: Referent::Symbol(0),
918 addend: 0,
919 subtrahend: None,
920 },
921 Reloc {
922 offset: 4,
923 kind: RelocKind::TlvpLoadPageOff12,
924 length: RelocLength::Word,
925 pcrel: false,
926 referent: Referent::Symbol(0),
927 addend: 0,
928 subtrahend: None,
929 },
930 ];
931 let object = synth_object("_ext_tls", encode_raw_relocs(&relocs));
932
933 let mut atoms = AtomTable::new();
934 atoms.push(Atom {
935 id: crate::resolve::AtomId(0),
936 origin: input_id,
937 input_section: 1,
938 section: AtomSection::Text,
939 input_offset: 0,
940 size: 12,
941 align_pow2: 2,
942 owner: None,
943 alt_entries: Vec::new(),
944 data: vec![0; 12],
945 flags: AtomFlags::default().with(AtomFlags::PURE_INSTRUCTIONS),
946 parent_of: None,
947 });
948
949 let plan = SyntheticPlan::build(
950 &[LayoutInput {
951 id: input_id,
952 object: &object,
953 load_order: 0,
954 archive_member_offset: None,
955 }],
956 &atoms,
957 &mut sym_table,
958 &[libsystem_input()],
959 )
960 .unwrap();
961
962 assert_eq!(plan.got.entries.len(), 1);
963 assert_eq!(plan.got.entries[0].symbol, import);
964 assert!(plan.thread_pointers.entries.is_empty());
965 assert!(plan.direct_binds.is_empty());
966 assert!(plan.tlv_bootstrap_symbol.is_none());
967 }
968
969 #[test]
970 fn synthetic_plan_collects_direct_import_bind_sites() {
971 let mut sym_table = SymbolTable::new();
972 let name = sym_table.intern("_ext_data");
973 let input_id = InputId(0);
974 let import = match sym_table
975 .insert(Symbol::DylibImport {
976 name,
977 dylib: DylibId(0),
978 ordinal: 2,
979 weak_import: false,
980 })
981 .unwrap()
982 {
983 crate::resolve::InsertOutcome::Inserted(id) => id,
984 other => panic!("unexpected insert outcome: {other:?}"),
985 };
986
987 let relocs = vec![Reloc {
988 offset: 0,
989 kind: RelocKind::Unsigned,
990 length: RelocLength::Quad,
991 pcrel: false,
992 referent: Referent::Symbol(0),
993 addend: 0,
994 subtrahend: None,
995 }];
996 let object = synth_object("_ext_data", encode_raw_relocs(&relocs));
997
998 let mut atoms = AtomTable::new();
999 let atom_id = atoms.push(Atom {
1000 id: crate::resolve::AtomId(0),
1001 origin: input_id,
1002 input_section: 1,
1003 section: AtomSection::Data,
1004 input_offset: 0,
1005 size: 8,
1006 align_pow2: 3,
1007 owner: None,
1008 alt_entries: Vec::new(),
1009 data: vec![0; 8],
1010 flags: AtomFlags::default(),
1011 parent_of: None,
1012 });
1013
1014 let plan = SyntheticPlan::build(
1015 &[LayoutInput {
1016 id: input_id,
1017 object: &object,
1018 load_order: 0,
1019 archive_member_offset: None,
1020 }],
1021 &atoms,
1022 &mut sym_table,
1023 &[libsystem_input()],
1024 )
1025 .unwrap();
1026
1027 assert!(plan.got.entries.is_empty());
1028 assert_eq!(plan.direct_binds.len(), 1);
1029 assert_eq!(plan.direct_binds[0].atom, atom_id);
1030 assert_eq!(plan.direct_binds[0].atom_offset, 0);
1031 assert_eq!(plan.direct_binds[0].symbol, import);
1032 }
1033
1034 #[test]
1035 fn synthetic_plan_collects_got_for_compact_unwind_personality_import() {
1036 let mut sym_table = SymbolTable::new();
1037 let name = sym_table.intern("___gxx_personality_v0");
1038 let input_id = InputId(0);
1039 let import = match sym_table
1040 .insert(Symbol::DylibImport {
1041 name,
1042 dylib: DylibId(0),
1043 ordinal: 2,
1044 weak_import: false,
1045 })
1046 .unwrap()
1047 {
1048 crate::resolve::InsertOutcome::Inserted(id) => id,
1049 other => panic!("unexpected insert outcome: {other:?}"),
1050 };
1051
1052 let relocs = vec![Reloc {
1053 offset: 16,
1054 kind: RelocKind::Unsigned,
1055 length: RelocLength::Quad,
1056 pcrel: false,
1057 referent: Referent::Symbol(0),
1058 addend: 0,
1059 subtrahend: None,
1060 }];
1061 let object = compact_unwind_object("___gxx_personality_v0", encode_raw_relocs(&relocs));
1062
1063 let mut atoms = AtomTable::new();
1064 atoms.push(Atom {
1065 id: crate::resolve::AtomId(0),
1066 origin: input_id,
1067 input_section: 1,
1068 section: AtomSection::CompactUnwind,
1069 input_offset: 0,
1070 size: 32,
1071 align_pow2: 3,
1072 owner: None,
1073 alt_entries: Vec::new(),
1074 data: vec![0; 32],
1075 flags: AtomFlags::default(),
1076 parent_of: None,
1077 });
1078
1079 let plan = SyntheticPlan::build(
1080 &[LayoutInput {
1081 id: input_id,
1082 object: &object,
1083 load_order: 0,
1084 archive_member_offset: None,
1085 }],
1086 &atoms,
1087 &mut sym_table,
1088 &[libsystem_input()],
1089 )
1090 .unwrap();
1091
1092 assert_eq!(plan.got.entries.len(), 1);
1093 assert_eq!(plan.got.entries[0].symbol, import);
1094 assert!(plan.stubs.entries.is_empty());
1095 assert!(plan.lazy_pointers.entries.is_empty());
1096 }
1097
1098 fn libsystem_input() -> DylibInput {
1099 DylibInput {
1100 path: PathBuf::from("/tmp/libSystem.tbd"),
1101 load_install_name: "/usr/lib/libSystem.B.dylib".into(),
1102 load_current_version: 0,
1103 load_compatibility_version: 0,
1104 file: DylibFile {
1105 path: PathBuf::from("/tmp/libSystem.tbd"),
1106 header: MachHeader64 {
1107 magic: MH_MAGIC_64,
1108 cputype: CPU_TYPE_ARM64,
1109 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1110 filetype: MH_DYLIB,
1111 ncmds: 0,
1112 sizeofcmds: 0,
1113 flags: 0,
1114 reserved: 0,
1115 },
1116 commands: Vec::<LoadCommand>::new(),
1117 install_name: "/usr/lib/libSystem.B.dylib".into(),
1118 current_version: 0,
1119 compatibility_version: 0,
1120 dependencies: Vec::new(),
1121 rpaths: Vec::new(),
1122 symtab: None,
1123 exports: Exports::empty(),
1124 },
1125 ordinal: 1,
1126 }
1127 }
1128
1129 fn encode_raw_relocs(relocs: &[Reloc]) -> Vec<u8> {
1130 let raws = write_relocs(relocs).unwrap();
1131 let mut bytes = Vec::new();
1132 write_raw_relocs(&raws, &mut bytes);
1133 bytes
1134 }
1135
1136 fn synth_object(symbol_name: &str, raw_relocs: Vec<u8>) -> ObjectFile {
1137 let mut strings = vec![0];
1138 let strx = strings.len() as u32;
1139 strings.extend_from_slice(symbol_name.as_bytes());
1140 strings.push(0);
1141 ObjectFile {
1142 path: PathBuf::from("/tmp/synth-got.o"),
1143 header: MachHeader64 {
1144 magic: MH_MAGIC_64,
1145 cputype: CPU_TYPE_ARM64,
1146 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1147 filetype: MH_OBJECT,
1148 ncmds: 0,
1149 sizeofcmds: 0,
1150 flags: 0,
1151 reserved: 0,
1152 },
1153 commands: Vec::new(),
1154 sections: vec![InputSection {
1155 segname: "__TEXT".into(),
1156 sectname: "__text".into(),
1157 kind: SectionKind::Text,
1158 addr: 0,
1159 size: 12,
1160 align_pow2: 2,
1161 flags: S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
1162 offset: 0,
1163 reloff: 0,
1164 nreloc: (raw_relocs.len() / 8) as u32,
1165 reserved1: 0,
1166 reserved2: 0,
1167 reserved3: 0,
1168 data: vec![0; 12],
1169 raw_relocs,
1170 }],
1171 symbols: vec![InputSymbol::from_raw(RawNlist {
1172 strx,
1173 n_type: N_UNDF | N_EXT,
1174 n_sect: 0,
1175 n_desc: 0,
1176 n_value: 0,
1177 })],
1178 strings: StringTable::from_bytes(strings),
1179 symtab: None,
1180 dysymtab: None,
1181 data_in_code: Vec::new(),
1182 }
1183 }
1184
1185 fn tlvp_object(symbol_name: &str, raw_relocs: Vec<u8>) -> ObjectFile {
1186 let mut strings = vec![0];
1187 let strx = strings.len() as u32;
1188 strings.extend_from_slice(symbol_name.as_bytes());
1189 strings.push(0);
1190 ObjectFile {
1191 path: PathBuf::from("/tmp/synth-tlv.o"),
1192 header: MachHeader64 {
1193 magic: MH_MAGIC_64,
1194 cputype: CPU_TYPE_ARM64,
1195 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1196 filetype: MH_OBJECT,
1197 ncmds: 0,
1198 sizeofcmds: 0,
1199 flags: 0,
1200 reserved: 0,
1201 },
1202 commands: Vec::new(),
1203 sections: vec![
1204 InputSection {
1205 segname: "__TEXT".into(),
1206 sectname: "__text".into(),
1207 kind: SectionKind::Text,
1208 addr: 0,
1209 size: 8,
1210 align_pow2: 2,
1211 flags: S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS,
1212 offset: 0,
1213 reloff: 0,
1214 nreloc: (raw_relocs.len() / 8) as u32,
1215 reserved1: 0,
1216 reserved2: 0,
1217 reserved3: 0,
1218 data: vec![0; 8],
1219 raw_relocs,
1220 },
1221 InputSection {
1222 segname: "__DATA".into(),
1223 sectname: "__thread_vars".into(),
1224 kind: SectionKind::ThreadLocalVariables,
1225 addr: 0,
1226 size: 24,
1227 align_pow2: 3,
1228 flags: crate::macho::constants::S_THREAD_LOCAL_VARIABLES,
1229 offset: 0,
1230 reloff: 0,
1231 nreloc: 0,
1232 reserved1: 0,
1233 reserved2: 0,
1234 reserved3: 0,
1235 data: vec![0; 24],
1236 raw_relocs: Vec::new(),
1237 },
1238 InputSection {
1239 segname: "__DATA".into(),
1240 sectname: "__thread_data".into(),
1241 kind: SectionKind::ThreadLocalRegular,
1242 addr: 0,
1243 size: 8,
1244 align_pow2: 3,
1245 flags: crate::macho::constants::S_THREAD_LOCAL_REGULAR,
1246 offset: 0,
1247 reloff: 0,
1248 nreloc: 0,
1249 reserved1: 0,
1250 reserved2: 0,
1251 reserved3: 0,
1252 data: vec![0; 8],
1253 raw_relocs: Vec::new(),
1254 },
1255 ],
1256 symbols: vec![InputSymbol::from_raw(RawNlist {
1257 strx,
1258 n_type: N_EXT | crate::macho::constants::N_SECT,
1259 n_sect: 2,
1260 n_desc: 0,
1261 n_value: 0,
1262 })],
1263 strings: StringTable::from_bytes(strings),
1264 symtab: None,
1265 dysymtab: None,
1266 data_in_code: Vec::new(),
1267 }
1268 }
1269
1270 fn compact_unwind_object(symbol_name: &str, raw_relocs: Vec<u8>) -> ObjectFile {
1271 let mut strings = vec![0];
1272 let strx = strings.len() as u32;
1273 strings.extend_from_slice(symbol_name.as_bytes());
1274 strings.push(0);
1275 ObjectFile {
1276 path: PathBuf::from("/tmp/synth-compact-unwind.o"),
1277 header: MachHeader64 {
1278 magic: MH_MAGIC_64,
1279 cputype: CPU_TYPE_ARM64,
1280 cpusubtype: CPU_SUBTYPE_ARM64_ALL,
1281 filetype: MH_OBJECT,
1282 ncmds: 0,
1283 sizeofcmds: 0,
1284 flags: 0,
1285 reserved: 0,
1286 },
1287 commands: Vec::new(),
1288 sections: vec![InputSection {
1289 segname: "__LD".into(),
1290 sectname: "__compact_unwind".into(),
1291 kind: SectionKind::CompactUnwind,
1292 addr: 0,
1293 size: 32,
1294 align_pow2: 3,
1295 flags: crate::macho::constants::S_REGULAR,
1296 offset: 0,
1297 reloff: 0,
1298 nreloc: (raw_relocs.len() / 8) as u32,
1299 reserved1: 0,
1300 reserved2: 0,
1301 reserved3: 0,
1302 data: vec![0; 32],
1303 raw_relocs,
1304 }],
1305 symbols: vec![InputSymbol::from_raw(RawNlist {
1306 strx,
1307 n_type: N_UNDF | N_EXT,
1308 n_sect: 0,
1309 n_desc: 0,
1310 n_value: 0,
1311 })],
1312 strings: StringTable::from_bytes(strings),
1313 symtab: None,
1314 dysymtab: None,
1315 data_in_code: Vec::new(),
1316 }
1317 }
1318 }
1319