afs-ld Public
Comparing changes
Choose two branches to see what's changed or to start a new pull request.
Able to merge.
These branches can be automatically merged.
16 commits
15 files changed
1 contributor
Commits on runtime-hello-parity
src/layout.rsmodified@@ -10,7 +10,7 @@ use crate::input::ObjectFile; | ||
| 10 | 10 | use crate::macho::constants::SG_READ_ONLY; |
| 11 | 11 | use crate::resolve::InputId; |
| 12 | 12 | use crate::section::{ |
| 13 | - is_zerofill, OutputAtom, OutputSection, OutputSectionId, OutputSegment, Prot, | |
| 13 | + is_zerofill, InputSection, OutputAtom, OutputSection, OutputSectionId, OutputSegment, Prot, | |
| 14 | 14 | }; |
| 15 | 15 | use crate::synth::SyntheticPlan; |
| 16 | 16 | use crate::OutputKind; |
@@ -31,6 +31,8 @@ const DYLIB_SEGMENTS: [&str; 4] = ["__TEXT", "__DATA_CONST", "__DATA", "__LINKED | ||
| 31 | 31 | pub struct LayoutInput<'a> { |
| 32 | 32 | pub id: InputId, |
| 33 | 33 | pub object: &'a ObjectFile, |
| 34 | + pub load_order: usize, | |
| 35 | + pub archive_member_offset: Option<u32>, | |
| 34 | 36 | } |
| 35 | 37 | |
| 36 | 38 | #[derive(Debug, Clone, PartialEq, Eq)] |
@@ -46,6 +48,22 @@ struct SectionKey { | ||
| 46 | 48 | name: String, |
| 47 | 49 | } |
| 48 | 50 | |
| 51 | +fn output_section_key(input_section: &InputSection) -> SectionKey { | |
| 52 | + match ( | |
| 53 | + input_section.segname.as_str(), | |
| 54 | + input_section.sectname.as_str(), | |
| 55 | + ) { | |
| 56 | + ("__DATA", "__const") => SectionKey { | |
| 57 | + segment: "__DATA_CONST".to_string(), | |
| 58 | + name: "__const".to_string(), | |
| 59 | + }, | |
| 60 | + _ => SectionKey { | |
| 61 | + segment: input_section.segname.clone(), | |
| 62 | + name: input_section.sectname.clone(), | |
| 63 | + }, | |
| 64 | + } | |
| 65 | +} | |
| 66 | + | |
| 49 | 67 | impl Layout { |
| 50 | 68 | pub fn empty(kind: OutputKind, header_size: u64) -> Self { |
| 51 | 69 | Self::build(kind, &[], &AtomTable::new(), header_size) |
@@ -67,34 +85,30 @@ impl Layout { | ||
| 67 | 85 | header_size: u64, |
| 68 | 86 | synthetic_plan: Option<&SyntheticPlan>, |
| 69 | 87 | ) -> Self { |
| 70 | - let input_map: HashMap<InputId, &ObjectFile> = inputs | |
| 71 | - .iter() | |
| 72 | - .map(|input| (input.id, input.object)) | |
| 73 | - .collect(); | |
| 88 | + let input_map: HashMap<InputId, LayoutInput<'_>> = | |
| 89 | + inputs.iter().map(|input| (input.id, *input)).collect(); | |
| 74 | 90 | |
| 75 | 91 | let mut sections: Vec<OutputSection> = Vec::new(); |
| 76 | 92 | let mut section_index: HashMap<SectionKey, usize> = HashMap::new(); |
| 77 | 93 | |
| 78 | 94 | for (atom_id, atom) in atoms.iter() { |
| 79 | - let obj = input_map | |
| 95 | + let input = input_map | |
| 80 | 96 | .get(&atom.origin) |
| 81 | 97 | .unwrap_or_else(|| panic!("missing object for input {:?}", atom.origin)); |
| 82 | - let input_section = obj | |
| 98 | + let input_section = input | |
| 99 | + .object | |
| 83 | 100 | .sections |
| 84 | 101 | .get((atom.input_section as usize).saturating_sub(1)) |
| 85 | 102 | .unwrap_or_else(|| { |
| 86 | 103 | panic!( |
| 87 | 104 | "input {} section {} missing for atom {:?}", |
| 88 | - obj.path.display(), | |
| 105 | + input.object.path.display(), | |
| 89 | 106 | atom.input_section, |
| 90 | 107 | atom_id |
| 91 | 108 | ) |
| 92 | 109 | }); |
| 93 | 110 | |
| 94 | - let key = SectionKey { | |
| 95 | - segment: input_section.segname.clone(), | |
| 96 | - name: input_section.sectname.clone(), | |
| 97 | - }; | |
| 111 | + let key = output_section_key(input_section); | |
| 98 | 112 | let idx = match section_index.get(&key) { |
| 99 | 113 | Some(&idx) => idx, |
| 100 | 114 | None => { |
@@ -103,7 +117,10 @@ impl Layout { | ||
| 103 | 117 | segment: key.segment.clone(), |
| 104 | 118 | name: key.name.clone(), |
| 105 | 119 | kind: input_section.kind, |
| 106 | - align_pow2: input_section.align_pow2.min(u8::MAX as u32) as u8, | |
| 120 | + align_pow2: normalize_output_alignment( | |
| 121 | + input_section.kind, | |
| 122 | + input_section.align_pow2.min(u8::MAX as u32) as u8, | |
| 123 | + ), | |
| 107 | 124 | flags: input_section.flags, |
| 108 | 125 | reserved1: input_section.reserved1, |
| 109 | 126 | reserved2: input_section.reserved2, |
@@ -121,7 +138,8 @@ impl Layout { | ||
| 121 | 138 | }; |
| 122 | 139 | |
| 123 | 140 | let out = &mut sections[idx]; |
| 124 | - out.align_pow2 = out.align_pow2.max(atom.align_pow2); | |
| 141 | + out.align_pow2 = | |
| 142 | + normalize_output_alignment(out.kind, out.align_pow2.max(atom.align_pow2)); | |
| 125 | 143 | out.atoms.push(OutputAtom { |
| 126 | 144 | atom: atom_id, |
| 127 | 145 | offset: 0, |
@@ -156,8 +174,22 @@ impl Layout { | ||
| 156 | 174 | section.atoms.sort_by(|a, b| { |
| 157 | 175 | let lhs = atoms.get(a.atom); |
| 158 | 176 | let rhs = atoms.get(b.atom); |
| 159 | - lhs.origin | |
| 160 | - .cmp(&rhs.origin) | |
| 177 | + let lhs_input = input_map | |
| 178 | + .get(&lhs.origin) | |
| 179 | + .unwrap_or_else(|| panic!("missing object for input {:?}", lhs.origin)); | |
| 180 | + let rhs_input = input_map | |
| 181 | + .get(&rhs.origin) | |
| 182 | + .unwrap_or_else(|| panic!("missing object for input {:?}", rhs.origin)); | |
| 183 | + lhs_input | |
| 184 | + .load_order | |
| 185 | + .cmp(&rhs_input.load_order) | |
| 186 | + .then_with(|| { | |
| 187 | + lhs_input | |
| 188 | + .archive_member_offset | |
| 189 | + .unwrap_or(0) | |
| 190 | + .cmp(&rhs_input.archive_member_offset.unwrap_or(0)) | |
| 191 | + }) | |
| 192 | + .then_with(|| lhs.origin.cmp(&rhs.origin)) | |
| 161 | 193 | .then_with(|| lhs.input_offset.cmp(&rhs.input_offset)) |
| 162 | 194 | .then_with(|| a.atom.cmp(&b.atom)) |
| 163 | 195 | }); |
@@ -357,7 +389,8 @@ impl Layout { | ||
| 357 | 389 | fn merge_synthetic_section(existing: &mut OutputSection, synthetic: OutputSection) { |
| 358 | 390 | debug_assert_eq!(existing.segment, synthetic.segment); |
| 359 | 391 | debug_assert_eq!(existing.name, synthetic.name); |
| 360 | - existing.align_pow2 = existing.align_pow2.max(synthetic.align_pow2); | |
| 392 | + existing.align_pow2 = | |
| 393 | + normalize_output_alignment(existing.kind, existing.align_pow2.max(synthetic.align_pow2)); | |
| 361 | 394 | existing.flags = synthetic.flags; |
| 362 | 395 | existing.reserved1 = synthetic.reserved1; |
| 363 | 396 | existing.reserved2 = synthetic.reserved2; |
@@ -369,6 +402,17 @@ fn merge_synthetic_section(existing: &mut OutputSection, synthetic: OutputSectio | ||
| 369 | 402 | } |
| 370 | 403 | } |
| 371 | 404 | |
| 405 | +fn normalize_output_alignment(kind: crate::section::SectionKind, align_pow2: u8) -> u8 { | |
| 406 | + match kind { | |
| 407 | + crate::section::SectionKind::ThreadLocalRegular | |
| 408 | + | crate::section::SectionKind::ThreadLocalZeroFill | |
| 409 | + | crate::section::SectionKind::ThreadLocalVariables | |
| 410 | + | crate::section::SectionKind::ThreadLocalVariablePointers | |
| 411 | + | crate::section::SectionKind::ThreadLocalInitPointers => align_pow2.max(3), | |
| 412 | + _ => align_pow2, | |
| 413 | + } | |
| 414 | +} | |
| 415 | + | |
| 372 | 416 | fn build_segments(kind: OutputKind, sections: &[OutputSection]) -> Vec<OutputSegment> { |
| 373 | 417 | let names: &[&str] = match kind { |
| 374 | 418 | OutputKind::Executable => &EXEC_SEGMENTS, |
@@ -460,6 +504,7 @@ fn section_rank(segment: &str, section: &str) -> usize { | ||
| 460 | 504 | "__thread_ptrs", |
| 461 | 505 | "__thread_data", |
| 462 | 506 | "__thread_bss", |
| 507 | + "__common", | |
| 463 | 508 | "__bss", |
| 464 | 509 | ], |
| 465 | 510 | "__LINKEDIT" => &[], |
@@ -647,6 +692,8 @@ mod tests { | ||
| 647 | 692 | &[LayoutInput { |
| 648 | 693 | id: InputId(0), |
| 649 | 694 | object: &object, |
| 695 | + load_order: 0, | |
| 696 | + archive_member_offset: None, | |
| 650 | 697 | }], |
| 651 | 698 | &atoms, |
| 652 | 699 | 0x200, |
@@ -668,6 +715,68 @@ mod tests { | ||
| 668 | 715 | ); |
| 669 | 716 | } |
| 670 | 717 | |
| 718 | + #[test] | |
| 719 | + fn layout_promotes_data_const_into_data_const_segment() { | |
| 720 | + let object = ObjectFile { | |
| 721 | + path: PathBuf::from("/tmp/layout-const.o"), | |
| 722 | + header: MachHeader64 { | |
| 723 | + magic: MH_MAGIC_64, | |
| 724 | + cputype: CPU_TYPE_ARM64, | |
| 725 | + cpusubtype: CPU_SUBTYPE_ARM64_ALL, | |
| 726 | + filetype: MH_OBJECT, | |
| 727 | + ncmds: 0, | |
| 728 | + sizeofcmds: 0, | |
| 729 | + flags: 0, | |
| 730 | + reserved: 0, | |
| 731 | + }, | |
| 732 | + commands: Vec::new(), | |
| 733 | + sections: vec![input_section( | |
| 734 | + "__DATA", | |
| 735 | + "__const", | |
| 736 | + SectionKind::ConstData, | |
| 737 | + 3, | |
| 738 | + S_REGULAR, | |
| 739 | + )], | |
| 740 | + symbols: Vec::new(), | |
| 741 | + strings: crate::string_table::StringTable::from_bytes(vec![0]), | |
| 742 | + symtab: None, | |
| 743 | + dysymtab: None, | |
| 744 | + data_in_code: Vec::new(), | |
| 745 | + }; | |
| 746 | + | |
| 747 | + let mut atoms = AtomTable::new(); | |
| 748 | + atoms.push(atom( | |
| 749 | + InputId(0), | |
| 750 | + 1, | |
| 751 | + AtomSection::ConstData, | |
| 752 | + 0, | |
| 753 | + 16, | |
| 754 | + 3, | |
| 755 | + vec![1; 16], | |
| 756 | + )); | |
| 757 | + | |
| 758 | + let layout = Layout::build( | |
| 759 | + OutputKind::Executable, | |
| 760 | + &[LayoutInput { | |
| 761 | + id: InputId(0), | |
| 762 | + object: &object, | |
| 763 | + load_order: 0, | |
| 764 | + archive_member_offset: None, | |
| 765 | + }], | |
| 766 | + &atoms, | |
| 767 | + 0x200, | |
| 768 | + ); | |
| 769 | + | |
| 770 | + assert!(layout | |
| 771 | + .sections | |
| 772 | + .iter() | |
| 773 | + .any(|section| section.segment == "__DATA_CONST" && section.name == "__const")); | |
| 774 | + assert!(!layout | |
| 775 | + .sections | |
| 776 | + .iter() | |
| 777 | + .any(|section| section.segment == "__DATA" && section.name == "__const")); | |
| 778 | + } | |
| 779 | + | |
| 671 | 780 | #[test] |
| 672 | 781 | fn executable_layout_has_pagezero_and_zerofill_has_no_file_offset() { |
| 673 | 782 | let object = ObjectFile { |
@@ -713,6 +822,8 @@ mod tests { | ||
| 713 | 822 | &[LayoutInput { |
| 714 | 823 | id: InputId(0), |
| 715 | 824 | object: &object, |
| 825 | + load_order: 0, | |
| 826 | + archive_member_offset: None, | |
| 716 | 827 | }], |
| 717 | 828 | &atoms, |
| 718 | 829 | 0x300, |
@@ -727,6 +838,72 @@ mod tests { | ||
| 727 | 838 | assert!(bss.addr >= EXECUTABLE_TEXT_BASE + PAGE_SIZE); |
| 728 | 839 | } |
| 729 | 840 | |
| 841 | + #[test] | |
| 842 | + fn layout_orders_common_before_bss() { | |
| 843 | + let object = ObjectFile { | |
| 844 | + path: PathBuf::from("/tmp/layout-common-bss.o"), | |
| 845 | + header: MachHeader64 { | |
| 846 | + magic: MH_MAGIC_64, | |
| 847 | + cputype: CPU_TYPE_ARM64, | |
| 848 | + cpusubtype: CPU_SUBTYPE_ARM64_ALL, | |
| 849 | + filetype: MH_OBJECT, | |
| 850 | + ncmds: 0, | |
| 851 | + sizeofcmds: 0, | |
| 852 | + flags: 0, | |
| 853 | + reserved: 0, | |
| 854 | + }, | |
| 855 | + commands: Vec::new(), | |
| 856 | + sections: vec![ | |
| 857 | + input_section("__DATA", "__common", SectionKind::ZeroFill, 3, S_ZEROFILL), | |
| 858 | + input_section("__DATA", "__bss", SectionKind::ZeroFill, 3, S_ZEROFILL), | |
| 859 | + ], | |
| 860 | + symbols: Vec::new(), | |
| 861 | + strings: crate::string_table::StringTable::from_bytes(vec![0]), | |
| 862 | + symtab: None, | |
| 863 | + dysymtab: None, | |
| 864 | + data_in_code: Vec::new(), | |
| 865 | + }; | |
| 866 | + | |
| 867 | + let mut atoms = AtomTable::new(); | |
| 868 | + atoms.push(atom( | |
| 869 | + InputId(0), | |
| 870 | + 1, | |
| 871 | + AtomSection::ZeroFill, | |
| 872 | + 0, | |
| 873 | + 16, | |
| 874 | + 3, | |
| 875 | + Vec::new(), | |
| 876 | + )); | |
| 877 | + atoms.push(atom( | |
| 878 | + InputId(0), | |
| 879 | + 2, | |
| 880 | + AtomSection::ZeroFill, | |
| 881 | + 0, | |
| 882 | + 32, | |
| 883 | + 3, | |
| 884 | + Vec::new(), | |
| 885 | + )); | |
| 886 | + | |
| 887 | + let layout = Layout::build( | |
| 888 | + OutputKind::Executable, | |
| 889 | + &[LayoutInput { | |
| 890 | + id: InputId(0), | |
| 891 | + object: &object, | |
| 892 | + load_order: 0, | |
| 893 | + archive_member_offset: None, | |
| 894 | + }], | |
| 895 | + &atoms, | |
| 896 | + 0x200, | |
| 897 | + ); | |
| 898 | + | |
| 899 | + let names: Vec<(&str, &str)> = layout | |
| 900 | + .sections | |
| 901 | + .iter() | |
| 902 | + .map(|section| (section.segment.as_str(), section.name.as_str())) | |
| 903 | + .collect(); | |
| 904 | + assert_eq!(names, vec![("__DATA", "__common"), ("__DATA", "__bss")]); | |
| 905 | + } | |
| 906 | + | |
| 730 | 907 | #[test] |
| 731 | 908 | fn file_backed_segments_round_to_page_boundaries() { |
| 732 | 909 | let object = ObjectFile { |
@@ -776,6 +953,8 @@ mod tests { | ||
| 776 | 953 | &[LayoutInput { |
| 777 | 954 | id: InputId(0), |
| 778 | 955 | object: &object, |
| 956 | + load_order: 0, | |
| 957 | + archive_member_offset: None, | |
| 779 | 958 | }], |
| 780 | 959 | &atoms, |
| 781 | 960 | 0x200, |
@@ -866,6 +1045,8 @@ mod tests { | ||
| 866 | 1045 | &[LayoutInput { |
| 867 | 1046 | id: InputId(0), |
| 868 | 1047 | object: &object, |
| 1048 | + load_order: 0, | |
| 1049 | + archive_member_offset: None, | |
| 869 | 1050 | }], |
| 870 | 1051 | &atoms, |
| 871 | 1052 | 0x200, |
@@ -990,6 +1171,8 @@ mod tests { | ||
| 990 | 1171 | &[LayoutInput { |
| 991 | 1172 | id: InputId(0), |
| 992 | 1173 | object: &object, |
| 1174 | + load_order: 0, | |
| 1175 | + archive_member_offset: None, | |
| 993 | 1176 | }], |
| 994 | 1177 | &atoms, |
| 995 | 1178 | 0x200, |
@@ -1044,6 +1227,8 @@ mod tests { | ||
| 1044 | 1227 | &[LayoutInput { |
| 1045 | 1228 | id: InputId(0), |
| 1046 | 1229 | object: &object, |
| 1230 | + load_order: 0, | |
| 1231 | + archive_member_offset: None, | |
| 1047 | 1232 | }], |
| 1048 | 1233 | &atoms, |
| 1049 | 1234 | 0x200, |
src/lib.rsmodified@@ -35,6 +35,8 @@ use resolve::{ | ||
| 35 | 35 | seed_all, DylibLoadMeta, InputAddError, Inputs, Symbol, SymbolTable, UndefinedTreatment, |
| 36 | 36 | }; |
| 37 | 37 | |
| 38 | +const DEFAULT_TBD_VERSION: u32 = 1 << 16; | |
| 39 | + | |
| 38 | 40 | /// What kind of Mach-O file the linker is producing. |
| 39 | 41 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 40 | 42 | pub enum OutputKind { |
@@ -207,8 +209,8 @@ impl Linker { | ||
| 207 | 209 | } |
| 208 | 210 | |
| 209 | 211 | let mut inputs = Inputs::new(); |
| 210 | - for path in &opts.inputs { | |
| 211 | - register_input(&mut inputs, path)?; | |
| 212 | + for (load_order, path) in opts.inputs.iter().enumerate() { | |
| 213 | + register_input(&mut inputs, path, load_order)?; | |
| 212 | 214 | } |
| 213 | 215 | |
| 214 | 216 | let mut sym_table = SymbolTable::new(); |
@@ -259,7 +261,15 @@ impl Linker { | ||
| 259 | 261 | |
| 260 | 262 | let layout_inputs: Vec<LayoutInput<'_>> = objects |
| 261 | 263 | .iter() |
| 262 | - .map(|(id, object)| LayoutInput { id: *id, object }) | |
| 264 | + .map(|(id, object)| { | |
| 265 | + let input = inputs.object(*id); | |
| 266 | + LayoutInput { | |
| 267 | + id: *id, | |
| 268 | + object, | |
| 269 | + load_order: input.load_order, | |
| 270 | + archive_member_offset: input.archive_member_offset, | |
| 271 | + } | |
| 272 | + }) | |
| 263 | 273 | .collect(); |
| 264 | 274 | let mut dylib_loads = Vec::new(); |
| 265 | 275 | let mut seen_ordinals = std::collections::BTreeSet::new(); |
@@ -355,11 +365,15 @@ fn default_output_path(opts: &LinkOptions) -> PathBuf { | ||
| 355 | 365 | .unwrap_or_else(|| PathBuf::from("a.out")) |
| 356 | 366 | } |
| 357 | 367 | |
| 358 | -fn register_input(inputs: &mut Inputs, path: &std::path::Path) -> Result<(), LinkError> { | |
| 368 | +fn register_input( | |
| 369 | + inputs: &mut Inputs, | |
| 370 | + path: &std::path::Path, | |
| 371 | + load_order: usize, | |
| 372 | +) -> Result<(), LinkError> { | |
| 359 | 373 | let bytes = fs::read(path)?; |
| 360 | 374 | match path.extension().and_then(|ext| ext.to_str()) { |
| 361 | 375 | Some("a") => { |
| 362 | - let _ = inputs.add_archive(path.to_path_buf(), bytes)?; | |
| 376 | + let _ = inputs.add_archive(path.to_path_buf(), bytes, load_order)?; | |
| 363 | 377 | } |
| 364 | 378 | Some("dylib") => { |
| 365 | 379 | let _ = inputs.add_dylib(path.to_path_buf(), bytes)?; |
@@ -385,12 +399,12 @@ fn register_input(inputs: &mut Inputs, path: &std::path::Path) -> Result<(), Lin | ||
| 385 | 399 | .current_version |
| 386 | 400 | .as_deref() |
| 387 | 401 | .map(parse_version) |
| 388 | - .unwrap_or(0), | |
| 402 | + .unwrap_or(DEFAULT_TBD_VERSION), | |
| 389 | 403 | compatibility_version: canonical |
| 390 | 404 | .compatibility_version |
| 391 | 405 | .as_deref() |
| 392 | 406 | .map(parse_version) |
| 393 | - .unwrap_or(0), | |
| 407 | + .unwrap_or(DEFAULT_TBD_VERSION), | |
| 394 | 408 | ordinal: inputs.next_dylib_ordinal(), |
| 395 | 409 | }; |
| 396 | 410 | let mut loaded = false; |
@@ -408,7 +422,7 @@ fn register_input(inputs: &mut Inputs, path: &std::path::Path) -> Result<(), Lin | ||
| 408 | 422 | } |
| 409 | 423 | } |
| 410 | 424 | _ => { |
| 411 | - let _ = inputs.add_object(path.to_path_buf(), bytes)?; | |
| 425 | + let _ = inputs.add_object(path.to_path_buf(), bytes, load_order)?; | |
| 412 | 426 | } |
| 413 | 427 | } |
| 414 | 428 | Ok(()) |
@@ -418,20 +432,37 @@ fn resolve_entry_point( | ||
| 418 | 432 | opts: &LinkOptions, |
| 419 | 433 | sym_table: &SymbolTable, |
| 420 | 434 | ) -> Result<Option<macho::writer::EntryPoint>, LinkError> { |
| 421 | - let Some(name) = &opts.entry else { | |
| 435 | + let name = if let Some(name) = &opts.entry { | |
| 436 | + name.as_str() | |
| 437 | + } else if opts.kind == OutputKind::Executable { | |
| 438 | + if symbol_defined(sym_table, "_main") { | |
| 439 | + "_main" | |
| 440 | + } else if symbol_defined(sym_table, "_start") { | |
| 441 | + "_start" | |
| 442 | + } else { | |
| 443 | + return Ok(None); | |
| 444 | + } | |
| 445 | + } else { | |
| 422 | 446 | return Ok(None); |
| 423 | 447 | }; |
| 424 | 448 | let Some((symbol_id, _)) = sym_table |
| 425 | 449 | .iter() |
| 426 | 450 | .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name) |
| 427 | 451 | else { |
| 428 | - return Err(LinkError::EntrySymbolNotFound(name.clone())); | |
| 452 | + return Err(LinkError::EntrySymbolNotFound(name.to_string())); | |
| 429 | 453 | }; |
| 430 | 454 | let Symbol::Defined { atom, value, .. } = sym_table.get(symbol_id) else { |
| 431 | - return Err(LinkError::EntrySymbolNotFound(name.clone())); | |
| 455 | + return Err(LinkError::EntrySymbolNotFound(name.to_string())); | |
| 432 | 456 | }; |
| 433 | 457 | Ok(Some(macho::writer::EntryPoint { |
| 434 | 458 | atom: *atom, |
| 435 | 459 | atom_value: *value, |
| 436 | 460 | })) |
| 437 | 461 | } |
| 462 | + | |
| 463 | +fn symbol_defined(sym_table: &SymbolTable, name: &str) -> bool { | |
| 464 | + sym_table.iter().any(|(_, symbol)| { | |
| 465 | + sym_table.interner.resolve(symbol.name()) == name | |
| 466 | + && matches!(symbol, Symbol::Defined { .. }) | |
| 467 | + }) | |
| 468 | +} | |
src/macho/constants.rsmodified@@ -115,6 +115,10 @@ pub const N_WEAK_DEF: u16 = 0x0080; | ||
| 115 | 115 | /// `alt_entries` during atomization. |
| 116 | 116 | pub const N_ALT_ENTRY: u16 = 0x0200; |
| 117 | 117 | |
| 118 | +// Indirect symbol table sentinels (<mach-o/loader.h>) | |
| 119 | +pub const INDIRECT_SYMBOL_LOCAL: u32 = 0x8000_0000; | |
| 120 | +pub const INDIRECT_SYMBOL_ABS: u32 = 0x4000_0000; | |
| 121 | + | |
| 118 | 122 | // ARM64 relocation kinds |
| 119 | 123 | pub const ARM64_RELOC_UNSIGNED: u8 = 0; |
| 120 | 124 | pub const ARM64_RELOC_SUBTRACTOR: u8 = 1; |
src/macho/dylib.rsmodified@@ -16,6 +16,8 @@ use super::reader::{ | ||
| 16 | 16 | }; |
| 17 | 17 | use super::tbd::{parse_version, SymbolLists, Target, Tbd}; |
| 18 | 18 | |
| 19 | +const DEFAULT_TBD_VERSION: u32 = 1 << 16; | |
| 20 | + | |
| 19 | 21 | /// How a consumer loaded this dylib. The filetype of the dylib itself is |
| 20 | 22 | /// always `MH_DYLIB`; this kind captures the *relationship*. |
| 21 | 23 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
@@ -222,12 +224,12 @@ impl DylibFile { | ||
| 222 | 224 | .current_version |
| 223 | 225 | .as_deref() |
| 224 | 226 | .map(parse_version) |
| 225 | - .unwrap_or(0), | |
| 227 | + .unwrap_or(DEFAULT_TBD_VERSION), | |
| 226 | 228 | compatibility_version: tbd |
| 227 | 229 | .compatibility_version |
| 228 | 230 | .as_deref() |
| 229 | 231 | .map(parse_version) |
| 230 | - .unwrap_or(0), | |
| 232 | + .unwrap_or(DEFAULT_TBD_VERSION), | |
| 231 | 233 | dependencies, |
| 232 | 234 | rpaths: Vec::new(), |
| 233 | 235 | symtab: None, |
src/macho/writer.rsmodified@@ -337,29 +337,44 @@ fn build_commands( | ||
| 337 | 337 | )?)); |
| 338 | 338 | } |
| 339 | 339 | |
| 340 | - commands.push(LoadCommand::BuildVersion(BuildVersionCmd { | |
| 341 | - platform: PLATFORM_MACOS, | |
| 342 | - minos: pack_version(11, 0, 0), | |
| 343 | - sdk: pack_version(11, 0, 0), | |
| 344 | - tools: vec![BuildTool { | |
| 345 | - tool: 3, | |
| 346 | - version: pack_version(0, 1, 0), | |
| 347 | - }], | |
| 348 | - })); | |
| 349 | - commands.push(raw_uuid_command(stable_uuid(layout, kind))); | |
| 350 | - | |
| 351 | 340 | match kind { |
| 352 | 341 | OutputKind::Executable => { |
| 342 | + commands.push(LoadCommand::DyldInfoOnly(linkedit.dyld_info)); | |
| 343 | + commands.push(LoadCommand::Symtab(linkedit.symtab)); | |
| 344 | + commands.push(LoadCommand::Dysymtab(linkedit.dysymtab)); | |
| 353 | 345 | commands.push(raw_dylinker_command("/usr/lib/dyld")); |
| 346 | + commands.push(raw_uuid_command(stable_uuid(layout, kind))); | |
| 347 | + commands.push(LoadCommand::BuildVersion(BuildVersionCmd { | |
| 348 | + platform: PLATFORM_MACOS, | |
| 349 | + minos: pack_version(11, 0, 0), | |
| 350 | + sdk: pack_version(11, 0, 0), | |
| 351 | + tools: vec![BuildTool { | |
| 352 | + tool: 3, | |
| 353 | + version: pack_version(0, 1, 0), | |
| 354 | + }], | |
| 355 | + })); | |
| 356 | + commands.push(raw_source_version_command(0)); | |
| 354 | 357 | commands.push(raw_entry_point(resolve_entryoff(layout, entry_point)?, 0)); |
| 355 | 358 | } |
| 356 | - OutputKind::Dylib => commands.push(LoadCommand::Dylib(DylibCmd { | |
| 357 | - cmd: LC_ID_DYLIB, | |
| 358 | - name: dylib_install_name(opts), | |
| 359 | - timestamp: 2, | |
| 360 | - current_version: pack_version(1, 0, 0), | |
| 361 | - compatibility_version: pack_version(1, 0, 0), | |
| 362 | - })), | |
| 359 | + OutputKind::Dylib => { | |
| 360 | + commands.push(LoadCommand::BuildVersion(BuildVersionCmd { | |
| 361 | + platform: PLATFORM_MACOS, | |
| 362 | + minos: pack_version(11, 0, 0), | |
| 363 | + sdk: pack_version(11, 0, 0), | |
| 364 | + tools: vec![BuildTool { | |
| 365 | + tool: 3, | |
| 366 | + version: pack_version(0, 1, 0), | |
| 367 | + }], | |
| 368 | + })); | |
| 369 | + commands.push(raw_uuid_command(stable_uuid(layout, kind))); | |
| 370 | + commands.push(LoadCommand::Dylib(DylibCmd { | |
| 371 | + cmd: LC_ID_DYLIB, | |
| 372 | + name: dylib_install_name(opts), | |
| 373 | + timestamp: 2, | |
| 374 | + current_version: pack_version(1, 0, 0), | |
| 375 | + compatibility_version: pack_version(1, 0, 0), | |
| 376 | + })); | |
| 377 | + } | |
| 363 | 378 | } |
| 364 | 379 | |
| 365 | 380 | for dylib in dylibs { |
@@ -372,8 +387,6 @@ fn build_commands( | ||
| 372 | 387 | })); |
| 373 | 388 | } |
| 374 | 389 | |
| 375 | - commands.push(LoadCommand::Symtab(linkedit.symtab)); | |
| 376 | - commands.push(LoadCommand::Dysymtab(linkedit.dysymtab)); | |
| 377 | 390 | commands.push(raw_linkedit_command( |
| 378 | 391 | LC_FUNCTION_STARTS, |
| 379 | 392 | linkedit.function_starts.dataoff, |
@@ -393,7 +406,11 @@ fn build_commands( | ||
| 393 | 406 | } else { |
| 394 | 407 | commands.push(raw_linkedit_command(LC_CODE_SIGNATURE, 0, 0)); |
| 395 | 408 | } |
| 396 | - commands.push(LoadCommand::DyldInfoOnly(linkedit.dyld_info)); | |
| 409 | + if kind == OutputKind::Dylib { | |
| 410 | + commands.push(LoadCommand::DyldInfoOnly(linkedit.dyld_info)); | |
| 411 | + commands.push(LoadCommand::Symtab(linkedit.symtab)); | |
| 412 | + commands.push(LoadCommand::Dysymtab(linkedit.dysymtab)); | |
| 413 | + } | |
| 397 | 414 | |
| 398 | 415 | Ok(commands) |
| 399 | 416 | } |
@@ -421,7 +438,11 @@ fn estimate_header_size( | ||
| 421 | 438 | .wire_size() as u64; |
| 422 | 439 | size += 24; |
| 423 | 440 | size += match kind { |
| 424 | - OutputKind::Executable => raw_dylinker_command("/usr/lib/dyld").cmdsize() as u64 + 24, | |
| 441 | + OutputKind::Executable => { | |
| 442 | + raw_dylinker_command("/usr/lib/dyld").cmdsize() as u64 | |
| 443 | + + 24 | |
| 444 | + + raw_source_version_command(0).cmdsize() as u64 | |
| 445 | + } | |
| 425 | 446 | OutputKind::Dylib => DylibCmd { |
| 426 | 447 | cmd: LC_ID_DYLIB, |
| 427 | 448 | name: dylib_install_name(opts), |
@@ -530,6 +551,14 @@ fn raw_uuid_command(uuid: [u8; 16]) -> LoadCommand { | ||
| 530 | 551 | } |
| 531 | 552 | } |
| 532 | 553 | |
| 554 | +fn raw_source_version_command(version: u64) -> LoadCommand { | |
| 555 | + LoadCommand::Raw { | |
| 556 | + cmd: LC_SOURCE_VERSION, | |
| 557 | + cmdsize: 16, | |
| 558 | + data: version.to_le_bytes().to_vec(), | |
| 559 | + } | |
| 560 | +} | |
| 561 | + | |
| 533 | 562 | fn raw_linkedit_command(cmd: u32, dataoff: u32, datasize: u32) -> LoadCommand { |
| 534 | 563 | let mut data = Vec::with_capacity(8); |
| 535 | 564 | data.extend_from_slice(&dataoff.to_le_bytes()); |
@@ -711,7 +740,6 @@ fn build_linkedit_plan( | ||
| 711 | 740 | .map(|record| (record.symbol, record)) |
| 712 | 741 | .collect(); |
| 713 | 742 | let symbol_plan = build_output_symbols(layout, kind, opts.strip_locals, inputs, &imports)?; |
| 714 | - | |
| 715 | 743 | let mut symtab_bytes = Vec::new(); |
| 716 | 744 | write_nlist_table(&symbol_plan.symbols, &mut symtab_bytes); |
| 717 | 745 | |
@@ -721,30 +749,25 @@ fn build_linkedit_plan( | ||
| 721 | 749 | &mut indirect_symbols, |
| 722 | 750 | &mut indirect_starts, |
| 723 | 751 | ("__TEXT", "__stubs"), |
| 724 | - synthetic_plan | |
| 725 | - .stubs | |
| 726 | - .entries | |
| 727 | - .iter() | |
| 728 | - .map(|entry| entry.symbol), | |
| 729 | - &symbol_plan.symbol_indices, | |
| 752 | + synthetic_plan.stubs.entries.iter().map(|entry| { | |
| 753 | + indirect_symbol_index(entry.symbol, &import_lookup, &symbol_plan.symbol_indices) | |
| 754 | + }), | |
| 730 | 755 | ); |
| 731 | 756 | push_indirect_section( |
| 732 | 757 | &mut indirect_symbols, |
| 733 | 758 | &mut indirect_starts, |
| 734 | 759 | ("__DATA_CONST", "__got"), |
| 735 | - synthetic_plan.got.entries.iter().map(|entry| entry.symbol), | |
| 736 | - &symbol_plan.symbol_indices, | |
| 760 | + synthetic_plan.got.entries.iter().map(|entry| { | |
| 761 | + indirect_symbol_index(entry.symbol, &import_lookup, &symbol_plan.symbol_indices) | |
| 762 | + }), | |
| 737 | 763 | ); |
| 738 | 764 | push_indirect_section( |
| 739 | 765 | &mut indirect_symbols, |
| 740 | 766 | &mut indirect_starts, |
| 741 | 767 | ("__DATA", "__la_symbol_ptr"), |
| 742 | - synthetic_plan | |
| 743 | - .lazy_pointers | |
| 744 | - .entries | |
| 745 | - .iter() | |
| 746 | - .map(|entry| entry.symbol), | |
| 747 | - &symbol_plan.symbol_indices, | |
| 768 | + synthetic_plan.lazy_pointers.entries.iter().map(|entry| { | |
| 769 | + indirect_symbol_index(entry.symbol, &import_lookup, &symbol_plan.symbol_indices) | |
| 770 | + }), | |
| 748 | 771 | ); |
| 749 | 772 | |
| 750 | 773 | let mut indirect_bytes = Vec::with_capacity(indirect_symbols.len() * 4); |
@@ -758,7 +781,8 @@ fn build_linkedit_plan( | ||
| 758 | 781 | let weak_bind_bytes = pad_dyld_info_stream(bind_streams.weak_bind); |
| 759 | 782 | let lazy_bind_bytes = pad_dyld_info_stream(bind_streams.lazy_bind); |
| 760 | 783 | let export_bytes = pad_dyld_info_stream(build_export_trie(&symbol_plan.exports)); |
| 761 | - let function_starts_bytes = build_function_starts(layout, inputs.0.atom_table)?; | |
| 784 | + let function_starts_bytes = | |
| 785 | + build_function_starts(layout, inputs.0.layout_inputs, inputs.0.atom_table)?; | |
| 762 | 786 | let data_in_code_bytes = |
| 763 | 787 | build_data_in_code(layout, inputs.0.layout_inputs, inputs.0.atom_table)?; |
| 764 | 788 | |
@@ -959,6 +983,11 @@ fn collect_rebase_sites( | ||
| 959 | 983 | inputs: LinkEditInputs<'_>, |
| 960 | 984 | ) -> Result<Vec<RebaseSite>, WriteError> { |
| 961 | 985 | let mut sites = collect_lazy_pointer_rebase_sites(layout, synthetic_plan)?; |
| 986 | + sites.extend(collect_local_got_rebase_sites( | |
| 987 | + layout, | |
| 988 | + synthetic_plan, | |
| 989 | + inputs.0.sym_table, | |
| 990 | + )?); | |
| 962 | 991 | let mut reloc_cache: HashMap<(InputId, u8), Vec<Reloc>> = HashMap::new(); |
| 963 | 992 | let input_map: HashMap<InputId, &ObjectFile> = inputs |
| 964 | 993 | .0 |
@@ -1052,6 +1081,38 @@ fn collect_lazy_pointer_rebase_sites( | ||
| 1052 | 1081 | .collect()) |
| 1053 | 1082 | } |
| 1054 | 1083 | |
| 1084 | +fn collect_local_got_rebase_sites( | |
| 1085 | + layout: &Layout, | |
| 1086 | + synthetic_plan: &SyntheticPlan, | |
| 1087 | + sym_table: &SymbolTable, | |
| 1088 | +) -> Result<Vec<RebaseSite>, WriteError> { | |
| 1089 | + if synthetic_plan.got.entries.is_empty() { | |
| 1090 | + return Ok(Vec::new()); | |
| 1091 | + } | |
| 1092 | + | |
| 1093 | + let segment_index = segment_index(layout, "__DATA_CONST")?; | |
| 1094 | + let segment = layout | |
| 1095 | + .segment("__DATA_CONST") | |
| 1096 | + .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1097 | + let section = layout | |
| 1098 | + .sections | |
| 1099 | + .iter() | |
| 1100 | + .find(|section| section.segment == "__DATA_CONST" && section.name == "__got") | |
| 1101 | + .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1102 | + | |
| 1103 | + Ok(synthetic_plan | |
| 1104 | + .got | |
| 1105 | + .entries | |
| 1106 | + .iter() | |
| 1107 | + .enumerate() | |
| 1108 | + .filter(|(_, entry)| !matches!(sym_table.get(entry.symbol), Symbol::DylibImport { .. })) | |
| 1109 | + .map(|(idx, _)| RebaseSite { | |
| 1110 | + segment_index, | |
| 1111 | + segment_offset: section.addr + (idx as u64) * 8 - segment.vm_addr, | |
| 1112 | + }) | |
| 1113 | + .collect()) | |
| 1114 | +} | |
| 1115 | + | |
| 1055 | 1116 | fn relocs_for_rebase<'a>( |
| 1056 | 1117 | relocs: &'a [Reloc], |
| 1057 | 1118 | atom: &crate::atom::Atom, |
@@ -1075,15 +1136,20 @@ fn reloc_needs_rebase(obj: &ObjectFile, reloc: Reloc, sym_table: &SymbolTable) - | ||
| 1075 | 1136 | |
| 1076 | 1137 | match reloc.referent { |
| 1077 | 1138 | Referent::Section(_) => true, |
| 1078 | - Referent::Symbol(_) => match symbol_referent_id(obj, reloc.referent, sym_table) { | |
| 1079 | - Some(symbol_id) => match sym_table.get(symbol_id) { | |
| 1080 | - Symbol::DylibImport { .. } => false, | |
| 1081 | - Symbol::Defined { atom, .. } => atom.0 != 0, | |
| 1082 | - Symbol::Common { .. } => true, | |
| 1083 | - _ => false, | |
| 1084 | - }, | |
| 1085 | - None => false, | |
| 1086 | - }, | |
| 1139 | + Referent::Symbol(sym_idx) => { | |
| 1140 | + let Some(input_sym) = obj.symbols.get(sym_idx as usize) else { | |
| 1141 | + return false; | |
| 1142 | + }; | |
| 1143 | + match symbol_referent_id(obj, reloc.referent, sym_table) { | |
| 1144 | + Some(symbol_id) => match sym_table.get(symbol_id) { | |
| 1145 | + Symbol::DylibImport { .. } => false, | |
| 1146 | + Symbol::Defined { atom, .. } => atom.0 != 0, | |
| 1147 | + Symbol::Common { .. } => true, | |
| 1148 | + _ => false, | |
| 1149 | + }, | |
| 1150 | + None => matches!(input_sym.kind(), SymKind::Sect), | |
| 1151 | + } | |
| 1152 | + } | |
| 1087 | 1153 | } |
| 1088 | 1154 | } |
| 1089 | 1155 | |
@@ -1103,11 +1169,19 @@ fn symbol_referent_id( | ||
| 1103 | 1169 | Some(symbol_id) |
| 1104 | 1170 | } |
| 1105 | 1171 | |
| 1106 | -fn build_function_starts(layout: &Layout, atom_table: &AtomTable) -> Result<Vec<u8>, WriteError> { | |
| 1172 | +fn build_function_starts( | |
| 1173 | + layout: &Layout, | |
| 1174 | + inputs: &[LayoutInput<'_>], | |
| 1175 | + atom_table: &AtomTable, | |
| 1176 | +) -> Result<Vec<u8>, WriteError> { | |
| 1107 | 1177 | let image_base = layout |
| 1108 | 1178 | .segment("__TEXT") |
| 1109 | 1179 | .ok_or(WriteError::MissingSegment("__TEXT"))? |
| 1110 | 1180 | .vm_addr; |
| 1181 | + let input_map: HashMap<InputId, &ObjectFile> = inputs | |
| 1182 | + .iter() | |
| 1183 | + .map(|input| (input.id, input.object)) | |
| 1184 | + .collect(); | |
| 1111 | 1185 | let mut starts = Vec::new(); |
| 1112 | 1186 | |
| 1113 | 1187 | for section in &layout.sections { |
@@ -1122,6 +1196,37 @@ fn build_function_starts(layout: &Layout, atom_table: &AtomTable) -> Result<Vec< | ||
| 1122 | 1196 | section.addr + placed.offset + alt.offset_within_atom as u64 - image_base, |
| 1123 | 1197 | ); |
| 1124 | 1198 | } |
| 1199 | + let Some(object) = input_map.get(&atom.origin) else { | |
| 1200 | + continue; | |
| 1201 | + }; | |
| 1202 | + let Some(input_section) = object | |
| 1203 | + .sections | |
| 1204 | + .get((atom.input_section as usize).saturating_sub(1)) | |
| 1205 | + else { | |
| 1206 | + continue; | |
| 1207 | + }; | |
| 1208 | + let atom_start = input_section.addr + atom.input_offset as u64; | |
| 1209 | + let atom_end = atom_start + atom.size as u64; | |
| 1210 | + for input_sym in &object.symbols { | |
| 1211 | + if input_sym.stab_kind().is_some() | |
| 1212 | + || input_sym.kind() != SymKind::Sect | |
| 1213 | + || input_sym.alt_entry() | |
| 1214 | + || input_sym.sect_idx() != atom.input_section | |
| 1215 | + { | |
| 1216 | + continue; | |
| 1217 | + } | |
| 1218 | + let Ok(name) = object.symbol_name(input_sym) else { | |
| 1219 | + continue; | |
| 1220 | + }; | |
| 1221 | + if is_assembler_temporary_symbol(name) { | |
| 1222 | + continue; | |
| 1223 | + } | |
| 1224 | + let value = input_sym.value(); | |
| 1225 | + if !(atom_start < value && value < atom_end) { | |
| 1226 | + continue; | |
| 1227 | + } | |
| 1228 | + starts.push(section.addr + placed.offset + (value - atom_start) - image_base); | |
| 1229 | + } | |
| 1125 | 1230 | } |
| 1126 | 1231 | } |
| 1127 | 1232 | |
@@ -1158,6 +1263,7 @@ fn build_data_in_code( | ||
| 1158 | 1263 | kind: u16, |
| 1159 | 1264 | } |
| 1160 | 1265 | |
| 1266 | + let atoms_by_input_section = atom_table.by_input_section(); | |
| 1161 | 1267 | let mut remapped = Vec::new(); |
| 1162 | 1268 | for (input_order, input) in inputs.iter().enumerate() { |
| 1163 | 1269 | for (input_entry_index, entry) in input.object.data_in_code.iter().copied().enumerate() { |
@@ -1165,6 +1271,7 @@ fn build_data_in_code( | ||
| 1165 | 1271 | remap_data_in_code_to_section(input.object, entry)?; |
| 1166 | 1272 | let (atom_id, atom_delta) = find_containing_atom_range( |
| 1167 | 1273 | atom_table, |
| 1274 | + &atoms_by_input_section, | |
| 1168 | 1275 | input.id, |
| 1169 | 1276 | section_index, |
| 1170 | 1277 | section_relative, |
@@ -1304,7 +1411,7 @@ fn collect_imports( | ||
| 1304 | 1411 | .. |
| 1305 | 1412 | } = symbol |
| 1306 | 1413 | else { |
| 1307 | - return Err(WriteError::ImportSymbolWrongKind(id)); | |
| 1414 | + continue; | |
| 1308 | 1415 | }; |
| 1309 | 1416 | out.push(ImportSymbolRecord { |
| 1310 | 1417 | symbol: id, |
@@ -1326,6 +1433,7 @@ fn build_output_symbols( | ||
| 1326 | 1433 | ) -> Result<SymbolTablePlan, WriteError> { |
| 1327 | 1434 | let sym_table = inputs.0.sym_table; |
| 1328 | 1435 | let atom_sections = atom_section_ordinals(layout); |
| 1436 | + let atoms_by_input_section = inputs.0.atom_table.by_input_section(); | |
| 1329 | 1437 | let image_base = layout.segment("__TEXT").map(|seg| seg.vm_addr).unwrap_or(0); |
| 1330 | 1438 | let mut locals = Vec::new(); |
| 1331 | 1439 | let mut external_defineds = Vec::new(); |
@@ -1351,6 +1459,7 @@ fn build_output_symbols( | ||
| 1351 | 1459 | collect_local_symbols( |
| 1352 | 1460 | layout, |
| 1353 | 1461 | inputs.0.atom_table, |
| 1462 | + &atoms_by_input_section, | |
| 1354 | 1463 | &atom_sections, |
| 1355 | 1464 | input.id, |
| 1356 | 1465 | input.object, |
@@ -1358,6 +1467,7 @@ fn build_output_symbols( | ||
| 1358 | 1467 | )?; |
| 1359 | 1468 | } |
| 1360 | 1469 | collect_synthetic_local_symbols(layout, inputs.0.synthetic_plan, &mut locals)?; |
| 1470 | + sort_local_symbols(&mut locals); | |
| 1361 | 1471 | |
| 1362 | 1472 | for (symbol_id, symbol) in sym_table.iter() { |
| 1363 | 1473 | let Symbol::Defined { |
@@ -1504,6 +1614,16 @@ fn build_output_symbols( | ||
| 1504 | 1614 | }) |
| 1505 | 1615 | } |
| 1506 | 1616 | |
| 1617 | +fn sort_local_symbols(locals: &mut [OutputSymbolSpec]) { | |
| 1618 | + locals.sort_by(|lhs, rhs| { | |
| 1619 | + lhs.n_sect | |
| 1620 | + .cmp(&rhs.n_sect) | |
| 1621 | + .then_with(|| lhs.n_value.cmp(&rhs.n_value)) | |
| 1622 | + .then_with(|| lhs.n_type.cmp(&rhs.n_type)) | |
| 1623 | + .then_with(|| lhs.name.cmp(&rhs.name)) | |
| 1624 | + }); | |
| 1625 | +} | |
| 1626 | + | |
| 1507 | 1627 | fn collect_synthetic_local_symbols( |
| 1508 | 1628 | layout: &Layout, |
| 1509 | 1629 | synthetic_plan: &SyntheticPlan, |
@@ -1537,6 +1657,7 @@ fn collect_synthetic_local_symbols( | ||
| 1537 | 1657 | fn collect_local_symbols( |
| 1538 | 1658 | layout: &Layout, |
| 1539 | 1659 | atom_table: &AtomTable, |
| 1660 | + atoms_by_input_section: &HashMap<(InputId, u8), Vec<crate::resolve::AtomId>>, | |
| 1540 | 1661 | atom_sections: &HashMap<crate::resolve::AtomId, u8>, |
| 1541 | 1662 | input_id: InputId, |
| 1542 | 1663 | object: &ObjectFile, |
@@ -1559,9 +1680,14 @@ fn collect_local_symbols( | ||
| 1559 | 1680 | .section_for_symbol(input_sym) |
| 1560 | 1681 | .expect("section symbol without section"); |
| 1561 | 1682 | let offset = input_sym.value().saturating_sub(section.addr) as u32; |
| 1562 | - let (atom_id, delta) = | |
| 1563 | - find_containing_atom(atom_table, input_id, input_sym.sect_idx(), offset) | |
| 1564 | - .ok_or(WriteError::MissingSegment("__UNKNOWN"))?; | |
| 1683 | + let (atom_id, delta) = find_containing_atom( | |
| 1684 | + atom_table, | |
| 1685 | + atoms_by_input_section, | |
| 1686 | + input_id, | |
| 1687 | + input_sym.sect_idx(), | |
| 1688 | + offset, | |
| 1689 | + ) | |
| 1690 | + .ok_or(WriteError::MissingSegment("__UNKNOWN"))?; | |
| 1565 | 1691 | let addr = |
| 1566 | 1692 | layout |
| 1567 | 1693 | .atom_addr(atom_id) |
@@ -1610,30 +1736,40 @@ fn is_assembler_temporary_symbol(name: &str) -> bool { | ||
| 1610 | 1736 | |
| 1611 | 1737 | fn find_containing_atom( |
| 1612 | 1738 | atom_table: &AtomTable, |
| 1739 | + atoms_by_input_section: &HashMap<(InputId, u8), Vec<crate::resolve::AtomId>>, | |
| 1613 | 1740 | input_id: InputId, |
| 1614 | 1741 | input_section: u8, |
| 1615 | 1742 | offset: u32, |
| 1616 | 1743 | ) -> Option<(crate::resolve::AtomId, u32)> { |
| 1617 | - find_containing_atom_range(atom_table, input_id, input_section, offset, 1) | |
| 1744 | + find_containing_atom_range( | |
| 1745 | + atom_table, | |
| 1746 | + atoms_by_input_section, | |
| 1747 | + input_id, | |
| 1748 | + input_section, | |
| 1749 | + offset, | |
| 1750 | + 1, | |
| 1751 | + ) | |
| 1618 | 1752 | } |
| 1619 | 1753 | |
| 1620 | 1754 | fn find_containing_atom_range( |
| 1621 | 1755 | atom_table: &AtomTable, |
| 1756 | + atoms_by_input_section: &HashMap<(InputId, u8), Vec<crate::resolve::AtomId>>, | |
| 1622 | 1757 | input_id: InputId, |
| 1623 | 1758 | input_section: u8, |
| 1624 | 1759 | offset: u32, |
| 1625 | 1760 | len: u32, |
| 1626 | 1761 | ) -> Option<(crate::resolve::AtomId, u32)> { |
| 1627 | - let atoms = atom_table.by_input_section(); | |
| 1628 | - atoms.get(&(input_id, input_section)).and_then(|ids| { | |
| 1629 | - ids.iter().find_map(|atom_id| { | |
| 1630 | - let atom = atom_table.get(*atom_id); | |
| 1631 | - let start = atom.input_offset; | |
| 1632 | - let end = atom.input_offset.saturating_add(atom.size); | |
| 1633 | - let range_end = offset.checked_add(len)?; | |
| 1634 | - (start <= offset && range_end <= end).then_some((*atom_id, offset - start)) | |
| 1762 | + atoms_by_input_section | |
| 1763 | + .get(&(input_id, input_section)) | |
| 1764 | + .and_then(|ids| { | |
| 1765 | + ids.iter().find_map(|atom_id| { | |
| 1766 | + let atom = atom_table.get(*atom_id); | |
| 1767 | + let start = atom.input_offset; | |
| 1768 | + let end = atom.input_offset.saturating_add(atom.size); | |
| 1769 | + let range_end = offset.checked_add(len)?; | |
| 1770 | + (start <= offset && range_end <= end).then_some((*atom_id, offset - start)) | |
| 1771 | + }) | |
| 1635 | 1772 | }) |
| 1636 | - }) | |
| 1637 | 1773 | } |
| 1638 | 1774 | |
| 1639 | 1775 | fn input_symbol_type(input_sym: &InputSymbol) -> u8 { |
@@ -1767,20 +1903,34 @@ fn push_indirect_section( | ||
| 1767 | 1903 | indirect_symbols: &mut Vec<u32>, |
| 1768 | 1904 | indirect_starts: &mut HashMap<(String, String), u32>, |
| 1769 | 1905 | key: (&str, &str), |
| 1770 | - symbols: impl Iterator<Item = SymbolId>, | |
| 1771 | - symbol_indices: &HashMap<SymbolId, u32>, | |
| 1906 | + symbols: impl Iterator<Item = u32>, | |
| 1772 | 1907 | ) { |
| 1773 | 1908 | let start = indirect_symbols.len() as u32; |
| 1774 | 1909 | let mut saw_any = false; |
| 1775 | 1910 | for symbol in symbols { |
| 1776 | 1911 | saw_any = true; |
| 1777 | - indirect_symbols.push(*symbol_indices.get(&symbol).expect("symbol index missing")); | |
| 1912 | + indirect_symbols.push(symbol); | |
| 1778 | 1913 | } |
| 1779 | 1914 | if saw_any { |
| 1780 | 1915 | indirect_starts.insert((key.0.to_string(), key.1.to_string()), start); |
| 1781 | 1916 | } |
| 1782 | 1917 | } |
| 1783 | 1918 | |
| 1919 | +fn indirect_symbol_index( | |
| 1920 | + symbol: SymbolId, | |
| 1921 | + import_lookup: &HashMap<SymbolId, &ImportSymbolRecord>, | |
| 1922 | + symbol_indices: &HashMap<SymbolId, u32>, | |
| 1923 | +) -> u32 { | |
| 1924 | + if import_lookup.contains_key(&symbol) { | |
| 1925 | + symbol_indices | |
| 1926 | + .get(&symbol) | |
| 1927 | + .copied() | |
| 1928 | + .unwrap_or(INDIRECT_SYMBOL_LOCAL) | |
| 1929 | + } else { | |
| 1930 | + INDIRECT_SYMBOL_LOCAL | |
| 1931 | + } | |
| 1932 | +} | |
| 1933 | + | |
| 1784 | 1934 | fn build_bind_streams( |
| 1785 | 1935 | layout: &Layout, |
| 1786 | 1936 | synthetic_plan: &SyntheticPlan, |
@@ -1791,34 +1941,6 @@ fn build_bind_streams( | ||
| 1791 | 1941 | let mut lazy_bind = OpcodeStream::new(); |
| 1792 | 1942 | let mut lazy_offsets = HashMap::new(); |
| 1793 | 1943 | |
| 1794 | - if !synthetic_plan.got.entries.is_empty() { | |
| 1795 | - let segment_index = segment_index(layout, "__DATA_CONST")?; | |
| 1796 | - let segment = layout | |
| 1797 | - .segment("__DATA_CONST") | |
| 1798 | - .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1799 | - let section = layout | |
| 1800 | - .sections | |
| 1801 | - .iter() | |
| 1802 | - .find(|section| section.segment == "__DATA_CONST" && section.name == "__got") | |
| 1803 | - .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1804 | - for (idx, entry) in synthetic_plan.got.entries.iter().enumerate() { | |
| 1805 | - let import = imports | |
| 1806 | - .get(&entry.symbol) | |
| 1807 | - .copied() | |
| 1808 | - .ok_or(WriteError::ImportSymbolMissing(entry.symbol))?; | |
| 1809 | - let slot_addr = section.addr + (idx as u64) * 8; | |
| 1810 | - bind_specs.push(BindRecordSpec { | |
| 1811 | - segment_index, | |
| 1812 | - segment_offset: slot_addr - segment.vm_addr, | |
| 1813 | - ordinal: import.ordinal, | |
| 1814 | - name: &import.name, | |
| 1815 | - weak_import: import.weak_import, | |
| 1816 | - addend: 0, | |
| 1817 | - terminate: false, | |
| 1818 | - }); | |
| 1819 | - } | |
| 1820 | - } | |
| 1821 | - | |
| 1822 | 1944 | if let Some(tlv_bootstrap) = synthetic_plan.tlv_bootstrap_symbol { |
| 1823 | 1945 | let segment_index = segment_index(layout, "__DATA")?; |
| 1824 | 1946 | let segment = layout |
@@ -1852,6 +1974,33 @@ fn build_bind_streams( | ||
| 1852 | 1974 | } |
| 1853 | 1975 | } |
| 1854 | 1976 | |
| 1977 | + if !synthetic_plan.got.entries.is_empty() { | |
| 1978 | + let segment_index = segment_index(layout, "__DATA_CONST")?; | |
| 1979 | + let segment = layout | |
| 1980 | + .segment("__DATA_CONST") | |
| 1981 | + .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1982 | + let section = layout | |
| 1983 | + .sections | |
| 1984 | + .iter() | |
| 1985 | + .find(|section| section.segment == "__DATA_CONST" && section.name == "__got") | |
| 1986 | + .ok_or(WriteError::MissingSegment("__DATA_CONST"))?; | |
| 1987 | + for (idx, entry) in synthetic_plan.got.entries.iter().enumerate() { | |
| 1988 | + let Some(import) = imports.get(&entry.symbol).copied() else { | |
| 1989 | + continue; | |
| 1990 | + }; | |
| 1991 | + let slot_addr = section.addr + (idx as u64) * 8; | |
| 1992 | + bind_specs.push(BindRecordSpec { | |
| 1993 | + segment_index, | |
| 1994 | + segment_offset: slot_addr - segment.vm_addr, | |
| 1995 | + ordinal: import.ordinal, | |
| 1996 | + name: &import.name, | |
| 1997 | + weak_import: import.weak_import, | |
| 1998 | + addend: 0, | |
| 1999 | + terminate: false, | |
| 2000 | + }); | |
| 2001 | + } | |
| 2002 | + } | |
| 2003 | + | |
| 1855 | 2004 | for entry in &synthetic_plan.direct_binds { |
| 1856 | 2005 | let import = imports |
| 1857 | 2006 | .get(&entry.symbol) |
@@ -1865,6 +2014,13 @@ fn build_bind_streams( | ||
| 1865 | 2014 | .iter() |
| 1866 | 2015 | .find(|section| section.atoms.iter().any(|placed| placed.atom == entry.atom)) |
| 1867 | 2016 | .ok_or(WriteError::DirectBindSectionMissing(entry.atom))?; |
| 2017 | + if section.segment == "__DATA" && section.name == "__thread_vars" { | |
| 2018 | + // `__thread_vars` starts are emitted through the dedicated | |
| 2019 | + // `__tlv_bootstrap` pass above. Descriptor tails are rewritten to | |
| 2020 | + // template offsets before write, so any generic direct bind landing | |
| 2021 | + // back in this section is stale and would override the TLV bind. | |
| 2022 | + continue; | |
| 2023 | + } | |
| 1868 | 2024 | let segment_index = segment_index(layout, §ion.segment)?; |
| 1869 | 2025 | let segment = layout |
| 1870 | 2026 | .segment(§ion.segment) |
@@ -2267,10 +2423,53 @@ mod tests { | ||
| 2267 | 2423 | ], |
| 2268 | 2424 | }; |
| 2269 | 2425 | |
| 2270 | - let blob = build_function_starts(&layout, &atoms).unwrap(); | |
| 2426 | + let blob = build_function_starts(&layout, &[], &atoms).unwrap(); | |
| 2271 | 2427 | assert_eq!( |
| 2272 | 2428 | decode_function_starts_blob(&blob), |
| 2273 | 2429 | vec![0x1000, 0x1008, 0x1040] |
| 2274 | 2430 | ); |
| 2275 | 2431 | } |
| 2432 | + | |
| 2433 | + #[test] | |
| 2434 | + fn containing_atom_lookup_reuses_precomputed_section_index() { | |
| 2435 | + let mut atoms = AtomTable::new(); | |
| 2436 | + let first = atoms.push(Atom { | |
| 2437 | + id: AtomId(0), | |
| 2438 | + origin: InputId(7), | |
| 2439 | + input_section: 3, | |
| 2440 | + section: AtomSection::Text, | |
| 2441 | + input_offset: 0, | |
| 2442 | + size: 8, | |
| 2443 | + align_pow2: 2, | |
| 2444 | + owner: None, | |
| 2445 | + alt_entries: Vec::new(), | |
| 2446 | + data: vec![0; 8], | |
| 2447 | + flags: AtomFlags::NONE, | |
| 2448 | + parent_of: None, | |
| 2449 | + }); | |
| 2450 | + let second = atoms.push(Atom { | |
| 2451 | + id: AtomId(0), | |
| 2452 | + origin: InputId(7), | |
| 2453 | + input_section: 3, | |
| 2454 | + section: AtomSection::Text, | |
| 2455 | + input_offset: 8, | |
| 2456 | + size: 12, | |
| 2457 | + align_pow2: 2, | |
| 2458 | + owner: None, | |
| 2459 | + alt_entries: Vec::new(), | |
| 2460 | + data: vec![0; 12], | |
| 2461 | + flags: AtomFlags::NONE, | |
| 2462 | + parent_of: None, | |
| 2463 | + }); | |
| 2464 | + | |
| 2465 | + let by_input_section = atoms.by_input_section(); | |
| 2466 | + assert_eq!( | |
| 2467 | + find_containing_atom(&atoms, &by_input_section, InputId(7), 3, 4), | |
| 2468 | + Some((first, 4)) | |
| 2469 | + ); | |
| 2470 | + assert_eq!( | |
| 2471 | + find_containing_atom_range(&atoms, &by_input_section, InputId(7), 3, 10, 2), | |
| 2472 | + Some((second, 2)) | |
| 2473 | + ); | |
| 2474 | + } | |
| 2276 | 2475 | } |
src/reloc/arm64.rsmodified@@ -42,7 +42,9 @@ impl std::error::Error for RelocError {} | ||
| 42 | 42 | |
| 43 | 43 | struct ResolveView<'a> { |
| 44 | 44 | sym_table: &'a SymbolTable, |
| 45 | + atom_table: &'a AtomTable, | |
| 45 | 46 | atom_addrs: &'a HashMap<crate::resolve::AtomId, u64>, |
| 47 | + atoms_by_input_section: &'a HashMap<(InputId, u8), Vec<crate::resolve::AtomId>>, | |
| 46 | 48 | section_addrs: &'a HashMap<(InputId, u8), u64>, |
| 47 | 49 | stub_addrs: &'a HashMap<SymbolId, u64>, |
| 48 | 50 | got_addrs: &'a HashMap<SymbolId, u64>, |
@@ -104,11 +106,14 @@ pub fn apply_layout( | ||
| 104 | 106 | } |
| 105 | 107 | |
| 106 | 108 | let atom_addrs = atom_address_map(layout); |
| 109 | + let atoms_by_input_section = atoms.by_input_section(); | |
| 107 | 110 | let section_addrs = input_section_address_map(layout, atoms); |
| 108 | 111 | let synth_addrs = synthetic_address_maps(layout, synthetic_plan); |
| 109 | 112 | let resolve = ResolveView { |
| 110 | 113 | sym_table, |
| 114 | + atom_table: atoms, | |
| 111 | 115 | atom_addrs: &atom_addrs, |
| 116 | + atoms_by_input_section: &atoms_by_input_section, | |
| 112 | 117 | section_addrs: §ion_addrs, |
| 113 | 118 | stub_addrs: &synth_addrs.stub_addrs, |
| 114 | 119 | got_addrs: &synth_addrs.got_addrs, |
@@ -145,7 +150,15 @@ pub fn apply_layout( | ||
| 145 | 150 | } |
| 146 | 151 | |
| 147 | 152 | if let Some(plan) = synthetic_plan { |
| 148 | - synthesize_thread_variable_section(layout, plan)?; | |
| 153 | + synthesize_thread_variable_section( | |
| 154 | + layout, | |
| 155 | + plan, | |
| 156 | + atoms, | |
| 157 | + &input_map, | |
| 158 | + &reloc_cache, | |
| 159 | + &resolve, | |
| 160 | + )?; | |
| 161 | + synthesize_got_section(layout, plan, &resolve)?; | |
| 149 | 162 | synthesize_stub_section(layout, plan, &resolve)?; |
| 150 | 163 | synthesize_lazy_pointer_section(layout, plan, &resolve)?; |
| 151 | 164 | synthesize_stub_helper_section(layout, plan, &resolve, linkedit)?; |
@@ -367,16 +380,24 @@ fn apply_one( | ||
| 367 | 380 | local_offset, |
| 368 | 381 | reloc, |
| 369 | 382 | place, |
| 370 | - resolve_got_target(obj, atom, reloc, resolve)?, | |
| 371 | - ), | |
| 372 | - RelocKind::GotLoadPageOff12 => patch_pageoff12( | |
| 373 | - bytes, | |
| 374 | - atom, | |
| 375 | - obj, | |
| 376 | - local_offset, | |
| 377 | - reloc, | |
| 378 | - resolve_got_target(obj, atom, reloc, resolve)?, | |
| 383 | + if got_reloc_relaxes_locally(obj, reloc, resolve) { | |
| 384 | + resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)? | |
| 385 | + } else { | |
| 386 | + resolve_got_target(obj, atom, reloc, resolve)? | |
| 387 | + }, | |
| 379 | 388 | ), |
| 389 | + RelocKind::GotLoadPageOff12 => { | |
| 390 | + let target = if got_reloc_relaxes_locally(obj, reloc, resolve) { | |
| 391 | + resolve_referent(obj, atom, reloc.kind, reloc.referent, resolve)? | |
| 392 | + } else { | |
| 393 | + resolve_got_target(obj, atom, reloc, resolve)? | |
| 394 | + }; | |
| 395 | + if got_reloc_relaxes_locally(obj, reloc, resolve) { | |
| 396 | + patch_got_pageoff12_relaxed(bytes, atom, obj, local_offset, reloc, target) | |
| 397 | + } else { | |
| 398 | + patch_pageoff12(bytes, atom, obj, local_offset, reloc, target) | |
| 399 | + } | |
| 400 | + } | |
| 380 | 401 | RelocKind::PointerToGot => patch_unsigned( |
| 381 | 402 | bytes, |
| 382 | 403 | atom, |
@@ -432,14 +453,14 @@ fn resolve_got_target( | ||
| 432 | 453 | reloc: Reloc, |
| 433 | 454 | resolve: &ResolveView<'_>, |
| 434 | 455 | ) -> Result<u64, RelocError> { |
| 435 | - let Some(symbol_id) = dylib_import_symbol_id(obj, reloc.referent, resolve.sym_table) else { | |
| 456 | + let Some(symbol_id) = symbol_referent_id(obj, reloc.referent, resolve.sym_table) else { | |
| 436 | 457 | return Err(reloc_error( |
| 437 | 458 | atom, |
| 438 | 459 | &obj.path, |
| 439 | 460 | reloc.offset.saturating_sub(atom.input_offset), |
| 440 | 461 | reloc.kind, |
| 441 | 462 | &describe_referent(obj, reloc.referent), |
| 442 | - "GOT relocations currently require a dylib import target".to_string(), | |
| 463 | + "GOT relocations require a symbol target".to_string(), | |
| 443 | 464 | )); |
| 444 | 465 | }; |
| 445 | 466 | resolve.got_addrs.get(&symbol_id).copied().ok_or_else(|| { |
@@ -449,11 +470,17 @@ fn resolve_got_target( | ||
| 449 | 470 | reloc.offset.saturating_sub(atom.input_offset), |
| 450 | 471 | reloc.kind, |
| 451 | 472 | &describe_referent(obj, reloc.referent), |
| 452 | - "dylib import is missing synthetic GOT slot".to_string(), | |
| 473 | + "symbol is missing synthetic GOT slot".to_string(), | |
| 453 | 474 | ) |
| 454 | 475 | }) |
| 455 | 476 | } |
| 456 | 477 | |
| 478 | +fn got_reloc_relaxes_locally(obj: &ObjectFile, reloc: Reloc, resolve: &ResolveView<'_>) -> bool { | |
| 479 | + symbol_referent_id(obj, reloc.referent, resolve.sym_table) | |
| 480 | + .map(|symbol_id| !matches!(resolve.sym_table.get(symbol_id), Symbol::DylibImport { .. })) | |
| 481 | + .unwrap_or(false) | |
| 482 | +} | |
| 483 | + | |
| 457 | 484 | fn resolve_tlvp_target( |
| 458 | 485 | obj: &ObjectFile, |
| 459 | 486 | atom: &Atom, |
@@ -541,6 +568,15 @@ fn dylib_import_symbol_id( | ||
| 541 | 568 | obj: &ObjectFile, |
| 542 | 569 | referent: Referent, |
| 543 | 570 | sym_table: &SymbolTable, |
| 571 | +) -> Option<SymbolId> { | |
| 572 | + let symbol_id = symbol_referent_id(obj, referent, sym_table)?; | |
| 573 | + matches!(sym_table.get(symbol_id), Symbol::DylibImport { .. }).then_some(symbol_id) | |
| 574 | +} | |
| 575 | + | |
| 576 | +fn symbol_referent_id( | |
| 577 | + obj: &ObjectFile, | |
| 578 | + referent: Referent, | |
| 579 | + sym_table: &SymbolTable, | |
| 544 | 580 | ) -> Option<SymbolId> { |
| 545 | 581 | let Referent::Symbol(sym_idx) = referent else { |
| 546 | 582 | return None; |
@@ -550,7 +586,8 @@ fn dylib_import_symbol_id( | ||
| 550 | 586 | let (symbol_id, symbol) = sym_table |
| 551 | 587 | .iter() |
| 552 | 588 | .find(|(_, symbol)| sym_table.interner.resolve(symbol.name()) == name)?; |
| 553 | - matches!(symbol, Symbol::DylibImport { .. }).then_some(symbol_id) | |
| 589 | + let _ = symbol; | |
| 590 | + Some(symbol_id) | |
| 554 | 591 | } |
| 555 | 592 | |
| 556 | 593 | fn resolve_global_symbol( |
@@ -607,6 +644,17 @@ fn resolve_input_symbol( | ||
| 607 | 644 | kind: RelocKind, |
| 608 | 645 | input_sym: &InputSymbol, |
| 609 | 646 | resolve: &ResolveView<'_>, |
| 647 | +) -> Result<u64, RelocError> { | |
| 648 | + resolve_input_symbol_at_origin(atom.origin, obj, atom, kind, input_sym, resolve) | |
| 649 | +} | |
| 650 | + | |
| 651 | +fn resolve_input_symbol_at_origin( | |
| 652 | + origin: InputId, | |
| 653 | + obj: &ObjectFile, | |
| 654 | + atom: &Atom, | |
| 655 | + kind: RelocKind, | |
| 656 | + input_sym: &InputSymbol, | |
| 657 | + resolve: &ResolveView<'_>, | |
| 610 | 658 | ) -> Result<u64, RelocError> { |
| 611 | 659 | match input_sym.kind() { |
| 612 | 660 | SymKind::Abs => Ok(input_sym.value()), |
@@ -621,21 +669,17 @@ fn resolve_input_symbol( | ||
| 621 | 669 | "section-backed symbol did not resolve to an input section".to_string(), |
| 622 | 670 | ) |
| 623 | 671 | })?; |
| 624 | - let section_addr = resolve | |
| 625 | - .section_addrs | |
| 626 | - .get(&(atom.origin, input_sym.sect_idx())) | |
| 627 | - .copied() | |
| 628 | - .ok_or_else(|| { | |
| 629 | - reloc_error( | |
| 630 | - atom, | |
| 631 | - &obj.path, | |
| 632 | - 0, | |
| 633 | - kind, | |
| 634 | - &describe_input_symbol(obj, input_sym), | |
| 635 | - "section-backed symbol's output section is missing".to_string(), | |
| 636 | - ) | |
| 637 | - })?; | |
| 638 | - Ok(section_addr + input_sym.value().saturating_sub(section.addr)) | |
| 672 | + let section_offset = input_sym.value().saturating_sub(section.addr) as u32; | |
| 673 | + resolve_input_section_offset( | |
| 674 | + origin, | |
| 675 | + obj, | |
| 676 | + atom, | |
| 677 | + kind, | |
| 678 | + input_sym.sect_idx(), | |
| 679 | + section_offset, | |
| 680 | + &describe_input_symbol(obj, input_sym), | |
| 681 | + resolve, | |
| 682 | + ) | |
| 639 | 683 | } |
| 640 | 684 | SymKind::Undef => Err(reloc_error( |
| 641 | 685 | atom, |
@@ -656,6 +700,65 @@ fn resolve_input_symbol( | ||
| 656 | 700 | } |
| 657 | 701 | } |
| 658 | 702 | |
| 703 | +fn resolve_input_section_offset( | |
| 704 | + origin: InputId, | |
| 705 | + obj: &ObjectFile, | |
| 706 | + atom: &Atom, | |
| 707 | + kind: RelocKind, | |
| 708 | + input_section: u8, | |
| 709 | + input_offset: u32, | |
| 710 | + referent: &str, | |
| 711 | + resolve: &ResolveView<'_>, | |
| 712 | +) -> Result<u64, RelocError> { | |
| 713 | + if let Some(atom_ids) = resolve.atoms_by_input_section.get(&(origin, input_section)) { | |
| 714 | + if let Some((target_atom, delta)) = atom_ids.iter().find_map(|atom_id| { | |
| 715 | + let candidate = resolve.atom_table.get(*atom_id); | |
| 716 | + let start = candidate.input_offset; | |
| 717 | + let end = candidate.input_offset.saturating_add(candidate.size); | |
| 718 | + if start <= input_offset && input_offset < end { | |
| 719 | + Some((*atom_id, input_offset - start)) | |
| 720 | + } else if input_offset == end { | |
| 721 | + Some((*atom_id, candidate.size)) | |
| 722 | + } else { | |
| 723 | + None | |
| 724 | + } | |
| 725 | + }) { | |
| 726 | + let atom_addr = resolve | |
| 727 | + .atom_addrs | |
| 728 | + .get(&target_atom) | |
| 729 | + .copied() | |
| 730 | + .ok_or_else(|| { | |
| 731 | + reloc_error( | |
| 732 | + atom, | |
| 733 | + &obj.path, | |
| 734 | + 0, | |
| 735 | + kind, | |
| 736 | + referent, | |
| 737 | + "section-backed symbol's containing atom is missing a final address" | |
| 738 | + .to_string(), | |
| 739 | + ) | |
| 740 | + })?; | |
| 741 | + return Ok(atom_addr + delta as u64); | |
| 742 | + } | |
| 743 | + } | |
| 744 | + | |
| 745 | + let section_addr = resolve | |
| 746 | + .section_addrs | |
| 747 | + .get(&(origin, input_section)) | |
| 748 | + .copied() | |
| 749 | + .ok_or_else(|| { | |
| 750 | + reloc_error( | |
| 751 | + atom, | |
| 752 | + &obj.path, | |
| 753 | + 0, | |
| 754 | + kind, | |
| 755 | + referent, | |
| 756 | + "section-backed symbol's output section is missing".to_string(), | |
| 757 | + ) | |
| 758 | + })?; | |
| 759 | + Ok(section_addr + input_offset as u64) | |
| 760 | +} | |
| 761 | + | |
| 659 | 762 | fn patch_unsigned( |
| 660 | 763 | bytes: &mut [u8], |
| 661 | 764 | atom: &Atom, |
@@ -911,7 +1014,7 @@ fn patch_pageoff12( | ||
| 911 | 1014 | let imm = if is_add_immediate(insn) { |
| 912 | 1015 | pageoff |
| 913 | 1016 | } else { |
| 914 | - let shift = ((insn >> 30) & 0b11) as u64; | |
| 1017 | + let shift = pageoff_load_store_shift(insn); | |
| 915 | 1018 | let scale = 1u64 << shift; |
| 916 | 1019 | if !pageoff.is_multiple_of(scale) { |
| 917 | 1020 | return Err(reloc_error( |
@@ -947,6 +1050,28 @@ fn patch_pageoff12( | ||
| 947 | 1050 | ) |
| 948 | 1051 | } |
| 949 | 1052 | |
| 1053 | +fn pageoff_load_store_shift(insn: u32) -> u64 { | |
| 1054 | + if is_simd_fp_pageoff(insn) { | |
| 1055 | + simd_fp_pageoff_shift(insn) | |
| 1056 | + } else { | |
| 1057 | + ((insn >> 30) & 0b11) as u64 | |
| 1058 | + } | |
| 1059 | +} | |
| 1060 | + | |
| 1061 | +fn is_simd_fp_pageoff(insn: u32) -> bool { | |
| 1062 | + ((insn >> 24) & 0b111) == 0b101 | |
| 1063 | +} | |
| 1064 | + | |
| 1065 | +fn simd_fp_pageoff_shift(insn: u32) -> u64 { | |
| 1066 | + let size = ((insn >> 30) & 0b11) as u64; | |
| 1067 | + let opc = ((insn >> 22) & 0b11) as u64; | |
| 1068 | + if size == 0 && (opc & 0b10) != 0 { | |
| 1069 | + 4 | |
| 1070 | + } else { | |
| 1071 | + size | |
| 1072 | + } | |
| 1073 | +} | |
| 1074 | + | |
| 950 | 1075 | fn read_implicit_addend( |
| 951 | 1076 | bytes: &[u8], |
| 952 | 1077 | local_offset: u32, |
@@ -1026,9 +1151,55 @@ fn patch_tlvp_pageoff12( | ||
| 1026 | 1151 | ) |
| 1027 | 1152 | } |
| 1028 | 1153 | |
| 1154 | +fn patch_got_pageoff12_relaxed( | |
| 1155 | + bytes: &mut [u8], | |
| 1156 | + atom: &Atom, | |
| 1157 | + obj: &ObjectFile, | |
| 1158 | + local_offset: u32, | |
| 1159 | + reloc: Reloc, | |
| 1160 | + target: u64, | |
| 1161 | +) -> Result<(), RelocError> { | |
| 1162 | + let pageoff = target.wrapping_add_signed(reloc.addend) & 0xfff; | |
| 1163 | + if pageoff > 0xfff { | |
| 1164 | + return Err(reloc_error( | |
| 1165 | + atom, | |
| 1166 | + &obj.path, | |
| 1167 | + local_offset, | |
| 1168 | + reloc.kind, | |
| 1169 | + &describe_referent(obj, reloc.referent), | |
| 1170 | + format!("pageoff immediate 0x{pageoff:x} exceeds 12 bits"), | |
| 1171 | + )); | |
| 1172 | + } | |
| 1173 | + | |
| 1174 | + let insn = read_u32( | |
| 1175 | + bytes, | |
| 1176 | + local_offset, | |
| 1177 | + atom, | |
| 1178 | + obj, | |
| 1179 | + reloc.kind, | |
| 1180 | + &describe_referent(obj, reloc.referent), | |
| 1181 | + )?; | |
| 1182 | + let rd = insn & 0x1f; | |
| 1183 | + let rn = (insn >> 5) & 0x1f; | |
| 1184 | + let patched = 0x9100_0000 | ((pageoff as u32) << 10) | (rn << 5) | rd; | |
| 1185 | + write_u32( | |
| 1186 | + bytes, | |
| 1187 | + local_offset, | |
| 1188 | + patched, | |
| 1189 | + atom, | |
| 1190 | + obj, | |
| 1191 | + reloc.kind, | |
| 1192 | + &describe_referent(obj, reloc.referent), | |
| 1193 | + ) | |
| 1194 | +} | |
| 1195 | + | |
| 1029 | 1196 | fn synthesize_thread_variable_section( |
| 1030 | 1197 | layout: &mut Layout, |
| 1031 | 1198 | plan: &SyntheticPlan, |
| 1199 | + atoms: &AtomTable, | |
| 1200 | + input_map: &HashMap<InputId, &ObjectFile>, | |
| 1201 | + reloc_cache: &HashMap<(InputId, u8), Vec<Reloc>>, | |
| 1202 | + resolve: &ResolveView<'_>, | |
| 1032 | 1203 | ) -> Result<(), RelocError> { |
| 1033 | 1204 | let Some(_bootstrap_symbol) = plan.tlv_bootstrap_symbol else { |
| 1034 | 1205 | return Ok(()); |
@@ -1057,6 +1228,19 @@ fn synthesize_thread_variable_section( | ||
| 1057 | 1228 | }; |
| 1058 | 1229 | |
| 1059 | 1230 | for placed in &mut section.atoms { |
| 1231 | + let atom = atoms.get(placed.atom); | |
| 1232 | + let obj = input_map.get(&atom.origin).ok_or_else(|| RelocError { | |
| 1233 | + input: PathBuf::from("<missing object>"), | |
| 1234 | + atom: placed.atom, | |
| 1235 | + atom_offset: 0, | |
| 1236 | + kind: RelocKind::Unsigned, | |
| 1237 | + referent: "__thread_vars".to_string(), | |
| 1238 | + detail: "missing parsed object for TLV descriptor atom".to_string(), | |
| 1239 | + })?; | |
| 1240 | + let relocs = reloc_cache | |
| 1241 | + .get(&(atom.origin, atom.input_section)) | |
| 1242 | + .map(Vec::as_slice) | |
| 1243 | + .unwrap_or(&[]); | |
| 1060 | 1244 | if placed.size % THREAD_VARIABLE_DESCRIPTOR_SIZE as u64 != 0 { |
| 1061 | 1245 | return Err(RelocError { |
| 1062 | 1246 | input: PathBuf::from("<synthetic tlv>"), |
@@ -1074,6 +1258,7 @@ fn synthesize_thread_variable_section( | ||
| 1074 | 1258 | for descriptor_offset in |
| 1075 | 1259 | (0..placed.size as usize).step_by(THREAD_VARIABLE_DESCRIPTOR_SIZE as usize) |
| 1076 | 1260 | { |
| 1261 | + let descriptor_offset_u32 = descriptor_offset as u32; | |
| 1077 | 1262 | let start = descriptor_offset; |
| 1078 | 1263 | let end = start + THREAD_VARIABLE_DESCRIPTOR_SIZE as usize; |
| 1079 | 1264 | let descriptor = placed.data.get_mut(start..end).ok_or_else(|| RelocError { |
@@ -1086,11 +1271,15 @@ fn synthesize_thread_variable_section( | ||
| 1086 | 1271 | })?; |
| 1087 | 1272 | |
| 1088 | 1273 | descriptor[0..8].fill(0); |
| 1089 | - let init_addr = u64::from_le_bytes( | |
| 1090 | - descriptor[16..24] | |
| 1091 | - .try_into() | |
| 1092 | - .expect("8-byte descriptor tail"), | |
| 1093 | - ); | |
| 1274 | + let init_addr = resolve_tlv_init_address( | |
| 1275 | + descriptor, | |
| 1276 | + atom, | |
| 1277 | + obj, | |
| 1278 | + relocs, | |
| 1279 | + descriptor_offset_u32, | |
| 1280 | + input_map, | |
| 1281 | + resolve, | |
| 1282 | + )?; | |
| 1094 | 1283 | if init_addr < template_base { |
| 1095 | 1284 | return Err(RelocError { |
| 1096 | 1285 | input: PathBuf::from("<synthetic tlv>"), |
@@ -1111,6 +1300,180 @@ fn synthesize_thread_variable_section( | ||
| 1111 | 1300 | Ok(()) |
| 1112 | 1301 | } |
| 1113 | 1302 | |
| 1303 | +fn resolve_tlv_init_address( | |
| 1304 | + descriptor: &[u8], | |
| 1305 | + atom: &Atom, | |
| 1306 | + obj: &ObjectFile, | |
| 1307 | + relocs: &[Reloc], | |
| 1308 | + descriptor_offset: u32, | |
| 1309 | + input_map: &HashMap<InputId, &ObjectFile>, | |
| 1310 | + resolve: &ResolveView<'_>, | |
| 1311 | +) -> Result<u64, RelocError> { | |
| 1312 | + for owner in descriptor_owner_symbols(obj, atom, descriptor_offset) { | |
| 1313 | + if let Some(init_addr) = resolve_named_tlv_init(owner, atom, input_map, resolve)? { | |
| 1314 | + return Ok(init_addr); | |
| 1315 | + } | |
| 1316 | + } | |
| 1317 | + | |
| 1318 | + let field_offset = atom.input_offset + descriptor_offset + 16; | |
| 1319 | + if let Some(reloc) = relocs_for_atom(relocs, atom).find(|reloc| reloc.offset == field_offset) { | |
| 1320 | + let target = resolve_tlv_descriptor_referent(obj, atom, reloc, resolve)?; | |
| 1321 | + return Ok(target.wrapping_add_signed(reloc.addend)); | |
| 1322 | + } | |
| 1323 | + | |
| 1324 | + Ok(u64::from_le_bytes( | |
| 1325 | + descriptor[16..24] | |
| 1326 | + .try_into() | |
| 1327 | + .expect("8-byte descriptor tail"), | |
| 1328 | + )) | |
| 1329 | +} | |
| 1330 | + | |
| 1331 | +fn descriptor_owner_symbols<'a>( | |
| 1332 | + obj: &'a ObjectFile, | |
| 1333 | + atom: &'a Atom, | |
| 1334 | + descriptor_offset: u32, | |
| 1335 | +) -> impl Iterator<Item = &'a InputSymbol> + 'a { | |
| 1336 | + let descriptor_start = atom.input_offset as u64 + descriptor_offset as u64; | |
| 1337 | + obj.symbols.iter().filter(move |input_sym| { | |
| 1338 | + input_sym.kind() == SymKind::Sect | |
| 1339 | + && input_sym.sect_idx() == atom.input_section | |
| 1340 | + && obj.section_for_symbol(input_sym).is_some_and(|section| { | |
| 1341 | + input_sym.value().saturating_sub(section.addr) == descriptor_start | |
| 1342 | + }) | |
| 1343 | + }) | |
| 1344 | +} | |
| 1345 | + | |
| 1346 | +fn matching_tlv_init_symbol<'a>( | |
| 1347 | + obj: &'a ObjectFile, | |
| 1348 | + owner: &InputSymbol, | |
| 1349 | +) -> Result<Option<&'a InputSymbol>, RelocError> { | |
| 1350 | + let owner_name = match obj.symbol_name(owner) { | |
| 1351 | + Ok(name) => name, | |
| 1352 | + Err(_) => return Ok(None), | |
| 1353 | + }; | |
| 1354 | + let init_name = format!("{owner_name}$tlv$init"); | |
| 1355 | + Ok(obj.symbols.iter().find(|input_sym| { | |
| 1356 | + obj.symbol_name(input_sym) | |
| 1357 | + .is_ok_and(|name| name == init_name) | |
| 1358 | + })) | |
| 1359 | +} | |
| 1360 | + | |
| 1361 | +fn resolve_named_tlv_init( | |
| 1362 | + owner: &InputSymbol, | |
| 1363 | + atom: &Atom, | |
| 1364 | + input_map: &HashMap<InputId, &ObjectFile>, | |
| 1365 | + resolve: &ResolveView<'_>, | |
| 1366 | +) -> Result<Option<u64>, RelocError> { | |
| 1367 | + for (&origin, obj) in input_map { | |
| 1368 | + let Some(init_symbol) = matching_tlv_init_symbol(obj, owner)? else { | |
| 1369 | + continue; | |
| 1370 | + }; | |
| 1371 | + return Ok(Some(resolve_input_symbol_at_origin( | |
| 1372 | + origin, | |
| 1373 | + obj, | |
| 1374 | + atom, | |
| 1375 | + RelocKind::Unsigned, | |
| 1376 | + init_symbol, | |
| 1377 | + resolve, | |
| 1378 | + )?)); | |
| 1379 | + } | |
| 1380 | + Ok(None) | |
| 1381 | +} | |
| 1382 | + | |
| 1383 | +fn resolve_tlv_descriptor_referent( | |
| 1384 | + obj: &ObjectFile, | |
| 1385 | + atom: &Atom, | |
| 1386 | + reloc: Reloc, | |
| 1387 | + resolve: &ResolveView<'_>, | |
| 1388 | +) -> Result<u64, RelocError> { | |
| 1389 | + match reloc.referent { | |
| 1390 | + Referent::Section(section_idx) => resolve | |
| 1391 | + .section_addrs | |
| 1392 | + .get(&(atom.origin, section_idx)) | |
| 1393 | + .copied() | |
| 1394 | + .ok_or_else(|| { | |
| 1395 | + reloc_error( | |
| 1396 | + atom, | |
| 1397 | + &obj.path, | |
| 1398 | + reloc.offset.saturating_sub(atom.input_offset), | |
| 1399 | + reloc.kind, | |
| 1400 | + &format!("section #{section_idx}"), | |
| 1401 | + "TLV descriptor referent section was not laid out".to_string(), | |
| 1402 | + ) | |
| 1403 | + }), | |
| 1404 | + Referent::Symbol(sym_idx) => { | |
| 1405 | + let input_sym = obj.symbols.get(sym_idx as usize).ok_or_else(|| { | |
| 1406 | + reloc_error( | |
| 1407 | + atom, | |
| 1408 | + &obj.path, | |
| 1409 | + reloc.offset.saturating_sub(atom.input_offset), | |
| 1410 | + reloc.kind, | |
| 1411 | + &format!("symbol #{sym_idx}"), | |
| 1412 | + "TLV descriptor symbol index is out of range".to_string(), | |
| 1413 | + ) | |
| 1414 | + })?; | |
| 1415 | + resolve_input_symbol_at_origin(atom.origin, obj, atom, reloc.kind, input_sym, resolve) | |
| 1416 | + } | |
| 1417 | + } | |
| 1418 | +} | |
| 1419 | + | |
| 1420 | +fn synthesize_got_section( | |
| 1421 | + layout: &mut Layout, | |
| 1422 | + plan: &SyntheticPlan, | |
| 1423 | + resolve: &ResolveView<'_>, | |
| 1424 | +) -> Result<(), RelocError> { | |
| 1425 | + let Some(section) = layout | |
| 1426 | + .sections | |
| 1427 | + .iter_mut() | |
| 1428 | + .find(|section| section.segment == "__DATA_CONST" && section.name == "__got") | |
| 1429 | + else { | |
| 1430 | + return Ok(()); | |
| 1431 | + }; | |
| 1432 | + | |
| 1433 | + for (idx, entry) in plan.got.entries.iter().enumerate() { | |
| 1434 | + let start = idx * 8; | |
| 1435 | + let end = start + 8; | |
| 1436 | + let value = match resolve.sym_table.get(entry.symbol) { | |
| 1437 | + Symbol::DylibImport { .. } => 0, | |
| 1438 | + Symbol::Defined { | |
| 1439 | + atom: target_atom, | |
| 1440 | + value, | |
| 1441 | + .. | |
| 1442 | + } => { | |
| 1443 | + resolve | |
| 1444 | + .atom_addrs | |
| 1445 | + .get(target_atom) | |
| 1446 | + .copied() | |
| 1447 | + .ok_or_else(|| RelocError { | |
| 1448 | + input: PathBuf::from("<synthetic got>"), | |
| 1449 | + atom: crate::resolve::AtomId(0), | |
| 1450 | + atom_offset: start as u32, | |
| 1451 | + kind: RelocKind::PointerToGot, | |
| 1452 | + referent: format!("symbol {:?}", entry.symbol), | |
| 1453 | + detail: "defined GOT target is missing final address".to_string(), | |
| 1454 | + })? | |
| 1455 | + + *value | |
| 1456 | + } | |
| 1457 | + other => { | |
| 1458 | + return Err(RelocError { | |
| 1459 | + input: PathBuf::from("<synthetic got>"), | |
| 1460 | + atom: crate::resolve::AtomId(0), | |
| 1461 | + atom_offset: start as u32, | |
| 1462 | + kind: RelocKind::PointerToGot, | |
| 1463 | + referent: format!("symbol {:?}", entry.symbol), | |
| 1464 | + detail: format!( | |
| 1465 | + "synthetic GOT currently does not support symbol kind {:?}", | |
| 1466 | + other.kind() | |
| 1467 | + ), | |
| 1468 | + }); | |
| 1469 | + } | |
| 1470 | + }; | |
| 1471 | + section.synthetic_data[start..end].copy_from_slice(&value.to_le_bytes()); | |
| 1472 | + } | |
| 1473 | + | |
| 1474 | + Ok(()) | |
| 1475 | +} | |
| 1476 | + | |
| 1114 | 1477 | fn synthesize_stub_section( |
| 1115 | 1478 | layout: &mut Layout, |
| 1116 | 1479 | plan: &SyntheticPlan, |
@@ -1587,7 +1950,7 @@ mod tests { | ||
| 1587 | 1950 | fn load_store_pageoff_uses_size_scaling() { |
| 1588 | 1951 | let insn = 0xf940_0000u32; |
| 1589 | 1952 | assert!(!is_add_immediate(insn)); |
| 1590 | - let shift = (insn >> 30) & 0b11; | |
| 1953 | + let shift = pageoff_load_store_shift(insn); | |
| 1591 | 1954 | assert_eq!(shift, 0b11); |
| 1592 | 1955 | let pageoff = 0x3f8u64; |
| 1593 | 1956 | let imm = pageoff >> shift; |
@@ -1595,6 +1958,20 @@ mod tests { | ||
| 1595 | 1958 | assert_eq!((patched >> 10) & 0xfff, 0x7f); |
| 1596 | 1959 | } |
| 1597 | 1960 | |
| 1961 | + #[test] | |
| 1962 | + fn simd_q_pageoff_uses_16_byte_scaling() { | |
| 1963 | + let insn = 0x3dc0_0100u32; | |
| 1964 | + assert!(!is_add_immediate(insn)); | |
| 1965 | + assert!(is_simd_fp_pageoff(insn)); | |
| 1966 | + let shift = pageoff_load_store_shift(insn); | |
| 1967 | + assert_eq!(shift, 4); | |
| 1968 | + let pageoff = 0x690u64; | |
| 1969 | + let imm = pageoff >> shift; | |
| 1970 | + let patched = (insn & !(0xfff << 10)) | ((imm as u32) << 10); | |
| 1971 | + assert_eq!((patched >> 10) & 0xfff, 0x69); | |
| 1972 | + assert_eq!(patched, 0x3dc1_a500); | |
| 1973 | + } | |
| 1974 | + | |
| 1598 | 1975 | #[test] |
| 1599 | 1976 | fn signed_fit_helper_matches_branch_range() { |
| 1600 | 1977 | assert!(fits_signed((1 << 25) - 1, 26)); |
src/resolve.rsmodified@@ -134,6 +134,8 @@ opaque_id!( | ||
| 134 | 134 | #[derive(Debug)] |
| 135 | 135 | pub struct ObjectInput { |
| 136 | 136 | pub path: PathBuf, |
| 137 | + pub load_order: usize, | |
| 138 | + pub archive_member_offset: Option<u32>, | |
| 137 | 139 | /// Raw bytes; `ObjectFile::parse` re-runs cheaply against this on |
| 138 | 140 | /// demand. We don't cache a parsed view because `ObjectFile` copies |
| 139 | 141 | /// the fields it needs on construction, so re-parse is idempotent. |
@@ -143,6 +145,7 @@ pub struct ObjectInput { | ||
| 143 | 145 | #[derive(Debug)] |
| 144 | 146 | pub struct ArchiveInput { |
| 145 | 147 | pub path: PathBuf, |
| 148 | + pub load_order: usize, | |
| 146 | 149 | pub bytes: Vec<u8>, |
| 147 | 150 | /// Members we've already fetched (keyed by `ar_hdr` offset). Prevents |
| 148 | 151 | /// the fixed-point loop from re-ingesting the same object twice — |
@@ -225,11 +228,21 @@ impl Inputs { | ||
| 225 | 228 | /// Register an `.o` file. Validates the Mach-O header by parsing once, |
| 226 | 229 | /// then keeps only the raw bytes (re-parsing on demand is cheap and |
| 227 | 230 | /// sidesteps borrow-lifetime headaches). |
| 228 | - pub fn add_object(&mut self, path: PathBuf, bytes: Vec<u8>) -> Result<InputId, InputAddError> { | |
| 231 | + pub fn add_object( | |
| 232 | + &mut self, | |
| 233 | + path: PathBuf, | |
| 234 | + bytes: Vec<u8>, | |
| 235 | + load_order: usize, | |
| 236 | + ) -> Result<InputId, InputAddError> { | |
| 229 | 237 | // Validate now — we'd rather catch a bad object at the add site. |
| 230 | 238 | ObjectFile::parse(&path, &bytes)?; |
| 231 | 239 | let id = InputId(self.objects.len() as u32); |
| 232 | - self.objects.push(ObjectInput { path, bytes }); | |
| 240 | + self.objects.push(ObjectInput { | |
| 241 | + path, | |
| 242 | + load_order, | |
| 243 | + archive_member_offset: None, | |
| 244 | + bytes, | |
| 245 | + }); | |
| 233 | 246 | Ok(id) |
| 234 | 247 | } |
| 235 | 248 | |
@@ -238,11 +251,13 @@ impl Inputs { | ||
| 238 | 251 | &mut self, |
| 239 | 252 | path: PathBuf, |
| 240 | 253 | bytes: Vec<u8>, |
| 254 | + load_order: usize, | |
| 241 | 255 | ) -> Result<ArchiveId, InputAddError> { |
| 242 | 256 | Archive::open(&path, &bytes)?; // validate |
| 243 | 257 | let id = ArchiveId(self.archives.len() as u32); |
| 244 | 258 | self.archives.push(ArchiveInput { |
| 245 | 259 | path, |
| 260 | + load_order, | |
| 246 | 261 | bytes, |
| 247 | 262 | fetched: std::collections::HashSet::new(), |
| 248 | 263 | }); |
@@ -1134,6 +1149,7 @@ fn ingest_member_bytes( | ||
| 1134 | 1149 | member_id: MemberId, |
| 1135 | 1150 | report: &mut DrainReport, |
| 1136 | 1151 | ) -> Result<Vec<PendingFetch>, FetchError> { |
| 1152 | + let archive_load_order = inputs.archives[archive_id.0 as usize].load_order; | |
| 1137 | 1153 | let ai = &inputs.archives[archive_id.0 as usize]; |
| 1138 | 1154 | if ai.fetched.contains(&member_id.0) { |
| 1139 | 1155 | return Ok(Vec::new()); |
@@ -1158,6 +1174,8 @@ fn ingest_member_bytes( | ||
| 1158 | 1174 | let input_id = InputId(inputs.objects.len() as u32); |
| 1159 | 1175 | inputs.objects.push(ObjectInput { |
| 1160 | 1176 | path: PathBuf::from(logical_path), |
| 1177 | + load_order: archive_load_order, | |
| 1178 | + archive_member_offset: Some(member_id.0), | |
| 1161 | 1179 | bytes: member_bytes, |
| 1162 | 1180 | }); |
| 1163 | 1181 | report.fetched_members += 1; |
src/section.rsmodified@@ -18,7 +18,7 @@ pub enum SectionKind { | ||
| 18 | 18 | Text, |
| 19 | 19 | /// Regular data (`S_REGULAR` with none of the attribute markers). |
| 20 | 20 | Data, |
| 21 | - /// `__TEXT,__const` — immutable data. | |
| 21 | + /// Immutable data such as `__TEXT,__const` or `__DATA_CONST,__const`. | |
| 22 | 22 | ConstData, |
| 23 | 23 | /// `__TEXT,__cstring` (`S_CSTRING_LITERALS`). |
| 24 | 24 | CStringLiterals, |
@@ -94,7 +94,7 @@ fn classify_regular(segname: &str, sectname: &str, flags: u32) -> SectionKind { | ||
| 94 | 94 | if flags & S_ATTR_PURE_INSTRUCTIONS != 0 { |
| 95 | 95 | return SectionKind::Text; |
| 96 | 96 | } |
| 97 | - if segname == "__TEXT" && sectname == "__const" { | |
| 97 | + if sectname == "__const" && matches!(segname, "__TEXT" | "__DATA" | "__DATA_CONST") { | |
| 98 | 98 | return SectionKind::ConstData; |
| 99 | 99 | } |
| 100 | 100 | SectionKind::Data |
src/synth/dyld_info.rsmodified@@ -3,11 +3,12 @@ use std::collections::BTreeMap; | ||
| 3 | 3 | use crate::leb::{write_sleb, write_uleb}; |
| 4 | 4 | use crate::macho::constants::{ |
| 5 | 5 | BIND_IMMEDIATE_MASK, BIND_OPCODE_ADD_ADDR_ULEB, BIND_OPCODE_DO_BIND, |
| 6 | - BIND_OPCODE_SET_ADDEND_SLEB, BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, | |
| 7 | - BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, | |
| 8 | - BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, | |
| 9 | - BIND_OPCODE_SET_TYPE_IMM, BIND_SYMBOL_FLAGS_WEAK_IMPORT, BIND_TYPE_POINTER, | |
| 10 | - REBASE_IMMEDIATE_MASK, REBASE_OPCODE_DO_REBASE_IMM_TIMES, REBASE_OPCODE_DO_REBASE_ULEB_TIMES, | |
| 6 | + BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB, BIND_OPCODE_SET_ADDEND_SLEB, | |
| 7 | + BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, | |
| 8 | + BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, | |
| 9 | + BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, BIND_OPCODE_SET_TYPE_IMM, | |
| 10 | + BIND_SYMBOL_FLAGS_WEAK_IMPORT, BIND_TYPE_POINTER, REBASE_IMMEDIATE_MASK, | |
| 11 | + REBASE_OPCODE_DO_REBASE_IMM_TIMES, REBASE_OPCODE_DO_REBASE_ULEB_TIMES, | |
| 11 | 12 | }; |
| 12 | 13 | use crate::macho::exports::{ExportEntry, ExportKind}; |
| 13 | 14 | |
@@ -253,7 +254,9 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> { | ||
| 253 | 254 | let mut state = BindState::default(); |
| 254 | 255 | let mut current_symbol: Option<String> = None; |
| 255 | 256 | |
| 256 | - for spec in specs { | |
| 257 | + let mut idx = 0usize; | |
| 258 | + while idx < specs.len() { | |
| 259 | + let spec = specs[idx]; | |
| 257 | 260 | if state.ordinal != Some(spec.ordinal) { |
| 258 | 261 | emit_bind_ordinal(&mut out, spec.ordinal); |
| 259 | 262 | state.ordinal = Some(spec.ordinal); |
@@ -301,9 +304,21 @@ pub fn emit_bind_records(specs: &[BindRecordSpec<'_>]) -> Vec<u8> { | ||
| 301 | 304 | } |
| 302 | 305 | } |
| 303 | 306 | |
| 304 | - out.byte(BIND_OPCODE_DO_BIND); | |
| 307 | + let run_len = bind_run_len(specs, idx); | |
| 308 | + if run_len > 1 { | |
| 309 | + let stride = specs[idx + 1].segment_offset - spec.segment_offset; | |
| 310 | + let skip = stride - 8; | |
| 311 | + out.byte(BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB); | |
| 312 | + out.uleb(run_len as u64); | |
| 313 | + out.uleb(skip); | |
| 314 | + state.next_segment_offset = Some(spec.segment_offset + (run_len as u64) * stride); | |
| 315 | + idx += run_len; | |
| 316 | + } else { | |
| 317 | + out.byte(BIND_OPCODE_DO_BIND); | |
| 318 | + state.next_segment_offset = Some(spec.segment_offset + 8); | |
| 319 | + idx += 1; | |
| 320 | + } | |
| 305 | 321 | state.segment_index = Some(spec.segment_index); |
| 306 | - state.next_segment_offset = Some(spec.segment_offset + 8); | |
| 307 | 322 | } |
| 308 | 323 | |
| 309 | 324 | if specs.last().is_some_and(|spec| spec.terminate) { |
@@ -351,6 +366,41 @@ fn bind_symbol_flags(weak_import: bool) -> u8 { | ||
| 351 | 366 | } |
| 352 | 367 | } |
| 353 | 368 | |
| 369 | +fn bind_run_len(specs: &[BindRecordSpec<'_>], start: usize) -> usize { | |
| 370 | + let Some(next) = specs.get(start + 1) else { | |
| 371 | + return 1; | |
| 372 | + }; | |
| 373 | + let first = specs[start]; | |
| 374 | + if first.segment_index != next.segment_index | |
| 375 | + || first.ordinal != next.ordinal | |
| 376 | + || first.name != next.name | |
| 377 | + || first.weak_import != next.weak_import | |
| 378 | + || first.addend != next.addend | |
| 379 | + { | |
| 380 | + return 1; | |
| 381 | + } | |
| 382 | + let stride = next.segment_offset.saturating_sub(first.segment_offset); | |
| 383 | + if stride < 8 { | |
| 384 | + return 1; | |
| 385 | + } | |
| 386 | + | |
| 387 | + let mut len = 2usize; | |
| 388 | + while let Some(spec) = specs.get(start + len) { | |
| 389 | + let expected_offset = first.segment_offset + (len as u64) * stride; | |
| 390 | + if spec.segment_index != first.segment_index | |
| 391 | + || spec.ordinal != first.ordinal | |
| 392 | + || spec.name != first.name | |
| 393 | + || spec.weak_import != first.weak_import | |
| 394 | + || spec.addend != first.addend | |
| 395 | + || spec.segment_offset != expected_offset | |
| 396 | + { | |
| 397 | + break; | |
| 398 | + } | |
| 399 | + len += 1; | |
| 400 | + } | |
| 401 | + len | |
| 402 | +} | |
| 403 | + | |
| 354 | 404 | #[cfg(test)] |
| 355 | 405 | mod tests { |
| 356 | 406 | use crate::leb::{read_sleb, read_uleb}; |
@@ -528,4 +578,46 @@ mod tests { | ||
| 528 | 578 | .any(|window| window == [BIND_OPCODE_SET_ADDEND_SLEB, 0]); |
| 529 | 579 | assert!(zero_reset, "expected explicit addend reset back to zero"); |
| 530 | 580 | } |
| 581 | + | |
| 582 | + #[test] | |
| 583 | + fn bind_encoder_batches_constant_stride_runs() { | |
| 584 | + let stream = emit_bind_records(&[ | |
| 585 | + BindRecordSpec { | |
| 586 | + segment_index: 3, | |
| 587 | + segment_offset: 0x98, | |
| 588 | + ordinal: 1, | |
| 589 | + name: "__tlv_bootstrap", | |
| 590 | + weak_import: false, | |
| 591 | + addend: 0, | |
| 592 | + terminate: false, | |
| 593 | + }, | |
| 594 | + BindRecordSpec { | |
| 595 | + segment_index: 3, | |
| 596 | + segment_offset: 0xb0, | |
| 597 | + ordinal: 1, | |
| 598 | + name: "__tlv_bootstrap", | |
| 599 | + weak_import: false, | |
| 600 | + addend: 0, | |
| 601 | + terminate: false, | |
| 602 | + }, | |
| 603 | + BindRecordSpec { | |
| 604 | + segment_index: 3, | |
| 605 | + segment_offset: 0xc8, | |
| 606 | + ordinal: 1, | |
| 607 | + name: "__tlv_bootstrap", | |
| 608 | + weak_import: false, | |
| 609 | + addend: 0, | |
| 610 | + terminate: true, | |
| 611 | + }, | |
| 612 | + ]); | |
| 613 | + | |
| 614 | + assert!(stream.contains(&BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB)); | |
| 615 | + let idx = stream | |
| 616 | + .iter() | |
| 617 | + .position(|byte| *byte == BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB) | |
| 618 | + .unwrap(); | |
| 619 | + assert_eq!(stream[idx + 1], 3); | |
| 620 | + assert_eq!(stream[idx + 2], 0x10); | |
| 621 | + assert_eq!(stream.last().copied(), Some(0)); | |
| 622 | + } | |
| 531 | 623 | } |
src/synth/mod.rsmodified@@ -166,10 +166,28 @@ impl SyntheticPlan { | ||
| 166 | 166 | RelocKind::GotLoadPage21 |
| 167 | 167 | | RelocKind::GotLoadPageOff12 |
| 168 | 168 | | RelocKind::PointerToGot => { |
| 169 | - let Some(symbol_id) = dylib_import_referent(obj, reloc.referent, sym_table) | |
| 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) | |
| 170 | 182 | else { |
| 171 | 183 | continue; |
| 172 | 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 | + } | |
| 173 | 191 | got.intern(symbol_id, dylib_import_is_weak(sym_table, symbol_id)); |
| 174 | 192 | } |
| 175 | 193 | RelocKind::Branch26 => { |
@@ -705,6 +723,8 @@ mod tests { | ||
| 705 | 723 | &[LayoutInput { |
| 706 | 724 | id: input_id, |
| 707 | 725 | object: &object, |
| 726 | + load_order: 0, | |
| 727 | + archive_member_offset: None, | |
| 708 | 728 | }], |
| 709 | 729 | &atoms, |
| 710 | 730 | &mut sym_table, |
@@ -775,6 +795,8 @@ mod tests { | ||
| 775 | 795 | &[LayoutInput { |
| 776 | 796 | id: input_id, |
| 777 | 797 | object: &object, |
| 798 | + load_order: 0, | |
| 799 | + archive_member_offset: None, | |
| 778 | 800 | }], |
| 779 | 801 | &atoms, |
| 780 | 802 | &mut sym_table, |
@@ -849,6 +871,8 @@ mod tests { | ||
| 849 | 871 | &[LayoutInput { |
| 850 | 872 | id: input_id, |
| 851 | 873 | object: &object, |
| 874 | + load_order: 0, | |
| 875 | + archive_member_offset: None, | |
| 852 | 876 | }], |
| 853 | 877 | &atoms, |
| 854 | 878 | &mut sym_table, |
@@ -926,6 +950,8 @@ mod tests { | ||
| 926 | 950 | &[LayoutInput { |
| 927 | 951 | id: input_id, |
| 928 | 952 | object: &object, |
| 953 | + load_order: 0, | |
| 954 | + archive_member_offset: None, | |
| 929 | 955 | }], |
| 930 | 956 | &atoms, |
| 931 | 957 | &mut sym_table, |
@@ -989,6 +1015,8 @@ mod tests { | ||
| 989 | 1015 | &[LayoutInput { |
| 990 | 1016 | id: input_id, |
| 991 | 1017 | object: &object, |
| 1018 | + load_order: 0, | |
| 1019 | + archive_member_offset: None, | |
| 992 | 1020 | }], |
| 993 | 1021 | &atoms, |
| 994 | 1022 | &mut sym_table, |
@@ -1052,6 +1080,8 @@ mod tests { | ||
| 1052 | 1080 | &[LayoutInput { |
| 1053 | 1081 | id: input_id, |
| 1054 | 1082 | object: &object, |
| 1083 | + load_order: 0, | |
| 1084 | + archive_member_offset: None, | |
| 1055 | 1085 | }], |
| 1056 | 1086 | &atoms, |
| 1057 | 1087 | &mut sym_table, |
src/synth/unwind.rsmodified@@ -14,6 +14,8 @@ const PAGE_SIZE: usize = 4096; | ||
| 14 | 14 | const UNWIND_INFO_VERSION: u32 = 1; |
| 15 | 15 | const UNWIND_SECOND_LEVEL_REGULAR: u32 = 2; |
| 16 | 16 | const UNWIND_SECOND_LEVEL_COMPRESSED: u32 = 3; |
| 17 | +const MAX_COMPRESSED_FUNCTION_DELTA: u32 = 0x00ff_ffff; | |
| 18 | +const MAX_COMPRESSED_ENCODING_INDEX: usize = 0xff; | |
| 17 | 19 | const FIRST_LEVEL_ENTRY_SIZE: usize = 12; |
| 18 | 20 | const FIRST_LEVEL_INDEX_GAP_SIZE: usize = FIRST_LEVEL_ENTRY_SIZE; |
| 19 | 21 | const COMPRESSED_PAGE_HEADER_SIZE: usize = 12; |
@@ -539,9 +541,10 @@ fn read_u64(atom: &Atom, offset: usize) -> Result<u64, UnwindError> { | ||
| 539 | 541 | |
| 540 | 542 | fn serialize_unwind_info(records: &[UnwindRecord]) -> Result<Vec<u8>, UnwindReadError> { |
| 541 | 543 | let (records, personalities, lsdas) = finalize_unwind_records(records)?; |
| 542 | - let pages = build_pages(&records); | |
| 544 | + let common_encodings = select_common_encodings(&records); | |
| 545 | + let pages = build_pages(&records, &common_encodings); | |
| 543 | 546 | let common_encodings_offset = 7 * 4; |
| 544 | - let common_encodings_count = 0u32; | |
| 547 | + let common_encodings_count = common_encodings.len() as u32; | |
| 545 | 548 | let personalities_offset = common_encodings_offset + common_encodings_count as usize * 4; |
| 546 | 549 | let indices_offset = personalities_offset + personalities.len() * 4; |
| 547 | 550 | let indices_count = (pages.len() + 1) as u32; |
@@ -564,6 +567,9 @@ fn serialize_unwind_info(records: &[UnwindRecord]) -> Result<Vec<u8>, UnwindRead | ||
| 564 | 567 | out.extend_from_slice(&(indices_offset as u32).to_le_bytes()); |
| 565 | 568 | out.extend_from_slice(&indices_count.to_le_bytes()); |
| 566 | 569 | |
| 570 | + for encoding in &common_encodings { | |
| 571 | + out.extend_from_slice(&encoding.to_le_bytes()); | |
| 572 | + } | |
| 567 | 573 | for personality in &personalities { |
| 568 | 574 | out.extend_from_slice(&personality.to_le_bytes()); |
| 569 | 575 | } |
@@ -825,51 +831,87 @@ fn finalize_unwind_records( | ||
| 825 | 831 | Ok((finalized, personalities, lsdas)) |
| 826 | 832 | } |
| 827 | 833 | |
| 828 | -fn build_pages(records: &[UnwindRecord]) -> Vec<CompressedPage> { | |
| 834 | +fn select_common_encodings(records: &[UnwindRecord]) -> Vec<u32> { | |
| 835 | + let mut stats: HashMap<u32, (u32, usize)> = HashMap::new(); | |
| 836 | + for (idx, record) in records.iter().enumerate() { | |
| 837 | + stats | |
| 838 | + .entry(record.encoding) | |
| 839 | + .and_modify(|(count, _)| *count += 1) | |
| 840 | + .or_insert((1, idx)); | |
| 841 | + } | |
| 842 | + | |
| 843 | + let mut encodings: Vec<(u32, u32, usize)> = stats | |
| 844 | + .into_iter() | |
| 845 | + .filter_map(|(encoding, (count, first_seen))| { | |
| 846 | + (count > 1).then_some((encoding, count, first_seen)) | |
| 847 | + }) | |
| 848 | + .collect(); | |
| 849 | + encodings.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.2.cmp(&b.2))); | |
| 850 | + encodings.truncate(127); | |
| 851 | + encodings | |
| 852 | + .into_iter() | |
| 853 | + .map(|(encoding, _, _)| encoding) | |
| 854 | + .collect() | |
| 855 | +} | |
| 856 | + | |
| 857 | +fn build_pages(records: &[UnwindRecord], common_encodings: &[u32]) -> Vec<CompressedPage> { | |
| 829 | 858 | let mut pages = Vec::new(); |
| 830 | 859 | let mut current: Option<CompressedPage> = None; |
| 860 | + let common_indices: HashMap<u32, usize> = common_encodings | |
| 861 | + .iter() | |
| 862 | + .copied() | |
| 863 | + .enumerate() | |
| 864 | + .map(|(idx, encoding)| (encoding, idx)) | |
| 865 | + .collect(); | |
| 831 | 866 | |
| 832 | 867 | for record in records { |
| 833 | - let page = current.get_or_insert_with(|| CompressedPage { | |
| 834 | - start_function_offset: record.function_offset, | |
| 835 | - entries: Vec::new(), | |
| 836 | - local_encodings: Vec::new(), | |
| 837 | - }); | |
| 838 | - | |
| 839 | - let mut encodings = page.local_encodings.clone(); | |
| 840 | - if encodings | |
| 841 | - .iter() | |
| 842 | - .all(|encoding| *encoding != record.encoding) | |
| 843 | - { | |
| 844 | - encodings.push(record.encoding); | |
| 845 | - } | |
| 846 | - let delta = record | |
| 847 | - .function_offset | |
| 848 | - .saturating_sub(page.start_function_offset); | |
| 849 | - let projected_size = | |
| 850 | - COMPRESSED_PAGE_HEADER_SIZE + (page.entries.len() + 1) * 4 + encodings.len() * 4; | |
| 851 | - if projected_size > PAGE_SIZE && !page.entries.is_empty() { | |
| 852 | - pages.push(current.take().unwrap()); | |
| 853 | - current = Some(CompressedPage { | |
| 868 | + loop { | |
| 869 | + let page = current.get_or_insert_with(|| CompressedPage { | |
| 854 | 870 | start_function_offset: record.function_offset, |
| 855 | 871 | entries: Vec::new(), |
| 856 | 872 | local_encodings: Vec::new(), |
| 857 | 873 | }); |
| 858 | - } | |
| 859 | 874 | |
| 860 | - let page = current.as_mut().unwrap(); | |
| 861 | - let encoding_index = if let Some(index) = page | |
| 862 | - .local_encodings | |
| 863 | - .iter() | |
| 864 | - .position(|encoding| *encoding == record.encoding) | |
| 865 | - { | |
| 866 | - index | |
| 867 | - } else { | |
| 868 | - page.local_encodings.push(record.encoding); | |
| 869 | - page.local_encodings.len() - 1 | |
| 870 | - }; | |
| 871 | - page.entries | |
| 872 | - .push(((encoding_index as u32) << 24) | (delta & 0x00ff_ffff)); | |
| 875 | + let needs_local_encoding = !common_indices.contains_key(&record.encoding) | |
| 876 | + && page | |
| 877 | + .local_encodings | |
| 878 | + .iter() | |
| 879 | + .all(|encoding| *encoding != record.encoding); | |
| 880 | + let prospective_local_count = | |
| 881 | + page.local_encodings.len() + usize::from(needs_local_encoding); | |
| 882 | + let delta = record | |
| 883 | + .function_offset | |
| 884 | + .saturating_sub(page.start_function_offset); | |
| 885 | + let projected_size = COMPRESSED_PAGE_HEADER_SIZE | |
| 886 | + + (page.entries.len() + 1) * 4 | |
| 887 | + + prospective_local_count * 4; | |
| 888 | + let projected_encoding_count = common_encodings.len() + prospective_local_count; | |
| 889 | + | |
| 890 | + if !page.entries.is_empty() | |
| 891 | + && (projected_size > PAGE_SIZE | |
| 892 | + || delta > MAX_COMPRESSED_FUNCTION_DELTA | |
| 893 | + || projected_encoding_count > MAX_COMPRESSED_ENCODING_INDEX + 1) | |
| 894 | + { | |
| 895 | + pages.push(current.take().unwrap()); | |
| 896 | + continue; | |
| 897 | + } | |
| 898 | + | |
| 899 | + let page = current.as_mut().unwrap(); | |
| 900 | + let encoding_index = if let Some(index) = common_indices.get(&record.encoding) { | |
| 901 | + *index | |
| 902 | + } else if let Some(index) = page | |
| 903 | + .local_encodings | |
| 904 | + .iter() | |
| 905 | + .position(|encoding| *encoding == record.encoding) | |
| 906 | + { | |
| 907 | + common_encodings.len() + index | |
| 908 | + } else { | |
| 909 | + page.local_encodings.push(record.encoding); | |
| 910 | + common_encodings.len() + page.local_encodings.len() - 1 | |
| 911 | + }; | |
| 912 | + page.entries.push(((encoding_index as u32) << 24) | delta); | |
| 913 | + break; | |
| 914 | + } | |
| 873 | 915 | } |
| 874 | 916 | |
| 875 | 917 | if let Some(page) = current { |
@@ -1149,6 +1191,132 @@ mod tests { | ||
| 1149 | 1191 | ); |
| 1150 | 1192 | } |
| 1151 | 1193 | |
| 1194 | + #[test] | |
| 1195 | + fn repeated_encodings_promote_to_common_table() { | |
| 1196 | + let bytes = serialize_unwind_info(&[ | |
| 1197 | + UnwindRecord { | |
| 1198 | + function_offset: 0x348, | |
| 1199 | + code_len: 0x18, | |
| 1200 | + encoding: 0x0400_0000, | |
| 1201 | + personality_offset: None, | |
| 1202 | + lsda_offset: None, | |
| 1203 | + }, | |
| 1204 | + UnwindRecord { | |
| 1205 | + function_offset: 0x360, | |
| 1206 | + code_len: 0x20, | |
| 1207 | + encoding: 0x0200_2000, | |
| 1208 | + personality_offset: None, | |
| 1209 | + lsda_offset: None, | |
| 1210 | + }, | |
| 1211 | + UnwindRecord { | |
| 1212 | + function_offset: 0x390, | |
| 1213 | + code_len: 0x10, | |
| 1214 | + encoding: 0x0400_0000, | |
| 1215 | + personality_offset: None, | |
| 1216 | + lsda_offset: None, | |
| 1217 | + }, | |
| 1218 | + ]) | |
| 1219 | + .unwrap(); | |
| 1220 | + let words: Vec<u32> = bytes | |
| 1221 | + .chunks_exact(4) | |
| 1222 | + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) | |
| 1223 | + .collect(); | |
| 1224 | + assert_eq!(words[2], 1, "expected one promoted common encoding"); | |
| 1225 | + assert_eq!(words[7], 0x0400_0000); | |
| 1226 | + | |
| 1227 | + let decoded = decode_unwind_info(&bytes).unwrap(); | |
| 1228 | + assert_eq!( | |
| 1229 | + decoded.records, | |
| 1230 | + vec![ | |
| 1231 | + DecodedUnwindRecord { | |
| 1232 | + function_offset: 0x348, | |
| 1233 | + encoding: 0x0400_0000, | |
| 1234 | + }, | |
| 1235 | + DecodedUnwindRecord { | |
| 1236 | + function_offset: 0x360, | |
| 1237 | + encoding: 0x0200_2000, | |
| 1238 | + }, | |
| 1239 | + DecodedUnwindRecord { | |
| 1240 | + function_offset: 0x390, | |
| 1241 | + encoding: 0x0400_0000, | |
| 1242 | + }, | |
| 1243 | + ] | |
| 1244 | + ); | |
| 1245 | + } | |
| 1246 | + | |
| 1247 | + #[test] | |
| 1248 | + fn large_function_gaps_start_new_pages_before_delta_overflow() { | |
| 1249 | + let bytes = serialize_unwind_info(&[ | |
| 1250 | + UnwindRecord { | |
| 1251 | + function_offset: 0x348, | |
| 1252 | + code_len: 0x14, | |
| 1253 | + encoding: 0x0200_0000, | |
| 1254 | + personality_offset: None, | |
| 1255 | + lsda_offset: None, | |
| 1256 | + }, | |
| 1257 | + UnwindRecord { | |
| 1258 | + function_offset: 0x0100_0360, | |
| 1259 | + code_len: 0x14, | |
| 1260 | + encoding: 0x0200_0000, | |
| 1261 | + personality_offset: None, | |
| 1262 | + lsda_offset: None, | |
| 1263 | + }, | |
| 1264 | + ]) | |
| 1265 | + .unwrap(); | |
| 1266 | + let words: Vec<u32> = bytes | |
| 1267 | + .chunks_exact(4) | |
| 1268 | + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) | |
| 1269 | + .collect(); | |
| 1270 | + assert_eq!(words[6], 3, "expected two pages plus the sentinel index"); | |
| 1271 | + let decoded = decode_unwind_info(&bytes).unwrap(); | |
| 1272 | + assert_eq!( | |
| 1273 | + decoded.records, | |
| 1274 | + vec![ | |
| 1275 | + DecodedUnwindRecord { | |
| 1276 | + function_offset: 0x348, | |
| 1277 | + encoding: 0x0200_0000, | |
| 1278 | + }, | |
| 1279 | + DecodedUnwindRecord { | |
| 1280 | + function_offset: 0x0100_0360, | |
| 1281 | + encoding: 0x0200_0000, | |
| 1282 | + }, | |
| 1283 | + ] | |
| 1284 | + ); | |
| 1285 | + } | |
| 1286 | + | |
| 1287 | + #[test] | |
| 1288 | + fn pages_split_before_encoding_index_overflow() { | |
| 1289 | + let records = (0..300u32) | |
| 1290 | + .map(|idx| UnwindRecord { | |
| 1291 | + function_offset: 0x400 + idx * 4, | |
| 1292 | + code_len: 4, | |
| 1293 | + encoding: 0x0200_0000 | idx, | |
| 1294 | + personality_offset: None, | |
| 1295 | + lsda_offset: None, | |
| 1296 | + }) | |
| 1297 | + .collect::<Vec<_>>(); | |
| 1298 | + let bytes = serialize_unwind_info(&records).unwrap(); | |
| 1299 | + let words: Vec<u32> = bytes | |
| 1300 | + .chunks_exact(4) | |
| 1301 | + .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) | |
| 1302 | + .collect(); | |
| 1303 | + assert_eq!( | |
| 1304 | + words[2], 0, | |
| 1305 | + "all encodings are unique so nothing should be common" | |
| 1306 | + ); | |
| 1307 | + assert_eq!( | |
| 1308 | + words[6], 3, | |
| 1309 | + "expected the encoding pressure to force a second page" | |
| 1310 | + ); | |
| 1311 | + let decoded = decode_unwind_info(&bytes).unwrap(); | |
| 1312 | + assert_eq!(decoded.records.len(), records.len()); | |
| 1313 | + assert_eq!(decoded.records[0].function_offset, 0x400); | |
| 1314 | + assert_eq!( | |
| 1315 | + decoded.records.last().unwrap().function_offset, | |
| 1316 | + 0x400 + 299 * 4 | |
| 1317 | + ); | |
| 1318 | + } | |
| 1319 | + | |
| 1152 | 1320 | #[test] |
| 1153 | 1321 | fn decode_rejects_bad_encoding_index() { |
| 1154 | 1322 | let mut bytes = serialize_unwind_info(&[UnwindRecord { |
tests/atom_integration.rsmodified@@ -97,7 +97,7 @@ fn atomize_splits_text_at_symbol_boundaries_and_backpatches_symbols() { | ||
| 97 | 97 | |
| 98 | 98 | let bytes = fs::read(&obj_path).unwrap(); |
| 99 | 99 | let mut inputs = Inputs::new(); |
| 100 | - let input_id = inputs.add_object(obj_path.clone(), bytes).unwrap(); | |
| 100 | + let input_id = inputs.add_object(obj_path.clone(), bytes, 0).unwrap(); | |
| 101 | 101 | |
| 102 | 102 | // Seed the symbol table (produces Defined entries with AtomId(0) |
| 103 | 103 | // placeholders). |
@@ -204,7 +204,7 @@ fn atomize_cstring_splits_at_null_terminators() { | ||
| 204 | 204 | |
| 205 | 205 | let bytes = fs::read(&obj_path).unwrap(); |
| 206 | 206 | let mut inputs = Inputs::new(); |
| 207 | - let input_id = inputs.add_object(obj_path.clone(), bytes).unwrap(); | |
| 207 | + let input_id = inputs.add_object(obj_path.clone(), bytes, 0).unwrap(); | |
| 208 | 208 | let mut sym_table = SymbolTable::new(); |
| 209 | 209 | let _ = seed_all(&inputs, &mut sym_table).expect("seed_all"); |
| 210 | 210 | let obj = inputs.object_file(input_id).unwrap(); |
tests/linker_run.rsmodified1389 lines changed — click to load
@@ -16,7 +16,9 @@ use afs_ld::macho::constants::{ | ||
| 16 | 16 | BIND_OPCODE_SET_DYLIB_ORDINAL_IMM, BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB, |
| 17 | 17 | BIND_OPCODE_SET_DYLIB_SPECIAL_IMM, BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB, |
| 18 | 18 | BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM, BIND_OPCODE_SET_TYPE_IMM, |
| 19 | - BIND_SYMBOL_FLAGS_WEAK_IMPORT, DICE_KIND_JUMP_TABLE32, LC_DATA_IN_CODE, LC_FUNCTION_STARTS, | |
| 19 | + BIND_SYMBOL_FLAGS_WEAK_IMPORT, DICE_KIND_JUMP_TABLE32, INDIRECT_SYMBOL_ABS, | |
| 20 | + INDIRECT_SYMBOL_LOCAL, LC_BUILD_VERSION, LC_DATA_IN_CODE, LC_DYLD_INFO_ONLY, LC_DYSYMTAB, | |
| 21 | + LC_FUNCTION_STARTS, LC_SEGMENT_64, LC_SYMTAB, | |
| 20 | 22 | REBASE_IMMEDIATE_MASK, REBASE_OPCODE_ADD_ADDR_IMM_SCALED, REBASE_OPCODE_ADD_ADDR_ULEB, |
| 21 | 23 | REBASE_OPCODE_DONE, REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB, REBASE_OPCODE_DO_REBASE_IMM_TIMES, |
| 22 | 24 | REBASE_OPCODE_DO_REBASE_ULEB_TIMES, REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB, |
@@ -63,6 +65,20 @@ fn sdk_version() -> Option<String> { | ||
| 63 | 65 | Some(String::from_utf8_lossy(&out.stdout).trim().to_string()) |
| 64 | 66 | } |
| 65 | 67 | |
| 68 | +fn find_runtime_archive() -> Option<PathBuf> { | |
| 69 | + let workspace = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(".."); | |
| 70 | + for profile in ["debug", "release"] { | |
| 71 | + let candidate = workspace | |
| 72 | + .join("target") | |
| 73 | + .join(profile) | |
| 74 | + .join("libarmfortas_rt.a"); | |
| 75 | + if candidate.is_file() { | |
| 76 | + return Some(candidate); | |
| 77 | + } | |
| 78 | + } | |
| 79 | + None | |
| 80 | +} | |
| 81 | + | |
| 66 | 82 | fn have_xcrun_tool(tool: &str) -> bool { |
| 67 | 83 | Command::new("xcrun") |
| 68 | 84 | .arg("-f") |
@@ -245,6 +261,28 @@ fn segment_vmaddr(bytes: &[u8], segname: &str) -> Option<u64> { | ||
| 245 | 261 | None |
| 246 | 262 | } |
| 247 | 263 | |
| 264 | +fn symbol_values(bytes: &[u8]) -> HashMap<String, u64> { | |
| 265 | + let header = parse_header(bytes).unwrap(); | |
| 266 | + let commands = parse_commands(&header, bytes).unwrap(); | |
| 267 | + let symtab = commands | |
| 268 | + .iter() | |
| 269 | + .find_map(|cmd| match cmd { | |
| 270 | + LoadCommand::Symtab(cmd) => Some(*cmd), | |
| 271 | + _ => None, | |
| 272 | + }) | |
| 273 | + .unwrap(); | |
| 274 | + let symbols = parse_nlist_table(bytes, symtab.symoff, symtab.nsyms).unwrap(); | |
| 275 | + let strings = StringTable::from_file(bytes, symtab.stroff, symtab.strsize).unwrap(); | |
| 276 | + let mut out = HashMap::new(); | |
| 277 | + for symbol in symbols { | |
| 278 | + let Ok(name) = strings.get(symbol.strx()) else { | |
| 279 | + continue; | |
| 280 | + }; | |
| 281 | + out.insert(name.to_string(), symbol.value()); | |
| 282 | + } | |
| 283 | + out | |
| 284 | +} | |
| 285 | + | |
| 248 | 286 | fn symtab_and_dysymtab( |
| 249 | 287 | bytes: &[u8], |
| 250 | 288 | ) -> ( |
@@ -444,6 +482,29 @@ fn indirect_symbol_table(bytes: &[u8]) -> Vec<u32> { | ||
| 444 | 482 | .collect() |
| 445 | 483 | } |
| 446 | 484 | |
| 485 | +fn indirect_symbol_identities(bytes: &[u8]) -> Vec<String> { | |
| 486 | + let (symtab, _) = symtab_and_dysymtab(bytes); | |
| 487 | + let symbols = parse_nlist_table(bytes, symtab.symoff, symtab.nsyms).unwrap(); | |
| 488 | + let strings = StringTable::from_file(bytes, symtab.stroff, symtab.strsize).unwrap(); | |
| 489 | + indirect_symbol_table(bytes) | |
| 490 | + .into_iter() | |
| 491 | + .map(|index| { | |
| 492 | + if index & INDIRECT_SYMBOL_LOCAL != 0 { | |
| 493 | + if index & INDIRECT_SYMBOL_ABS != 0 { | |
| 494 | + "<LOCAL|ABS>".to_string() | |
| 495 | + } else { | |
| 496 | + "<LOCAL>".to_string() | |
| 497 | + } | |
| 498 | + } else if index & INDIRECT_SYMBOL_ABS != 0 { | |
| 499 | + "<ABS>".to_string() | |
| 500 | + } else { | |
| 501 | + let symbol = &symbols[index as usize]; | |
| 502 | + strings.get(symbol.strx()).unwrap().to_string() | |
| 503 | + } | |
| 504 | + }) | |
| 505 | + .collect() | |
| 506 | +} | |
| 507 | + | |
| 447 | 508 | fn raw_linkedit_data_cmd(bytes: &[u8], expected_cmd: u32) -> (u32, u32) { |
| 448 | 509 | let header = parse_header(bytes).unwrap(); |
| 449 | 510 | let commands = parse_commands(&header, bytes).unwrap(); |
@@ -482,6 +543,31 @@ fn decode_function_starts(bytes: &[u8]) -> Vec<u64> { | ||
| 482 | 543 | offsets |
| 483 | 544 | } |
| 484 | 545 | |
| 546 | +fn command_ids(bytes: &[u8]) -> Vec<u32> { | |
| 547 | + let header = parse_header(bytes).unwrap(); | |
| 548 | + let commands = parse_commands(&header, bytes).unwrap(); | |
| 549 | + commands | |
| 550 | + .into_iter() | |
| 551 | + .map(|cmd| match cmd { | |
| 552 | + LoadCommand::Segment64(_) => LC_SEGMENT_64, | |
| 553 | + LoadCommand::Symtab(_) => LC_SYMTAB, | |
| 554 | + LoadCommand::Dysymtab(_) => LC_DYSYMTAB, | |
| 555 | + LoadCommand::BuildVersion(_) => LC_BUILD_VERSION, | |
| 556 | + LoadCommand::Dylib(d) => d.cmd, | |
| 557 | + LoadCommand::DyldInfoOnly(_) => LC_DYLD_INFO_ONLY, | |
| 558 | + LoadCommand::Raw { cmd, .. } => cmd, | |
| 559 | + other => panic!("unexpected load command in command_ids helper: {other:?}"), | |
| 560 | + }) | |
| 561 | + .collect() | |
| 562 | +} | |
| 563 | + | |
| 564 | +fn normalize_function_start_offsets(starts: &[u64]) -> Vec<u64> { | |
| 565 | + let Some(&base) = starts.first() else { | |
| 566 | + return Vec::new(); | |
| 567 | + }; | |
| 568 | + starts.iter().map(|offset| offset - base).collect() | |
| 569 | +} | |
| 570 | + | |
| 485 | 571 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 486 | 572 | struct DataInCodeRecord { |
| 487 | 573 | offset: u32, |
@@ -489,30 +575,68 @@ struct DataInCodeRecord { | ||
| 489 | 575 | kind: u16, |
| 490 | 576 | } |
| 491 | 577 | |
| 492 | -fn normalized_unwind_words(bytes: &[u8]) -> Vec<u32> { | |
| 578 | +fn rebased_unwind_bytes(bytes: &[u8]) -> Vec<u8> { | |
| 579 | + let header_base = segment_vmaddr(bytes, "__TEXT").unwrap_or(0); | |
| 580 | + let text_base = output_section(bytes, "__TEXT", "__text").unwrap().0 - header_base; | |
| 581 | + let got_range = output_section(bytes, "__DATA_CONST", "__got") | |
| 582 | + .map(|(addr, data)| (addr - header_base, addr - header_base + data.len() as u64)); | |
| 583 | + let lsda_base = | |
| 584 | + output_section(bytes, "__TEXT", "__gcc_except_tab").map(|(addr, _)| addr - header_base); | |
| 493 | 585 | let (_, unwind) = output_section(bytes, "__TEXT", "__unwind_info").unwrap(); |
| 494 | - let decoded = decode_unwind_info(&unwind).unwrap(); | |
| 495 | - let mut words: Vec<u32> = unwind | |
| 496 | - .chunks_exact(4) | |
| 497 | - .map(|chunk| u32::from_le_bytes(chunk.try_into().unwrap())) | |
| 498 | - .collect(); | |
| 499 | - if words.len() < 7 { | |
| 500 | - return words; | |
| 501 | - } | |
| 502 | - let indices_offset_words = words[5] as usize / 4; | |
| 503 | - let indices_count = words[6] as usize; | |
| 504 | - if indices_count == 0 || words.len() < indices_offset_words + indices_count * 3 { | |
| 505 | - return words; | |
| 586 | + let mut out = unwind; | |
| 587 | + if out.len() < 28 { | |
| 588 | + return out; | |
| 589 | + } | |
| 590 | + | |
| 591 | + let personalities_offset = u32_le(&out[12..16]) as usize; | |
| 592 | + let personalities_count = u32_le(&out[16..20]) as usize; | |
| 593 | + let indices_offset = u32_le(&out[20..24]) as usize; | |
| 594 | + let indices_count = u32_le(&out[24..28]) as usize; | |
| 595 | + | |
| 596 | + for idx in 0..personalities_count { | |
| 597 | + let off = personalities_offset + idx * 4; | |
| 598 | + let value = u32_le(&out[off..off + 4]) as u64; | |
| 599 | + let rebased = if let Some((got_start, got_end)) = got_range { | |
| 600 | + if got_start <= value && value < got_end { | |
| 601 | + value - got_start | |
| 602 | + } else if value >= text_base { | |
| 603 | + value - text_base | |
| 604 | + } else { | |
| 605 | + value | |
| 606 | + } | |
| 607 | + } else if value >= text_base { | |
| 608 | + value - text_base | |
| 609 | + } else { | |
| 610 | + value | |
| 611 | + }; | |
| 612 | + out[off..off + 4].copy_from_slice(&(rebased as u32).to_le_bytes()); | |
| 506 | 613 | } |
| 507 | - let base = words[indices_offset_words]; | |
| 614 | + | |
| 615 | + let mut lsda_offsets = Vec::with_capacity(indices_count); | |
| 508 | 616 | for idx in 0..indices_count { |
| 509 | - let word = indices_offset_words + idx * 3; | |
| 510 | - words[word] = words[word].saturating_sub(base); | |
| 617 | + let entry_off = indices_offset + idx * 12; | |
| 618 | + let function_offset = u32_le(&out[entry_off..entry_off + 4]) as u64; | |
| 619 | + let rebased = function_offset.saturating_sub(text_base); | |
| 620 | + out[entry_off..entry_off + 4].copy_from_slice(&(rebased as u32).to_le_bytes()); | |
| 621 | + lsda_offsets.push(u32_le(&out[entry_off + 8..entry_off + 12]) as usize); | |
| 511 | 622 | } |
| 512 | - if !decoded.records.is_empty() { | |
| 513 | - assert_eq!(decoded.records[0].function_offset, base); | |
| 623 | + | |
| 624 | + if let (Some(lsda_base), Some(&start), Some(&end)) = | |
| 625 | + (lsda_base, lsda_offsets.first(), lsda_offsets.last()) | |
| 626 | + { | |
| 627 | + let mut entry_off = start; | |
| 628 | + while entry_off < end { | |
| 629 | + let function_offset = u32_le(&out[entry_off..entry_off + 4]) as u64; | |
| 630 | + let lsda_offset = u32_le(&out[entry_off + 4..entry_off + 8]) as u64; | |
| 631 | + out[entry_off..entry_off + 4] | |
| 632 | + .copy_from_slice(&(function_offset.saturating_sub(text_base) as u32).to_le_bytes()); | |
| 633 | + out[entry_off + 4..entry_off + 8] | |
| 634 | + .copy_from_slice(&(lsda_offset.saturating_sub(lsda_base) as u32).to_le_bytes()); | |
| 635 | + entry_off += 8; | |
| 636 | + } | |
| 514 | 637 | } |
| 515 | - words | |
| 638 | + | |
| 639 | + out | |
| 516 | 640 | } |
| 517 | 641 | |
| 518 | 642 | fn normalized_eh_frame_dump(path: &PathBuf, text_base: u64) -> Result<String, String> { |
@@ -1679,9 +1803,18 @@ fn linker_run_emits_non_empty_executable_from_real_object() { | ||
| 1679 | 1803 | eprintln!("skipping: xcrun as or codesign unavailable"); |
| 1680 | 1804 | return; |
| 1681 | 1805 | } |
| 1806 | + let Some(sdk) = sdk_path() else { | |
| 1807 | + eprintln!("skipping: xcrun --show-sdk-path unavailable"); | |
| 1808 | + return; | |
| 1809 | + }; | |
| 1810 | + let Some(sdk_ver) = sdk_version() else { | |
| 1811 | + eprintln!("skipping: xcrun --show-sdk-version unavailable"); | |
| 1812 | + return; | |
| 1813 | + }; | |
| 1682 | 1814 | |
| 1683 | 1815 | let obj = scratch("main.o"); |
| 1684 | 1816 | let out = scratch("a.out"); |
| 1817 | + let apple_out = scratch("a-apple.out"); | |
| 1685 | 1818 | let src = r#" |
| 1686 | 1819 | .section __TEXT,__text,regular,pure_instructions |
| 1687 | 1820 | .globl _main |
@@ -1702,13 +1835,16 @@ fn linker_run_emits_non_empty_executable_from_real_object() { | ||
| 1702 | 1835 | ..LinkOptions::default() |
| 1703 | 1836 | }; |
| 1704 | 1837 | Linker::run(&opts).unwrap(); |
| 1838 | + apple_link_classic_lazy(&obj, &apple_out, "_main", &sdk, &sdk_ver).unwrap(); | |
| 1705 | 1839 | |
| 1706 | 1840 | let bytes = fs::read(&out).unwrap(); |
| 1841 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 1707 | 1842 | let header = parse_header(&bytes).unwrap(); |
| 1708 | 1843 | let commands = parse_commands(&header, &bytes).unwrap(); |
| 1709 | 1844 | let mut text_size = 0u64; |
| 1710 | 1845 | let mut has_dylinker = false; |
| 1711 | 1846 | let mut has_uuid = false; |
| 1847 | + let mut has_source_version = false; | |
| 1712 | 1848 | for cmd in commands { |
| 1713 | 1849 | match cmd { |
| 1714 | 1850 | LoadCommand::Segment64(seg) => { |
@@ -1728,6 +1864,9 @@ fn linker_run_emits_non_empty_executable_from_real_object() { | ||
| 1728 | 1864 | LoadCommand::Raw { cmd, data, .. } if cmd == afs_ld::macho::constants::LC_UUID => { |
| 1729 | 1865 | has_uuid = data.len() == 16 && data.iter().any(|byte| *byte != 0); |
| 1730 | 1866 | } |
| 1867 | + LoadCommand::Raw { cmd, .. } if cmd == afs_ld::macho::constants::LC_SOURCE_VERSION => { | |
| 1868 | + has_source_version = true; | |
| 1869 | + } | |
| 1731 | 1870 | _ => {} |
| 1732 | 1871 | } |
| 1733 | 1872 | } |
@@ -1737,6 +1876,19 @@ fn linker_run_emits_non_empty_executable_from_real_object() { | ||
| 1737 | 1876 | "expected LC_LOAD_DYLINKER in executable output" |
| 1738 | 1877 | ); |
| 1739 | 1878 | assert!(has_uuid, "expected LC_UUID in executable output"); |
| 1879 | + assert!( | |
| 1880 | + has_source_version, | |
| 1881 | + "expected LC_SOURCE_VERSION in executable output" | |
| 1882 | + ); | |
| 1883 | + let our_cmds: Vec<u32> = command_ids(&bytes) | |
| 1884 | + .into_iter() | |
| 1885 | + .filter(|cmd| *cmd != afs_ld::macho::constants::LC_LOAD_DYLIB) | |
| 1886 | + .collect(); | |
| 1887 | + let apple_cmds: Vec<u32> = command_ids(&apple_bytes) | |
| 1888 | + .into_iter() | |
| 1889 | + .filter(|cmd| *cmd != afs_ld::macho::constants::LC_LOAD_DYLIB) | |
| 1890 | + .collect(); | |
| 1891 | + assert_eq!(our_cmds, apple_cmds); | |
| 1740 | 1892 | assert!( |
| 1741 | 1893 | fs::metadata(&out).unwrap().permissions().mode() & 0o111 != 0, |
| 1742 | 1894 | "expected executable output mode" |
@@ -1756,6 +1908,7 @@ fn linker_run_emits_non_empty_executable_from_real_object() { | ||
| 1756 | 1908 | |
| 1757 | 1909 | let _ = fs::remove_file(obj); |
| 1758 | 1910 | let _ = fs::remove_file(out); |
| 1911 | + let _ = fs::remove_file(apple_out); | |
| 1759 | 1912 | } |
| 1760 | 1913 | |
| 1761 | 1914 | #[test] |
@@ -2357,6 +2510,51 @@ fn linker_run_uses_requested_entry_symbol() { | ||
| 2357 | 2510 | let _ = fs::remove_file(out); |
| 2358 | 2511 | } |
| 2359 | 2512 | |
| 2513 | +#[test] | |
| 2514 | +fn linker_run_defaults_entry_to_main_symbol() { | |
| 2515 | + if !have_xcrun() { | |
| 2516 | + eprintln!("skipping: xcrun as unavailable"); | |
| 2517 | + return; | |
| 2518 | + } | |
| 2519 | + | |
| 2520 | + let obj = scratch("default-entry.o"); | |
| 2521 | + let out = scratch("default-entry.out"); | |
| 2522 | + let src = r#" | |
| 2523 | + .section __TEXT,__text,regular,pure_instructions | |
| 2524 | + .globl _helper | |
| 2525 | + _helper: | |
| 2526 | + mov w0, #7 | |
| 2527 | + ret | |
| 2528 | + .globl _main | |
| 2529 | + _main: | |
| 2530 | + mov w0, #0 | |
| 2531 | + ret | |
| 2532 | + .subsections_via_symbols | |
| 2533 | + "#; | |
| 2534 | + if let Err(e) = assemble(src, &obj) { | |
| 2535 | + eprintln!("skipping: assemble failed: {e}"); | |
| 2536 | + return; | |
| 2537 | + } | |
| 2538 | + | |
| 2539 | + let opts = LinkOptions { | |
| 2540 | + inputs: vec![obj.clone()], | |
| 2541 | + output: Some(out.clone()), | |
| 2542 | + kind: OutputKind::Executable, | |
| 2543 | + ..LinkOptions::default() | |
| 2544 | + }; | |
| 2545 | + Linker::run(&opts).unwrap(); | |
| 2546 | + | |
| 2547 | + let status = Command::new(&out).status().unwrap(); | |
| 2548 | + assert_eq!( | |
| 2549 | + status.code(), | |
| 2550 | + Some(0), | |
| 2551 | + "default executable entry should prefer _main over the first text atom" | |
| 2552 | + ); | |
| 2553 | + | |
| 2554 | + let _ = fs::remove_file(obj); | |
| 2555 | + let _ = fs::remove_file(out); | |
| 2556 | +} | |
| 2557 | + | |
| 2360 | 2558 | #[test] |
| 2361 | 2559 | fn linker_run_applies_core_arm64_relocations() { |
| 2362 | 2560 | if !have_xcrun() { |
@@ -2913,6 +3111,18 @@ fn linker_run_routes_dylib_imports_through_synthetic_sections() { | ||
| 2913 | 3111 | _ => None, |
| 2914 | 3112 | }) |
| 2915 | 3113 | .unwrap(); |
| 3114 | + let libsystem_load = commands | |
| 3115 | + .iter() | |
| 3116 | + .find_map(|cmd| match cmd { | |
| 3117 | + LoadCommand::Dylib(cmd) | |
| 3118 | + if cmd.cmd == afs_ld::macho::constants::LC_LOAD_DYLIB | |
| 3119 | + && cmd.name == "/usr/lib/libSystem.B.dylib" => | |
| 3120 | + { | |
| 3121 | + Some(cmd.clone()) | |
| 3122 | + } | |
| 3123 | + _ => None, | |
| 3124 | + }) | |
| 3125 | + .unwrap(); | |
| 2916 | 3126 | let symbols = parse_nlist_table(&bytes, symtab.symoff, symtab.nsyms).unwrap(); |
| 2917 | 3127 | let strings = StringTable::from_file(&bytes, symtab.stroff, symtab.strsize).unwrap(); |
| 2918 | 3128 | let symbol_names: Vec<&str> = symbols |
@@ -2933,6 +3143,8 @@ fn linker_run_routes_dylib_imports_through_synthetic_sections() { | ||
| 2933 | 3143 | assert_eq!(got_hdr.reserved1, 1); |
| 2934 | 3144 | assert_eq!(lazy_hdr.reserved1, 3); |
| 2935 | 3145 | assert_eq!(stubs_hdr.reserved2, 12); |
| 3146 | + assert!(libsystem_load.current_version >= (1 << 16)); | |
| 3147 | + assert_eq!(libsystem_load.compatibility_version, 1 << 16); | |
| 2936 | 3148 | assert!(dyld_info.rebase_size > 0); |
| 2937 | 3149 | assert!(dyld_info.bind_size > 0); |
| 2938 | 3150 | assert!(dyld_info.lazy_bind_size > 0); |
@@ -3439,126 +3651,304 @@ fn linker_run_rebases_local_absolute_pointers_like_ld() { | ||
| 3439 | 3651 | } |
| 3440 | 3652 | |
| 3441 | 3653 | #[test] |
| 3442 | -fn linker_run_partitions_symtab_like_ld() { | |
| 3443 | - if !have_xcrun() { | |
| 3444 | - eprintln!("skipping: xcrun unavailable"); | |
| 3654 | +fn linker_run_routes_local_got_loads_through_rebased_slots() { | |
| 3655 | + if !have_xcrun() || !have_tool("codesign") { | |
| 3656 | + eprintln!("skipping: xcrun or codesign unavailable"); | |
| 3445 | 3657 | return; |
| 3446 | 3658 | } |
| 3447 | - | |
| 3448 | - let dylib = scratch("symtab-partition.dylib"); | |
| 3449 | - let obj = scratch("symtab-partition.o"); | |
| 3450 | - let our_out = scratch("symtab-partition-ours.out"); | |
| 3451 | - let apple_out = scratch("symtab-partition-apple.out"); | |
| 3452 | - | |
| 3453 | - let dylib_src = r#" | |
| 3454 | - int ext_data = 5; | |
| 3455 | - "#; | |
| 3456 | - if let Err(e) = compile_dylib_c(dylib_src, &dylib) { | |
| 3457 | - eprintln!("skipping: dylib compile failed: {e}"); | |
| 3659 | + let Some(sdk) = sdk_path() else { | |
| 3660 | + eprintln!("skipping: xcrun --show-sdk-path unavailable"); | |
| 3661 | + return; | |
| 3662 | + }; | |
| 3663 | + let Some(sdk_ver) = sdk_version() else { | |
| 3664 | + eprintln!("skipping: xcrun --show-sdk-version unavailable"); | |
| 3665 | + return; | |
| 3666 | + }; | |
| 3667 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 3668 | + if !tbd.exists() { | |
| 3669 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 3458 | 3670 | return; |
| 3459 | 3671 | } |
| 3460 | 3672 | |
| 3461 | - let asm = r#" | |
| 3462 | - .text | |
| 3463 | - .private_extern _hidden | |
| 3464 | - .globl _visible | |
| 3673 | + let obj = scratch("local-got.o"); | |
| 3674 | + let our_out = scratch("local-got-ours.out"); | |
| 3675 | + let apple_out = scratch("local-got-apple.out"); | |
| 3676 | + let src = r#" | |
| 3677 | + .section __TEXT,__text,regular,pure_instructions | |
| 3465 | 3678 | .globl _main |
| 3466 | - .p2align 2 | |
| 3467 | - _local: | |
| 3468 | - ret | |
| 3469 | - _hidden: | |
| 3470 | - ret | |
| 3471 | - _visible: | |
| 3472 | - ret | |
| 3473 | - _main: | |
| 3474 | - ret | |
| 3679 | + _main: | |
| 3680 | + adrp x8, _value@GOTPAGE | |
| 3681 | + ldr x8, [x8, _value@GOTPAGEOFF] | |
| 3682 | + ldr w0, [x8] | |
| 3683 | + ret | |
| 3475 | 3684 | |
| 3476 | - .data | |
| 3477 | - .quad _ext_data | |
| 3685 | + .section __DATA,__data | |
| 3686 | + .globl _value | |
| 3687 | + .p2align 2 | |
| 3688 | + _value: | |
| 3689 | + .long 7 | |
| 3478 | 3690 | .subsections_via_symbols |
| 3479 | 3691 | "#; |
| 3480 | - if let Err(e) = assemble(asm, &obj) { | |
| 3692 | + if let Err(e) = assemble(src, &obj) { | |
| 3481 | 3693 | eprintln!("skipping: assemble failed: {e}"); |
| 3482 | 3694 | return; |
| 3483 | 3695 | } |
| 3484 | 3696 | |
| 3485 | 3697 | let opts = LinkOptions { |
| 3486 | - inputs: vec![obj.clone(), dylib.clone()], | |
| 3698 | + inputs: vec![obj.clone(), tbd.clone()], | |
| 3487 | 3699 | output: Some(our_out.clone()), |
| 3488 | 3700 | kind: OutputKind::Executable, |
| 3489 | 3701 | ..LinkOptions::default() |
| 3490 | 3702 | }; |
| 3491 | 3703 | Linker::run(&opts).unwrap(); |
| 3492 | - | |
| 3493 | - let apple = Command::new("xcrun") | |
| 3494 | - .args(["ld", "-arch", "arm64", "-e", "_main", "-o"]) | |
| 3495 | - .arg(&apple_out) | |
| 3496 | - .arg(&obj) | |
| 3497 | - .arg(&dylib) | |
| 3498 | - .output() | |
| 3499 | - .unwrap(); | |
| 3500 | - assert!( | |
| 3501 | - apple.status.success(), | |
| 3502 | - "xcrun ld failed: {}", | |
| 3503 | - String::from_utf8_lossy(&apple.stderr) | |
| 3504 | - ); | |
| 3704 | + apple_link_classic_lazy(&obj, &apple_out, "_main", &sdk, &sdk_ver).unwrap(); | |
| 3505 | 3705 | |
| 3506 | 3706 | let our_bytes = fs::read(&our_out).unwrap(); |
| 3507 | 3707 | let apple_bytes = fs::read(&apple_out).unwrap(); |
| 3508 | - let (our_symtab, our_dysymtab) = symtab_and_dysymtab(&our_bytes); | |
| 3509 | - let (apple_symtab, apple_dysymtab) = symtab_and_dysymtab(&apple_bytes); | |
| 3510 | - | |
| 3511 | - assert_eq!(our_symtab.nsyms, apple_symtab.nsyms); | |
| 3512 | - assert_eq!(our_dysymtab.ilocalsym, apple_dysymtab.ilocalsym); | |
| 3513 | - assert_eq!(our_dysymtab.nlocalsym, apple_dysymtab.nlocalsym); | |
| 3514 | - assert_eq!(our_dysymtab.iextdefsym, apple_dysymtab.iextdefsym); | |
| 3515 | - assert_eq!(our_dysymtab.nextdefsym, apple_dysymtab.nextdefsym); | |
| 3516 | - assert_eq!(our_dysymtab.iundefsym, apple_dysymtab.iundefsym); | |
| 3517 | - assert_eq!(our_dysymtab.nundefsym, apple_dysymtab.nundefsym); | |
| 3708 | + let our_binds = decode_bind_records(&our_bytes, false).unwrap(); | |
| 3709 | + let apple_binds = decode_bind_records(&apple_bytes, false).unwrap(); | |
| 3710 | + assert_eq!(our_binds, apple_binds); | |
| 3711 | + assert!( | |
| 3712 | + our_binds.iter().all(|record| record.symbol != "_value"), | |
| 3713 | + "local GOT target should not be emitted as a dylib bind: {our_binds:#?}" | |
| 3714 | + ); | |
| 3518 | 3715 | assert_eq!( |
| 3519 | - canonical_symbol_records(&our_bytes), | |
| 3520 | - canonical_symbol_records(&apple_bytes) | |
| 3716 | + output_section(&our_bytes, "__DATA_CONST", "__got") | |
| 3717 | + .expect("missing __got section") | |
| 3718 | + .1 | |
| 3719 | + .len(), | |
| 3720 | + 8 | |
| 3521 | 3721 | ); |
| 3522 | - assert_strtab_within_five_percent( | |
| 3523 | - &raw_string_table(&our_bytes), | |
| 3524 | - &raw_string_table(&apple_bytes), | |
| 3722 | + let verify = Command::new("codesign") | |
| 3723 | + .arg("-v") | |
| 3724 | + .arg(&our_out) | |
| 3725 | + .output() | |
| 3726 | + .unwrap(); | |
| 3727 | + assert!( | |
| 3728 | + verify.status.success(), | |
| 3729 | + "codesign verify failed: {}", | |
| 3730 | + String::from_utf8_lossy(&verify.stderr) | |
| 3525 | 3731 | ); |
| 3526 | - | |
| 3732 | + let status = Command::new(&our_out).status().unwrap(); | |
| 3527 | 3733 | assert_eq!( |
| 3528 | - symbol_partition_names(&our_bytes), | |
| 3529 | - symbol_partition_names(&apple_bytes) | |
| 3734 | + status.code(), | |
| 3735 | + Some(7), | |
| 3736 | + "expected local GOT executable to exit 7" | |
| 3530 | 3737 | ); |
| 3531 | 3738 | |
| 3532 | - let _ = fs::remove_file(dylib); | |
| 3533 | 3739 | let _ = fs::remove_file(obj); |
| 3534 | 3740 | let _ = fs::remove_file(our_out); |
| 3535 | 3741 | let _ = fs::remove_file(apple_out); |
| 3536 | 3742 | } |
| 3537 | 3743 | |
| 3538 | 3744 | #[test] |
| 3539 | -fn linker_run_strips_locals_with_x_like_ld() { | |
| 3540 | - if !have_xcrun() { | |
| 3541 | - eprintln!("skipping: xcrun unavailable"); | |
| 3745 | +fn linker_run_relaxes_hidden_got_loads_like_apple_ld() { | |
| 3746 | + if !have_xcrun() || !have_tool("codesign") { | |
| 3747 | + eprintln!("skipping: xcrun or codesign unavailable"); | |
| 3542 | 3748 | return; |
| 3543 | 3749 | } |
| 3544 | - | |
| 3545 | - let dylib = scratch("symtab-strip.dylib"); | |
| 3546 | - let obj = scratch("symtab-strip.o"); | |
| 3547 | - let our_out = scratch("symtab-strip-ours.out"); | |
| 3548 | - let apple_out = scratch("symtab-strip-apple.out"); | |
| 3549 | - | |
| 3550 | - let dylib_src = r#" | |
| 3551 | - int ext_data = 5; | |
| 3552 | - "#; | |
| 3553 | - if let Err(e) = compile_dylib_c(dylib_src, &dylib) { | |
| 3554 | - eprintln!("skipping: dylib compile failed: {e}"); | |
| 3750 | + let Some(sdk) = sdk_path() else { | |
| 3751 | + eprintln!("skipping: xcrun --show-sdk-path unavailable"); | |
| 3752 | + return; | |
| 3753 | + }; | |
| 3754 | + let Some(sdk_ver) = sdk_version() else { | |
| 3755 | + eprintln!("skipping: xcrun --show-sdk-version unavailable"); | |
| 3756 | + return; | |
| 3757 | + }; | |
| 3758 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 3759 | + if !tbd.exists() { | |
| 3760 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 3555 | 3761 | return; |
| 3556 | 3762 | } |
| 3557 | 3763 | |
| 3558 | - let asm = r#" | |
| 3559 | - .text | |
| 3560 | - .private_extern _hidden | |
| 3561 | - .globl _visible | |
| 3764 | + let obj = scratch("hidden-got.o"); | |
| 3765 | + let our_out = scratch("hidden-got-ours.out"); | |
| 3766 | + let apple_out = scratch("hidden-got-apple.out"); | |
| 3767 | + let src = r#" | |
| 3768 | + .section __TEXT,__text,regular,pure_instructions | |
| 3769 | + .globl _main | |
| 3770 | + _main: | |
| 3771 | + adrp x8, _value@GOTPAGE | |
| 3772 | + ldr x8, [x8, _value@GOTPAGEOFF] | |
| 3773 | + ldr w0, [x8] | |
| 3774 | + ret | |
| 3775 | + | |
| 3776 | + .private_extern _value | |
| 3777 | + .section __DATA,__data | |
| 3778 | + .p2align 2 | |
| 3779 | + _value: | |
| 3780 | + .long 7 | |
| 3781 | + .subsections_via_symbols | |
| 3782 | + "#; | |
| 3783 | + if let Err(e) = assemble(src, &obj) { | |
| 3784 | + eprintln!("skipping: assemble failed: {e}"); | |
| 3785 | + return; | |
| 3786 | + } | |
| 3787 | + | |
| 3788 | + let opts = LinkOptions { | |
| 3789 | + inputs: vec![obj.clone(), tbd.clone()], | |
| 3790 | + output: Some(our_out.clone()), | |
| 3791 | + kind: OutputKind::Executable, | |
| 3792 | + ..LinkOptions::default() | |
| 3793 | + }; | |
| 3794 | + Linker::run(&opts).unwrap(); | |
| 3795 | + apple_link_classic_lazy(&obj, &apple_out, "_main", &sdk, &sdk_ver).unwrap(); | |
| 3796 | + | |
| 3797 | + let our_bytes = fs::read(&our_out).unwrap(); | |
| 3798 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 3799 | + let (our_text_addr, our_text) = output_section(&our_bytes, "__TEXT", "__text").unwrap(); | |
| 3800 | + let (apple_text_addr, apple_text) = output_section(&apple_bytes, "__TEXT", "__text").unwrap(); | |
| 3801 | + assert_eq!( | |
| 3802 | + decode_page_reference(&our_text, our_text_addr, 0, &PageRefKind::Add).unwrap(), | |
| 3803 | + decode_page_reference(&apple_text, apple_text_addr, 0, &PageRefKind::Add).unwrap() | |
| 3804 | + ); | |
| 3805 | + assert_eq!(our_text, apple_text); | |
| 3806 | + assert!(output_section(&our_bytes, "__DATA_CONST", "__got").is_none()); | |
| 3807 | + assert!(output_section(&apple_bytes, "__DATA_CONST", "__got").is_none()); | |
| 3808 | + | |
| 3809 | + let verify = Command::new("codesign") | |
| 3810 | + .arg("-v") | |
| 3811 | + .arg(&our_out) | |
| 3812 | + .output() | |
| 3813 | + .unwrap(); | |
| 3814 | + assert!( | |
| 3815 | + verify.status.success(), | |
| 3816 | + "codesign verify failed: {}", | |
| 3817 | + String::from_utf8_lossy(&verify.stderr) | |
| 3818 | + ); | |
| 3819 | + let status = Command::new(&our_out).status().unwrap(); | |
| 3820 | + assert_eq!( | |
| 3821 | + status.code(), | |
| 3822 | + Some(7), | |
| 3823 | + "expected hidden GOT executable to exit 7" | |
| 3824 | + ); | |
| 3825 | + | |
| 3826 | + let _ = fs::remove_file(obj); | |
| 3827 | + let _ = fs::remove_file(our_out); | |
| 3828 | + let _ = fs::remove_file(apple_out); | |
| 3829 | +} | |
| 3830 | + | |
| 3831 | +#[test] | |
| 3832 | +fn linker_run_partitions_symtab_like_ld() { | |
| 3833 | + if !have_xcrun() { | |
| 3834 | + eprintln!("skipping: xcrun unavailable"); | |
| 3835 | + return; | |
| 3836 | + } | |
| 3837 | + | |
| 3838 | + let dylib = scratch("symtab-partition.dylib"); | |
| 3839 | + let obj = scratch("symtab-partition.o"); | |
| 3840 | + let our_out = scratch("symtab-partition-ours.out"); | |
| 3841 | + let apple_out = scratch("symtab-partition-apple.out"); | |
| 3842 | + | |
| 3843 | + let dylib_src = r#" | |
| 3844 | + int ext_data = 5; | |
| 3845 | + "#; | |
| 3846 | + if let Err(e) = compile_dylib_c(dylib_src, &dylib) { | |
| 3847 | + eprintln!("skipping: dylib compile failed: {e}"); | |
| 3848 | + return; | |
| 3849 | + } | |
| 3850 | + | |
| 3851 | + let asm = r#" | |
| 3852 | + .text | |
| 3853 | + .private_extern _hidden | |
| 3854 | + .globl _visible | |
| 3855 | + .globl _main | |
| 3856 | + .p2align 2 | |
| 3857 | + _local: | |
| 3858 | + ret | |
| 3859 | + _hidden: | |
| 3860 | + ret | |
| 3861 | + _visible: | |
| 3862 | + ret | |
| 3863 | + _main: | |
| 3864 | + ret | |
| 3865 | + | |
| 3866 | + .data | |
| 3867 | + .quad _ext_data | |
| 3868 | + .subsections_via_symbols | |
| 3869 | + "#; | |
| 3870 | + if let Err(e) = assemble(asm, &obj) { | |
| 3871 | + eprintln!("skipping: assemble failed: {e}"); | |
| 3872 | + return; | |
| 3873 | + } | |
| 3874 | + | |
| 3875 | + let opts = LinkOptions { | |
| 3876 | + inputs: vec![obj.clone(), dylib.clone()], | |
| 3877 | + output: Some(our_out.clone()), | |
| 3878 | + kind: OutputKind::Executable, | |
| 3879 | + ..LinkOptions::default() | |
| 3880 | + }; | |
| 3881 | + Linker::run(&opts).unwrap(); | |
| 3882 | + | |
| 3883 | + let apple = Command::new("xcrun") | |
| 3884 | + .args(["ld", "-arch", "arm64", "-e", "_main", "-o"]) | |
| 3885 | + .arg(&apple_out) | |
| 3886 | + .arg(&obj) | |
| 3887 | + .arg(&dylib) | |
| 3888 | + .output() | |
| 3889 | + .unwrap(); | |
| 3890 | + assert!( | |
| 3891 | + apple.status.success(), | |
| 3892 | + "xcrun ld failed: {}", | |
| 3893 | + String::from_utf8_lossy(&apple.stderr) | |
| 3894 | + ); | |
| 3895 | + | |
| 3896 | + let our_bytes = fs::read(&our_out).unwrap(); | |
| 3897 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 3898 | + let (our_symtab, our_dysymtab) = symtab_and_dysymtab(&our_bytes); | |
| 3899 | + let (apple_symtab, apple_dysymtab) = symtab_and_dysymtab(&apple_bytes); | |
| 3900 | + | |
| 3901 | + assert_eq!(our_symtab.nsyms, apple_symtab.nsyms); | |
| 3902 | + assert_eq!(our_dysymtab.ilocalsym, apple_dysymtab.ilocalsym); | |
| 3903 | + assert_eq!(our_dysymtab.nlocalsym, apple_dysymtab.nlocalsym); | |
| 3904 | + assert_eq!(our_dysymtab.iextdefsym, apple_dysymtab.iextdefsym); | |
| 3905 | + assert_eq!(our_dysymtab.nextdefsym, apple_dysymtab.nextdefsym); | |
| 3906 | + assert_eq!(our_dysymtab.iundefsym, apple_dysymtab.iundefsym); | |
| 3907 | + assert_eq!(our_dysymtab.nundefsym, apple_dysymtab.nundefsym); | |
| 3908 | + assert_eq!( | |
| 3909 | + canonical_symbol_records(&our_bytes), | |
| 3910 | + canonical_symbol_records(&apple_bytes) | |
| 3911 | + ); | |
| 3912 | + assert_strtab_within_five_percent( | |
| 3913 | + &raw_string_table(&our_bytes), | |
| 3914 | + &raw_string_table(&apple_bytes), | |
| 3915 | + ); | |
| 3916 | + | |
| 3917 | + assert_eq!( | |
| 3918 | + symbol_partition_names(&our_bytes), | |
| 3919 | + symbol_partition_names(&apple_bytes) | |
| 3920 | + ); | |
| 3921 | + | |
| 3922 | + let _ = fs::remove_file(dylib); | |
| 3923 | + let _ = fs::remove_file(obj); | |
| 3924 | + let _ = fs::remove_file(our_out); | |
| 3925 | + let _ = fs::remove_file(apple_out); | |
| 3926 | +} | |
| 3927 | + | |
| 3928 | +#[test] | |
| 3929 | +fn linker_run_strips_locals_with_x_like_ld() { | |
| 3930 | + if !have_xcrun() { | |
| 3931 | + eprintln!("skipping: xcrun unavailable"); | |
| 3932 | + return; | |
| 3933 | + } | |
| 3934 | + | |
| 3935 | + let dylib = scratch("symtab-strip.dylib"); | |
| 3936 | + let obj = scratch("symtab-strip.o"); | |
| 3937 | + let our_out = scratch("symtab-strip-ours.out"); | |
| 3938 | + let apple_out = scratch("symtab-strip-apple.out"); | |
| 3939 | + | |
| 3940 | + let dylib_src = r#" | |
| 3941 | + int ext_data = 5; | |
| 3942 | + "#; | |
| 3943 | + if let Err(e) = compile_dylib_c(dylib_src, &dylib) { | |
| 3944 | + eprintln!("skipping: dylib compile failed: {e}"); | |
| 3945 | + return; | |
| 3946 | + } | |
| 3947 | + | |
| 3948 | + let asm = r#" | |
| 3949 | + .text | |
| 3950 | + .private_extern _hidden | |
| 3951 | + .globl _visible | |
| 3562 | 3952 | .globl _main |
| 3563 | 3953 | .p2align 2 |
| 3564 | 3954 | _local: |
@@ -3676,8 +4066,8 @@ fn linker_run_emits_leaf_unwind_info_like_ld() { | ||
| 3676 | 4066 | let our_bytes = fs::read(&our_out).unwrap(); |
| 3677 | 4067 | let apple_bytes = fs::read(&apple_out).unwrap(); |
| 3678 | 4068 | assert_eq!( |
| 3679 | - normalized_unwind_words(&our_bytes), | |
| 3680 | - normalized_unwind_words(&apple_bytes) | |
| 4069 | + rebased_unwind_bytes(&our_bytes), | |
| 4070 | + rebased_unwind_bytes(&apple_bytes) | |
| 3681 | 4071 | ); |
| 3682 | 4072 | assert!(output_section(&our_bytes, "__LD", "__compact_unwind").is_none()); |
| 3683 | 4073 | |
@@ -3730,8 +4120,8 @@ fn linker_run_emits_multi_function_unwind_info_like_ld() { | ||
| 3730 | 4120 | let our_bytes = fs::read(&our_out).unwrap(); |
| 3731 | 4121 | let apple_bytes = fs::read(&apple_out).unwrap(); |
| 3732 | 4122 | assert_eq!( |
| 3733 | - normalized_unwind_words(&our_bytes), | |
| 3734 | - normalized_unwind_words(&apple_bytes) | |
| 4123 | + rebased_unwind_bytes(&our_bytes), | |
| 4124 | + rebased_unwind_bytes(&apple_bytes) | |
| 3735 | 4125 | ); |
| 3736 | 4126 | assert!(output_section(&our_bytes, "__LD", "__compact_unwind").is_none()); |
| 3737 | 4127 | |
@@ -3740,6 +4130,61 @@ fn linker_run_emits_multi_function_unwind_info_like_ld() { | ||
| 3740 | 4130 | let _ = fs::remove_file(apple_out); |
| 3741 | 4131 | } |
| 3742 | 4132 | |
| 4133 | +#[test] | |
| 4134 | +fn linker_run_handles_large_unwind_function_gaps() { | |
| 4135 | + if !have_xcrun() { | |
| 4136 | + eprintln!("skipping: xcrun unavailable"); | |
| 4137 | + return; | |
| 4138 | + } | |
| 4139 | + | |
| 4140 | + let obj = scratch("unwind-gap.o"); | |
| 4141 | + let out = scratch("unwind-gap-ours.out"); | |
| 4142 | + let asm = r#" | |
| 4143 | + .text | |
| 4144 | + .globl _main | |
| 4145 | + .p2align 2 | |
| 4146 | + _main: | |
| 4147 | + .cfi_startproc | |
| 4148 | + bl _helper | |
| 4149 | + ret | |
| 4150 | + .cfi_endproc | |
| 4151 | + .space 0x1000010 | |
| 4152 | + .globl _helper | |
| 4153 | + .p2align 2 | |
| 4154 | + _helper: | |
| 4155 | + .cfi_startproc | |
| 4156 | + ret | |
| 4157 | + .cfi_endproc | |
| 4158 | + .subsections_via_symbols | |
| 4159 | + "#; | |
| 4160 | + if let Err(e) = assemble(asm, &obj) { | |
| 4161 | + eprintln!("skipping: assemble failed: {e}"); | |
| 4162 | + return; | |
| 4163 | + } | |
| 4164 | + | |
| 4165 | + let opts = LinkOptions { | |
| 4166 | + inputs: vec![obj.clone()], | |
| 4167 | + output: Some(out.clone()), | |
| 4168 | + kind: OutputKind::Executable, | |
| 4169 | + ..LinkOptions::default() | |
| 4170 | + }; | |
| 4171 | + Linker::run(&opts).unwrap(); | |
| 4172 | + | |
| 4173 | + let bytes = fs::read(&out).unwrap(); | |
| 4174 | + let (_, unwind) = output_section(&bytes, "__TEXT", "__unwind_info").unwrap(); | |
| 4175 | + let decoded = decode_unwind_info(&unwind).unwrap(); | |
| 4176 | + assert!( | |
| 4177 | + decoded | |
| 4178 | + .records | |
| 4179 | + .windows(2) | |
| 4180 | + .all(|pair| pair[0].function_offset < pair[1].function_offset), | |
| 4181 | + "expected strictly ascending unwind records after large-gap pagination" | |
| 4182 | + ); | |
| 4183 | + | |
| 4184 | + let _ = fs::remove_file(obj); | |
| 4185 | + let _ = fs::remove_file(out); | |
| 4186 | +} | |
| 4187 | + | |
| 3743 | 4188 | #[test] |
| 3744 | 4189 | fn linker_run_preserves_eh_frame_like_ld() { |
| 3745 | 4190 | if !have_xcrun() || !have_xcrun_tool("dwarfdump") { |
@@ -3826,6 +4271,79 @@ fn linker_run_preserves_eh_frame_like_ld() { | ||
| 3826 | 4271 | let _ = fs::remove_file(apple_out); |
| 3827 | 4272 | } |
| 3828 | 4273 | |
| 4274 | +#[test] | |
| 4275 | +fn linker_run_emits_backtrace_metadata_like_apple_ld() { | |
| 4276 | + if !have_xcrun() { | |
| 4277 | + eprintln!("skipping: xcrun unavailable"); | |
| 4278 | + return; | |
| 4279 | + } | |
| 4280 | + let Some(sdk) = sdk_path() else { | |
| 4281 | + eprintln!("skipping: xcrun --show-sdk-path unavailable"); | |
| 4282 | + return; | |
| 4283 | + }; | |
| 4284 | + let Some(sdk_ver) = sdk_version() else { | |
| 4285 | + eprintln!("skipping: xcrun --show-sdk-version unavailable"); | |
| 4286 | + return; | |
| 4287 | + }; | |
| 4288 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 4289 | + if !tbd.exists() { | |
| 4290 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 4291 | + return; | |
| 4292 | + } | |
| 4293 | + | |
| 4294 | + let obj = scratch("unwind-backtrace.o"); | |
| 4295 | + let our_out = scratch("unwind-backtrace-ours.out"); | |
| 4296 | + let apple_out = scratch("unwind-backtrace-apple.out"); | |
| 4297 | + let src = r#" | |
| 4298 | + #include <unwind.h> | |
| 4299 | + | |
| 4300 | + static _Unwind_Reason_Code cb(struct _Unwind_Context* ctx, void* arg) { | |
| 4301 | + (void)ctx; | |
| 4302 | + int* count = (int*)arg; | |
| 4303 | + (*count)++; | |
| 4304 | + return *count >= 8 ? _URC_END_OF_STACK : _URC_NO_REASON; | |
| 4305 | + } | |
| 4306 | + | |
| 4307 | + __attribute__((noinline)) int helper(void) { | |
| 4308 | + int count = 0; | |
| 4309 | + _Unwind_Backtrace(cb, &count); | |
| 4310 | + return count; | |
| 4311 | + } | |
| 4312 | + | |
| 4313 | + int main(void) { | |
| 4314 | + return helper() > 1 ? 0 : 1; | |
| 4315 | + } | |
| 4316 | + "#; | |
| 4317 | + if let Err(e) = compile_c(src, &obj) { | |
| 4318 | + eprintln!("skipping: clang compile failed: {e}"); | |
| 4319 | + return; | |
| 4320 | + } | |
| 4321 | + | |
| 4322 | + let opts = LinkOptions { | |
| 4323 | + inputs: vec![obj.clone(), tbd], | |
| 4324 | + output: Some(our_out.clone()), | |
| 4325 | + kind: OutputKind::Executable, | |
| 4326 | + ..LinkOptions::default() | |
| 4327 | + }; | |
| 4328 | + Linker::run(&opts).unwrap(); | |
| 4329 | + apple_link_classic_lazy(&obj, &apple_out, "_main", &sdk, &sdk_ver).unwrap(); | |
| 4330 | + | |
| 4331 | + let our_bytes = fs::read(&our_out).unwrap(); | |
| 4332 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 4333 | + assert_eq!( | |
| 4334 | + rebased_unwind_bytes(&our_bytes), | |
| 4335 | + rebased_unwind_bytes(&apple_bytes) | |
| 4336 | + ); | |
| 4337 | + assert_eq!( | |
| 4338 | + normalize_function_start_offsets(&decode_function_starts(&our_bytes)), | |
| 4339 | + normalize_function_start_offsets(&decode_function_starts(&apple_bytes)) | |
| 4340 | + ); | |
| 4341 | + | |
| 4342 | + let _ = fs::remove_file(obj); | |
| 4343 | + let _ = fs::remove_file(our_out); | |
| 4344 | + let _ = fs::remove_file(apple_out); | |
| 4345 | +} | |
| 4346 | + | |
| 3829 | 4347 | #[test] |
| 3830 | 4348 | fn linker_run_preserves_exception_unwind_metadata_like_apple_ld() { |
| 3831 | 4349 | if !have_xcrun() || !have_xcrun_tool("clang++") || !have_tool("codesign") { |
@@ -3837,61 +4355,157 @@ fn linker_run_preserves_exception_unwind_metadata_like_apple_ld() { | ||
| 3837 | 4355 | eprintln!("skipping: xcrun --show-sdk-path unavailable"); |
| 3838 | 4356 | return; |
| 3839 | 4357 | }; |
| 3840 | - let libsystem = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 3841 | - let libcxx = PathBuf::from(format!("{sdk}/usr/lib/libc++.tbd")); | |
| 3842 | - if !libsystem.exists() { | |
| 3843 | - eprintln!("skipping: no libSystem.tbd at {}", libsystem.display()); | |
| 4358 | + let libsystem = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 4359 | + let libcxx = PathBuf::from(format!("{sdk}/usr/lib/libc++.tbd")); | |
| 4360 | + if !libsystem.exists() { | |
| 4361 | + eprintln!("skipping: no libSystem.tbd at {}", libsystem.display()); | |
| 4362 | + return; | |
| 4363 | + } | |
| 4364 | + if !libcxx.exists() { | |
| 4365 | + eprintln!("skipping: no libc++.tbd at {}", libcxx.display()); | |
| 4366 | + return; | |
| 4367 | + } | |
| 4368 | + | |
| 4369 | + let obj = scratch("cxx-exc.o"); | |
| 4370 | + let our_out = scratch("cxx-exc-ours.out"); | |
| 4371 | + let apple_out = scratch("cxx-exc-apple.out"); | |
| 4372 | + let src = r#" | |
| 4373 | + int helper() { throw 7; } | |
| 4374 | + int main() { | |
| 4375 | + try { return helper(); } | |
| 4376 | + catch (...) { return 42; } | |
| 4377 | + } | |
| 4378 | + "#; | |
| 4379 | + if let Err(e) = compile_cxx(src, &obj) { | |
| 4380 | + eprintln!("skipping: clang++ compile failed: {e}"); | |
| 4381 | + return; | |
| 4382 | + } | |
| 4383 | + | |
| 4384 | + let opts = LinkOptions { | |
| 4385 | + inputs: vec![obj.clone(), libcxx.clone(), libsystem.clone()], | |
| 4386 | + output: Some(our_out.clone()), | |
| 4387 | + kind: OutputKind::Executable, | |
| 4388 | + ..LinkOptions::default() | |
| 4389 | + }; | |
| 4390 | + Linker::run(&opts).unwrap(); | |
| 4391 | + apple_link_cxx_classic(&obj, &apple_out).unwrap(); | |
| 4392 | + | |
| 4393 | + let our_bytes = fs::read(&our_out).unwrap(); | |
| 4394 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 4395 | + assert_eq!( | |
| 4396 | + decode_bind_records(&our_bytes, false).unwrap(), | |
| 4397 | + decode_bind_records(&apple_bytes, false).unwrap() | |
| 4398 | + ); | |
| 4399 | + assert_eq!( | |
| 4400 | + decode_bind_records(&our_bytes, true).unwrap(), | |
| 4401 | + decode_bind_records(&apple_bytes, true).unwrap() | |
| 4402 | + ); | |
| 4403 | + assert_eq!( | |
| 4404 | + canonical_lazy_bind_stream(&our_bytes).unwrap(), | |
| 4405 | + canonical_lazy_bind_stream(&apple_bytes).unwrap() | |
| 4406 | + ); | |
| 4407 | + let our_decoded = canonical_unwind_info(&our_bytes); | |
| 4408 | + let apple_decoded = canonical_unwind_info(&apple_bytes); | |
| 4409 | + assert_eq!(our_decoded, apple_decoded); | |
| 4410 | + assert_eq!(our_decoded.personalities.len(), 1); | |
| 4411 | + assert_eq!(our_decoded.lsdas.len(), 1); | |
| 4412 | + assert!(output_section(&our_bytes, "__TEXT", "__gcc_except_tab").is_some()); | |
| 4413 | + let our_status = Command::new(&our_out).status().unwrap(); | |
| 4414 | + let apple_status = Command::new(&apple_out).status().unwrap(); | |
| 4415 | + assert_eq!(our_status.code(), Some(42)); | |
| 4416 | + assert_eq!(apple_status.code(), Some(42)); | |
| 4417 | + | |
| 4418 | + let _ = fs::remove_file(obj); | |
| 4419 | + let _ = fs::remove_file(our_out); | |
| 4420 | + let _ = fs::remove_file(apple_out); | |
| 4421 | +} | |
| 4422 | + | |
| 4423 | +#[test] | |
| 4424 | +fn linker_run_resolves_backtrace_symbols_at_runtime() { | |
| 4425 | + if !have_xcrun() { | |
| 4426 | + eprintln!("skipping: xcrun unavailable"); | |
| 4427 | + return; | |
| 4428 | + } | |
| 4429 | + let Some(sdk) = sdk_path() else { | |
| 4430 | + eprintln!("skipping: xcrun --show-sdk-path unavailable"); | |
| 4431 | + return; | |
| 4432 | + }; | |
| 4433 | + let Some(sdk_ver) = sdk_version() else { | |
| 4434 | + eprintln!("skipping: xcrun --show-sdk-version unavailable"); | |
| 3844 | 4435 | return; |
| 3845 | - } | |
| 3846 | - if !libcxx.exists() { | |
| 3847 | - eprintln!("skipping: no libc++.tbd at {}", libcxx.display()); | |
| 4436 | + }; | |
| 4437 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 4438 | + if !tbd.exists() { | |
| 4439 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 3848 | 4440 | return; |
| 3849 | 4441 | } |
| 3850 | 4442 | |
| 3851 | - let obj = scratch("cxx-exc.o"); | |
| 3852 | - let our_out = scratch("cxx-exc-ours.out"); | |
| 3853 | - let apple_out = scratch("cxx-exc-apple.out"); | |
| 4443 | + let obj = scratch("execinfo-backtrace.o"); | |
| 4444 | + let our_out = scratch("execinfo-backtrace-ours.out"); | |
| 4445 | + let apple_out = scratch("execinfo-backtrace-apple.out"); | |
| 3854 | 4446 | let src = r#" |
| 3855 | - int helper() { throw 7; } | |
| 3856 | - int main() { | |
| 3857 | - try { return helper(); } | |
| 3858 | - catch (...) { return 42; } | |
| 4447 | + #include <execinfo.h> | |
| 4448 | + #include <stdio.h> | |
| 4449 | + #include <stdlib.h> | |
| 4450 | + #include <string.h> | |
| 4451 | + | |
| 4452 | + __attribute__((noinline)) int helper(void) { | |
| 4453 | + void *frames[8]; | |
| 4454 | + int n = backtrace(frames, 8); | |
| 4455 | + char **syms = backtrace_symbols(frames, n); | |
| 4456 | + int saw_helper = 0; | |
| 4457 | + int saw_main = 0; | |
| 4458 | + if (!syms) return 2; | |
| 4459 | + for (int i = 0; i < n; i++) { | |
| 4460 | + puts(syms[i]); | |
| 4461 | + saw_helper |= strstr(syms[i], "helper") != NULL; | |
| 4462 | + saw_main |= strstr(syms[i], "main") != NULL; | |
| 4463 | + } | |
| 4464 | + free(syms); | |
| 4465 | + return (saw_helper && saw_main) ? 0 : 1; | |
| 4466 | + } | |
| 4467 | + | |
| 4468 | + int main(void) { | |
| 4469 | + return helper(); | |
| 3859 | 4470 | } |
| 3860 | 4471 | "#; |
| 3861 | - if let Err(e) = compile_cxx(src, &obj) { | |
| 3862 | - eprintln!("skipping: clang++ compile failed: {e}"); | |
| 4472 | + if let Err(e) = compile_c(src, &obj) { | |
| 4473 | + eprintln!("skipping: clang compile failed: {e}"); | |
| 3863 | 4474 | return; |
| 3864 | 4475 | } |
| 3865 | 4476 | |
| 3866 | 4477 | let opts = LinkOptions { |
| 3867 | - inputs: vec![obj.clone(), libcxx.clone(), libsystem.clone()], | |
| 4478 | + inputs: vec![obj.clone(), tbd], | |
| 3868 | 4479 | output: Some(our_out.clone()), |
| 3869 | 4480 | kind: OutputKind::Executable, |
| 3870 | 4481 | ..LinkOptions::default() |
| 3871 | 4482 | }; |
| 3872 | 4483 | Linker::run(&opts).unwrap(); |
| 3873 | - apple_link_cxx_classic(&obj, &apple_out).unwrap(); | |
| 4484 | + apple_link_classic_lazy(&obj, &apple_out, "_main", &sdk, &sdk_ver).unwrap(); | |
| 3874 | 4485 | |
| 3875 | - let our_bytes = fs::read(&our_out).unwrap(); | |
| 3876 | - let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 3877 | - assert_eq!( | |
| 3878 | - decode_bind_records(&our_bytes, false).unwrap(), | |
| 3879 | - decode_bind_records(&apple_bytes, false).unwrap() | |
| 4486 | + let our_output = Command::new(&our_out).output().unwrap(); | |
| 4487 | + let apple_output = Command::new(&apple_out).output().unwrap(); | |
| 4488 | + let our_stdout = String::from_utf8_lossy(&our_output.stdout); | |
| 4489 | + let apple_stdout = String::from_utf8_lossy(&apple_output.stdout); | |
| 4490 | + | |
| 4491 | + assert_eq!(our_output.status.code(), Some(0)); | |
| 4492 | + assert_eq!(apple_output.status.code(), Some(0)); | |
| 4493 | + assert!( | |
| 4494 | + our_stdout.contains("helper"), | |
| 4495 | + "expected helper in output: {our_stdout}" | |
| 3880 | 4496 | ); |
| 3881 | - assert_eq!( | |
| 3882 | - decode_bind_records(&our_bytes, true).unwrap(), | |
| 3883 | - decode_bind_records(&apple_bytes, true).unwrap() | |
| 4497 | + assert!( | |
| 4498 | + our_stdout.contains("main"), | |
| 4499 | + "expected main in output: {our_stdout}" | |
| 3884 | 4500 | ); |
| 3885 | - assert_eq!( | |
| 3886 | - canonical_lazy_bind_stream(&our_bytes).unwrap(), | |
| 3887 | - canonical_lazy_bind_stream(&apple_bytes).unwrap() | |
| 4501 | + assert!( | |
| 4502 | + apple_stdout.contains("helper"), | |
| 4503 | + "expected helper in apple output: {apple_stdout}" | |
| 4504 | + ); | |
| 4505 | + assert!( | |
| 4506 | + apple_stdout.contains("main"), | |
| 4507 | + "expected main in apple output: {apple_stdout}" | |
| 3888 | 4508 | ); |
| 3889 | - let our_decoded = canonical_unwind_info(&our_bytes); | |
| 3890 | - let apple_decoded = canonical_unwind_info(&apple_bytes); | |
| 3891 | - assert_eq!(our_decoded, apple_decoded); | |
| 3892 | - assert_eq!(our_decoded.personalities.len(), 1); | |
| 3893 | - assert_eq!(our_decoded.lsdas.len(), 1); | |
| 3894 | - assert!(output_section(&our_bytes, "__TEXT", "__gcc_except_tab").is_some()); | |
| 3895 | 4509 | |
| 3896 | 4510 | let _ = fs::remove_file(obj); |
| 3897 | 4511 | let _ = fs::remove_file(our_out); |
@@ -4686,3 +5300,301 @@ fn linker_run_routes_imported_tlv_through_got() { | ||
| 4686 | 5300 | let _ = fs::remove_file(our_out); |
| 4687 | 5301 | let _ = fs::remove_file(apple_out); |
| 4688 | 5302 | } |
| 5303 | + | |
| 5304 | +#[test] | |
| 5305 | +fn linker_run_preserves_runtime_tlv_descriptor_offsets() { | |
| 5306 | + if !have_xcrun() || !have_tool("codesign") { | |
| 5307 | + eprintln!("skipping: xcrun or codesign unavailable"); | |
| 5308 | + return; | |
| 5309 | + } | |
| 5310 | + let Some(runtime) = find_runtime_archive() else { | |
| 5311 | + eprintln!("skipping: libarmfortas_rt.a not built"); | |
| 5312 | + return; | |
| 5313 | + }; | |
| 5314 | + let Some(sdk) = sdk_path() else { | |
| 5315 | + eprintln!("skipping: no macOS SDK path"); | |
| 5316 | + return; | |
| 5317 | + }; | |
| 5318 | + let Some(sdk_ver) = sdk_version() else { | |
| 5319 | + eprintln!("skipping: no macOS SDK version"); | |
| 5320 | + return; | |
| 5321 | + }; | |
| 5322 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 5323 | + if !tbd.exists() { | |
| 5324 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 5325 | + return; | |
| 5326 | + } | |
| 5327 | + | |
| 5328 | + let obj = scratch("runtime-hello.o"); | |
| 5329 | + let out = scratch("runtime-hello.out"); | |
| 5330 | + let apple_out = scratch("runtime-hello-apple.out"); | |
| 5331 | + let src = r#" | |
| 5332 | + extern void afs_program_init(void); | |
| 5333 | + extern void afs_program_finalize(void); | |
| 5334 | + extern void afs_write_string(int, const char *, long); | |
| 5335 | + extern void afs_write_newline(int); | |
| 5336 | + | |
| 5337 | + int main(void) { | |
| 5338 | + afs_program_init(); | |
| 5339 | + afs_write_string(6, "Hello, World!", 13); | |
| 5340 | + afs_write_newline(6); | |
| 5341 | + afs_program_finalize(); | |
| 5342 | + return 0; | |
| 5343 | + } | |
| 5344 | + "#; | |
| 5345 | + if let Err(e) = compile_c(src, &obj) { | |
| 5346 | + eprintln!("skipping: compile failed: {e}"); | |
| 5347 | + return; | |
| 5348 | + } | |
| 5349 | + | |
| 5350 | + let opts = LinkOptions { | |
| 5351 | + inputs: vec![obj.clone(), runtime.clone(), tbd], | |
| 5352 | + output: Some(out.clone()), | |
| 5353 | + kind: OutputKind::Executable, | |
| 5354 | + ..LinkOptions::default() | |
| 5355 | + }; | |
| 5356 | + Linker::run(&opts).unwrap(); | |
| 5357 | + | |
| 5358 | + let apple = Command::new("xcrun") | |
| 5359 | + .args([ | |
| 5360 | + "ld", | |
| 5361 | + "-arch", | |
| 5362 | + "arm64", | |
| 5363 | + "-platform_version", | |
| 5364 | + "macos", | |
| 5365 | + &sdk_ver, | |
| 5366 | + &sdk_ver, | |
| 5367 | + "-syslibroot", | |
| 5368 | + &sdk, | |
| 5369 | + "-lSystem", | |
| 5370 | + "-e", | |
| 5371 | + "_main", | |
| 5372 | + "-no_fixup_chains", | |
| 5373 | + "-o", | |
| 5374 | + ]) | |
| 5375 | + .arg(&apple_out) | |
| 5376 | + .arg(&obj) | |
| 5377 | + .arg(&runtime) | |
| 5378 | + .output() | |
| 5379 | + .unwrap(); | |
| 5380 | + assert!( | |
| 5381 | + apple.status.success(), | |
| 5382 | + "xcrun ld failed: {}", | |
| 5383 | + String::from_utf8_lossy(&apple.stderr) | |
| 5384 | + ); | |
| 5385 | + | |
| 5386 | + let verify = Command::new("codesign") | |
| 5387 | + .arg("-v") | |
| 5388 | + .arg(&out) | |
| 5389 | + .output() | |
| 5390 | + .unwrap(); | |
| 5391 | + assert!( | |
| 5392 | + verify.status.success(), | |
| 5393 | + "codesign verify failed: {}", | |
| 5394 | + String::from_utf8_lossy(&verify.stderr) | |
| 5395 | + ); | |
| 5396 | + | |
| 5397 | + let bytes = fs::read(&out).unwrap(); | |
| 5398 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 5399 | + assert!( | |
| 5400 | + output_section(&bytes, "__DATA_CONST", "__const").is_some(), | |
| 5401 | + "runtime hello should promote file-backed __const data into __DATA_CONST" | |
| 5402 | + ); | |
| 5403 | + assert!( | |
| 5404 | + output_section(&bytes, "__DATA", "__const").is_none(), | |
| 5405 | + "runtime hello should not leave file-backed __const data in __DATA" | |
| 5406 | + ); | |
| 5407 | + let (thread_vars_addr, thread_vars) = | |
| 5408 | + output_section(&bytes, "__DATA", "__thread_vars").unwrap(); | |
| 5409 | + let (thread_data_addr, _) = output_section(&bytes, "__DATA", "__thread_data").unwrap(); | |
| 5410 | + let symbols = symbol_values(&bytes); | |
| 5411 | + let tlv_binds: Vec<_> = decode_bind_records(&bytes, false) | |
| 5412 | + .unwrap() | |
| 5413 | + .into_iter() | |
| 5414 | + .filter(|record| record.section == "__thread_vars") | |
| 5415 | + .collect(); | |
| 5416 | + assert_eq!( | |
| 5417 | + tlv_binds.len(), | |
| 5418 | + thread_vars.len() / 24, | |
| 5419 | + "every TLV descriptor should carry exactly one bootstrap bind" | |
| 5420 | + ); | |
| 5421 | + assert!(tlv_binds | |
| 5422 | + .iter() | |
| 5423 | + .all(|record| record.symbol == "__tlv_bootstrap")); | |
| 5424 | + | |
| 5425 | + assert_eq!( | |
| 5426 | + decode_bind_records(&bytes, false).unwrap(), | |
| 5427 | + decode_bind_records(&apple_bytes, false).unwrap(), | |
| 5428 | + "runtime hello bind records diverged from Apple ld" | |
| 5429 | + ); | |
| 5430 | + assert_eq!( | |
| 5431 | + decode_bind_records(&bytes, true).unwrap(), | |
| 5432 | + decode_bind_records(&apple_bytes, true).unwrap(), | |
| 5433 | + "runtime hello lazy-bind records diverged from Apple ld" | |
| 5434 | + ); | |
| 5435 | + assert_eq!( | |
| 5436 | + decode_rebase_records(&bytes).unwrap(), | |
| 5437 | + decode_rebase_records(&apple_bytes).unwrap(), | |
| 5438 | + "runtime hello rebase records diverged from Apple ld" | |
| 5439 | + ); | |
| 5440 | + assert_eq!( | |
| 5441 | + indirect_symbol_identities(&bytes), | |
| 5442 | + indirect_symbol_identities(&apple_bytes), | |
| 5443 | + "runtime hello indirect symbol identities diverged from Apple ld" | |
| 5444 | + ); | |
| 5445 | + | |
| 5446 | + for (name, descriptor_addr) in symbols.iter().filter(|(name, value)| { | |
| 5447 | + !name.ends_with("$tlv$init") | |
| 5448 | + && **value >= thread_vars_addr | |
| 5449 | + && **value < thread_vars_addr + thread_vars.len() as u64 | |
| 5450 | + }) { | |
| 5451 | + let init_name = format!("{name}$tlv$init"); | |
| 5452 | + let Some(init_addr) = symbols.get(&init_name) else { | |
| 5453 | + continue; | |
| 5454 | + }; | |
| 5455 | + let offset = (*descriptor_addr - thread_vars_addr) as usize; | |
| 5456 | + let actual = u64::from_le_bytes(thread_vars[offset + 16..offset + 24].try_into().unwrap()); | |
| 5457 | + let expected = init_addr - thread_data_addr; | |
| 5458 | + assert_eq!( | |
| 5459 | + actual, expected, | |
| 5460 | + "TLV descriptor {} should point at {} via template offset", | |
| 5461 | + name, init_name | |
| 5462 | + ); | |
| 5463 | + } | |
| 5464 | + | |
| 5465 | + let output = Command::new(&out).output().unwrap(); | |
| 5466 | + let apple_output = Command::new(&apple_out).output().unwrap(); | |
| 5467 | + assert_eq!( | |
| 5468 | + output.status.code(), | |
| 5469 | + Some(0), | |
| 5470 | + "expected runtime hello executable to exit 0, stderr={}", | |
| 5471 | + String::from_utf8_lossy(&output.stderr) | |
| 5472 | + ); | |
| 5473 | + assert_eq!( | |
| 5474 | + apple_output.status.code(), | |
| 5475 | + Some(0), | |
| 5476 | + "expected Apple-linked runtime hello executable to exit 0, stderr={}", | |
| 5477 | + String::from_utf8_lossy(&apple_output.stderr) | |
| 5478 | + ); | |
| 5479 | + assert_eq!( | |
| 5480 | + String::from_utf8_lossy(&output.stdout), | |
| 5481 | + String::from_utf8_lossy(&apple_output.stdout) | |
| 5482 | + ); | |
| 5483 | + | |
| 5484 | + let _ = fs::remove_file(obj); | |
| 5485 | + let _ = fs::remove_file(out); | |
| 5486 | + let _ = fs::remove_file(apple_out); | |
| 5487 | +} | |
| 5488 | + | |
| 5489 | +#[test] | |
| 5490 | +fn linker_run_rebases_runtime_init_metadata_like_apple_ld() { | |
| 5491 | + if !have_xcrun() || !have_tool("codesign") { | |
| 5492 | + eprintln!("skipping: xcrun or codesign unavailable"); | |
| 5493 | + return; | |
| 5494 | + } | |
| 5495 | + let Some(runtime) = find_runtime_archive() else { | |
| 5496 | + eprintln!("skipping: libarmfortas_rt.a not built"); | |
| 5497 | + return; | |
| 5498 | + }; | |
| 5499 | + let Some(sdk) = sdk_path() else { | |
| 5500 | + eprintln!("skipping: no macOS SDK path"); | |
| 5501 | + return; | |
| 5502 | + }; | |
| 5503 | + let Some(sdk_ver) = sdk_version() else { | |
| 5504 | + eprintln!("skipping: no macOS SDK version"); | |
| 5505 | + return; | |
| 5506 | + }; | |
| 5507 | + let tbd = PathBuf::from(format!("{sdk}/usr/lib/libSystem.tbd")); | |
| 5508 | + if !tbd.exists() { | |
| 5509 | + eprintln!("skipping: no libSystem.tbd at {}", tbd.display()); | |
| 5510 | + return; | |
| 5511 | + } | |
| 5512 | + | |
| 5513 | + let obj = scratch("runtime-init-only.o"); | |
| 5514 | + let our_out = scratch("runtime-init-only-ours.out"); | |
| 5515 | + let apple_out = scratch("runtime-init-only-apple.out"); | |
| 5516 | + let src = r#" | |
| 5517 | + extern void afs_program_init(void); | |
| 5518 | + | |
| 5519 | + int main(void) { | |
| 5520 | + afs_program_init(); | |
| 5521 | + return 0; | |
| 5522 | + } | |
| 5523 | + "#; | |
| 5524 | + if let Err(e) = compile_c(src, &obj) { | |
| 5525 | + eprintln!("skipping: compile failed: {e}"); | |
| 5526 | + return; | |
| 5527 | + } | |
| 5528 | + | |
| 5529 | + let opts = LinkOptions { | |
| 5530 | + inputs: vec![obj.clone(), runtime.clone(), tbd], | |
| 5531 | + output: Some(our_out.clone()), | |
| 5532 | + kind: OutputKind::Executable, | |
| 5533 | + ..LinkOptions::default() | |
| 5534 | + }; | |
| 5535 | + Linker::run(&opts).unwrap(); | |
| 5536 | + | |
| 5537 | + let apple = Command::new("xcrun") | |
| 5538 | + .args([ | |
| 5539 | + "ld", | |
| 5540 | + "-arch", | |
| 5541 | + "arm64", | |
| 5542 | + "-platform_version", | |
| 5543 | + "macos", | |
| 5544 | + &sdk_ver, | |
| 5545 | + &sdk_ver, | |
| 5546 | + "-syslibroot", | |
| 5547 | + &sdk, | |
| 5548 | + "-lSystem", | |
| 5549 | + "-e", | |
| 5550 | + "_main", | |
| 5551 | + "-no_fixup_chains", | |
| 5552 | + "-o", | |
| 5553 | + ]) | |
| 5554 | + .arg(&apple_out) | |
| 5555 | + .arg(&obj) | |
| 5556 | + .arg(&runtime) | |
| 5557 | + .output() | |
| 5558 | + .unwrap(); | |
| 5559 | + assert!( | |
| 5560 | + apple.status.success(), | |
| 5561 | + "xcrun ld failed: {}", | |
| 5562 | + String::from_utf8_lossy(&apple.stderr) | |
| 5563 | + ); | |
| 5564 | + | |
| 5565 | + let our_bytes = fs::read(&our_out).unwrap(); | |
| 5566 | + let apple_bytes = fs::read(&apple_out).unwrap(); | |
| 5567 | + let our_rebases = decode_rebase_records(&our_bytes).unwrap(); | |
| 5568 | + let apple_rebases = decode_rebase_records(&apple_bytes).unwrap(); | |
| 5569 | + assert_eq!( | |
| 5570 | + our_rebases | |
| 5571 | + .iter() | |
| 5572 | + .filter(|record| record.section == "__const") | |
| 5573 | + .count(), | |
| 5574 | + apple_rebases | |
| 5575 | + .iter() | |
| 5576 | + .filter(|record| record.section == "__const") | |
| 5577 | + .count(), | |
| 5578 | + "runtime init const rebases diverged from Apple ld" | |
| 5579 | + ); | |
| 5580 | + assert_eq!( | |
| 5581 | + our_rebases | |
| 5582 | + .iter() | |
| 5583 | + .filter(|record| record.section == "__la_symbol_ptr") | |
| 5584 | + .count(), | |
| 5585 | + apple_rebases | |
| 5586 | + .iter() | |
| 5587 | + .filter(|record| record.section == "__la_symbol_ptr") | |
| 5588 | + .count(), | |
| 5589 | + "runtime init lazy-pointer rebases diverged from Apple ld" | |
| 5590 | + ); | |
| 5591 | + | |
| 5592 | + let our_status = Command::new(&our_out).status().unwrap(); | |
| 5593 | + let apple_status = Command::new(&apple_out).status().unwrap(); | |
| 5594 | + assert_eq!(our_status.code(), Some(0)); | |
| 5595 | + assert_eq!(apple_status.code(), Some(0)); | |
| 5596 | + | |
| 5597 | + let _ = fs::remove_file(obj); | |
| 5598 | + let _ = fs::remove_file(our_out); | |
| 5599 | + let _ = fs::remove_file(apple_out); | |
| 5600 | +} | |
tests/resolve_integration.rsmodified@@ -146,13 +146,13 @@ fn resolve_pipeline_pulls_archive_member_and_flags_missing() { | ||
| 146 | 146 | // Register the inputs with the resolver. |
| 147 | 147 | let mut inputs = Inputs::new(); |
| 148 | 148 | let a_id = inputs |
| 149 | - .add_object(a_o.clone(), fs::read(&a_o).unwrap()) | |
| 149 | + .add_object(a_o.clone(), fs::read(&a_o).unwrap(), 0) | |
| 150 | 150 | .unwrap(); |
| 151 | 151 | inputs |
| 152 | - .add_object(b_o.clone(), fs::read(&b_o).unwrap()) | |
| 152 | + .add_object(b_o.clone(), fs::read(&b_o).unwrap(), 1) | |
| 153 | 153 | .unwrap(); |
| 154 | 154 | let archive_id = inputs |
| 155 | - .add_archive(libtest.clone(), fs::read(&libtest).unwrap()) | |
| 155 | + .add_archive(libtest.clone(), fs::read(&libtest).unwrap(), 2) | |
| 156 | 156 | .unwrap(); |
| 157 | 157 | let _ = archive_id; |
| 158 | 158 | |
tests/tbd_integration.rsmodified@@ -57,6 +57,8 @@ fn libsystem_tbd_materializes_into_dylib_file() { | ||
| 57 | 57 | let dy = DylibFile::from_tbd(&path, main, &target); |
| 58 | 58 | |
| 59 | 59 | assert_eq!(dy.install_name, "/usr/lib/libSystem.B.dylib"); |
| 60 | + assert!(dy.current_version >= (1 << 16)); | |
| 61 | + assert_eq!(dy.compatibility_version, 1 << 16); | |
| 60 | 62 | |
| 61 | 63 | // libSystem's main TBD doc only exposes a short list of internal |
| 62 | 64 | // symbols directly; everything useful (malloc, printf, dyld binder) |