Rust · 33824 bytes Raw Blame History
1 //! afs-ld — standalone ARM64 Mach-O linker.
2 //!
3 //! Sprint 0 scaffolding: public surface is declared but every link attempt
4 //! returns `LinkError::NotYetImplemented`. Subsequent sprints fill in the
5 //! reader, resolver, layout, reloc, synth, writer, and signing paths.
6
7 pub mod archive;
8 pub mod args;
9 pub mod atom;
10 pub mod diag;
11 pub mod dump;
12 pub mod icf;
13 pub mod input;
14 pub mod layout;
15 pub mod leb;
16 pub mod link_map;
17 pub mod loh;
18 pub mod macho;
19 pub mod reloc;
20 pub mod resolve;
21 pub mod section;
22 pub mod string_table;
23 pub mod symbol;
24 pub mod synth;
25 pub mod why_live;
26
27 use std::os::unix::fs::PermissionsExt;
28 use std::path::PathBuf;
29 use std::time::{Duration, Instant};
30 use std::{fs, io};
31
32 use atom::{atomize_object, backpatch_symbol_atoms, AtomTable};
33 use icf::IcfError;
34 use layout::{ExtraLayoutSections, Layout, LayoutInput};
35 use macho::dylib::{DylibDependency, DylibFile, DylibLoadKind};
36 use macho::reader::ReadError;
37 use macho::tbd::{parse_tbd, parse_version, Arch, Platform, Target};
38 use reloc::arm64::RelocError;
39 use resolve::{
40 classify_unresolved, drain_fetches, find_archive_by_path, force_load_all, force_load_archive,
41 format_duplicate_diagnostic, format_undefined_diagnostic, format_undefined_warning_diagnostic,
42 seed_all, DrainReport, DylibLoadMeta, InputAddError, Inputs, Symbol, SymbolTable,
43 UndefinedTreatment,
44 };
45
46 const DEFAULT_TBD_VERSION: u32 = 1 << 16;
47 const THUNK_PLAN_MAX_ITERATIONS: usize = 16;
48
49 /// What kind of Mach-O file the linker is producing.
50 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
51 pub enum OutputKind {
52 Executable,
53 Dylib,
54 }
55
56 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
57 pub enum IcfMode {
58 None,
59 Safe,
60 All,
61 }
62
63 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
64 pub enum ThunkMode {
65 None,
66 Safe,
67 All,
68 }
69
70 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
71 pub struct PlatformVersion {
72 pub minos: u32,
73 pub sdk: u32,
74 }
75
76 #[derive(Debug, Clone, PartialEq, Eq)]
77 pub struct FrameworkSpec {
78 pub name: String,
79 pub weak: bool,
80 }
81
82 /// User-facing linker configuration, populated by the CLI parser.
83 #[derive(Debug, Clone)]
84 pub struct LinkOptions {
85 pub inputs: Vec<PathBuf>,
86 pub library_names: Vec<String>,
87 pub frameworks: Vec<FrameworkSpec>,
88 pub search_paths: Vec<PathBuf>,
89 pub syslibroot: Option<PathBuf>,
90 pub platform_version: Option<PlatformVersion>,
91 pub undefined_treatment: UndefinedTreatment,
92 pub rpaths: Vec<String>,
93 pub install_name: Option<String>,
94 pub current_version: Option<u32>,
95 pub compatibility_version: Option<u32>,
96 pub exported_symbols_lists: Vec<PathBuf>,
97 pub unexported_symbols_lists: Vec<PathBuf>,
98 pub exported_symbols: Vec<String>,
99 pub unexported_symbols: Vec<String>,
100 pub map: Option<PathBuf>,
101 pub why_live: Vec<String>,
102 pub trace_inputs: bool,
103 pub show_version: bool,
104 pub show_help: bool,
105 pub output: Option<PathBuf>,
106 pub entry: Option<String>,
107 pub arch: Option<String>,
108 pub relocatable: bool,
109 pub bundle: bool,
110 pub objc_force_load: bool,
111 pub strip_locals: bool,
112 pub strip_debug: bool,
113 pub emit_uuid: bool,
114 pub dead_strip: bool,
115 pub no_loh: bool,
116 pub icf_mode: IcfMode,
117 pub thunks: ThunkMode,
118 pub fixup_chains: bool,
119 pub all_load: bool,
120 pub force_load_archives: Vec<PathBuf>,
121 pub kind: OutputKind,
122 /// When set, afs-ld operates in dump mode and prints the given file's
123 /// header + load commands instead of linking.
124 pub dump: Option<PathBuf>,
125 /// When set, afs-ld dumps the named static archive's structure.
126 pub dump_archive: Option<PathBuf>,
127 /// When set, afs-ld dumps the named MH_DYLIB's load commands + exports.
128 pub dump_dylib: Option<PathBuf>,
129 /// When set, afs-ld dumps the named TAPI TBD stub (all documents).
130 pub dump_tbd: Option<PathBuf>,
131 }
132
133 impl Default for LinkOptions {
134 fn default() -> Self {
135 Self {
136 inputs: Vec::new(),
137 library_names: Vec::new(),
138 frameworks: Vec::new(),
139 search_paths: Vec::new(),
140 syslibroot: None,
141 platform_version: None,
142 undefined_treatment: UndefinedTreatment::Error,
143 rpaths: Vec::new(),
144 install_name: None,
145 current_version: None,
146 compatibility_version: None,
147 exported_symbols_lists: Vec::new(),
148 unexported_symbols_lists: Vec::new(),
149 exported_symbols: Vec::new(),
150 unexported_symbols: Vec::new(),
151 map: None,
152 why_live: Vec::new(),
153 trace_inputs: false,
154 show_version: false,
155 show_help: false,
156 output: None,
157 entry: None,
158 arch: None,
159 relocatable: false,
160 bundle: false,
161 objc_force_load: false,
162 strip_locals: false,
163 strip_debug: false,
164 emit_uuid: true,
165 dead_strip: false,
166 no_loh: false,
167 icf_mode: IcfMode::None,
168 thunks: ThunkMode::Safe,
169 fixup_chains: false,
170 all_load: false,
171 force_load_archives: Vec::new(),
172 kind: OutputKind::Executable,
173 dump: None,
174 dump_archive: None,
175 dump_dylib: None,
176 dump_tbd: None,
177 }
178 }
179 }
180
181 #[derive(Debug)]
182 pub enum LinkError {
183 /// No input files were provided on the command line.
184 NoInputs,
185 Io(io::Error),
186 Input(InputAddError),
187 Seed(resolve::SeedError),
188 Fetch(resolve::FetchError),
189 Write(macho::writer::WriteError),
190 Tbd(macho::tbd::TbdError),
191 Reloc(RelocError),
192 Synth(synth::SynthError),
193 Unwind(synth::unwind::UnwindError),
194 Icf(IcfError),
195 Loh(loh::LohError),
196 DuplicateSymbols(String),
197 UndefinedSymbols(String),
198 UnsupportedArch(String),
199 NoTbdDocument(PathBuf),
200 EntrySymbolNotFound(String),
201 ForceLoadNotArchive(PathBuf),
202 LibraryNotFound(String),
203 FrameworkNotFound(String),
204 ThunkPlanningDidNotConverge,
205 WhyLive(String),
206 UnsupportedOption(String),
207 }
208
209 #[derive(Debug, Clone, Default, PartialEq, Eq)]
210 pub struct LinkPhaseTimings {
211 pub input_parsing: Duration,
212 pub symbol_resolution: Duration,
213 pub atomization: Duration,
214 pub layout: Duration,
215 pub synth_sections: Duration,
216 pub synth_linkedit_finalize: Duration,
217 pub synth_linkedit_symbol_plan: Duration,
218 pub synth_linkedit_symbol_plan_locals: Duration,
219 pub synth_linkedit_symbol_plan_globals: Duration,
220 pub synth_linkedit_symbol_plan_strtab: Duration,
221 pub synth_linkedit_dyld_info: Duration,
222 pub synth_linkedit_metadata_tables: Duration,
223 pub synth_linkedit_code_signature: Duration,
224 pub synth_unwind: Duration,
225 pub reloc_apply: Duration,
226 pub write_output: Duration,
227 }
228
229 impl LinkPhaseTimings {
230 pub fn accounted_total(&self) -> Duration {
231 self.input_parsing
232 + self.symbol_resolution
233 + self.atomization
234 + self.layout
235 + self.synth_sections
236 + self.reloc_apply
237 + self.write_output
238 }
239 }
240
241 #[derive(Debug, Clone, PartialEq, Eq)]
242 pub struct LinkProfile {
243 pub output: PathBuf,
244 pub phases: LinkPhaseTimings,
245 pub total_wall: Duration,
246 }
247
248 impl std::fmt::Display for LinkError {
249 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250 match self {
251 LinkError::NoInputs => write!(f, "no input files"),
252 LinkError::Io(e) => write!(f, "{e}"),
253 LinkError::Input(e) => write!(f, "{e}"),
254 LinkError::Seed(e) => write!(f, "{e}"),
255 LinkError::Fetch(e) => write!(f, "{e}"),
256 LinkError::Write(e) => write!(f, "{e}"),
257 LinkError::Tbd(e) => write!(f, "{e}"),
258 LinkError::Reloc(e) => write!(f, "{e}"),
259 LinkError::Synth(e) => write!(f, "{e}"),
260 LinkError::Unwind(e) => write!(f, "{e}"),
261 LinkError::Icf(e) => write!(f, "{e}"),
262 LinkError::Loh(e) => write!(f, "{e}"),
263 LinkError::DuplicateSymbols(msg) | LinkError::UndefinedSymbols(msg) => {
264 write!(f, "{msg}")
265 }
266 LinkError::UnsupportedArch(arch) => {
267 write!(f, "unsupported arch `{arch}` (afs-ld requires arm64)")
268 }
269 LinkError::NoTbdDocument(path) => {
270 write!(f, "{}: no arm64-macos TBD document found", path.display())
271 }
272 LinkError::EntrySymbolNotFound(name) => {
273 write!(f, "entry symbol `{name}` was not found in linked objects")
274 }
275 LinkError::ForceLoadNotArchive(path) => {
276 write!(
277 f,
278 "{}: -force_load requires a path that is also present as an archive input",
279 path.display()
280 )
281 }
282 LinkError::LibraryNotFound(name) => {
283 write!(f, "unable to find library `{name}`")
284 }
285 LinkError::FrameworkNotFound(name) => {
286 write!(f, "unable to find framework `{name}`")
287 }
288 LinkError::ThunkPlanningDidNotConverge => {
289 write!(f, "thunk planning did not converge")
290 }
291 LinkError::WhyLive(msg) => write!(f, "{msg}"),
292 LinkError::UnsupportedOption(msg) => write!(f, "{msg}"),
293 }
294 }
295 }
296
297 impl std::error::Error for LinkError {}
298
299 impl From<io::Error> for LinkError {
300 fn from(value: io::Error) -> Self {
301 LinkError::Io(value)
302 }
303 }
304
305 impl From<InputAddError> for LinkError {
306 fn from(value: InputAddError) -> Self {
307 LinkError::Input(value)
308 }
309 }
310
311 impl From<ReadError> for LinkError {
312 fn from(value: ReadError) -> Self {
313 LinkError::Input(InputAddError::from(value))
314 }
315 }
316
317 impl From<resolve::SeedError> for LinkError {
318 fn from(value: resolve::SeedError) -> Self {
319 LinkError::Seed(value)
320 }
321 }
322
323 impl From<resolve::FetchError> for LinkError {
324 fn from(value: resolve::FetchError) -> Self {
325 LinkError::Fetch(value)
326 }
327 }
328
329 impl From<macho::writer::WriteError> for LinkError {
330 fn from(value: macho::writer::WriteError) -> Self {
331 LinkError::Write(value)
332 }
333 }
334
335 impl From<macho::tbd::TbdError> for LinkError {
336 fn from(value: macho::tbd::TbdError) -> Self {
337 LinkError::Tbd(value)
338 }
339 }
340
341 impl From<RelocError> for LinkError {
342 fn from(value: RelocError) -> Self {
343 LinkError::Reloc(value)
344 }
345 }
346
347 impl From<synth::SynthError> for LinkError {
348 fn from(value: synth::SynthError) -> Self {
349 LinkError::Synth(value)
350 }
351 }
352
353 impl From<synth::unwind::UnwindError> for LinkError {
354 fn from(value: synth::unwind::UnwindError) -> Self {
355 LinkError::Unwind(value)
356 }
357 }
358
359 impl From<IcfError> for LinkError {
360 fn from(value: IcfError) -> Self {
361 LinkError::Icf(value)
362 }
363 }
364
365 impl From<loh::LohError> for LinkError {
366 fn from(value: loh::LohError) -> Self {
367 LinkError::Loh(value)
368 }
369 }
370
371 /// The linker itself. Sprint 0 only validates that inputs exist; later sprints
372 /// grow this into the full pipeline described in `.docs/overview.md`.
373 pub struct Linker;
374
375 impl Linker {
376 pub fn run(opts: &LinkOptions) -> Result<(), LinkError> {
377 Self::run_profiled(opts).map(|_| ())
378 }
379
380 pub fn run_profiled(opts: &LinkOptions) -> Result<LinkProfile, LinkError> {
381 let overall_started = Instant::now();
382 let mut phases = LinkPhaseTimings::default();
383 if opts.relocatable {
384 return Err(LinkError::UnsupportedOption(
385 "`-r` relocatable output is not yet supported".into(),
386 ));
387 }
388 if opts.bundle {
389 return Err(LinkError::UnsupportedOption(
390 "`-bundle` output is not yet supported".into(),
391 ));
392 }
393 if opts.fixup_chains {
394 return Err(LinkError::UnsupportedOption(
395 "`-fixup_chains` is not yet supported".into(),
396 ));
397 }
398 if opts.icf_mode == IcfMode::All {
399 return Err(LinkError::UnsupportedOption(
400 "`-icf=all` is not yet supported; use `-icf=safe` or `-icf=none`".into(),
401 ));
402 }
403 if opts.inputs.is_empty() && opts.library_names.is_empty() && opts.frameworks.is_empty() {
404 return Err(LinkError::NoInputs);
405 }
406
407 if let Some(arch) = &opts.arch {
408 if arch != "arm64" {
409 return Err(LinkError::UnsupportedArch(arch.clone()));
410 }
411 }
412
413 if opts.strip_debug {
414 crate::diag::warning(
415 "`-S` requested, but afs-ld does not currently emit debug symbols",
416 );
417 }
418 if opts.objc_force_load {
419 crate::diag::warning(
420 "`-ObjC` requested, but afs-ld does not yet scan Objective-C archive metadata; the flag currently has no effect",
421 );
422 }
423 if opts.no_loh {
424 crate::diag::warning(
425 "`-no_loh` requested, but afs-ld currently matches Apple ld by omitting final-output LOH; the flag has no effect",
426 );
427 }
428
429 let mut load_paths = Vec::new();
430 let mut positional_dylibs = Vec::new();
431 for path in &opts.inputs {
432 match path.extension().and_then(|ext| ext.to_str()) {
433 Some("dylib" | "tbd") => positional_dylibs.push(path.clone()),
434 _ => load_paths.push(path.clone()),
435 }
436 }
437 let mut dylib_load_kinds = std::collections::HashMap::new();
438 for name in &opts.library_names {
439 let path = resolve_library_input(opts, name)?;
440 dylib_load_kinds.insert(path.clone(), DylibLoadKind::Normal);
441 load_paths.push(path);
442 }
443 for framework in &opts.frameworks {
444 let path = resolve_framework_input(opts, &framework.name)?;
445 dylib_load_kinds.insert(
446 path.clone(),
447 if framework.weak {
448 DylibLoadKind::Weak
449 } else {
450 DylibLoadKind::Normal
451 },
452 );
453 load_paths.push(path);
454 }
455 load_paths.extend(positional_dylibs);
456
457 let mut inputs = Inputs::new();
458 let phase_started = Instant::now();
459 for (load_order, path) in load_paths.iter().enumerate() {
460 if opts.trace_inputs {
461 eprintln!("afs-ld: loading {}", path.display());
462 }
463 register_input(&mut inputs, path, load_order)?;
464 }
465 phases.input_parsing = phase_started.elapsed();
466
467 let mut sym_table = SymbolTable::new();
468 let phase_started = Instant::now();
469 let seed_report = seed_all(&inputs, &mut sym_table)?;
470 if seed_report.has_errors() {
471 let mut msg = String::new();
472 for err in &seed_report.duplicates {
473 msg.push_str(&format_duplicate_diagnostic(&sym_table, &inputs, err));
474 }
475 return Err(LinkError::DuplicateSymbols(msg));
476 }
477
478 let mut force_report = DrainReport::default();
479 if opts.all_load {
480 force_load_all(&mut inputs, &mut sym_table, &mut force_report)?;
481 }
482 for archive_path in &opts.force_load_archives {
483 let Some(archive_id) = find_archive_by_path(&inputs, archive_path) else {
484 return Err(LinkError::ForceLoadNotArchive(archive_path.clone()));
485 };
486 force_load_archive(&mut inputs, &mut sym_table, archive_id, &mut force_report)?;
487 }
488 if opts.trace_inputs {
489 for path in &force_report.loaded_paths {
490 eprintln!("afs-ld: loading {}", path.display());
491 }
492 }
493 if !force_report.duplicates.is_empty() {
494 let mut msg = String::new();
495 for err in &force_report.duplicates {
496 msg.push_str(&format_duplicate_diagnostic(&sym_table, &inputs, err));
497 }
498 return Err(LinkError::DuplicateSymbols(msg));
499 }
500
501 let drain_report = drain_fetches(&mut inputs, &mut sym_table, seed_report.pending_fetches)?;
502 if opts.trace_inputs {
503 for path in &drain_report.loaded_paths {
504 eprintln!("afs-ld: loading {}", path.display());
505 }
506 }
507 if !drain_report.duplicates.is_empty() {
508 let mut msg = String::new();
509 for err in &drain_report.duplicates {
510 msg.push_str(&format_duplicate_diagnostic(&sym_table, &inputs, err));
511 }
512 return Err(LinkError::DuplicateSymbols(msg));
513 }
514 let mut referrers = seed_report.referrers.clone();
515 referrers.extend_from(&force_report.referrers);
516 referrers.extend_from(&drain_report.referrers);
517 let unresolved = classify_unresolved(&mut sym_table, opts.undefined_treatment);
518 if !unresolved.errors.is_empty() {
519 return Err(LinkError::UndefinedSymbols(format_undefined_diagnostic(
520 &sym_table,
521 &inputs,
522 &referrers,
523 &unresolved.errors,
524 )));
525 }
526 if !unresolved.warnings.is_empty() {
527 crate::diag::warning_verbatim(&format_undefined_warning_diagnostic(
528 &sym_table,
529 &inputs,
530 &referrers,
531 &unresolved.warnings,
532 ));
533 }
534 phases.symbol_resolution = phase_started.elapsed();
535
536 let mut atom_table = AtomTable::new();
537 let mut objects = Vec::new();
538 let phase_started = Instant::now();
539 for idx in 0..inputs.objects.len() {
540 let input_id = resolve::InputId(idx as u32);
541 let obj = inputs.object_file(input_id)?;
542 let atomization = atomize_object(input_id, &obj, &mut atom_table);
543 backpatch_symbol_atoms(
544 &atomization,
545 input_id,
546 &obj,
547 &mut sym_table,
548 &mut atom_table,
549 );
550 objects.push((input_id, obj));
551 }
552 phases.atomization = phase_started.elapsed();
553
554 let layout_inputs: Vec<LayoutInput<'_>> = objects
555 .iter()
556 .map(|(id, object)| {
557 let input = inputs.object(*id);
558 LayoutInput {
559 id: *id,
560 object,
561 load_order: input.load_order,
562 archive_member_offset: input.archive_member_offset,
563 }
564 })
565 .collect();
566 let mut dylib_loads = Vec::new();
567 let mut seen_ordinals = std::collections::BTreeSet::new();
568 for dylib in &inputs.dylibs {
569 if !seen_ordinals.insert(dylib.ordinal) {
570 continue;
571 }
572 dylib_loads.push(DylibDependency {
573 kind: dylib_load_kinds
574 .get(&dylib.path)
575 .copied()
576 .unwrap_or(DylibLoadKind::Normal),
577 install_name: dylib.load_install_name.clone(),
578 current_version: dylib.load_current_version,
579 compatibility_version: dylib.load_compatibility_version,
580 ordinal: dylib.ordinal,
581 });
582 }
583 let phase_started = Instant::now();
584 let parsed_relocs = macho::writer::build_parsed_reloc_cache(&layout_inputs)?;
585 phases.input_parsing += phase_started.elapsed();
586 let phase_started = Instant::now();
587 let entry_symbol = find_entry_symbol_id(opts, &sym_table)?;
588 let dead_strip = opts.dead_strip.then(|| {
589 why_live::DeadStripAnalysis::build(
590 opts,
591 &layout_inputs,
592 &atom_table,
593 &sym_table,
594 entry_symbol,
595 )
596 });
597 let icf = (opts.icf_mode == IcfMode::Safe)
598 .then(|| {
599 icf::fold_safe(
600 &layout_inputs,
601 &mut atom_table,
602 &mut sym_table,
603 dead_strip.as_ref().map(|analysis| analysis.live_atoms()),
604 )
605 })
606 .transpose()?;
607 let kept_atoms = if let Some(icf) = &icf {
608 Some(icf.kept_atoms())
609 } else {
610 dead_strip.as_ref().map(|analysis| analysis.live_atoms())
611 };
612 let synthetic_plan = synth::SyntheticPlan::build_filtered(
613 &layout_inputs,
614 &atom_table,
615 &mut sym_table,
616 &inputs.dylibs,
617 kept_atoms,
618 )?;
619 let icf_redirects = icf.as_ref().map(|plan| plan.redirects());
620 let mut layout = Layout::build_with_synthetics_filtered(
621 opts.kind,
622 &layout_inputs,
623 &atom_table,
624 0,
625 Some(&synthetic_plan),
626 kept_atoms,
627 );
628 let mut thunk_plan = None;
629 let mut thunk_converged = false;
630 for _ in 0..THUNK_PLAN_MAX_ITERATIONS {
631 let next_plan = reloc::arm64::plan_thunks(
632 opts,
633 &layout,
634 &layout_inputs,
635 &atom_table,
636 &sym_table,
637 Some(&synthetic_plan),
638 icf_redirects,
639 )?;
640 if next_plan == thunk_plan {
641 thunk_converged = true;
642 break;
643 }
644 let extra_sections = next_plan
645 .as_ref()
646 .map_or_else(Vec::new, |plan| plan.output_sections());
647 let split_after_atoms = next_plan
648 .as_ref()
649 .map_or_else(Vec::new, |plan| plan.split_after_atoms());
650 layout = Layout::build_with_synthetics_and_extra_filtered(
651 opts.kind,
652 &layout_inputs,
653 &atom_table,
654 0,
655 Some(&synthetic_plan),
656 kept_atoms,
657 ExtraLayoutSections {
658 extra_sections: &extra_sections,
659 split_after_atoms: &split_after_atoms,
660 },
661 );
662 thunk_plan = next_plan;
663 }
664 if !thunk_converged {
665 return Err(LinkError::ThunkPlanningDidNotConverge);
666 }
667 phases.layout = phase_started.elapsed();
668 let linkedit_context = macho::writer::LinkEditContext {
669 layout_inputs: &layout_inputs,
670 atom_table: &atom_table,
671 sym_table: &sym_table,
672 synthetic_plan: &synthetic_plan,
673 icf_redirects,
674 parsed_relocs: &parsed_relocs,
675 };
676 let phase_started = Instant::now();
677 let mut linkedit = None;
678 let mut synth_linkedit_finalize = Duration::ZERO;
679 let mut synth_linkedit_symbol_plan = Duration::ZERO;
680 let mut synth_linkedit_symbol_plan_locals = Duration::ZERO;
681 let mut synth_linkedit_symbol_plan_globals = Duration::ZERO;
682 let mut synth_linkedit_symbol_plan_strtab = Duration::ZERO;
683 let mut synth_linkedit_dyld_info = Duration::ZERO;
684 let mut synth_linkedit_metadata_tables = Duration::ZERO;
685 let mut synth_linkedit_code_signature = Duration::ZERO;
686 let mut synth_unwind = Duration::ZERO;
687 for _ in 0..4 {
688 let phase_started = Instant::now();
689 let (next_layout, next_linkedit, linkedit_timings) =
690 macho::writer::finalize_layout_with_linkedit(
691 &layout,
692 opts.kind,
693 opts,
694 &dylib_loads,
695 linkedit_context,
696 )?;
697 synth_linkedit_finalize += phase_started.elapsed();
698 synth_linkedit_symbol_plan += linkedit_timings.symbol_plan;
699 synth_linkedit_symbol_plan_locals += linkedit_timings.symbol_plan_locals;
700 synth_linkedit_symbol_plan_globals += linkedit_timings.symbol_plan_globals;
701 synth_linkedit_symbol_plan_strtab += linkedit_timings.symbol_plan_strtab;
702 synth_linkedit_dyld_info += linkedit_timings.dyld_info;
703 synth_linkedit_metadata_tables += linkedit_timings.metadata_tables;
704 synth_linkedit_code_signature += linkedit_timings.code_signature;
705 layout = next_layout;
706 linkedit = Some(next_linkedit);
707 let phase_started = Instant::now();
708 let changed = synth::unwind::synthesize(
709 &mut layout,
710 &layout_inputs,
711 &atom_table,
712 &sym_table,
713 &synthetic_plan,
714 )?;
715 synth_unwind += phase_started.elapsed();
716 if !changed {
717 break;
718 }
719 }
720 let linkedit = linkedit.expect("finalize loop always runs at least once");
721 phases.synth_linkedit_finalize = synth_linkedit_finalize;
722 phases.synth_linkedit_symbol_plan = synth_linkedit_symbol_plan;
723 phases.synth_linkedit_symbol_plan_locals = synth_linkedit_symbol_plan_locals;
724 phases.synth_linkedit_symbol_plan_globals = synth_linkedit_symbol_plan_globals;
725 phases.synth_linkedit_symbol_plan_strtab = synth_linkedit_symbol_plan_strtab;
726 phases.synth_linkedit_dyld_info = synth_linkedit_dyld_info;
727 phases.synth_linkedit_metadata_tables = synth_linkedit_metadata_tables;
728 phases.synth_linkedit_code_signature = synth_linkedit_code_signature;
729 phases.synth_unwind = synth_unwind;
730 phases.synth_sections = phase_started.elapsed();
731 let phase_started = Instant::now();
732 reloc::arm64::apply_layout(
733 &mut layout,
734 &layout_inputs,
735 &atom_table,
736 &sym_table,
737 reloc::arm64::ApplyLayoutPlan {
738 synthetic_plan: Some(&synthetic_plan),
739 thunk_plan: thunk_plan.as_ref(),
740 linkedit: &linkedit,
741 icf_redirects,
742 },
743 )?;
744 phases.reloc_apply = phase_started.elapsed();
745 let folded_symbols = icf
746 .as_ref()
747 .map(|plan| plan.folded_symbols(&atom_table, &sym_table, &layout_inputs))
748 .unwrap_or_default();
749
750 if let Some(report) = why_live::format_explanations(
751 opts,
752 &layout_inputs,
753 &atom_table,
754 &sym_table,
755 entry_symbol,
756 dead_strip.as_ref(),
757 &folded_symbols,
758 )
759 .map_err(LinkError::WhyLive)?
760 {
761 print!("{report}");
762 }
763
764 let phase_started = Instant::now();
765 let mut image = Vec::new();
766 let entry_point = resolve_entry_point(opts, &sym_table)?;
767 macho::writer::write_finalized_with_linkedit(
768 &layout,
769 opts.kind,
770 opts,
771 entry_point,
772 &dylib_loads,
773 &linkedit,
774 &mut image,
775 )?;
776 let output = default_output_path(opts);
777 fs::write(&output, image)?;
778 if let Some(map_path) = &opts.map {
779 let dead_stripped = dead_strip
780 .as_ref()
781 .map(|analysis| {
782 analysis.dead_stripped_symbols(&atom_table, &sym_table, &layout_inputs)
783 })
784 .unwrap_or_default();
785 link_map::write_link_map(
786 map_path,
787 opts,
788 &layout,
789 &layout_inputs,
790 &linkedit,
791 &folded_symbols,
792 &dead_stripped,
793 )?;
794 }
795 if opts.kind == OutputKind::Executable {
796 let mut perms = fs::metadata(&output)?.permissions();
797 let mode = perms.mode();
798 perms.set_mode(mode | ((mode & 0o444) >> 2));
799 fs::set_permissions(&output, perms)?;
800 }
801 phases.write_output = phase_started.elapsed();
802 Ok(LinkProfile {
803 output,
804 phases,
805 total_wall: overall_started.elapsed(),
806 })
807 }
808 }
809
810 fn resolve_library_input(opts: &LinkOptions, name: &str) -> Result<PathBuf, LinkError> {
811 let mut search_dirs = Vec::new();
812 for dir in &opts.search_paths {
813 search_dirs.push(dir.clone());
814 if let Some(root) = &opts.syslibroot {
815 if let Ok(stripped) = dir.strip_prefix("/") {
816 search_dirs.push(root.join(stripped));
817 }
818 }
819 }
820 if let Some(root) = &opts.syslibroot {
821 search_dirs.push(root.join("usr/lib"));
822 } else {
823 search_dirs.push(PathBuf::from("/usr/lib"));
824 }
825
826 let candidates = [
827 format!("lib{name}.tbd"),
828 format!("lib{name}.dylib"),
829 format!("lib{name}.a"),
830 ];
831 for dir in search_dirs {
832 for candidate in &candidates {
833 let path = dir.join(candidate);
834 if path.is_file() {
835 return Ok(path);
836 }
837 }
838 }
839 Err(LinkError::LibraryNotFound(name.to_string()))
840 }
841
842 fn resolve_framework_input(opts: &LinkOptions, name: &str) -> Result<PathBuf, LinkError> {
843 let mut roots = Vec::new();
844 if let Some(root) = &opts.syslibroot {
845 roots.push(root.join("System/Library/Frameworks"));
846 roots.push(root.join("Library/Frameworks"));
847 } else {
848 roots.push(PathBuf::from("/System/Library/Frameworks"));
849 roots.push(PathBuf::from("/Library/Frameworks"));
850 }
851
852 for root in roots {
853 let framework_dir = root.join(format!("{name}.framework"));
854 for candidate in [
855 framework_dir.join(format!("{name}.tbd")),
856 framework_dir.join(name),
857 ] {
858 if candidate.is_file() {
859 return Ok(candidate);
860 }
861 }
862 }
863
864 Err(LinkError::FrameworkNotFound(name.to_string()))
865 }
866
867 fn default_output_path(opts: &LinkOptions) -> PathBuf {
868 opts.output
869 .clone()
870 .unwrap_or_else(|| PathBuf::from("a.out"))
871 }
872
873 fn register_input(
874 inputs: &mut Inputs,
875 path: &std::path::Path,
876 load_order: usize,
877 ) -> Result<(), LinkError> {
878 let bytes = fs::read(path)?;
879 match path.extension().and_then(|ext| ext.to_str()) {
880 Some("a") => {
881 let _ = inputs.add_archive(path.to_path_buf(), bytes, load_order)?;
882 }
883 Some("dylib") => {
884 let _ = inputs.add_dylib(path.to_path_buf(), bytes)?;
885 }
886 Some("tbd") => {
887 let text = std::str::from_utf8(&bytes).map_err(|e| {
888 LinkError::Tbd(macho::tbd::TbdError::Schema {
889 msg: format!("TBD input is not UTF-8: {e}"),
890 })
891 })?;
892 let docs = parse_tbd(text)?;
893 let target = Target {
894 arch: Arch::Arm64,
895 platform: Platform::MacOs,
896 };
897 let canonical = docs
898 .iter()
899 .find(|doc| doc.parent_umbrella.is_empty())
900 .unwrap_or_else(|| &docs[0]);
901 let load = DylibLoadMeta {
902 install_name: canonical.install_name.clone(),
903 current_version: canonical
904 .current_version
905 .as_deref()
906 .map(parse_version)
907 .unwrap_or(DEFAULT_TBD_VERSION),
908 compatibility_version: canonical
909 .compatibility_version
910 .as_deref()
911 .map(parse_version)
912 .unwrap_or(DEFAULT_TBD_VERSION),
913 ordinal: inputs.next_dylib_ordinal(),
914 };
915 let mut loaded = false;
916 for doc in docs
917 .iter()
918 .filter(|doc| doc.targets.iter().any(|t| t.matches_requested(&target)))
919 {
920 let file = DylibFile::from_tbd(path, doc, &target);
921 let _ =
922 inputs.add_dylib_from_file_with_meta(path.to_path_buf(), file, load.clone());
923 loaded = true;
924 }
925 if !loaded {
926 return Err(LinkError::NoTbdDocument(path.to_path_buf()));
927 }
928 }
929 _ => {
930 let _ = inputs.add_object(path.to_path_buf(), bytes, load_order)?;
931 }
932 }
933 Ok(())
934 }
935
936 fn resolve_entry_point(
937 opts: &LinkOptions,
938 sym_table: &SymbolTable,
939 ) -> Result<Option<macho::writer::EntryPoint>, LinkError> {
940 let Some(symbol_id) = find_entry_symbol_id(opts, sym_table)? else {
941 return Ok(None);
942 };
943 let Symbol::Defined { atom, value, .. } = sym_table.get(symbol_id) else {
944 let name = sym_table.interner.resolve(sym_table.get(symbol_id).name());
945 return Err(LinkError::EntrySymbolNotFound(name.to_string()));
946 };
947 Ok(Some(macho::writer::EntryPoint {
948 atom: *atom,
949 atom_value: *value,
950 }))
951 }
952
953 fn find_entry_symbol_id(
954 opts: &LinkOptions,
955 sym_table: &SymbolTable,
956 ) -> Result<Option<resolve::SymbolId>, LinkError> {
957 let name = if let Some(name) = &opts.entry {
958 name.as_str()
959 } else if opts.kind == OutputKind::Executable {
960 if symbol_defined(sym_table, "_main") {
961 "_main"
962 } else if symbol_defined(sym_table, "_start") {
963 "_start"
964 } else {
965 return Ok(None);
966 }
967 } else {
968 return Ok(None);
969 };
970 let Some((symbol_id, _)) = sym_table
971 .iter()
972 .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name)
973 else {
974 return Err(LinkError::EntrySymbolNotFound(name.to_string()));
975 };
976 Ok(Some(symbol_id))
977 }
978
979 fn symbol_defined(sym_table: &SymbolTable, name: &str) -> bool {
980 sym_table.iter().any(|(_, symbol)| {
981 sym_table.interner.resolve(symbol.name()) == name
982 && matches!(symbol, Symbol::Defined { .. })
983 })
984 }
985