From 691172272eb9a6c71caf8b3431a884f01b54ebc3 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Sat, 4 Apr 2026 06:57:56 +0100 Subject: [PATCH 01/21] feat: hello world mac Signed-off-by: Giles Cope --- libwild/src/args/macho.rs | 130 ++++- libwild/src/layout.rs | 2 +- libwild/src/lib.rs | 1 + libwild/src/macho.rs | 948 +++++++++++++++++++++-------------- libwild/src/macho_aarch64.rs | 203 ++++++-- libwild/src/macho_writer.rs | 363 ++++++++++++++ linker-utils/src/elf.rs | 3 +- 7 files changed, 1210 insertions(+), 440 deletions(-) create mode 100644 libwild/src/macho_writer.rs diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index fe95a1f30..de20a48a4 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -1,27 +1,25 @@ -// TODO +// Mach-O argument parsing for the macOS linker driver interface. #![allow(unused_variables)] -#![allow(unused)] use crate::args::ArgumentParser; use crate::args::CommonArgs; -use crate::args::FILES_PER_GROUP_ENV; +use crate::args::Input; +use crate::args::InputSpec; use crate::args::Modifiers; -use crate::args::REFERENCE_LINKER_ENV; use crate::args::RelocationModel; -use crate::ensure; use crate::error::Result; use crate::platform; -use crate::save_dir::SaveDir; -use jobserver::Client; use std::path::Path; use std::sync::Arc; #[derive(Debug)] pub struct MachOArgs { pub(crate) common: super::CommonArgs, - pub(crate) output: Arc, pub(crate) relocation_model: RelocationModel, + pub(crate) lib_search_paths: Vec>, + pub(crate) syslibroot: Option>, + pub(crate) entry_symbol: Option>, } impl MachOArgs { @@ -37,10 +35,11 @@ impl Default for MachOArgs { fn default() -> Self { Self { common: CommonArgs::default(), - - // TODO: move to CommonArgs relocation_model: RelocationModel::NonRelocatable, output: Arc::from(Path::new("a.out")), + lib_search_paths: Vec::new(), + syslibroot: None, + entry_symbol: Some(b"_main".to_vec()), } } } @@ -55,19 +54,21 @@ impl platform::Args for MachOArgs { } fn should_strip_debug(&self) -> bool { - todo!() + false } fn should_strip_all(&self) -> bool { - todo!() + false } fn entry_symbol_name<'a>(&'a self, linker_script_entry: Option<&'a [u8]>) -> &'a [u8] { - todo!() + linker_script_entry + .or(self.entry_symbol.as_deref()) + .unwrap_or(b"_main") } fn lib_search_path(&self) -> &[Box] { - todo!() + &self.lib_search_paths } fn output(&self) -> &std::sync::Arc { @@ -83,19 +84,20 @@ impl platform::Args for MachOArgs { } fn should_export_all_dynamic_symbols(&self) -> bool { - todo!() + false } - fn should_export_dynamic(&self, lib_name: &[u8]) -> bool { - todo!() + fn should_export_dynamic(&self, _lib_name: &[u8]) -> bool { + false } fn loadable_segment_alignment(&self) -> crate::alignment::Alignment { - todo!() + // Apple Silicon uses 16KB pages + crate::alignment::Alignment { exponent: 14 } } fn should_merge_sections(&self) -> bool { - todo!() + false } fn relocation_model(&self) -> crate::args::RelocationModel { @@ -103,12 +105,11 @@ impl platform::Args for MachOArgs { } fn should_output_executable(&self) -> bool { - // TODO true } } -// Parse the supplied input arguments, which should not include the program name. +/// Parse the supplied input arguments, which should not include the program name. pub(crate) fn parse, I: Iterator>( args: &mut MachOArgs, mut input: I, @@ -118,13 +119,30 @@ pub(crate) fn parse, I: Iterator>( let arg_parser = setup_argument_parser(); while let Some(arg) = input.next() { let arg = arg.as_ref(); - arg_parser.handle_argument(args, &mut modifier_stack, arg, &mut input)?; } Ok(()) } +/// Flags that macOS ld passes but we safely ignore for now. +const MACHO_IGNORED_FLAGS: &[&str] = &[ + "demangle", + "dynamic", + "lto_library", + "mllvm", + "no_deduplicate", + "no_compact_unwind", + "dead_strip", + "dead_strip_dylibs", + "headerpad_max_install_names", + "export_dynamic", + "application_extension", + "no_objc_category_merging", + "objc_abi_version", + "mark_dead_strippable_dylib", +]; + fn setup_argument_parser() -> ArgumentParser { let mut parser = ArgumentParser::::new(); @@ -138,5 +156,73 @@ fn setup_argument_parser() -> ArgumentParser { Ok(()) }); + parser + .declare_with_param() + .long("arch") + .help("Architecture") + .execute(|_args, _modifier_stack, _value| { + // We only support arm64 currently, ignore the flag + Ok(()) + }); + + parser + .declare_with_param() + .long("platform_version") + .help("Set platform version (takes 3 args: platform min_version sdk_version)") + .execute(|_args, _modifier_stack, _value| { + // platform_version takes 3 arguments: platform, min_version, sdk_version + // The ArgumentParser already consumed one arg for us, but we need 2 more. + // They'll get treated as unrecognised positional args. That's OK for now. + Ok(()) + }); + + parser + .declare_with_param() + .long("syslibroot") + .help("Set the system library root path") + .execute(|args, _modifier_stack, value| { + args.syslibroot = Some(Box::from(Path::new(value))); + Ok(()) + }); + + parser + .declare_with_param() + .short("e") + .help("Set the entry point symbol name") + .execute(|args, _modifier_stack, value| { + args.entry_symbol = Some(value.as_bytes().to_vec()); + Ok(()) + }); + + parser + .declare_with_param() + .prefix("l") + .help("Link with library") + .execute(|args, modifier_stack, value| { + let spec = InputSpec::Lib(Box::from(value)); + args.common.inputs.push(Input { + spec, + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + Ok(()) + }); + + parser + .declare_with_param() + .prefix("L") + .help("Add library search path") + .execute(|args, _modifier_stack, value| { + args.lib_search_paths.push(Box::from(Path::new(value))); + Ok(()) + }); + + // Register ignored flags + for flag in MACHO_IGNORED_FLAGS { + // Try to register flags that take no params as ignored + // Some take params (like lto_library, mllvm) -- we handle those by just + // letting them fall through to unrecognised options for now + } + parser } diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs index ebb3a9afe..08271031f 100644 --- a/libwild/src/layout.rs +++ b/libwild/src/layout.rs @@ -2984,7 +2984,7 @@ impl<'data, P: Platform> PreludeLayoutState<'data, P> { } } - if !resources.symbol_db.args.should_output_partial_object() { + if !resources.symbol_db.args.should_output_partial_object() && !keep_segments.is_empty() { // Always keep the program headers segment even though we don't emit any sections in it. keep_segments[0] = true; } diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index dcf3c3bd4..a8acdffac 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -31,6 +31,7 @@ mod linker_plugins; pub(crate) mod linker_script; pub(crate) mod macho; pub(crate) mod macho_aarch64; +pub(crate) mod macho_writer; pub(crate) mod output_kind; pub(crate) mod output_section_id; pub(crate) mod output_section_map; diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 0bca13fdd..70f61678b 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -1,28 +1,31 @@ -// TODO -#![allow(unused_variables)] -#![allow(unused)] +// Mach-O platform support for wild linker. +#![allow(unused_variables, dead_code)] use crate::OutputKind; use crate::args::macho::MachOArgs; use crate::ensure; +use crate::error; use crate::platform; -use object::Endian; use object::Endianness; use object::macho; -use object::macho::Section64; use object::read::macho::MachHeader; use object::read::macho::Nlist; -use object::read::macho::Section; -use object::read::macho::Segment; +use object::read::macho::Section as MachOSectionTrait; +use object::read::macho::Segment as MachOSegmentTrait; #[derive(Debug, Copy, Clone)] pub(crate) struct MachO; const LE: Endianness = Endianness::Little; -type SectionTable<'data> = &'data [Section64]; +type SectionTable<'data> = &'data [macho::Section64]; type SymbolTable<'data> = object::read::macho::SymbolTable<'data, macho::MachHeader64>; -type SymtabEntry = object::macho::Nlist64; +pub(crate) type SymtabEntry = macho::Nlist64; + +/// Wraps a Mach-O Section64 so we can implement platform traits on it. +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub(crate) struct SectionHeader(pub(crate) macho::Section64); #[derive(derive_more::Debug)] pub(crate) struct File<'data> { @@ -39,7 +42,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { type Platform = MachO; fn parse_bytes(input: &'data [u8], is_dynamic: bool) -> crate::error::Result { - let header = macho::MachHeader64::::parse(input, 0)?; + let header = macho::MachHeader64::::parse(input, 0)?; let mut commands = header.load_commands(LE, input, 0)?; let mut symbols = None; @@ -50,13 +53,9 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { ensure!(symbols.is_none(), "At most one symtab command expected"); symbols = Some(symtab_command.symbols::, _>(LE, input)?); } else if let Some((segment_command, segment_data)) = command.segment_64()? { - ensure!(sections.is_none(), "At most one segment command expected"); - let section_list = segment_command.sections(LE, segment_data)?; - sections = Some(section_list); - for section in section_list { - for r in section.relocations(LE, input)? { - dbg!(r.info(LE)); - } + // Mach-O object files have a single unnamed segment containing all sections. + if sections.is_none() { + sections = Some(segment_command.sections(LE, segment_data)?); } } } @@ -64,7 +63,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { Ok(File { data: input, symbols: symbols.ok_or("Missing symbol table")?, - sections: sections.ok_or("Missing segment command")?, + sections: sections.unwrap_or(&[]), flags: header.flags(LE), }) } @@ -73,12 +72,10 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { input: &crate::input_data::InputBytes<'data>, args: &::Args, ) -> crate::error::Result { - // TODO Self::parse_bytes(input.data, false) } fn is_dynamic(&self) -> bool { - // TODO false } @@ -87,41 +84,36 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } fn symbols_iter(&self) -> impl Iterator { - for s in self.symbols.iter() { - let name = s.name(LE, self.symbols.strings()).unwrap(); - dbg!(String::from_utf8_lossy(name)); - } - self.symbols.iter() } fn symbol( &self, index: object::SymbolIndex, - ) -> crate::error::Result<&'data ::SymtabEntry> { - todo!() + ) -> crate::error::Result<&'data SymtabEntry> { + self.symbols + .symbol(index) + .map_err(|e| error!("Symbol index {} out of range: {e}", index.0)) } - fn section_size( - &self, - header: &::SectionHeader, - ) -> crate::error::Result { - todo!() + fn section_size(&self, header: &SectionHeader) -> crate::error::Result { + Ok(header.0.size(LE)) } - fn symbol_name( - &self, - symbol: &::SymtabEntry, - ) -> crate::error::Result<&'data [u8]> { - todo!() + fn symbol_name(&self, symbol: &SymtabEntry) -> crate::error::Result<&'data [u8]> { + symbol + .name(LE, self.symbols.strings()) + .map_err(|e| error!("Failed to read symbol name: {e}")) } fn num_sections(&self) -> usize { - todo!() + self.sections.len() } - fn section_iter(&self) -> ::SectionIterator<'data> { - [].iter() + fn section_iter(&self) -> ::SectionIterator<'data> { + MachOSectionIter { + inner: self.sections.iter(), + } } fn enumerate_sections( @@ -129,398 +121,514 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { ) -> impl Iterator< Item = ( object::SectionIndex, - &'data ::SectionHeader, + &'data SectionHeader, ), > { - [].iter() + self.sections + .iter() .enumerate() - .map(|(i, section)| (object::SectionIndex(i), section)) + .map(|(i, section)| { + // Safety: SectionHeader is #[repr(transparent)] over Section64 + let header: &'data SectionHeader = + unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }; + (object::SectionIndex(i), header) + }) } fn section( &self, index: object::SectionIndex, - ) -> crate::error::Result<&'data ::SectionHeader> { - todo!() + ) -> crate::error::Result<&'data SectionHeader> { + let section = self.sections.get(index.0).ok_or_else(|| { + error!("Section index {} out of range", index.0) + })?; + Ok(unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }) } fn section_by_name( &self, name: &str, - ) -> Option<( - object::SectionIndex, - &'data ::SectionHeader, - )> { - todo!() + ) -> Option<(object::SectionIndex, &'data SectionHeader)> { + for (i, section) in self.sections.iter().enumerate() { + let sectname = trim_nul(section.sectname()); + if sectname == name.as_bytes() { + let header: &'data SectionHeader = + unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }; + return Some((object::SectionIndex(i), header)); + } + } + None } fn symbol_section( &self, - symbol: &::SymtabEntry, + symbol: &SymtabEntry, index: object::SymbolIndex, ) -> crate::error::Result> { - todo!() + let n_type = symbol.n_type() & macho::N_TYPE; + if n_type == macho::N_SECT { + // n_sect is 1-based in Mach-O + let sect = symbol.n_sect(); + if sect == 0 { + return Ok(None); + } + Ok(Some(object::SectionIndex(sect as usize - 1))) + } else { + Ok(None) + } } - fn symbol_versions(&self) -> &[::SymbolVersionIndex] { - todo!() + fn symbol_versions(&self) -> &[()]{ + // Mach-O doesn't have symbol versioning + &[] } fn dynamic_symbol_used( &self, - symbol_index: object::SymbolIndex, - state: &mut ::DynamicLayoutStateExt<'data>, + _symbol_index: object::SymbolIndex, + _state: &mut (), ) -> crate::error::Result { - todo!() + Ok(()) } fn finalise_sizes_dynamic( &self, - lib_name: &[u8], - state: &mut ::DynamicLayoutStateExt<'data>, - mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _lib_name: &[u8], + _state: &mut (), + _mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, ) -> crate::error::Result { - todo!() + Ok(()) } fn apply_non_addressable_indexes_dynamic( &self, - indexes: &mut ::NonAddressableIndexes, - counts: &mut ::NonAddressableCounts, - state: &mut ::DynamicLayoutStateExt<'data>, + _indexes: &mut NonAddressableIndexes, + _counts: &mut (), + _state: &mut (), ) -> crate::error::Result { - todo!() - } - - fn section_name( - &self, - section_header: &::SectionHeader, - ) -> crate::error::Result<&'data [u8]> { - todo!() + Ok(()) + } + + fn section_name(&self, section_header: &SectionHeader) -> crate::error::Result<&'data [u8]> { + // Section names in Mach-O are stored inline in the section header (16 bytes). + // We need to find this section in self.sections to get the 'data lifetime. + for s in self.sections { + if std::ptr::eq( + s as *const macho::Section64, + §ion_header.0 as *const macho::Section64, + ) { + return Ok(trim_nul(s.sectname())); + } + } + Err(error!("Section header not found in file's section table")) } - fn raw_section_data( - &self, - section: &::SectionHeader, - ) -> crate::error::Result<&'data [u8]> { - todo!() + fn raw_section_data(&self, section: &SectionHeader) -> crate::error::Result<&'data [u8]> { + let offset = section.0.offset(LE) as usize; + let size = section.0.size(LE) as usize; + if size == 0 { + return Ok(&[]); + } + self.data + .get(offset..offset + size) + .ok_or_else(|| error!("Section data out of range")) } fn section_data( &self, - section: &::SectionHeader, - member: &bumpalo_herd::Member<'data>, - loaded_metrics: &crate::resolution::LoadedMetrics, + section: &SectionHeader, + _member: &bumpalo_herd::Member<'data>, + _loaded_metrics: &crate::resolution::LoadedMetrics, ) -> crate::error::Result<&'data [u8]> { - todo!() + // Mach-O sections are never compressed + self.raw_section_data(section) } - fn copy_section_data( - &self, - section: &::SectionHeader, - out: &mut [u8], - ) -> crate::error::Result { - todo!() + fn copy_section_data(&self, section: &SectionHeader, out: &mut [u8]) -> crate::error::Result { + let data = self.raw_section_data(section)?; + out[..data.len()].copy_from_slice(data); + Ok(()) } fn section_data_cow( &self, - section: &::SectionHeader, + section: &SectionHeader, ) -> crate::error::Result> { - todo!() + Ok(std::borrow::Cow::Borrowed(self.raw_section_data(section)?)) } - fn section_alignment( - &self, - section: &::SectionHeader, - ) -> crate::error::Result { - todo!() + fn section_alignment(&self, section: &SectionHeader) -> crate::error::Result { + // Mach-O stores alignment as a power of 2 + Ok(1u64 << section.0.align(LE)) } fn relocations( &self, index: object::SectionIndex, - relocations: &::RelocationSections, - ) -> crate::error::Result<::RelocationList<'data>> { - todo!() + _relocations: &(), + ) -> crate::error::Result> { + let section = self.sections.get(index.0).ok_or_else(|| { + error!("Section index {} out of range for relocations", index.0) + })?; + let relocs = section + .relocations(LE, self.data) + .map_err(|e| error!("Failed to read relocations: {e}"))?; + Ok(RelocationList { relocations: relocs }) } - fn parse_relocations( - &self, - ) -> crate::error::Result<::RelocationSections> { - todo!() + fn parse_relocations(&self) -> crate::error::Result<()> { + // Mach-O relocations are stored per-section, accessed via `relocations` method + Ok(()) } - fn symbol_version_debug(&self, symbol_index: object::SymbolIndex) -> Option { - todo!() + fn symbol_version_debug(&self, _symbol_index: object::SymbolIndex) -> Option { + None } fn section_display_name(&self, index: object::SectionIndex) -> std::borrow::Cow<'data, str> { - todo!() + if let Some(section) = self.sections.get(index.0) { + let segname = String::from_utf8_lossy(trim_nul(section.segname())); + let sectname = String::from_utf8_lossy(trim_nul(section.sectname())); + std::borrow::Cow::Owned(format!("{segname},{sectname}")) + } else { + std::borrow::Cow::Borrowed("") + } } - fn dynamic_tag_values( - &self, - ) -> Option<::DynamicTagValues<'data>> { - todo!() + fn dynamic_tag_values(&self) -> Option> { + None } - fn get_version_names( - &self, - ) -> crate::error::Result<::VersionNames<'data>> { - todo!() + fn get_version_names(&self) -> crate::error::Result<()> { + Ok(()) } fn get_symbol_name_and_version( &self, - symbol: &::SymtabEntry, - local_index: usize, - version_names: &::VersionNames<'data>, - ) -> crate::error::Result<::RawSymbolName<'data>> { - todo!() + symbol: &SymtabEntry, + _local_index: usize, + _version_names: &(), + ) -> crate::error::Result> { + let name = symbol + .name(LE, self.symbols.strings()) + .map_err(|e| error!("Failed to read symbol name: {e}"))?; + Ok(RawSymbolName { name }) } fn should_enforce_undefined( &self, - resources: &crate::layout::GraphResources<'data, '_, Self::Platform>, + _resources: &crate::layout::GraphResources<'data, '_, MachO>, ) -> bool { - todo!() + false } - fn verneed_table( - &self, - ) -> crate::error::Result<::VerneedTable<'data>> { - todo!() + fn verneed_table(&self) -> crate::error::Result> { + Ok(VerneedTable { + _phantom: &[], + }) } fn process_gnu_note_section( &self, - state: &mut ::ObjectLayoutStateExt<'data>, - section_index: object::SectionIndex, + _state: &mut (), + _section_index: object::SectionIndex, ) -> crate::error::Result { - todo!() + Ok(()) } - fn dynamic_tags( - &self, - ) -> crate::error::Result<&'data [::DynamicEntry]> { - todo!() + fn dynamic_tags(&self) -> crate::error::Result<&'data [()]> { + Ok(&[]) } } -#[derive(Debug)] -pub(crate) struct SectionHeader {} +// -- SectionHeader trait impls -- impl platform::SectionHeader for SectionHeader { fn is_alloc(&self) -> bool { - todo!() + // In Mach-O, all sections in loadable segments are "allocated" + true } fn is_writable(&self) -> bool { - todo!() + // Check segment name: __DATA and __DATA_CONST segments are writable + let segname = trim_nul(self.0.segname()); + segname.starts_with(b"__DATA") } fn is_executable(&self) -> bool { - todo!() + let flags = self.0.flags(LE); + (flags & macho::S_ATTR_PURE_INSTRUCTIONS) != 0 + || (flags & macho::S_ATTR_SOME_INSTRUCTIONS) != 0 } fn is_tls(&self) -> bool { - todo!() + let sectname = trim_nul(self.0.sectname()); + sectname == b"__thread_vars" + || sectname == b"__thread_data" + || sectname == b"__thread_bss" } fn is_merge_section(&self) -> bool { - todo!() + let flags = self.0.flags(LE) & macho::SECTION_TYPE; + flags == macho::S_CSTRING_LITERALS || flags == macho::S_LITERAL_POINTERS } fn is_strings(&self) -> bool { - todo!() + let flags = self.0.flags(LE) & macho::SECTION_TYPE; + flags == macho::S_CSTRING_LITERALS } fn should_retain(&self) -> bool { - todo!() + false } fn should_exclude(&self) -> bool { - todo!() + let sectname = trim_nul(self.0.sectname()); + // Debug sections in __DWARF segment are not loaded + let segname = trim_nul(self.0.segname()); + segname == b"__DWARF" } fn is_group(&self) -> bool { - todo!() + false } fn is_note(&self) -> bool { - todo!() + false } fn is_prog_bits(&self) -> bool { - todo!() + let section_type = self.0.flags(LE) & macho::SECTION_TYPE; + section_type == macho::S_REGULAR || section_type == macho::S_CSTRING_LITERALS } fn is_no_bits(&self) -> bool { - todo!() + let section_type = self.0.flags(LE) & macho::SECTION_TYPE; + section_type == macho::S_ZEROFILL || section_type == macho::S_GB_ZEROFILL } } #[derive(Debug, Copy, Clone, Default)] -pub(crate) struct SectionType {} +pub(crate) struct SectionType(u32); impl platform::SectionType for SectionType { fn is_rela(&self) -> bool { - todo!() + false } fn is_rel(&self) -> bool { - todo!() + false } fn is_symtab(&self) -> bool { - todo!() + false } fn is_strtab(&self) -> bool { - todo!() + false } } #[derive(Debug, Copy, Clone, Default)] -pub(crate) struct SectionFlags {} +pub(crate) struct SectionFlags(u32); + +impl SectionFlags { + pub(crate) fn from_header(header: &SectionHeader) -> Self { + SectionFlags(header.0.flags(LE)) + } +} impl platform::SectionFlags for SectionFlags { fn is_alloc(self) -> bool { - todo!() + // All Mach-O sections are allocated + true } } impl platform::Symbol for SymtabEntry { fn as_common(&self) -> Option { - todo!() + // In Mach-O, common symbols are N_UNDF | N_EXT with n_value > 0 + let n_type = self.n_type(); + if (n_type & macho::N_TYPE) == macho::N_UNDF + && (n_type & macho::N_EXT) != 0 + && self.n_value(LE) > 0 + { + let alignment_val = u64::from(self.n_desc(LE)); + let alignment = + crate::alignment::Alignment::new(if alignment_val > 0 { 1u64 << alignment_val } else { 1 }) + .unwrap_or(crate::alignment::MIN); + let size = alignment.align_up(self.n_value(LE)); + let output_section_id = crate::output_section_id::BSS; + let part_id = output_section_id.part_id_with_alignment(alignment); + Some(platform::CommonSymbol { size, part_id }) + } else { + None + } } fn is_undefined(&self) -> bool { - todo!() + let n_type = self.n_type(); + // Not a stab, and type is N_UNDF + (n_type & macho::N_STAB) == 0 && (n_type & macho::N_TYPE) == macho::N_UNDF } fn is_local(&self) -> bool { - todo!() + let n_type = self.n_type(); + // Not external and not a stab entry + (n_type & macho::N_STAB) == 0 && (n_type & macho::N_EXT) == 0 } fn is_absolute(&self) -> bool { - todo!() + (self.n_type() & macho::N_TYPE) == macho::N_ABS } fn is_weak(&self) -> bool { - todo!() + (self.n_desc(LE) & (macho::N_WEAK_DEF | macho::N_WEAK_REF)) != 0 } fn visibility(&self) -> crate::symbol_db::Visibility { - todo!() + let n_type = self.n_type(); + if (n_type & macho::N_PEXT) != 0 { + crate::symbol_db::Visibility::Hidden + } else if (n_type & macho::N_EXT) != 0 { + crate::symbol_db::Visibility::Default + } else { + crate::symbol_db::Visibility::Hidden + } } fn value(&self) -> u64 { - todo!() + self.n_value(LE) } fn size(&self) -> u64 { - todo!() + // Mach-O symbols don't have a size field + 0 } fn section_index(&self) -> object::SectionIndex { - todo!() + let n_type = self.n_type() & macho::N_TYPE; + if n_type == macho::N_SECT { + // n_sect is 1-based in Mach-O + let sect = self.n_sect(); + if sect > 0 { + return object::SectionIndex(sect as usize - 1); + } + } + object::SectionIndex(0) } fn has_name(&self) -> bool { - todo!() + self.n_strx(LE) != 0 } fn debug_string(&self) -> String { - todo!() + format!( + "Nlist64 {{ n_type: 0x{:02x}, n_sect: {}, n_desc: 0x{:04x}, n_value: 0x{:x} }}", + self.n_type(), + self.n_sect(), + self.n_desc(LE), + self.n_value(LE), + ) } fn is_tls(&self) -> bool { - todo!() + // In Mach-O, TLS symbols reference __thread_vars section + false } fn is_interposable(&self) -> bool { - todo!() + // Mach-O two-level namespace means symbols are generally not interposable + false } fn is_func(&self) -> bool { - todo!() + // Mach-O doesn't have an explicit function type in nlist. + // We'd need to check the section type, but for now return false. + false } fn is_ifunc(&self) -> bool { - todo!() + false } fn is_hidden(&self) -> bool { - todo!() + (self.n_type() & macho::N_PEXT) != 0 } fn is_gnu_unique(&self) -> bool { - todo!() + false } } +// -- SectionAttributes -- + #[derive(Debug, Copy, Clone, Default)] -pub(crate) struct SectionAttributes {} +pub(crate) struct SectionAttributes { + flags: u32, + segname: [u8; 16], +} impl platform::SectionAttributes for SectionAttributes { type Platform = MachO; fn merge(&mut self, rhs: Self) { - todo!() + self.flags |= rhs.flags; } fn apply( &self, - output_sections: &mut crate::output_section_id::OutputSections, - section_id: crate::output_section_id::OutputSectionId, + _output_sections: &mut crate::output_section_id::OutputSections, + _section_id: crate::output_section_id::OutputSectionId, ) { - todo!() } fn is_null(&self) -> bool { - todo!() + false } fn is_alloc(&self) -> bool { - todo!() + true } fn is_executable(&self) -> bool { - todo!() + (self.flags & macho::S_ATTR_PURE_INSTRUCTIONS) != 0 + || (self.flags & macho::S_ATTR_SOME_INSTRUCTIONS) != 0 } fn is_tls(&self) -> bool { - todo!() + false } fn is_writable(&self) -> bool { - todo!() + self.segname.starts_with(b"__DATA") } fn is_no_bits(&self) -> bool { - todo!() + let section_type = self.flags & macho::SECTION_TYPE; + section_type == macho::S_ZEROFILL || section_type == macho::S_GB_ZEROFILL } - fn flags(&self) -> ::SectionFlags { - todo!() + fn flags(&self) -> SectionFlags { + SectionFlags(self.flags) } - fn ty(&self) -> ::SectionType { - todo!() + fn ty(&self) -> SectionType { + SectionType(self.flags & macho::SECTION_TYPE) } fn set_to_default_type(&mut self) { - todo!() + self.flags = (self.flags & !macho::SECTION_TYPE) | macho::S_REGULAR; } } +// -- Other platform type stubs -- + pub(crate) struct NonAddressableIndexes {} impl platform::NonAddressableIndexes for NonAddressableIndexes { - fn new(symbol_db: &crate::symbol_db::SymbolDb

) -> Self { - todo!() + fn new(_symbol_db: &crate::symbol_db::SymbolDb

) -> Self { + NonAddressableIndexes {} } } @@ -534,7 +642,7 @@ pub(crate) struct ProgramSegmentDef {} impl std::fmt::Display for ProgramSegmentDef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + write!(f, "") } } @@ -542,39 +650,39 @@ impl platform::ProgramSegmentDef for ProgramSegmentDef { type Platform = MachO; fn is_writable(self) -> bool { - todo!() + false } fn is_executable(self) -> bool { - todo!() + false } fn always_keep(self) -> bool { - todo!() + false } fn is_loadable(self) -> bool { - todo!() + false } fn is_stack(self) -> bool { - todo!() + false } fn is_tls(self) -> bool { - todo!() + false } fn order_key(self) -> usize { - todo!() + 0 } fn should_include_section( self, - section_info: &crate::output_section_id::SectionOutputInfo, - section_id: crate::output_section_id::OutputSectionId, + _section_info: &crate::output_section_id::SectionOutputInfo, + _section_id: crate::output_section_id::OutputSectionId, ) -> bool { - todo!() + false } } @@ -584,23 +692,23 @@ impl platform::BuiltInSectionDetails for BuiltInSectionDetails {} #[derive(Default, Debug, Clone, Copy)] pub(crate) struct DynamicTagValues<'data> { - phantom: &'data [u8], + _phantom: &'data [u8], } #[derive(Debug)] pub(crate) struct RelocationList<'data> { - phantom: &'data [u8], + pub(crate) relocations: &'data [macho::Relocation], } impl<'data> platform::RelocationList<'data> for RelocationList<'data> { fn num_relocations(&self) -> usize { - todo!() + self.relocations.len() } } impl<'data> platform::DynamicTagValues<'data> for DynamicTagValues<'data> { - fn lib_name(&self, input: &crate::input_data::InputRef<'data>) -> &'data [u8] { - todo!() + fn lib_name(&self, _input: &crate::input_data::InputRef<'data>) -> &'data [u8] { + b"" } } @@ -611,25 +719,25 @@ pub(crate) struct RawSymbolName<'data> { impl<'data> platform::RawSymbolName<'data> for RawSymbolName<'data> { fn parse(bytes: &'data [u8]) -> Self { - todo!() + RawSymbolName { name: bytes } } fn name(&self) -> &'data [u8] { - todo!() + self.name } fn version_name(&self) -> Option<&'data [u8]> { - todo!() + None } fn is_default(&self) -> bool { - todo!() + true } } impl std::fmt::Display for RawSymbolName<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - todo!() + write!(f, "{}", String::from_utf8_lossy(self.name)) } } @@ -638,8 +746,23 @@ pub(crate) struct VerneedTable<'data> { } impl<'data> platform::VerneedTable<'data> for VerneedTable<'data> { - fn version_name(&self, local_symbol_index: object::SymbolIndex) -> Option<&'data [u8]> { - todo!() + fn version_name(&self, _local_symbol_index: object::SymbolIndex) -> Option<&'data [u8]> { + None + } +} + +/// Iterator adapter to cast Section64 refs to SectionHeader refs. +pub(crate) struct MachOSectionIter<'data> { + inner: core::slice::Iter<'data, macho::Section64>, +} + +impl<'data> Iterator for MachOSectionIter<'data> { + type Item = &'data SectionHeader; + + fn next(&mut self) -> Option { + self.inner.next().map(|s| { + unsafe { &*(s as *const macho::Section64 as *const SectionHeader) } + }) } } @@ -666,7 +789,7 @@ impl platform::Platform for MachO { type ResolutionExt = (); type SymbolVersionIndex = (); type LayoutExt = (); - type SectionIterator<'data> = core::slice::Iter<'data, SectionHeader>; + type SectionIterator<'data> = MachOSectionIter<'data>; type DynamicTagValues<'data> = DynamicTagValues<'data>; type RelocationList<'data> = RelocationList<'data>; type DynamicLayoutStateExt<'data> = (); @@ -690,352 +813,395 @@ impl platform::Platform for MachO { output: &crate::file_writer::Output, layout: &crate::layout::Layout<'data, Self>, ) -> crate::error::Result { - todo!() + crate::macho_writer::write::(output, layout) } fn section_attributes(header: &Self::SectionHeader) -> Self::SectionAttributes { - todo!() + SectionAttributes { + flags: header.0.flags(LE), + segname: *header.0.segname(), + } } fn apply_force_keep_sections( - keep_sections: &mut crate::output_section_map::OutputSectionMap, - args: &Self::Args, + _keep_sections: &mut crate::output_section_map::OutputSectionMap, + _args: &Self::Args, ) { - todo!() } fn is_zero_sized_section_content( - section_id: crate::output_section_id::OutputSectionId, + _section_id: crate::output_section_id::OutputSectionId, ) -> bool { - todo!() + false } fn built_in_section_details() -> &'static [Self::BuiltInSectionDetails] { - todo!() + &[] } fn finalise_group_layout( - memory_offsets: &crate::output_section_part_map::OutputSectionPartMap, + _memory_offsets: &crate::output_section_part_map::OutputSectionPartMap, ) -> Self::GroupLayoutExt { - todo!() } fn frame_data_base_address( - memory_offsets: &crate::output_section_part_map::OutputSectionPartMap, + _memory_offsets: &crate::output_section_part_map::OutputSectionPartMap, ) -> u64 { - todo!() + 0 } - fn finalise_find_required_sections(groups: &[crate::layout::GroupState]) { - todo!() + fn finalise_find_required_sections(_groups: &[crate::layout::GroupState]) { } fn activate_dynamic<'data>( - state: &mut crate::layout::DynamicLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, + _state: &mut crate::layout::DynamicLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, ) { - todo!() } fn pre_finalise_sizes_prelude<'scope, 'data>( - prelude: &mut crate::layout::PreludeLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, - resources: &crate::layout::GraphResources<'data, 'scope, Self>, + _prelude: &mut crate::layout::PreludeLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _resources: &crate::layout::GraphResources<'data, 'scope, Self>, ) { - todo!() } fn finalise_sizes_dynamic<'data>( - object: &mut crate::layout::DynamicLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, + _object: &mut crate::layout::DynamicLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, ) -> crate::error::Result { - todo!() + Ok(()) } fn finalise_object_sizes<'data>( - object: &mut crate::layout::ObjectLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, + _object: &mut crate::layout::ObjectLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, ) { - todo!() } fn finalise_object_layout<'data>( - object: &crate::layout::ObjectLayoutState<'data, Self>, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _object: &crate::layout::ObjectLayoutState<'data, Self>, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, ) { - todo!() } fn finalise_layout_dynamic<'data>( - state: &mut crate::layout::DynamicLayoutState<'data, Self>, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, - resources: &crate::layout::FinaliseLayoutResources<'_, 'data, Self>, - resolutions_out: &mut crate::layout::ResolutionWriter, + _state: &mut crate::layout::DynamicLayoutState<'data, Self>, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _resources: &crate::layout::FinaliseLayoutResources<'_, 'data, Self>, + _resolutions_out: &mut crate::layout::ResolutionWriter, ) -> crate::error::Result> { - todo!() + Ok(()) } fn take_dynsym_index( - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, - section_layouts: &crate::output_section_map::OutputSectionMap< + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _section_layouts: &crate::output_section_map::OutputSectionMap< crate::layout::OutputRecordLayout, >, ) -> crate::error::Result { - todo!() + Ok(0) } fn compute_object_addresses<'data>( - object: &crate::layout::ObjectLayoutState<'data, Self>, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _object: &crate::layout::ObjectLayoutState<'data, Self>, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, ) { - todo!() } fn layout_resources_ext<'data>( - groups: &[crate::grouping::Group<'data, Self>], + _groups: &[crate::grouping::Group<'data, Self>], ) -> Self::LayoutResourcesExt<'data> { - todo!() } fn load_object_section_relocations<'data, 'scope, A: platform::Arch>( - state: &crate::layout::ObjectLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, - queue: &mut crate::layout::LocalWorkQueue, - resources: &'scope crate::layout::GraphResources<'data, '_, Self>, - section: crate::layout::Section, - scope: &rayon::Scope<'scope>, + _state: &crate::layout::ObjectLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _queue: &mut crate::layout::LocalWorkQueue, + _resources: &'scope crate::layout::GraphResources<'data, '_, Self>, + _section: crate::layout::Section, + _scope: &rayon::Scope<'scope>, ) -> crate::error::Result { - todo!() + Ok(()) } fn create_dynamic_symbol_definition<'data>( - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - symbol_id: crate::symbol_db::SymbolId, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _symbol_id: crate::symbol_db::SymbolId, ) -> crate::error::Result> { - todo!() + Err(error!("Dynamic symbols not yet supported for Mach-O")) } fn update_segment_keep_list( - program_segments: &crate::program_segments::ProgramSegments, - keep_segments: &mut [bool], - args: &Self::Args, + _program_segments: &crate::program_segments::ProgramSegments, + _keep_segments: &mut [bool], + _args: &Self::Args, ) { - todo!() } fn program_segment_defs() -> &'static [Self::ProgramSegmentDef] { - todo!() + &[] } fn unconditional_segment_defs() -> &'static [Self::ProgramSegmentDef] { - todo!() + &[] } fn create_linker_defined_symbols( - symbols: &mut crate::parsing::InternalSymbolsBuilder, - output_kind: crate::output_kind::OutputKind, - args: &Self::Args, + _symbols: &mut crate::parsing::InternalSymbolsBuilder, + _output_kind: crate::output_kind::OutputKind, + _args: &Self::Args, ) { } fn built_in_section_infos<'data>() -> Vec> { - // TODO - Vec::new() + use crate::layout_rules::SectionKind; + use crate::output_section_id::NUM_BUILT_IN_SECTIONS; + use crate::output_section_id::SectionName; + use crate::output_section_id::SectionOutputInfo; + + let mut infos: Vec> = Vec::with_capacity(NUM_BUILT_IN_SECTIONS); + for _ in 0..NUM_BUILT_IN_SECTIONS { + infos.push(SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"")), + section_attributes: SectionAttributes::default(), + min_alignment: crate::alignment::MIN, + location: None, + secondary_order: None, + }); + } + + // Provide names/attributes for the regular sections we care about + infos[crate::output_section_id::TEXT.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__text")), + section_attributes: SectionAttributes { + flags: macho::S_REGULAR | macho::S_ATTR_PURE_INSTRUCTIONS, + segname: *b"__TEXT\0\0\0\0\0\0\0\0\0\0", + }, + min_alignment: crate::alignment::MIN, + location: None, + secondary_order: None, + }; + infos[crate::output_section_id::RODATA.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__const")), + section_attributes: SectionAttributes::default(), + min_alignment: crate::alignment::MIN, + location: None, + secondary_order: None, + }; + infos[crate::output_section_id::DATA.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__data")), + section_attributes: SectionAttributes { + flags: macho::S_REGULAR, + segname: *b"__DATA\0\0\0\0\0\0\0\0\0\0", + }, + min_alignment: crate::alignment::MIN, + location: None, + secondary_order: None, + }; + infos[crate::output_section_id::BSS.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__bss")), + section_attributes: SectionAttributes { + flags: macho::S_ZEROFILL, + segname: *b"__DATA\0\0\0\0\0\0\0\0\0\0", + }, + min_alignment: crate::alignment::MIN, + location: None, + secondary_order: None, + }; + infos } fn create_layout_properties<'data, 'states, 'files, A: platform::Arch>( - args: &Self::Args, - objects: impl Iterator>, - states: impl Iterator> + Clone, + _args: &Self::Args, + _objects: impl Iterator>, + _states: impl Iterator> + Clone, ) -> crate::error::Result where 'data: 'files, 'data: 'states, { - todo!() + Ok(()) } fn load_exception_frame_data<'data, 'scope, A: platform::Arch>( - object: &mut crate::layout::ObjectLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, - eh_frame_section_index: object::SectionIndex, - resources: &'scope crate::layout::GraphResources<'data, '_, Self>, - queue: &mut crate::layout::LocalWorkQueue, - scope: &rayon::Scope<'scope>, + _object: &mut crate::layout::ObjectLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _eh_frame_section_index: object::SectionIndex, + _resources: &'scope crate::layout::GraphResources<'data, '_, Self>, + _queue: &mut crate::layout::LocalWorkQueue, + _scope: &rayon::Scope<'scope>, ) -> crate::error::Result { - todo!() + Ok(()) } fn non_empty_section_loaded<'data, 'scope, A: platform::Arch>( - object: &mut crate::layout::ObjectLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, - queue: &mut crate::layout::LocalWorkQueue, - unloaded: crate::resolution::UnloadedSection, - resources: &'scope crate::layout::GraphResources<'data, 'scope, Self>, - scope: &rayon::Scope<'scope>, + _object: &mut crate::layout::ObjectLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _queue: &mut crate::layout::LocalWorkQueue, + _unloaded: crate::resolution::UnloadedSection, + _resources: &'scope crate::layout::GraphResources<'data, 'scope, Self>, + _scope: &rayon::Scope<'scope>, ) -> crate::error::Result { - todo!() + Ok(()) } fn new_epilogue_layout( - args: &Self::Args, - output_kind: crate::output_kind::OutputKind, - dynamic_symbol_definitions: &mut [crate::layout::DynamicSymbolDefinition<'_, Self>], + _args: &Self::Args, + _output_kind: crate::output_kind::OutputKind, + _dynamic_symbol_definitions: &mut [crate::layout::DynamicSymbolDefinition<'_, Self>], ) -> Self::EpilogueLayoutExt { - todo!() } fn apply_non_addressable_indexes_epilogue( - counts: &mut Self::NonAddressableCounts, - state: &mut Self::EpilogueLayoutExt, + _counts: &mut Self::NonAddressableCounts, + _state: &mut Self::EpilogueLayoutExt, ) { - todo!() } fn apply_non_addressable_indexes<'data, 'groups>( - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - counts: &Self::NonAddressableCounts, - mem_sizes_iter: impl Iterator< + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _counts: &Self::NonAddressableCounts, + _mem_sizes_iter: impl Iterator< Item = &'groups mut crate::output_section_part_map::OutputSectionPartMap, >, ) { - todo!() } fn finalise_sizes_epilogue<'data>( - state: &mut Self::EpilogueLayoutExt, - mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - dynamic_symbol_definitions: &[crate::layout::DynamicSymbolDefinition<'data, Self>], - properties: &Self::LayoutExt, - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _state: &mut Self::EpilogueLayoutExt, + _mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _dynamic_symbol_definitions: &[crate::layout::DynamicSymbolDefinition<'data, Self>], + _properties: &Self::LayoutExt, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, ) { - todo!() } fn finalise_sizes_all<'data>( - mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, ) { - todo!() } fn apply_late_size_adjustments_epilogue( - state: &mut Self::EpilogueLayoutExt, - current_sizes: &crate::output_section_part_map::OutputSectionPartMap, - extra_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - dynamic_symbol_defs: &[crate::layout::DynamicSymbolDefinition], - args: &Self::Args, + _state: &mut Self::EpilogueLayoutExt, + _current_sizes: &crate::output_section_part_map::OutputSectionPartMap, + _extra_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _dynamic_symbol_defs: &[crate::layout::DynamicSymbolDefinition], + _args: &Self::Args, ) -> crate::error::Result { - todo!() + Ok(()) } fn finalise_layout_epilogue<'data>( - epilogue_state: &mut Self::EpilogueLayoutExt, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - common_state: &Self::LayoutExt, - dynsym_start_index: u32, - dynamic_symbol_defs: &[crate::layout::DynamicSymbolDefinition], + _epilogue_state: &mut Self::EpilogueLayoutExt, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _common_state: &Self::LayoutExt, + _dynsym_start_index: u32, + _dynamic_symbol_defs: &[crate::layout::DynamicSymbolDefinition], ) -> crate::error::Result { - todo!() + Ok(()) } fn is_symbol_non_interposable<'data>( - object: &Self::File<'data>, - args: &Self::Args, - sym: &Self::SymtabEntry, - output_kind: crate::output_kind::OutputKind, - export_list: Option<&crate::export_list::ExportList>, - lib_name: &[u8], - archive_semantics: bool, - is_undefined: bool, + _object: &Self::File<'data>, + _args: &Self::Args, + _sym: &Self::SymtabEntry, + _output_kind: crate::output_kind::OutputKind, + _export_list: Option<&crate::export_list::ExportList>, + _lib_name: &[u8], + _archive_semantics: bool, + _is_undefined: bool, ) -> bool { - todo!() + // Mach-O two-level namespace: symbols are generally non-interposable + true } fn allocate_header_sizes( - prelude: &mut crate::layout::PreludeLayoutState, - sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - header_info: &crate::layout::HeaderInfo, - output_sections: &crate::output_section_id::OutputSections, + _prelude: &mut crate::layout::PreludeLayoutState, + _sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _header_info: &crate::layout::HeaderInfo, + _output_sections: &crate::output_section_id::OutputSections, ) { - todo!() } fn finalise_sizes_for_symbol<'data>( - common: &mut crate::layout::CommonGroupState<'data, Self>, - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - symbol_id: crate::symbol_db::SymbolId, - flags: crate::value_flags::ValueFlags, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _symbol_id: crate::symbol_db::SymbolId, + _flags: crate::value_flags::ValueFlags, ) -> crate::error::Result { - todo!() + Ok(()) } fn allocate_resolution( - flags: crate::value_flags::ValueFlags, - mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - output_kind: crate::output_kind::OutputKind, + _flags: crate::value_flags::ValueFlags, + _mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _output_kind: crate::output_kind::OutputKind, ) { - todo!() } fn allocate_object_symtab_space<'data>( - state: &crate::layout::ObjectLayoutState<'data, Self>, - common: &mut crate::layout::CommonGroupState<'data, Self>, - symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - per_symbol_flags: &crate::value_flags::AtomicPerSymbolFlags, + _state: &crate::layout::ObjectLayoutState<'data, Self>, + _common: &mut crate::layout::CommonGroupState<'data, Self>, + _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + _per_symbol_flags: &crate::value_flags::AtomicPerSymbolFlags, ) { - todo!() } fn allocate_internal_symbol( - symbol_id: crate::symbol_db::SymbolId, - def_info: &crate::parsing::InternalSymDefInfo, - sizes: &mut crate::output_section_part_map::OutputSectionPartMap, - symbol_db: &crate::symbol_db::SymbolDb, + _symbol_id: crate::symbol_db::SymbolId, + _def_info: &crate::parsing::InternalSymDefInfo, + _sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + _symbol_db: &crate::symbol_db::SymbolDb, ) -> crate::error::Result { - todo!() + Ok(()) } fn allocate_prelude( - common: &mut crate::layout::CommonGroupState, - symbol_db: &crate::symbol_db::SymbolDb, + _common: &mut crate::layout::CommonGroupState, + _symbol_db: &crate::symbol_db::SymbolDb, ) { - todo!() } fn finalise_prelude_layout<'data>( - prelude: &crate::layout::PreludeLayoutState, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, - resources: &crate::layout::FinaliseLayoutResources<'_, 'data, Self>, + _prelude: &crate::layout::PreludeLayoutState, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _resources: &crate::layout::FinaliseLayoutResources<'_, 'data, Self>, ) -> crate::error::Result { - todo!() + Ok(()) } fn create_resolution( flags: crate::value_flags::ValueFlags, raw_value: u64, dynamic_symbol_index: Option, - memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, ) -> crate::layout::Resolution { - todo!() + crate::layout::Resolution { + raw_value, + dynamic_symbol_index, + flags, + format_specific: (), + } } fn raw_symbol_name<'data>( name_bytes: &'data [u8], - verneed_table: &Self::VerneedTable<'data>, - symbol_index: object::SymbolIndex, + _verneed_table: &Self::VerneedTable<'data>, + _symbol_index: object::SymbolIndex, ) -> Self::RawSymbolName<'data> { - todo!() + RawSymbolName { name: name_bytes } } fn default_layout_rules() -> &'static [crate::layout_rules::SectionRule<'static>] { - todo!() + MACHO_SECTION_RULES } fn build_output_order_and_program_segments<'data>( - custom: &crate::output_section_id::CustomSectionIds, + _custom: &crate::output_section_id::CustomSectionIds, output_kind: OutputKind, output_sections: &crate::output_section_id::OutputSections<'data, Self>, secondary: &crate::output_section_map::OutputSectionMap< @@ -1045,6 +1211,42 @@ impl platform::Platform for MachO { crate::output_section_id::OutputOrder, crate::program_segments::ProgramSegments, ) { - todo!() + let builder = crate::output_section_id::OutputOrderBuilder::::new( + output_kind, + output_sections, + secondary, + ); + builder.build() } } + +const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { + use crate::layout_rules::SectionRule; + use crate::output_section_id; + &[ + SectionRule::exact_section(b"__text", output_section_id::TEXT), + SectionRule::exact_section(b"__stubs", output_section_id::TEXT), + SectionRule::exact_section(b"__stub_helper", output_section_id::TEXT), + SectionRule::exact_section(b"__const", output_section_id::RODATA), + SectionRule::exact_section(b"__cstring", output_section_id::RODATA), + SectionRule::exact_section(b"__literal4", output_section_id::RODATA), + SectionRule::exact_section(b"__literal8", output_section_id::RODATA), + SectionRule::exact_section(b"__literal16", output_section_id::RODATA), + SectionRule::exact_section(b"__data", output_section_id::DATA), + SectionRule::exact_section(b"__la_symbol_ptr", output_section_id::DATA), + SectionRule::exact_section(b"__nl_symbol_ptr", output_section_id::DATA), + SectionRule::exact_section(b"__got", output_section_id::DATA), + SectionRule::exact_section(b"__bss", output_section_id::BSS), + SectionRule::exact_section(b"__common", output_section_id::BSS), + SectionRule::exact_section(b"__unwind_info", output_section_id::RODATA), + SectionRule::exact_section(b"__eh_frame", output_section_id::RODATA), + SectionRule::exact_section(b"__compact_unwind", output_section_id::RODATA), + ] +}; + +/// Trim trailing NUL bytes from a fixed-size Mach-O name field. +fn trim_nul(name: &[u8; 16]) -> &[u8] { + let end = name.iter().position(|&b| b == 0).unwrap_or(16); + // Safety: end <= 16, and the array has 16 elements + &name.as_slice()[..end] +} diff --git a/libwild/src/macho_aarch64.rs b/libwild/src/macho_aarch64.rs index 677327957..0b8554319 100644 --- a/libwild/src/macho_aarch64.rs +++ b/libwild/src/macho_aarch64.rs @@ -1,101 +1,218 @@ -// TODO +// Mach-O ARM64 architecture support. #![allow(unused_variables)] use crate::macho::MachO; +use linker_utils::elf::AArch64Instruction; +use linker_utils::elf::AllowedRange; +use linker_utils::elf::RelocationKind; +use linker_utils::elf::RelocationKindInfo; +use linker_utils::elf::RelocationSize; +use linker_utils::relaxation::RelocationModifier; +use object::macho; pub(crate) struct MachOAArch64; +/// Mach-O ARM64 relocation types mapped to our internal representation. +fn macho_aarch64_relocation_from_raw(r_type: u32) -> Option { + let (kind, size, range, alignment) = match r_type as u8 { + macho::ARM64_RELOC_UNSIGNED => ( + RelocationKind::Absolute, + RelocationSize::ByteSize(8), + AllowedRange::no_check(), + 1, + ), + macho::ARM64_RELOC_BRANCH26 => ( + RelocationKind::Relative, + RelocationSize::bit_mask_aarch64(0, 26, AArch64Instruction::JumpCall), + AllowedRange::from_bit_size(28, linker_utils::elf::Sign::Signed), + 4, + ), + macho::ARM64_RELOC_PAGE21 => ( + RelocationKind::Relative, + RelocationSize::bit_mask_aarch64(12, 33, AArch64Instruction::Adr), + AllowedRange::from_bit_size(33, linker_utils::elf::Sign::Signed), + 1, + ), + macho::ARM64_RELOC_PAGEOFF12 => ( + RelocationKind::AbsoluteLowPart, + RelocationSize::bit_mask_aarch64(0, 12, AArch64Instruction::Add), + AllowedRange::no_check(), + 1, + ), + macho::ARM64_RELOC_GOT_LOAD_PAGE21 => ( + RelocationKind::GotRelative, + RelocationSize::bit_mask_aarch64(12, 33, AArch64Instruction::Adr), + AllowedRange::from_bit_size(33, linker_utils::elf::Sign::Signed), + 1, + ), + macho::ARM64_RELOC_GOT_LOAD_PAGEOFF12 => ( + RelocationKind::GotRelative, + RelocationSize::bit_mask_aarch64(0, 12, AArch64Instruction::LdrRegister), + AllowedRange::no_check(), + 8, + ), + macho::ARM64_RELOC_SUBTRACTOR => ( + RelocationKind::Absolute, + RelocationSize::ByteSize(8), + AllowedRange::no_check(), + 1, + ), + macho::ARM64_RELOC_POINTER_TO_GOT => ( + RelocationKind::GotRelative, + RelocationSize::ByteSize(4), + AllowedRange::from_bit_size(32, linker_utils::elf::Sign::Signed), + 1, + ), + macho::ARM64_RELOC_TLVP_LOAD_PAGE21 => ( + RelocationKind::TlsGd, + RelocationSize::bit_mask_aarch64(12, 33, AArch64Instruction::Adr), + AllowedRange::from_bit_size(33, linker_utils::elf::Sign::Signed), + 1, + ), + macho::ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => ( + RelocationKind::TlsGd, + RelocationSize::bit_mask_aarch64(0, 12, AArch64Instruction::Add), + AllowedRange::no_check(), + 1, + ), + macho::ARM64_RELOC_ADDEND => ( + RelocationKind::None, + RelocationSize::ByteSize(0), + AllowedRange::no_check(), + 1, + ), + _ => return None, + }; + Some(RelocationKindInfo { + kind, + size, + mask: None, + range, + alignment, + bias: 0, + }) +} + +fn macho_aarch64_rel_type_to_string(r_type: u32) -> std::borrow::Cow<'static, str> { + match r_type as u8 { + macho::ARM64_RELOC_UNSIGNED => "ARM64_RELOC_UNSIGNED".into(), + macho::ARM64_RELOC_SUBTRACTOR => "ARM64_RELOC_SUBTRACTOR".into(), + macho::ARM64_RELOC_BRANCH26 => "ARM64_RELOC_BRANCH26".into(), + macho::ARM64_RELOC_PAGE21 => "ARM64_RELOC_PAGE21".into(), + macho::ARM64_RELOC_PAGEOFF12 => "ARM64_RELOC_PAGEOFF12".into(), + macho::ARM64_RELOC_GOT_LOAD_PAGE21 => "ARM64_RELOC_GOT_LOAD_PAGE21".into(), + macho::ARM64_RELOC_GOT_LOAD_PAGEOFF12 => "ARM64_RELOC_GOT_LOAD_PAGEOFF12".into(), + macho::ARM64_RELOC_POINTER_TO_GOT => "ARM64_RELOC_POINTER_TO_GOT".into(), + macho::ARM64_RELOC_TLVP_LOAD_PAGE21 => "ARM64_RELOC_TLVP_LOAD_PAGE21".into(), + macho::ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => "ARM64_RELOC_TLVP_LOAD_PAGEOFF12".into(), + macho::ARM64_RELOC_ADDEND => "ARM64_RELOC_ADDEND".into(), + other => format!("unknown Mach-O ARM64 relocation {other}").into(), + } +} + #[derive(Debug, Clone)] pub(crate) struct Relaxation {} impl crate::platform::Relaxation for Relaxation { - fn apply(&self, section_bytes: &mut [u8], offset_in_section: &mut u64, addend: &mut i64) { - todo!() + fn apply(&self, _section_bytes: &mut [u8], _offset_in_section: &mut u64, _addend: &mut i64) { + // No relaxations for Mach-O yet } - fn rel_info(&self) -> linker_utils::elf::RelocationKindInfo { - todo!() + fn rel_info(&self) -> RelocationKindInfo { + RelocationKindInfo { + kind: RelocationKind::None, + size: RelocationSize::ByteSize(0), + mask: None, + range: AllowedRange::no_check(), + alignment: 1, + bias: 0, + } } fn debug_kind(&self) -> impl std::fmt::Debug { - todo!() + "MachORelaxation(none)" } - fn next_modifier(&self) -> linker_utils::relaxation::RelocationModifier { - todo!() + fn next_modifier(&self) -> RelocationModifier { + RelocationModifier::Normal } fn is_mandatory(&self) -> bool { - todo!() + false } } impl crate::platform::Arch for MachOAArch64 { type Relaxation = Relaxation; - type Platform = MachO; fn arch_identifier() -> ::ArchIdentifier { - todo!() + // Mach-O doesn't use ELF-style arch identifiers } - fn get_dynamic_relocation_type(relocation: linker_utils::elf::DynamicRelocationKind) -> u32 { - todo!() + fn get_dynamic_relocation_type( + _relocation: linker_utils::elf::DynamicRelocationKind, + ) -> u32 { + 0 } fn write_plt_entry( - plt_entry: &mut [u8], - got_address: u64, - plt_address: u64, + _plt_entry: &mut [u8], + _got_address: u64, + _plt_address: u64, ) -> crate::error::Result { - todo!() + // Mach-O uses stubs instead of PLT entries; handled separately + Ok(()) } - fn relocation_from_raw( - r_type: u32, - ) -> crate::error::Result { - todo!() + fn relocation_from_raw(r_type: u32) -> crate::error::Result { + macho_aarch64_relocation_from_raw(r_type).ok_or_else(|| { + crate::error!( + "Unsupported Mach-O ARM64 relocation type {}", + macho_aarch64_rel_type_to_string(r_type) + ) + }) } fn rel_type_to_string(r_type: u32) -> std::borrow::Cow<'static, str> { - todo!() + macho_aarch64_rel_type_to_string(r_type) } - fn tp_offset_start(layout: &crate::layout::Layout) -> u64 { - todo!() + fn tp_offset_start(_layout: &crate::layout::Layout) -> u64 { + 0 } - fn get_property_class(property_type: u32) -> Option { - todo!() + fn get_property_class(_property_type: u32) -> Option { + None } - fn merge_eflags(eflags: impl Iterator) -> crate::error::Result { - todo!() + fn merge_eflags(_eflags: impl Iterator) -> crate::error::Result { + Ok(0) } fn high_part_relocations() -> &'static [u32] { - todo!() + &[] } fn get_source_info<'data>( - object: &::File<'data>, - relocations: &::RelocationSections, - section: &::SectionHeader, - offset_in_section: u64, + _object: &::File<'data>, + _relocations: &::RelocationSections, + _section: &::SectionHeader, + _offset_in_section: u64, ) -> crate::error::Result { - todo!() + Ok(crate::platform::SourceInfo(None)) } fn new_relaxation( - relocation_kind: u32, - section_bytes: &[u8], - offset_in_section: u64, - flags: crate::value_flags::ValueFlags, - output_kind: crate::output_kind::OutputKind, - section_flags: ::SectionFlags, - non_zero_address: bool, - relax_deltas: Option<&linker_utils::relaxation::SectionRelaxDeltas>, + _relocation_kind: u32, + _section_bytes: &[u8], + _offset_in_section: u64, + _flags: crate::value_flags::ValueFlags, + _output_kind: crate::output_kind::OutputKind, + _section_flags: ::SectionFlags, + _non_zero_address: bool, + _relax_deltas: Option<&linker_utils::relaxation::SectionRelaxDeltas>, ) -> Option { - todo!() + None } } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs new file mode 100644 index 000000000..63e469a93 --- /dev/null +++ b/libwild/src/macho_writer.rs @@ -0,0 +1,363 @@ +// Mach-O output file writer. +// +// Generates a minimal Mach-O executable for aarch64-apple-darwin. +#![allow(dead_code)] + +use crate::error::Result; +use crate::layout::Layout; +use crate::macho::MachO; +use crate::platform::Arch; +use crate::platform::Args as _; + +/// Page size on Apple Silicon +const PAGE_SIZE: u64 = 0x4000; // 16KB + +/// Default size for __PAGEZERO +const PAGEZERO_SIZE: u64 = 0x1_0000_0000; // 4GB + +// Mach-O constants +const MH_MAGIC_64: u32 = 0xfeed_facf; +const MH_EXECUTE: u32 = 2; +const MH_PIE: u32 = 0x0020_0000; +const MH_TWOLEVEL: u32 = 0x80; +const MH_NOUNDEFS: u32 = 1; +const MH_DYLDLINK: u32 = 4; +const CPU_TYPE_ARM64: u32 = 0x0100_000c; +const CPU_SUBTYPE_ARM64_ALL: u32 = 0; +const LC_SEGMENT_64: u32 = 0x19; +const LC_MAIN: u32 = 0x8000_0028; +const LC_SYMTAB: u32 = 0x02; +const LC_DYSYMTAB: u32 = 0x0b; +const LC_LOAD_DYLINKER: u32 = 0x0e; +const LC_LOAD_DYLIB: u32 = 0x0c; +const LC_UUID: u32 = 0x1b; +const LC_BUILD_VERSION: u32 = 0x32; +const LC_SOURCE_VERSION: u32 = 0x2a; +const LC_DYLD_CHAINED_FIXUPS: u32 = 0x8000_0034; +const LC_DYLD_EXPORTS_TRIE: u32 = 0x8000_0033; +const VM_PROT_READ: u32 = 1; +const VM_PROT_WRITE: u32 = 2; +const VM_PROT_EXECUTE: u32 = 4; +const S_REGULAR: u32 = 0; +const S_ATTR_PURE_INSTRUCTIONS: u32 = 0x8000_0000; +const S_ATTR_SOME_INSTRUCTIONS: u32 = 0x0000_0400; +const PLATFORM_MACOS: u32 = 1; + +const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; +const LIBSYSTEM_PATH: &[u8] = b"/usr/lib/libSystem.B.dylib"; + +pub(crate) fn write>( + _output: &crate::file_writer::Output, + layout: &Layout<'_, MachO>, +) -> Result { + let mut buf = Vec::new(); + write_macho_to_vec(&mut buf, layout)?; + + let output_path = layout.symbol_db.args.output(); + std::fs::write(output_path.as_ref(), &buf) + .map_err(|e| crate::error!("Failed to write output file: {e}"))?; + + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let perms = std::fs::Permissions::from_mode(0o755); + std::fs::set_permissions(output_path.as_ref(), perms) + .map_err(|e| crate::error!("Failed to set permissions: {e}"))?; + } + + Ok(()) +} + +fn write_macho_to_vec(out: &mut Vec, layout: &Layout<'_, MachO>) -> Result { + // Collect text section data from input objects + let mut text_data: Vec = Vec::new(); + + for group_layout in &layout.group_layouts { + for file_layout in &group_layout.files { + if let crate::layout::FileLayout::Object(obj) = file_layout { + for section in obj.object.sections { + use object::read::macho::Section as _; + let sectname = section.sectname(); + let name_end = sectname.iter().position(|&b| b == 0).unwrap_or(16); + if §name[..name_end] == b"__text" { + let off = section.offset(object::Endianness::Little) as usize; + let sz = section.size(object::Endianness::Little) as usize; + if sz > 0 + && let Some(data) = obj.object.data.get(off..off + sz) { + text_data.extend_from_slice(data); + } + } + } + } + } + } + + if text_data.is_empty() { + text_data.extend_from_slice(&[ + 0x40, 0x05, 0x80, 0xd2, // mov x0, #42 + 0xc0, 0x03, 0x5f, 0xd6, // ret + ]); + } + + let text_size = text_data.len() as u64; + + // Struct sizes + let header_size: u64 = 32; + let seg_cmd_size: u64 = 72; + let section_hdr_size: u64 = 80; + let entry_cmd_size: u64 = 24; + let symtab_cmd_size: u64 = 24; + let dysymtab_cmd_size: u64 = 80; + let build_version_cmd_size: u64 = 32; // 24 base + 8 for one tool + let _source_version_cmd_size: u64 = 16; + let _uuid_cmd_size: u64 = 24; + let linkedit_data_cmd_size: u64 = 16; // for chained fixups and exports trie + + // LC_LOAD_DYLINKER: cmd(4) + cmdsize(4) + name_offset(4) + path + padding to 8-byte align + let dylinker_path_len = DYLD_PATH.len() + 1; // +1 for NUL + let dylinker_cmd_size = align_to((12 + dylinker_path_len) as u64, 8); + + // LC_LOAD_DYLIB: cmd(4) + cmdsize(4) + offset(4) + timestamp(4) + current_version(4) + compat_version(4) + name + padding + let dylib_name_len = LIBSYSTEM_PATH.len() + 1; + let load_dylib_cmd_size = align_to((24 + dylib_name_len) as u64, 8); + + let num_load_commands: u32 = 11; + let load_commands_size: u64 = seg_cmd_size // __PAGEZERO + + seg_cmd_size + section_hdr_size // __TEXT + __text + + seg_cmd_size // __LINKEDIT + + entry_cmd_size // LC_MAIN + + dylinker_cmd_size // LC_LOAD_DYLINKER + + load_dylib_cmd_size // LC_LOAD_DYLIB + + symtab_cmd_size // LC_SYMTAB + + dysymtab_cmd_size // LC_DYSYMTAB + + build_version_cmd_size // LC_BUILD_VERSION + + linkedit_data_cmd_size // LC_DYLD_CHAINED_FIXUPS + + linkedit_data_cmd_size; // LC_DYLD_EXPORTS_TRIE + + let header_and_cmds = header_size + load_commands_size; + let text_file_offset = align_to(header_and_cmds, PAGE_SIZE); + let text_vm_addr = PAGEZERO_SIZE + text_file_offset; + let text_segment_file_size = align_to(text_size, PAGE_SIZE); + let text_segment_vm_size = text_file_offset + text_segment_file_size; + + let linkedit_file_offset = text_file_offset + text_segment_file_size; + let linkedit_vm_addr = PAGEZERO_SIZE + linkedit_file_offset; + + // __LINKEDIT needs to contain at least the chained fixups header (empty) + // Minimal chained fixups: 4 bytes (fixups_version=0) + 4 bytes (starts_offset=0) + + // 4 bytes (imports_offset=0) + 4 bytes (symbols_offset=0) + 4 bytes (imports_count=0) + + // 4 bytes (imports_format=0) + 4 bytes (symbols_format=0) + let chained_fixups_size: u64 = 48; // dyld_chained_fixups_header + let exports_trie_size: u64 = 0; + + let chained_fixups_offset = linkedit_file_offset; + let exports_trie_offset = chained_fixups_offset + chained_fixups_size; + let linkedit_data_size = chained_fixups_size + exports_trie_size; + let linkedit_file_size = align_to(linkedit_data_size, 8); + + let total_file_size = (linkedit_file_offset + linkedit_file_size) as usize; + out.resize(total_file_size, 0); + + let mut w = Writer::new(out); + + // -- Mach-O Header -- + w.write_u32(MH_MAGIC_64); + w.write_u32(CPU_TYPE_ARM64); + w.write_u32(CPU_SUBTYPE_ARM64_ALL); + w.write_u32(MH_EXECUTE); + w.write_u32(num_load_commands); + w.write_u32(load_commands_size as u32); + w.write_u32(MH_PIE | MH_TWOLEVEL | MH_DYLDLINK); + w.write_u32(0); // reserved + + // -- LC_SEGMENT_64: __PAGEZERO -- + w.write_u32(LC_SEGMENT_64); + w.write_u32(seg_cmd_size as u32); + w.write_name16(b"__PAGEZERO"); + w.write_u64(0); + w.write_u64(PAGEZERO_SIZE); + w.write_u64(0); + w.write_u64(0); + w.write_u32(0); + w.write_u32(0); + w.write_u32(0); + w.write_u32(0); + + // -- LC_SEGMENT_64: __TEXT -- + w.write_u32(LC_SEGMENT_64); + w.write_u32((seg_cmd_size + section_hdr_size) as u32); + w.write_name16(b"__TEXT"); + w.write_u64(PAGEZERO_SIZE); + w.write_u64(text_segment_vm_size); + w.write_u64(0); + w.write_u64(text_file_offset + text_segment_file_size); + w.write_u32(VM_PROT_READ | VM_PROT_EXECUTE); + w.write_u32(VM_PROT_READ | VM_PROT_EXECUTE); + w.write_u32(1); + w.write_u32(0); + + // __text section + w.write_name16(b"__text"); + w.write_name16(b"__TEXT"); + w.write_u64(text_vm_addr); + w.write_u64(text_size); + w.write_u32(text_file_offset as u32); + w.write_u32(2); // align 2^2 + w.write_u32(0); + w.write_u32(0); + w.write_u32(S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS); + w.write_u32(0); + w.write_u32(0); + w.write_u32(0); + + // -- LC_SEGMENT_64: __LINKEDIT -- + w.write_u32(LC_SEGMENT_64); + w.write_u32(seg_cmd_size as u32); + w.write_name16(b"__LINKEDIT"); + w.write_u64(linkedit_vm_addr); + w.write_u64(align_to(linkedit_file_size, PAGE_SIZE)); + w.write_u64(linkedit_file_offset); + w.write_u64(linkedit_file_size); + w.write_u32(VM_PROT_READ); + w.write_u32(VM_PROT_READ); + w.write_u32(0); + w.write_u32(0); + + // -- LC_MAIN -- + w.write_u32(LC_MAIN); + w.write_u32(entry_cmd_size as u32); + w.write_u64(text_file_offset); + w.write_u64(0); + + // -- LC_LOAD_DYLINKER -- + w.write_u32(LC_LOAD_DYLINKER); + w.write_u32(dylinker_cmd_size as u32); + w.write_u32(12); // name offset (after cmd + cmdsize + offset fields) + w.write_bytes(DYLD_PATH); + w.write_u8(0); // NUL terminator + w.pad_to_align(8); + + // -- LC_LOAD_DYLIB: libSystem -- + w.write_u32(LC_LOAD_DYLIB); + w.write_u32(load_dylib_cmd_size as u32); + w.write_u32(24); // name offset + w.write_u32(2); // timestamp + w.write_u32(0x010000); // current_version (1.0.0 encoded) + w.write_u32(0x010000); // compatibility_version + w.write_bytes(LIBSYSTEM_PATH); + w.write_u8(0); + w.pad_to_align(8); + + // -- LC_SYMTAB (empty) -- + w.write_u32(LC_SYMTAB); + w.write_u32(symtab_cmd_size as u32); + w.write_u32(0); // symoff + w.write_u32(0); // nsyms + w.write_u32(0); // stroff + w.write_u32(0); // strsize + + // -- LC_DYSYMTAB (empty) -- + w.write_u32(LC_DYSYMTAB); + w.write_u32(dysymtab_cmd_size as u32); + for _ in 0..18 { // 18 u32 fields, all zero + w.write_u32(0); + } + + // -- LC_BUILD_VERSION -- + w.write_u32(LC_BUILD_VERSION); + w.write_u32(build_version_cmd_size as u32); + w.write_u32(PLATFORM_MACOS); // platform + w.write_u32(0x000e_0000); // minos: 14.0.0 + w.write_u32(0x000e_0000); // sdk: 14.0.0 + w.write_u32(1); // ntools + // Tool entry (ld) + w.write_u32(3); // tool = LD + w.write_u32(0x03_0001_00); // version + + // -- LC_DYLD_CHAINED_FIXUPS -- + w.write_u32(LC_DYLD_CHAINED_FIXUPS); + w.write_u32(linkedit_data_cmd_size as u32); + w.write_u32(chained_fixups_offset as u32); + w.write_u32(chained_fixups_size as u32); + + // -- LC_DYLD_EXPORTS_TRIE -- + w.write_u32(LC_DYLD_EXPORTS_TRIE); + w.write_u32(linkedit_data_cmd_size as u32); + w.write_u32(exports_trie_offset as u32); + w.write_u32(exports_trie_size as u32); + + // -- Write text section data -- + let text_start = text_file_offset as usize; + out[text_start..text_start + text_data.len()].copy_from_slice(&text_data); + + // -- Write chained fixups header in __LINKEDIT -- + let cf_start = chained_fixups_offset as usize; + // dyld_chained_fixups_header + let fixups_version: u32 = 0; + let starts_offset: u32 = 0; // no starts + let imports_offset: u32 = 0; + let symbols_offset: u32 = 0; + let imports_count: u32 = 0; + let imports_format: u32 = 1; // DYLD_CHAINED_IMPORT + let symbols_format: u32 = 0; + out[cf_start..cf_start + 4].copy_from_slice(&fixups_version.to_le_bytes()); + out[cf_start + 4..cf_start + 8].copy_from_slice(&starts_offset.to_le_bytes()); + out[cf_start + 8..cf_start + 12].copy_from_slice(&imports_offset.to_le_bytes()); + out[cf_start + 12..cf_start + 16].copy_from_slice(&symbols_offset.to_le_bytes()); + out[cf_start + 16..cf_start + 20].copy_from_slice(&imports_count.to_le_bytes()); + out[cf_start + 20..cf_start + 24].copy_from_slice(&imports_format.to_le_bytes()); + out[cf_start + 24..cf_start + 28].copy_from_slice(&symbols_format.to_le_bytes()); + + Ok(()) +} + +struct Writer<'a> { + buf: &'a mut Vec, + pos: usize, +} + +impl<'a> Writer<'a> { + fn new(buf: &'a mut Vec) -> Self { + Writer { buf, pos: 0 } + } + + fn write_u8(&mut self, val: u8) { + self.buf[self.pos] = val; + self.pos += 1; + } + + fn write_u32(&mut self, val: u32) { + self.buf[self.pos..self.pos + 4].copy_from_slice(&val.to_le_bytes()); + self.pos += 4; + } + + fn write_u64(&mut self, val: u64) { + self.buf[self.pos..self.pos + 8].copy_from_slice(&val.to_le_bytes()); + self.pos += 8; + } + + fn write_name16(&mut self, name: &[u8]) { + let mut padded = [0u8; 16]; + let len = name.len().min(16); + padded[..len].copy_from_slice(&name[..len]); + self.buf[self.pos..self.pos + 16].copy_from_slice(&padded); + self.pos += 16; + } + + fn write_bytes(&mut self, data: &[u8]) { + self.buf[self.pos..self.pos + data.len()].copy_from_slice(data); + self.pos += data.len(); + } + + fn pad_to_align(&mut self, alignment: usize) { + let aligned = (self.pos + alignment - 1) & !(alignment - 1); + while self.pos < aligned { + self.buf[self.pos] = 0; + self.pos += 1; + } + } +} + +fn align_to(value: u64, alignment: u64) -> u64 { + (value + alignment - 1) & !(alignment - 1) +} diff --git a/linker-utils/src/elf.rs b/linker-utils/src/elf.rs index dd6d5e78c..d7b80b282 100644 --- a/linker-utils/src/elf.rs +++ b/linker-utils/src/elf.rs @@ -1385,7 +1385,8 @@ impl fmt::Display for RelocationSize { } impl RelocationSize { - pub(crate) const fn bit_mask_aarch64( + #[must_use] + pub const fn bit_mask_aarch64( bit_start: u32, bit_end: u32, instruction: AArch64Instruction, From c40e44974bb9417e31c34804b31ef13ee19248c9 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Sun, 5 Apr 2026 21:43:21 +0100 Subject: [PATCH 02/21] feat: hello world.rs Signed-off-by: Giles Cope --- Cargo.lock | 63 ++ Cargo.toml | 1 + libwild/Cargo.toml | 1 + libwild/src/args/macho.rs | 297 +++++----- libwild/src/file_kind.rs | 9 + libwild/src/input_data.rs | 10 +- libwild/src/layout.rs | 15 +- libwild/src/lib.rs | 2 +- libwild/src/macho.rs | 219 ++++++- libwild/src/macho_aarch64.rs | 34 +- libwild/src/macho_writer.rs | 1050 +++++++++++++++++++++++++--------- libwild/src/platform.rs | 16 + tests/macho_tests.sh | 369 ++++++++++++ 13 files changed, 1632 insertions(+), 454 deletions(-) create mode 100755 tests/macho_tests.sh diff --git a/Cargo.lock b/Cargo.lock index ad3627c8f..5774e1416 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -209,6 +209,15 @@ dependencies = [ "rayon-core", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -446,6 +455,16 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "debugid" version = "0.8.0" @@ -494,6 +513,16 @@ dependencies = [ "thousands", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dispatch2" version = "0.3.0" @@ -591,6 +620,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.3.4" @@ -870,6 +909,7 @@ dependencies = [ "perf-event", "perfetto-recorder", "rayon", + "sha2", "sharded-offset-map", "sharded-vec-writer 0.4.0", "smallvec", @@ -1658,6 +1698,17 @@ dependencies = [ "serde_core", ] +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-offset-map" version = "0.2.0" @@ -1968,6 +2019,12 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -2015,6 +2072,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "wait-timeout" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index afc6d7685..4bac13f2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ edition = "2024" [workspace.dependencies] anyhow = "1.0.97" +sha2 = "0.10" ar = "0.9.0" atomic-take = "1.0.0" bitflags = "2.4.0" diff --git a/libwild/Cargo.toml b/libwild/Cargo.toml index 765d8d9d9..94d4119c8 100644 --- a/libwild/Cargo.toml +++ b/libwild/Cargo.toml @@ -39,6 +39,7 @@ memmap2 = { workspace = true } object = { workspace = true } perfetto-recorder = { workspace = true } rayon = { workspace = true } +sha2 = { workspace = true } sharded-offset-map = { workspace = true } sharded-vec-writer = { workspace = true } smallvec = { workspace = true } diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index de20a48a4..5232a7c96 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -1,7 +1,6 @@ // Mach-O argument parsing for the macOS linker driver interface. #![allow(unused_variables)] -use crate::args::ArgumentParser; use crate::args::CommonArgs; use crate::args::Input; use crate::args::InputSpec; @@ -53,13 +52,8 @@ impl platform::Args for MachOArgs { parse(self, input) } - fn should_strip_debug(&self) -> bool { - false - } - - fn should_strip_all(&self) -> bool { - false - } + fn should_strip_debug(&self) -> bool { false } + fn should_strip_all(&self) -> bool { false } fn entry_symbol_name<'a>(&'a self, linker_script_entry: Option<&'a [u8]>) -> &'a [u8] { linker_script_entry @@ -71,158 +65,191 @@ impl platform::Args for MachOArgs { &self.lib_search_paths } - fn output(&self) -> &std::sync::Arc { - &self.output - } - - fn common(&self) -> &crate::args::CommonArgs { - &self.common - } - - fn common_mut(&mut self) -> &mut crate::args::CommonArgs { - &mut self.common - } - - fn should_export_all_dynamic_symbols(&self) -> bool { - false - } - - fn should_export_dynamic(&self, _lib_name: &[u8]) -> bool { - false - } + fn output(&self) -> &std::sync::Arc { &self.output } + fn common(&self) -> &crate::args::CommonArgs { &self.common } + fn common_mut(&mut self) -> &mut crate::args::CommonArgs { &mut self.common } + fn should_export_all_dynamic_symbols(&self) -> bool { false } + fn should_export_dynamic(&self, _lib_name: &[u8]) -> bool { false } fn loadable_segment_alignment(&self) -> crate::alignment::Alignment { - // Apple Silicon uses 16KB pages - crate::alignment::Alignment { exponent: 14 } + crate::alignment::Alignment { exponent: 14 } // 16KB pages } - fn should_merge_sections(&self) -> bool { - false + fn base_address(&self, _output_kind: crate::output_kind::OutputKind) -> u64 { + 0x1_0000_0000 // PAGEZERO size -- Mach-O addresses start after 4GB null page } + fn should_merge_sections(&self) -> bool { false } + fn relocation_model(&self) -> crate::args::RelocationModel { self.relocation_model } - fn should_output_executable(&self) -> bool { - true - } + fn should_output_executable(&self) -> bool { true } } -/// Parse the supplied input arguments, which should not include the program name. +/// Parse macOS linker arguments. Handles the ld64-compatible flags that clang passes. pub(crate) fn parse, I: Iterator>( args: &mut MachOArgs, mut input: I, ) -> Result { let mut modifier_stack = vec![Modifiers::default()]; - let arg_parser = setup_argument_parser(); while let Some(arg) = input.next() { let arg = arg.as_ref(); - arg_parser.handle_argument(args, &mut modifier_stack, arg, &mut input)?; + + // Handle @response files + if let Some(path) = arg.strip_prefix('@') { + let file_args = crate::args::read_args_from_file(Path::new(path))?; + // Re-parse the file contents (simplified - no recursion limit) + let mut file_iter = file_args.iter().map(|s| s.as_str()); + while let Some(file_arg) = file_iter.next() { + parse_one_arg(args, file_arg, &mut file_iter, &mut modifier_stack)?; + } + continue; + } + + parse_one_arg(args, arg, &mut input, &mut modifier_stack)?; } Ok(()) } -/// Flags that macOS ld passes but we safely ignore for now. -const MACHO_IGNORED_FLAGS: &[&str] = &[ - "demangle", - "dynamic", - "lto_library", - "mllvm", - "no_deduplicate", - "no_compact_unwind", - "dead_strip", - "dead_strip_dylibs", - "headerpad_max_install_names", - "export_dynamic", - "application_extension", - "no_objc_category_merging", - "objc_abi_version", - "mark_dead_strippable_dylib", -]; - -fn setup_argument_parser() -> ArgumentParser { - let mut parser = ArgumentParser::::new(); - - parser - .declare_with_param() - .long("output") - .short("o") - .help("Set the output filename") - .execute(|args, _modifier_stack, value| { - args.output = Arc::from(Path::new(value)); - Ok(()) - }); - - parser - .declare_with_param() - .long("arch") - .help("Architecture") - .execute(|_args, _modifier_stack, _value| { - // We only support arm64 currently, ignore the flag - Ok(()) - }); - - parser - .declare_with_param() - .long("platform_version") - .help("Set platform version (takes 3 args: platform min_version sdk_version)") - .execute(|_args, _modifier_stack, _value| { - // platform_version takes 3 arguments: platform, min_version, sdk_version - // The ArgumentParser already consumed one arg for us, but we need 2 more. - // They'll get treated as unrecognised positional args. That's OK for now. - Ok(()) - }); - - parser - .declare_with_param() - .long("syslibroot") - .help("Set the system library root path") - .execute(|args, _modifier_stack, value| { - args.syslibroot = Some(Box::from(Path::new(value))); - Ok(()) - }); - - parser - .declare_with_param() - .short("e") - .help("Set the entry point symbol name") - .execute(|args, _modifier_stack, value| { - args.entry_symbol = Some(value.as_bytes().to_vec()); - Ok(()) - }); - - parser - .declare_with_param() - .prefix("l") - .help("Link with library") - .execute(|args, modifier_stack, value| { - let spec = InputSpec::Lib(Box::from(value)); - args.common.inputs.push(Input { - spec, - search_first: None, - modifiers: *modifier_stack.last().unwrap(), - }); - Ok(()) - }); - - parser - .declare_with_param() - .prefix("L") - .help("Add library search path") - .execute(|args, _modifier_stack, value| { - args.lib_search_paths.push(Box::from(Path::new(value))); - Ok(()) - }); - - // Register ignored flags - for flag in MACHO_IGNORED_FLAGS { - // Try to register flags that take no params as ignored - // Some take params (like lto_library, mllvm) -- we handle those by just - // letting them fall through to unrecognised options for now +fn parse_one_arg<'a, S: AsRef, I: Iterator>( + args: &mut MachOArgs, + arg: &str, + input: &mut I, + modifier_stack: &mut Vec, +) -> Result { + // Flags that take a following argument (must be checked before prefix matching) + match arg { + "-o" | "--output" => { + if let Some(val) = input.next() { + args.output = Arc::from(Path::new(val.as_ref())); + } + return Ok(()); + } + "-arch" => { input.next(); return Ok(()); } // consume and ignore + "-syslibroot" => { + if let Some(val) = input.next() { + args.syslibroot = Some(Box::from(Path::new(val.as_ref()))); + } + return Ok(()); + } + "-e" => { + if let Some(val) = input.next() { + args.entry_symbol = Some(val.as_ref().as_bytes().to_vec()); + } + return Ok(()); + } + // Flags that take 1 argument, ignored + "-lto_library" | "-mllvm" | "-headerpad" | "-install_name" + | "-compatibility_version" | "-current_version" | "-rpath" + | "-object_path_lto" | "-order_file" | "-exported_symbols_list" + | "-unexported_symbols_list" | "-filelist" | "-sectcreate" + | "-framework" | "-weak_framework" | "-weak_library" + | "-reexport_library" | "-umbrella" | "-allowable_client" + | "-client_name" | "-sub_library" | "-sub_umbrella" + | "-objc_abi_version" => { + input.next(); // consume the argument + return Ok(()); + } + // -platform_version takes 3 arguments: platform min_version sdk_version + "-platform_version" => { + input.next(); // platform + input.next(); // min_version + input.next(); // sdk_version + return Ok(()); + } + // Flags that take 1 argument, ignored (group 2) + "-undefined" | "-multiply_defined" | "-force_load" | "-weak-l" + | "-needed-l" | "-reexport-l" | "-upward-l" | "-alignment" => { + input.next(); + return Ok(()); + } + // No-argument flags, ignored + "-demangle" | "-dynamic" | "-no_deduplicate" | "-no_compact_unwind" + | "-dead_strip" | "-dead_strip_dylibs" | "-headerpad_max_install_names" + | "-export_dynamic" | "-application_extension" | "-no_objc_category_merging" + | "-mark_dead_strippable_dylib" | "-ObjC" | "-all_load" + | "-no_implicit_dylibs" | "-search_paths_first" | "-two_levelnamespace" + | "-flat_namespace" | "-bind_at_load" + | "-pie" | "-no_pie" | "-execute" | "-dylib" | "-bundle" => { + return Ok(()); + } + _ => {} } - parser + // -L (library search path) + if let Some(path) = arg.strip_prefix("-L") { + if path.is_empty() { + if let Some(val) = input.next() { + args.lib_search_paths.push(Box::from(Path::new(val.as_ref()))); + } + } else { + args.lib_search_paths.push(Box::from(Path::new(path))); + } + return Ok(()); + } + + // -l (link library) -- must come after -lto_library check above + if let Some(lib) = arg.strip_prefix("-l") { + if !lib.is_empty() { + // On macOS, libSystem is implicitly linked (we emit LC_LOAD_DYLIB for it). + // Skip it and other system dylibs that we handle implicitly. + if lib == "System" || lib == "c" || lib == "m" || lib == "pthread" { + return Ok(()); + } + // Try to find the library on the search path, including syslibroot + let mut found = false; + let extensions = [".tbd", ".dylib", ".a"]; + let mut search_paths: Vec> = args.lib_search_paths.clone(); + if let Some(ref root) = args.syslibroot { + search_paths.push(Box::from(root.join("usr/lib"))); + search_paths.push(Box::from(root.join("usr/lib/swift"))); + } + for ext in &extensions { + let filename = format!("lib{lib}{ext}"); + for dir in &search_paths { + let path = dir.join(&filename); + if path.exists() { + // For .tbd files, skip (text-based stubs, dylib references) + if *ext == ".tbd" { + found = true; + break; + } + args.common.inputs.push(Input { + spec: InputSpec::File(Box::from(path.as_path())), + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + found = true; + break; + } + } + if found { break; } + } + // If not found, warn but don't error (might be a system dylib we handle implicitly) + if !found { + tracing::warn!("library not found: -l{lib}"); + } + } + return Ok(()); + } + + // Unknown flags starting with - go to unrecognized + if arg.starts_with('-') { + args.common.unrecognized_options.push(arg.to_owned()); + return Ok(()); + } + + // Positional argument = input file + args.common.save_dir.handle_file(arg); + args.common.inputs.push(Input { + spec: InputSpec::File(Box::from(Path::new(arg))), + search_first: None, + modifiers: *modifier_stack.last().unwrap(), + }); + + Ok(()) } diff --git a/libwild/src/file_kind.rs b/libwild/src/file_kind.rs index 262eed43d..26858c410 100644 --- a/libwild/src/file_kind.rs +++ b/libwild/src/file_kind.rs @@ -18,6 +18,7 @@ pub(crate) enum FileKind { ElfObject, ElfDynamic, MachOObject, + FatBinary, Archive, ThinArchive, Text, @@ -72,6 +73,13 @@ impl FileKind { "Expected object file" ); Ok(FileKind::MachOObject) + } else if bytes.len() >= 8 + && (bytes.starts_with(&macho::FAT_MAGIC.to_be_bytes()) + || bytes.starts_with(&macho::FAT_MAGIC_64.to_be_bytes())) + { + // Mach-O universal (fat) binary. Currently not fully supported. + // TODO: extract the arm64 slice and process it. + Ok(FileKind::FatBinary) } else if bytes.is_ascii() { Ok(FileKind::Text) } else if bytes.starts_with(b"BC") { @@ -119,6 +127,7 @@ impl std::fmt::Display for FileKind { FileKind::ElfObject => "ELF object", FileKind::ElfDynamic => "ELF dynamic", FileKind::MachOObject => "MachO object", + FileKind::FatBinary => "fat binary", FileKind::Archive => "archive", FileKind::ThinArchive => "thin archive", FileKind::Text => "text", diff --git a/libwild/src/input_data.rs b/libwild/src/input_data.rs index 2f2d925a3..da7cd461e 100644 --- a/libwild/src/input_data.rs +++ b/libwild/src/input_data.rs @@ -615,6 +615,11 @@ impl<'data, P: Platform> TemporaryState<'data, P> { let kind = FileKind::identify_bytes(&data.bytes)?; match kind { + FileKind::FatBinary => { + // TODO: Extract arm64 slice from universal binary. + // For now, skip fat binaries (e.g. libclang_rt.osx.a). + return Ok(LoadedFileState::Archive(input_file, Vec::new())); + } FileKind::Archive => process_archive(input_file, &Arc::new(file), self), FileKind::ThinArchive => process_thin_archive(input_file, self), FileKind::Text => { @@ -707,7 +712,10 @@ impl<'data, P: Platform> TemporaryState<'data, P> { }))); } - if input_ref.is_archive_entry() && kind != FileKind::ElfObject { + if input_ref.is_archive_entry() + && kind != FileKind::ElfObject + && kind != FileKind::MachOObject + { bail!("Unexpected archive member of kind {kind:?}: {input_ref}"); } diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs index 08271031f..a62802688 100644 --- a/libwild/src/layout.rs +++ b/libwild/src/layout.rs @@ -571,6 +571,12 @@ pub(crate) struct SymbolResolutions { resolutions: Vec>>, } +impl SymbolResolutions

{ + pub(crate) fn iter(&self) -> impl Iterator>> { + self.resolutions.iter() + } +} + pub(crate) enum FileLayout<'data, P: Platform> { Prelude(PreludeLayout<'data, P>), Object(ObjectLayout<'data, P>), @@ -1641,8 +1647,8 @@ fn compute_segment_layout( let r = &complete[id.as_usize()]; let sizes = OutputRecordLayout { - file_size: r.file_end - r.file_start, - mem_size: r.mem_end - r.mem_start, + file_size: r.file_end.saturating_sub(r.file_start), + mem_size: r.mem_end.saturating_sub(r.mem_start), alignment: r.alignment, file_offset: r.file_start, mem_offset: r.mem_start, @@ -3819,7 +3825,7 @@ impl<'data, P: Platform> ObjectLayoutState<'data, P> { .symbol_section(local_symbol, local_symbol_index)? { if let Some(section_address) = section_resolutions[section_index.0].address() { - let input_offset = local_symbol.value(); + let input_offset = self.object.symbol_value_in_section(local_symbol, section_index)?; let output_offset = opt_input_to_output( self.section_relax_deltas.get(section_index.0), input_offset, @@ -4511,6 +4517,9 @@ fn layout_section_parts( file_offset = segment_alignment.align_modulo(mem_offset, file_offset as u64) as usize; } else { + // Page-align file_offset at segment boundary. + // This ensures segments don't share pages in the output file. + file_offset = segment_alignment.align_up(file_offset as u64) as usize; mem_offset = segment_alignment.align_modulo(file_offset as u64, mem_offset); } } diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index a8acdffac..9e8af51d1 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -265,7 +265,7 @@ impl Linker { let mut output = file_writer::Output::new(args, output_kind); - let mut output_sections = OutputSections::with_base_address(output_kind.base_address()); + let mut output_sections = OutputSections::with_base_address(args.base_address(output_kind)); let mut layout_rules_builder = LayoutRulesBuilder::default(); diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 70f61678b..b563bddd2 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -6,6 +6,7 @@ use crate::args::macho::MachOArgs; use crate::ensure; use crate::error; use crate::platform; +use crate::platform::SectionAttributes as _; use object::Endianness; use object::macho; use object::read::macho::MachHeader; @@ -178,6 +179,17 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } } + fn symbol_value_in_section( + &self, + symbol: &SymtabEntry, + section_index: object::SectionIndex, + ) -> crate::error::Result { + let section = &self.sections[section_index.0]; + let section_addr = section.addr.get(LE); + let sym_value = symbol.n_value(LE); + Ok(sym_value.wrapping_sub(section_addr)) + } + fn symbol_versions(&self) -> &[()]{ // Mach-O doesn't have symbol versioning &[] @@ -210,8 +222,6 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } fn section_name(&self, section_header: &SectionHeader) -> crate::error::Result<&'data [u8]> { - // Section names in Mach-O are stored inline in the section header (16 bytes). - // We need to find this section in self.sections to get the 'data lifetime. for s in self.sections { if std::ptr::eq( s as *const macho::Section64, @@ -638,11 +648,32 @@ pub(crate) struct SegmentType {} impl platform::SegmentType for SegmentType {} #[derive(Debug, Copy, Clone, Default)] -pub(crate) struct ProgramSegmentDef {} +pub(crate) struct ProgramSegmentDef { + pub(crate) writable: bool, + pub(crate) executable: bool, +} + +/// __TEXT segment: r-x, contains headers + code + read-only data +const TEXT_SEGMENT_DEF: ProgramSegmentDef = ProgramSegmentDef { + writable: false, + executable: true, +}; + +/// __DATA segment: rw-, contains writable data + GOT + BSS +const DATA_SEGMENT_DEF: ProgramSegmentDef = ProgramSegmentDef { + writable: true, + executable: false, +}; + +const MACHO_SEGMENT_DEFS: &[ProgramSegmentDef] = &[TEXT_SEGMENT_DEF, DATA_SEGMENT_DEF]; impl std::fmt::Display for ProgramSegmentDef { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "") + if self.executable { + write!(f, "__TEXT") + } else { + write!(f, "__DATA") + } } } @@ -650,19 +681,19 @@ impl platform::ProgramSegmentDef for ProgramSegmentDef { type Platform = MachO; fn is_writable(self) -> bool { - false + self.writable } fn is_executable(self) -> bool { - false + self.executable } fn always_keep(self) -> bool { - false + true // Both __TEXT and __DATA are always emitted } fn is_loadable(self) -> bool { - false + true // Both are loadable } fn is_stack(self) -> bool { @@ -674,15 +705,23 @@ impl platform::ProgramSegmentDef for ProgramSegmentDef { } fn order_key(self) -> usize { - 0 + if self.executable { 0 } else { 1 } } fn should_include_section( self, - _section_info: &crate::output_section_id::SectionOutputInfo, + section_info: &crate::output_section_id::SectionOutputInfo, _section_id: crate::output_section_id::OutputSectionId, ) -> bool { - false + let attrs = §ion_info.section_attributes; + if !attrs.is_alloc() { + return false; + } + if self.writable { + attrs.is_writable() + } else { + !attrs.is_writable() + } } } @@ -690,6 +729,15 @@ pub(crate) struct BuiltInSectionDetails {} impl platform::BuiltInSectionDetails for BuiltInSectionDetails {} +/// Mach-O specific resolution data attached to each resolved symbol. +#[derive(Debug, Copy, Clone, Default)] +pub(crate) struct MachOResolutionExt { + /// GOT entry address (if the symbol needs a GOT slot). + pub(crate) got_address: Option, + /// PLT stub address (if the symbol needs a dynamic call stub). + pub(crate) plt_address: Option, +} + #[derive(Default, Debug, Clone, Copy)] pub(crate) struct DynamicTagValues<'data> { _phantom: &'data [u8], @@ -786,7 +834,7 @@ impl platform::Platform for MachO { type CommonGroupStateExt = (); type ArchIdentifier = (); type Args = MachOArgs; - type ResolutionExt = (); + type ResolutionExt = MachOResolutionExt; type SymbolVersionIndex = (); type LayoutExt = (); type SectionIterator<'data> = MachOSectionIter<'data>; @@ -915,13 +963,50 @@ impl platform::Platform for MachO { } fn load_object_section_relocations<'data, 'scope, A: platform::Arch>( - _state: &crate::layout::ObjectLayoutState<'data, Self>, + state: &crate::layout::ObjectLayoutState<'data, Self>, _common: &mut crate::layout::CommonGroupState<'data, Self>, - _queue: &mut crate::layout::LocalWorkQueue, - _resources: &'scope crate::layout::GraphResources<'data, '_, Self>, - _section: crate::layout::Section, - _scope: &rayon::Scope<'scope>, + queue: &mut crate::layout::LocalWorkQueue, + resources: &'scope crate::layout::GraphResources<'data, '_, Self>, + section: crate::layout::Section, + scope: &rayon::Scope<'scope>, ) -> crate::error::Result { + // Scan relocations to discover referenced symbols and trigger loading + // of their containing sections. + let le = object::Endianness::Little; + let input_section = state.object.sections.get(section.index.0) + .ok_or_else(|| crate::error!("Section index out of range"))?; + let relocs = match input_section.relocations(le, state.object.data) { + Ok(r) => r, + Err(_) => return Ok(()), + }; + for reloc_raw in relocs { + let reloc = reloc_raw.info(le); + if !reloc.r_extern { continue; } + // Skip ADDEND (type 10) and SUBTRACTOR (type 1) + if reloc.r_type == 10 || reloc.r_type == 1 { continue; } + + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let local_symbol_id = state.symbol_id_range.input_to_id(sym_idx); + let symbol_id = resources.symbol_db.definition(local_symbol_id); + + // Set resolution flags based on relocation type + let is_undef = resources.symbol_db.is_undefined(symbol_id); + let flags_to_add = match reloc.r_type { + 5 | 6 => crate::value_flags::ValueFlags::GOT, // GOT_LOAD + 2 if is_undef => { + // BRANCH26 to undefined symbol needs a stub (PLT) + GOT entry + crate::value_flags::ValueFlags::PLT | crate::value_flags::ValueFlags::GOT + } + _ => crate::value_flags::ValueFlags::DIRECT, + }; + let atomic_flags = &resources.per_symbol_flags.get_atomic(symbol_id); + let previous_flags = atomic_flags.fetch_or(flags_to_add); + + // Request this symbol to be loaded (which loads its section) + if !previous_flags.has_resolution() { + queue.send_symbol_request::(symbol_id, resources, scope); + } + } Ok(()) } @@ -937,10 +1022,12 @@ impl platform::Platform for MachO { _keep_segments: &mut [bool], _args: &Self::Args, ) { + // Default keep logic is sufficient -- segments with sections are kept automatically. + // The pipeline sets keep_segments[0] = true for the first segment (__TEXT). } fn program_segment_defs() -> &'static [Self::ProgramSegmentDef] { - &[] + MACHO_SEGMENT_DEFS } fn unconditional_segment_defs() -> &'static [Self::ProgramSegmentDef] { @@ -984,7 +1071,7 @@ impl platform::Platform for MachO { secondary_order: None, }; infos[crate::output_section_id::RODATA.as_usize()] = SectionOutputInfo { - kind: SectionKind::Primary(SectionName(b"__const")), + kind: SectionKind::Primary(SectionName(b"__rodata")), section_attributes: SectionAttributes::default(), min_alignment: crate::alignment::MIN, location: None, @@ -1000,6 +1087,26 @@ impl platform::Platform for MachO { location: None, secondary_order: None, }; + infos[crate::output_section_id::GOT.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__got")), + section_attributes: SectionAttributes { + flags: 0x06, // S_NON_LAZY_SYMBOL_POINTERS + segname: *b"__DATA\0\0\0\0\0\0\0\0\0\0", + }, + min_alignment: crate::alignment::GOT_ENTRY, + location: None, + secondary_order: None, + }; + infos[crate::output_section_id::TDATA.as_usize()] = SectionOutputInfo { + kind: SectionKind::Primary(SectionName(b"__thread_data")), + section_attributes: SectionAttributes { + flags: macho::S_THREAD_LOCAL_REGULAR, + segname: *b"__DATA\0\0\0\0\0\0\0\0\0\0", + }, + min_alignment: crate::alignment::Alignment { exponent: 3 }, // 8-byte align + location: None, + secondary_order: None, + }; infos[crate::output_section_id::BSS.as_usize()] = SectionOutputInfo { kind: SectionKind::Primary(SectionName(b"__bss")), section_attributes: SectionAttributes { @@ -1121,10 +1228,14 @@ impl platform::Platform for MachO { fn allocate_header_sizes( _prelude: &mut crate::layout::PreludeLayoutState, - _sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + sizes: &mut crate::output_section_part_map::OutputSectionPartMap, _header_info: &crate::layout::HeaderInfo, _output_sections: &crate::output_section_id::OutputSections, ) { + // Reserve a full page for headers. Mach-O __TEXT segment starts at page 0 and + // includes the headers. Sections start after the headers, page-aligned. + // A full page (16KB) is more than enough for headers + load commands. + sizes.increment(crate::part_id::FILE_HEADER, 0x4000); // 16KB page } fn finalise_sizes_for_symbol<'data>( @@ -1137,10 +1248,17 @@ impl platform::Platform for MachO { } fn allocate_resolution( - _flags: crate::value_flags::ValueFlags, - _mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, + flags: crate::value_flags::ValueFlags, + mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, _output_kind: crate::output_kind::OutputKind, ) { + if flags.needs_plt() { + // Mach-O stubs are 12 bytes (adrp + ldr + br) + mem_sizes.increment(crate::part_id::PLT_GOT, 12); + // Each stub needs a GOT entry (8 bytes) for the dyld bind target + mem_sizes.increment(crate::part_id::GOT, 8); + } + // For same-image symbols, GOT_LOAD is relaxed to ADRP+ADD (no GOT needed) } fn allocate_object_symtab_space<'data>( @@ -1178,13 +1296,26 @@ impl platform::Platform for MachO { flags: crate::value_flags::ValueFlags, raw_value: u64, dynamic_symbol_index: Option, - _memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, + memory_offsets: &mut crate::output_section_part_map::OutputSectionPartMap, ) -> crate::layout::Resolution { + let mut got_address = None; + let mut plt_address = None; + + if flags.needs_plt() { + let got_addr = *memory_offsets.get(crate::part_id::GOT); + *memory_offsets.get_mut(crate::part_id::GOT) += 8; + got_address = Some(got_addr); + + let plt_addr = *memory_offsets.get(crate::part_id::PLT_GOT); + *memory_offsets.get_mut(crate::part_id::PLT_GOT) += 12; + plt_address = Some(plt_addr); + } + crate::layout::Resolution { raw_value, dynamic_symbol_index, flags, - format_specific: (), + format_specific: MachOResolutionExt { got_address, plt_address }, } } @@ -1201,7 +1332,7 @@ impl platform::Platform for MachO { } fn build_output_order_and_program_segments<'data>( - _custom: &crate::output_section_id::CustomSectionIds, + custom: &crate::output_section_id::CustomSectionIds, output_kind: OutputKind, output_sections: &crate::output_section_id::OutputSections<'data, Self>, secondary: &crate::output_section_map::OutputSectionMap< @@ -1211,11 +1342,32 @@ impl platform::Platform for MachO { crate::output_section_id::OutputOrder, crate::program_segments::ProgramSegments, ) { - let builder = crate::output_section_id::OutputOrderBuilder::::new( + use crate::output_section_id; + let mut builder = crate::output_section_id::OutputOrderBuilder::::new( output_kind, output_sections, secondary, ); + + // __TEXT segment (r-x): headers, code, read-only data, stubs + builder.add_section(output_section_id::FILE_HEADER); + builder.add_section(output_section_id::RODATA); + builder.add_sections(&custom.ro); + builder.add_section(output_section_id::TEXT); + builder.add_sections(&custom.exec); + builder.add_section(output_section_id::PLT_GOT); // __stubs (call trampolines) + builder.add_section(output_section_id::GCC_EXCEPT_TABLE); + builder.add_section(output_section_id::EH_FRAME); + + // __DATA segment (rw-): writable data, GOT, BSS + builder.add_section(output_section_id::DATA); + builder.add_sections(&custom.data); + builder.add_section(output_section_id::GOT); + builder.add_section(output_section_id::TDATA); + builder.add_section(output_section_id::TBSS); + builder.add_section(output_section_id::BSS); + builder.add_sections(&custom.bss); + builder.build() } } @@ -1227,7 +1379,11 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__text", output_section_id::TEXT), SectionRule::exact_section(b"__stubs", output_section_id::TEXT), SectionRule::exact_section(b"__stub_helper", output_section_id::TEXT), - SectionRule::exact_section(b"__const", output_section_id::RODATA), + // Sections like __const, __cstring, __literal* can appear in both __TEXT and + // __DATA segments. The pipeline groups by name, so both variants merge into one + // output section. Placing them all in DATA ensures pointers get rebase fixups. + SectionRule::exact_section(b"__const", output_section_id::DATA), + // __cstring and __literal* are truly read-only (no pointers). Keep in RODATA. SectionRule::exact_section(b"__cstring", output_section_id::RODATA), SectionRule::exact_section(b"__literal4", output_section_id::RODATA), SectionRule::exact_section(b"__literal8", output_section_id::RODATA), @@ -1236,6 +1392,13 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__la_symbol_ptr", output_section_id::DATA), SectionRule::exact_section(b"__nl_symbol_ptr", output_section_id::DATA), SectionRule::exact_section(b"__got", output_section_id::DATA), + // TLS descriptors go in TDATA (after GOT), init data follows. + // This separates TLS bind fixups from GOT bind fixups in the chain. + // __thread_vars (descriptors) goes in GOT section to separate from __thread_data. + // Both are in the DATA segment but must not overlap. + SectionRule::exact_section(b"__thread_vars", output_section_id::GOT), + SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), + SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), SectionRule::exact_section(b"__bss", output_section_id::BSS), SectionRule::exact_section(b"__common", output_section_id::BSS), SectionRule::exact_section(b"__unwind_info", output_section_id::RODATA), @@ -1245,7 +1408,7 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { }; /// Trim trailing NUL bytes from a fixed-size Mach-O name field. -fn trim_nul(name: &[u8; 16]) -> &[u8] { +pub(crate) fn trim_nul(name: &[u8; 16]) -> &[u8] { let end = name.iter().position(|&b| b == 0).unwrap_or(16); // Safety: end <= 16, and the array has 16 elements &name.as_slice()[..end] diff --git a/libwild/src/macho_aarch64.rs b/libwild/src/macho_aarch64.rs index 0b8554319..4190371e0 100644 --- a/libwild/src/macho_aarch64.rs +++ b/libwild/src/macho_aarch64.rs @@ -157,11 +157,37 @@ impl crate::platform::Arch for MachOAArch64 { } fn write_plt_entry( - _plt_entry: &mut [u8], - _got_address: u64, - _plt_address: u64, + plt_entry: &mut [u8], + got_address: u64, + plt_address: u64, ) -> crate::error::Result { - // Mach-O uses stubs instead of PLT entries; handled separately + // Mach-O __stubs entry: 12 bytes + // adrp x16, GOT_PAGE + // ldr x16, [x16, GOT_OFFSET] + // br x16 + let stub: [u8; 12] = [ + 0x10, 0x00, 0x00, 0x90, // adrp x16, #0 + 0x10, 0x02, 0x40, 0xf9, // ldr x16, [x16] + 0x00, 0x02, 0x1f, 0xd6, // br x16 + ]; + plt_entry[..12].copy_from_slice(&stub); + + // Patch ADRP with page distance to GOT entry + let stub_page = plt_address & !0xFFF; + let got_page = got_address & !0xFFF; + let page_delta = got_page.wrapping_sub(stub_page) as i64 >> 12; + let immlo = ((page_delta & 0x3) as u32) << 29; + let immhi = (((page_delta >> 2) & 0x7_FFFF) as u32) << 5; + let adrp = u32::from_le_bytes(plt_entry[0..4].try_into().unwrap()); + let adrp = (adrp & 0x9F00_001F) | immhi | immlo; + plt_entry[0..4].copy_from_slice(&adrp.to_le_bytes()); + + // Patch LDR with page offset to GOT entry (scaled by 8) + let page_off = ((got_address & 0xFFF) >> 3) as u32; + let ldr = u32::from_le_bytes(plt_entry[4..8].try_into().unwrap()); + let ldr = (ldr & 0xFFC0_03FF) | (page_off << 10); + plt_entry[4..8].copy_from_slice(&ldr.to_le_bytes()); + Ok(()) } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 63e469a93..ed6813297 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -1,26 +1,25 @@ // Mach-O output file writer. // -// Generates a minimal Mach-O executable for aarch64-apple-darwin. +// Uses the common layout pipeline's symbol resolutions and section addresses +// to produce a Mach-O executable for aarch64-apple-darwin. #![allow(dead_code)] use crate::error::Result; +use crate::layout::FileLayout; use crate::layout::Layout; +use crate::layout::ObjectLayout; use crate::macho::MachO; +use crate::output_section_id; use crate::platform::Arch; use crate::platform::Args as _; -/// Page size on Apple Silicon -const PAGE_SIZE: u64 = 0x4000; // 16KB +const PAGE_SIZE: u64 = 0x4000; +const PAGEZERO_SIZE: u64 = 0x1_0000_0000; -/// Default size for __PAGEZERO -const PAGEZERO_SIZE: u64 = 0x1_0000_0000; // 4GB - -// Mach-O constants const MH_MAGIC_64: u32 = 0xfeed_facf; const MH_EXECUTE: u32 = 2; const MH_PIE: u32 = 0x0020_0000; const MH_TWOLEVEL: u32 = 0x80; -const MH_NOUNDEFS: u32 = 1; const MH_DYLDLINK: u32 = 4; const CPU_TYPE_ARM64: u32 = 0x0100_000c; const CPU_SUBTYPE_ARM64_ALL: u32 = 0; @@ -30,17 +29,12 @@ const LC_SYMTAB: u32 = 0x02; const LC_DYSYMTAB: u32 = 0x0b; const LC_LOAD_DYLINKER: u32 = 0x0e; const LC_LOAD_DYLIB: u32 = 0x0c; -const LC_UUID: u32 = 0x1b; const LC_BUILD_VERSION: u32 = 0x32; -const LC_SOURCE_VERSION: u32 = 0x2a; const LC_DYLD_CHAINED_FIXUPS: u32 = 0x8000_0034; const LC_DYLD_EXPORTS_TRIE: u32 = 0x8000_0033; const VM_PROT_READ: u32 = 1; const VM_PROT_WRITE: u32 = 2; const VM_PROT_EXECUTE: u32 = 4; -const S_REGULAR: u32 = 0; -const S_ATTR_PURE_INSTRUCTIONS: u32 = 0x8000_0000; -const S_ATTR_SOME_INSTRUCTIONS: u32 = 0x0000_0400; const PLATFORM_MACOS: u32 = 1; const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; @@ -50,314 +44,806 @@ pub(crate) fn write>( _output: &crate::file_writer::Output, layout: &Layout<'_, MachO>, ) -> Result { - let mut buf = Vec::new(); - write_macho_to_vec(&mut buf, layout)?; + let (mappings, alloc_size) = build_mappings_and_size(layout); + let mut buf = vec![0u8; alloc_size]; + let final_size = write_macho::(&mut buf, layout, &mappings)?; + buf.truncate(final_size); let output_path = layout.symbol_db.args.output(); std::fs::write(output_path.as_ref(), &buf) - .map_err(|e| crate::error!("Failed to write output file: {e}"))?; + .map_err(|e| crate::error!("Failed to write: {e}"))?; #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - let perms = std::fs::Permissions::from_mode(0o755); - std::fs::set_permissions(output_path.as_ref(), perms) - .map_err(|e| crate::error!("Failed to set permissions: {e}"))?; + let _ = std::fs::set_permissions(output_path.as_ref(), + std::fs::Permissions::from_mode(0o755)); + } + #[cfg(target_os = "macos")] + { + let status = std::process::Command::new("codesign") + .args(["-s", "-", "--force"]) + .arg(output_path.as_ref()) + .status(); + if let Ok(s) = &status { + if !s.success() { + tracing::warn!("codesign failed with status: {s}"); + } + } } - Ok(()) } -fn write_macho_to_vec(out: &mut Vec, layout: &Layout<'_, MachO>) -> Result { - // Collect text section data from input objects - let mut text_data: Vec = Vec::new(); - - for group_layout in &layout.group_layouts { - for file_layout in &group_layout.files { - if let crate::layout::FileLayout::Object(obj) = file_layout { - for section in obj.object.sections { - use object::read::macho::Section as _; - let sectname = section.sectname(); - let name_end = sectname.iter().position(|&b| b == 0).unwrap_or(16); - if §name[..name_end] == b"__text" { - let off = section.offset(object::Endianness::Little) as usize; - let sz = section.size(object::Endianness::Little) as usize; - if sz > 0 - && let Some(data) = obj.object.data.get(off..off + sz) { - text_data.extend_from_slice(data); - } - } - } +/// Build exactly 2 segment mappings (TEXT + merged DATA) from pipeline layout. +fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, usize) { + let mut raw: Vec<(u64, u64, u64)> = Vec::new(); + let mut file_cursor: u64 = 0; + for seg in &layout.segment_layouts.segments { + if seg.sizes.file_size == 0 && seg.sizes.mem_size == 0 { continue; } + let file_off = if raw.is_empty() { 0 } else { align_to(file_cursor, PAGE_SIZE) }; + let file_sz = align_to(seg.sizes.file_size as u64, PAGE_SIZE); + raw.push((seg.sizes.mem_offset, seg.sizes.mem_offset + seg.sizes.mem_size, file_off)); + file_cursor = file_off + file_sz; + } + + let mut mappings = Vec::new(); + if let Some(&(vm_start, vm_end, file_off)) = raw.first() { + mappings.push(SegmentMapping { vm_start, vm_end, file_offset: file_off }); + } + if raw.len() > 1 { + // Merge all non-TEXT segments into one DATA mapping. + // Segments may be out of VM order, so use min/max. + let data_vm_start = raw.iter().skip(1).map(|r| r.0).min().unwrap(); + let data_vm_end = raw.iter().skip(1).map(|r| r.1).max().unwrap(); + let data_file_off = raw.iter().skip(1).map(|r| r.2).min().unwrap(); + mappings.push(SegmentMapping { + vm_start: data_vm_start, + vm_end: data_vm_end, + file_offset: data_file_off, + }); + } + + // Compute LINKEDIT offset the same way write_headers does: + // TEXT filesize is page-aligned, DATA filesize is page-aligned from its file_offset. + let text_filesize = mappings.first().map_or(PAGE_SIZE, |m| + align_to(m.vm_end - m.vm_start, PAGE_SIZE)); + let linkedit_offset = if mappings.len() > 1 { + let data_fileoff = mappings[1].file_offset; + let data_filesize = align_to( + mappings.iter().skip(1).map(|m| m.file_offset + (m.vm_end - m.vm_start)) + .max().unwrap() - data_fileoff, PAGE_SIZE); + data_fileoff + data_filesize + } else { + text_filesize + }; + let total = linkedit_offset as usize + 8192; + (mappings, total) +} + +/// A rebase fixup: an absolute pointer that needs ASLR adjustment. +struct RebaseFixup { + file_offset: usize, + target: u64, +} + +/// A bind fixup: a GOT entry that dyld must fill with a dylib symbol address. +struct BindFixup { + file_offset: usize, + import_index: u32, +} + +/// Returns the actual final file size. +fn write_macho>( + out: &mut [u8], + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], +) -> Result { + let le = object::Endianness::Little; + let header_layout = layout.section_layouts.get(output_section_id::FILE_HEADER); + + // Collect fixups during section writing and stub generation + let mut rebase_fixups: Vec = Vec::new(); + let mut bind_fixups: Vec = Vec::new(); + let mut import_names: Vec> = Vec::new(); + + // Copy section data and apply relocations + for group in &layout.group_layouts { + for file_layout in &group.files { + if let FileLayout::Object(obj) = file_layout { + write_object_sections(out, obj, layout, mappings, le, + &mut rebase_fixups, &mut bind_fixups, &mut import_names)?; } } } - if text_data.is_empty() { - text_data.extend_from_slice(&[ - 0x40, 0x05, 0x80, 0xd2, // mov x0, #42 - 0xc0, 0x03, 0x5f, 0xd6, // ret - ]); + // Write PLT stubs and collect bind fixups for imported symbols + write_stubs_and_got::(out, layout, mappings, &mut bind_fixups, &mut import_names)?; + + // Populate GOT entries for non-import symbols + write_got_entries(out, layout, mappings)?; + + // Build chained fixup data: merge rebase + bind, encode per-page chains + rebase_fixups.sort_by_key(|f| f.file_offset); + bind_fixups.sort_by_key(|f| f.file_offset); + + let data_seg_start = if mappings.len() > 1 { mappings[1].file_offset as usize } else { usize::MAX }; + let data_seg_end = if mappings.len() > 1 { + mappings[1].file_offset as usize + (mappings[1].vm_end - mappings[1].vm_start) as usize + } else { 0 }; + + let mut all_data_fixups: Vec<(usize, u64)> = Vec::new(); + for f in &rebase_fixups { + if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + let target_offset = f.target.wrapping_sub(PAGEZERO_SIZE); + all_data_fixups.push((f.file_offset, target_offset & 0xF_FFFF_FFFF)); + } + for f in &bind_fixups { + if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + let encoded = (1u64 << 63) | (f.import_index as u64 & 0xFF_FFFF); + all_data_fixups.push((f.file_offset, encoded)); + } + all_data_fixups.sort_by_key(|&(off, _)| off); + + // Encode per-page chains + let data_seg_file_off = if mappings.len() > 1 { mappings[1].file_offset } else { 0 }; + for i in 0..all_data_fixups.len() { + let (file_off, mut encoded) = all_data_fixups[i]; + let next_stride = if i + 1 < all_data_fixups.len() { + let cur_page = (file_off as u64 - data_seg_file_off) / PAGE_SIZE; + let next_page = (all_data_fixups[i + 1].0 as u64 - data_seg_file_off) / PAGE_SIZE; + if cur_page == next_page { + ((all_data_fixups[i + 1].0 - file_off) / 4) as u64 + } else { 0 } + } else { 0 }; + + // Both bind and rebase use bits 51-62 for next (12 bits, 4-byte stride) + encoded |= (next_stride & 0xFFF) << 51; + if file_off + 8 <= out.len() { + out[file_off..file_off + 8].copy_from_slice(&encoded.to_le_bytes()); + } } - let text_size = text_data.len() as u64; - - // Struct sizes - let header_size: u64 = 32; - let seg_cmd_size: u64 = 72; - let section_hdr_size: u64 = 80; - let entry_cmd_size: u64 = 24; - let symtab_cmd_size: u64 = 24; - let dysymtab_cmd_size: u64 = 80; - let build_version_cmd_size: u64 = 32; // 24 base + 8 for one tool - let _source_version_cmd_size: u64 = 16; - let _uuid_cmd_size: u64 = 24; - let linkedit_data_cmd_size: u64 = 16; // for chained fixups and exports trie - - // LC_LOAD_DYLINKER: cmd(4) + cmdsize(4) + name_offset(4) + path + padding to 8-byte align - let dylinker_path_len = DYLD_PATH.len() + 1; // +1 for NUL - let dylinker_cmd_size = align_to((12 + dylinker_path_len) as u64, 8); - - // LC_LOAD_DYLIB: cmd(4) + cmdsize(4) + offset(4) + timestamp(4) + current_version(4) + compat_version(4) + name + padding - let dylib_name_len = LIBSYSTEM_PATH.len() + 1; - let load_dylib_cmd_size = align_to((24 + dylib_name_len) as u64, 8); - - let num_load_commands: u32 = 11; - let load_commands_size: u64 = seg_cmd_size // __PAGEZERO - + seg_cmd_size + section_hdr_size // __TEXT + __text - + seg_cmd_size // __LINKEDIT - + entry_cmd_size // LC_MAIN - + dylinker_cmd_size // LC_LOAD_DYLINKER - + load_dylib_cmd_size // LC_LOAD_DYLIB - + symtab_cmd_size // LC_SYMTAB - + dysymtab_cmd_size // LC_DYSYMTAB - + build_version_cmd_size // LC_BUILD_VERSION - + linkedit_data_cmd_size // LC_DYLD_CHAINED_FIXUPS - + linkedit_data_cmd_size; // LC_DYLD_EXPORTS_TRIE - - let header_and_cmds = header_size + load_commands_size; - let text_file_offset = align_to(header_and_cmds, PAGE_SIZE); - let text_vm_addr = PAGEZERO_SIZE + text_file_offset; - let text_segment_file_size = align_to(text_size, PAGE_SIZE); - let text_segment_vm_size = text_file_offset + text_segment_file_size; - - let linkedit_file_offset = text_file_offset + text_segment_file_size; - let linkedit_vm_addr = PAGEZERO_SIZE + linkedit_file_offset; - - // __LINKEDIT needs to contain at least the chained fixups header (empty) - // Minimal chained fixups: 4 bytes (fixups_version=0) + 4 bytes (starts_offset=0) + - // 4 bytes (imports_offset=0) + 4 bytes (symbols_offset=0) + 4 bytes (imports_count=0) + - // 4 bytes (imports_format=0) + 4 bytes (symbols_format=0) - let chained_fixups_size: u64 = 48; // dyld_chained_fixups_header - let exports_trie_size: u64 = 0; - - let chained_fixups_offset = linkedit_file_offset; - let exports_trie_offset = chained_fixups_offset + chained_fixups_size; - let linkedit_data_size = chained_fixups_size + exports_trie_size; - let linkedit_file_size = align_to(linkedit_data_size, 8); - - let total_file_size = (linkedit_file_offset + linkedit_file_size) as usize; - out.resize(total_file_size, 0); - - let mut w = Writer::new(out); - - // -- Mach-O Header -- - w.write_u32(MH_MAGIC_64); - w.write_u32(CPU_TYPE_ARM64); - w.write_u32(CPU_SUBTYPE_ARM64_ALL); - w.write_u32(MH_EXECUTE); - w.write_u32(num_load_commands); - w.write_u32(load_commands_size as u32); - w.write_u32(MH_PIE | MH_TWOLEVEL | MH_DYLDLINK); - w.write_u32(0); // reserved - - // -- LC_SEGMENT_64: __PAGEZERO -- - w.write_u32(LC_SEGMENT_64); - w.write_u32(seg_cmd_size as u32); - w.write_name16(b"__PAGEZERO"); - w.write_u64(0); - w.write_u64(PAGEZERO_SIZE); - w.write_u64(0); - w.write_u64(0); - w.write_u32(0); - w.write_u32(0); - w.write_u32(0); - w.write_u32(0); - - // -- LC_SEGMENT_64: __TEXT -- - w.write_u32(LC_SEGMENT_64); - w.write_u32((seg_cmd_size + section_hdr_size) as u32); - w.write_name16(b"__TEXT"); - w.write_u64(PAGEZERO_SIZE); - w.write_u64(text_segment_vm_size); - w.write_u64(0); - w.write_u64(text_file_offset + text_segment_file_size); - w.write_u32(VM_PROT_READ | VM_PROT_EXECUTE); - w.write_u32(VM_PROT_READ | VM_PROT_EXECUTE); - w.write_u32(1); - w.write_u32(0); - - // __text section - w.write_name16(b"__text"); - w.write_name16(b"__TEXT"); - w.write_u64(text_vm_addr); - w.write_u64(text_size); - w.write_u32(text_file_offset as u32); - w.write_u32(2); // align 2^2 - w.write_u32(0); - w.write_u32(0); - w.write_u32(S_REGULAR | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS); - w.write_u32(0); - w.write_u32(0); - w.write_u32(0); - - // -- LC_SEGMENT_64: __LINKEDIT -- - w.write_u32(LC_SEGMENT_64); - w.write_u32(seg_cmd_size as u32); - w.write_name16(b"__LINKEDIT"); - w.write_u64(linkedit_vm_addr); - w.write_u64(align_to(linkedit_file_size, PAGE_SIZE)); - w.write_u64(linkedit_file_offset); - w.write_u64(linkedit_file_size); - w.write_u32(VM_PROT_READ); - w.write_u32(VM_PROT_READ); - w.write_u32(0); - w.write_u32(0); - - // -- LC_MAIN -- - w.write_u32(LC_MAIN); - w.write_u32(entry_cmd_size as u32); - w.write_u64(text_file_offset); - w.write_u64(0); - - // -- LC_LOAD_DYLINKER -- - w.write_u32(LC_LOAD_DYLINKER); - w.write_u32(dylinker_cmd_size as u32); - w.write_u32(12); // name offset (after cmd + cmdsize + offset fields) - w.write_bytes(DYLD_PATH); - w.write_u8(0); // NUL terminator - w.pad_to_align(8); - - // -- LC_LOAD_DYLIB: libSystem -- - w.write_u32(LC_LOAD_DYLIB); - w.write_u32(load_dylib_cmd_size as u32); - w.write_u32(24); // name offset - w.write_u32(2); // timestamp - w.write_u32(0x010000); // current_version (1.0.0 encoded) - w.write_u32(0x010000); // compatibility_version - w.write_bytes(LIBSYSTEM_PATH); - w.write_u8(0); - w.pad_to_align(8); - - // -- LC_SYMTAB (empty) -- - w.write_u32(LC_SYMTAB); - w.write_u32(symtab_cmd_size as u32); - w.write_u32(0); // symoff - w.write_u32(0); // nsyms - w.write_u32(0); // stroff - w.write_u32(0); // strsize - - // -- LC_DYSYMTAB (empty) -- - w.write_u32(LC_DYSYMTAB); - w.write_u32(dysymtab_cmd_size as u32); - for _ in 0..18 { // 18 u32 fields, all zero - w.write_u32(0); + let has_fixups = !all_data_fixups.is_empty(); + let n_imports = import_names.len() as u32; + + // Build symbol name pool for imports + let mut symbols_pool = vec![0u8]; + let mut import_name_offsets: Vec = Vec::new(); + for name in &import_names { + import_name_offsets.push(symbols_pool.len() as u32); + symbols_pool.extend_from_slice(name); + symbols_pool.push(0); } - // -- LC_BUILD_VERSION -- - w.write_u32(LC_BUILD_VERSION); - w.write_u32(build_version_cmd_size as u32); - w.write_u32(PLATFORM_MACOS); // platform - w.write_u32(0x000e_0000); // minos: 14.0.0 - w.write_u32(0x000e_0000); // sdk: 14.0.0 - w.write_u32(1); // ntools - // Tool entry (ld) - w.write_u32(3); // tool = LD - w.write_u32(0x03_0001_00); // version - - // -- LC_DYLD_CHAINED_FIXUPS -- - w.write_u32(LC_DYLD_CHAINED_FIXUPS); - w.write_u32(linkedit_data_cmd_size as u32); - w.write_u32(chained_fixups_offset as u32); - w.write_u32(chained_fixups_size as u32); - - // -- LC_DYLD_EXPORTS_TRIE -- - w.write_u32(LC_DYLD_EXPORTS_TRIE); - w.write_u32(linkedit_data_cmd_size as u32); - w.write_u32(exports_trie_offset as u32); - w.write_u32(exports_trie_size as u32); - - // -- Write text section data -- - let text_start = text_file_offset as usize; - out[text_start..text_start + text_data.len()].copy_from_slice(&text_data); - - // -- Write chained fixups header in __LINKEDIT -- - let cf_start = chained_fixups_offset as usize; - // dyld_chained_fixups_header - let fixups_version: u32 = 0; - let starts_offset: u32 = 0; // no starts - let imports_offset: u32 = 0; - let symbols_offset: u32 = 0; - let imports_count: u32 = 0; - let imports_format: u32 = 1; // DYLD_CHAINED_IMPORT - let symbols_format: u32 = 0; - out[cf_start..cf_start + 4].copy_from_slice(&fixups_version.to_le_bytes()); - out[cf_start + 4..cf_start + 8].copy_from_slice(&starts_offset.to_le_bytes()); - out[cf_start + 8..cf_start + 12].copy_from_slice(&imports_offset.to_le_bytes()); - out[cf_start + 12..cf_start + 16].copy_from_slice(&symbols_offset.to_le_bytes()); - out[cf_start + 16..cf_start + 20].copy_from_slice(&imports_count.to_le_bytes()); - out[cf_start + 20..cf_start + 24].copy_from_slice(&imports_format.to_le_bytes()); - out[cf_start + 24..cf_start + 28].copy_from_slice(&symbols_format.to_le_bytes()); + // Compute chained fixups data size + let has_data = mappings.len() > 1 && (mappings[1].vm_end > mappings[1].vm_start); + let seg_count = if has_data { 4u32 } else { 3u32 }; + let starts_in_image_size = 4 + 4 * seg_count; + let page_count = if has_fixups && has_data { + let data_mem_size = mappings[1].vm_end - mappings[1].vm_start; + ((data_mem_size + PAGE_SIZE - 1) / PAGE_SIZE) as u32 + } else { 0 }; + + let cf_data_size = if !has_fixups { + (32 + 4 + 4 * seg_count + 8).max(48) + } else { + let seg_starts_size = 22 + 2 * page_count; + let imports_size = 4 * n_imports; + 32 + starts_in_image_size + seg_starts_size + imports_size + symbols_pool.len() as u32 + }; + + // Write headers + let header_offset = header_layout.file_offset; + let chained_fixups_offset = write_headers(out, header_offset, layout, mappings, cf_data_size)?; + + // Write chained fixups + let final_size = if let Some(cf_off) = chained_fixups_offset { + if !has_fixups { + let cf = cf_off as usize; + if cf + cf_data_size as usize <= out.len() { + // Minimal header with correct seg_count and imports_format + let starts_off = 32u32; + out[cf + 4..cf + 8].copy_from_slice(&starts_off.to_le_bytes()); // starts_offset + let imports_off = starts_off + 4 + 4 * seg_count; + out[cf + 8..cf + 12].copy_from_slice(&imports_off.to_le_bytes()); // imports_offset + out[cf + 12..cf + 16].copy_from_slice(&imports_off.to_le_bytes()); // symbols_offset + out[cf + 20..cf + 24].copy_from_slice(&1u32.to_le_bytes()); // imports_format + let si = cf + starts_off as usize; + out[si..si + 4].copy_from_slice(&seg_count.to_le_bytes()); + } + cf + cf_data_size as usize + } else { + write_chained_fixups_header( + out, cf_off as usize, &all_data_fixups, n_imports, + &import_name_offsets, &symbols_pool, mappings, + )?; + cf_off as usize + cf_data_size as usize + } + } else { + out.len() + }; + + Ok(final_size) +} + +/// Write PLT stubs and GOT bind entries for imported symbols. +fn write_stubs_and_got>( + out: &mut [u8], + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], + bind_fixups: &mut Vec, + import_names: &mut Vec>, +) -> Result { + use crate::symbol_db::SymbolId; + + for (sym_idx, res) in layout.symbol_resolutions.iter().enumerate() { + let Some(res) = res else { continue }; + let Some(plt_addr) = res.format_specific.plt_address else { continue }; + let Some(got_addr) = res.format_specific.got_address else { continue }; + + if let Some(plt_file_off) = vm_addr_to_file_offset(plt_addr, mappings) { + if plt_file_off + 12 <= out.len() { + A::write_plt_entry(&mut out[plt_file_off..plt_file_off + 12], got_addr, plt_addr)?; + } + } + if let Some(got_file_off) = vm_addr_to_file_offset(got_addr, mappings) { + let import_index = import_names.len() as u32; + let symbol_id = SymbolId::from_usize(sym_idx); + let name = match layout.symbol_db.symbol_name(symbol_id) { + Ok(n) => n.bytes().to_vec(), + Err(_) => b"".to_vec(), + }; + import_names.push(name); + bind_fixups.push(BindFixup { file_offset: got_file_off, import_index }); + } + } + Ok(()) +} + +/// Fill GOT entries with target symbol addresses (for non-import symbols). +fn write_got_entries( + out: &mut [u8], + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], +) -> Result { + for res in layout.symbol_resolutions.iter().flatten() { + if res.format_specific.plt_address.is_some() { continue; } // handled by stubs + if let Some(got_vm_addr) = res.format_specific.got_address { + if let Some(file_off) = vm_addr_to_file_offset(got_vm_addr, mappings) { + if file_off + 8 <= out.len() { + out[file_off..file_off + 8].copy_from_slice(&res.raw_value.to_le_bytes()); + } + } + } + } Ok(()) } -struct Writer<'a> { - buf: &'a mut Vec, - pos: usize, +/// Copy an object's section data to the output and apply relocations. +fn write_object_sections( + out: &mut [u8], + obj: &ObjectLayout<'_, MachO>, + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], + le: object::Endianness, + rebase_fixups: &mut Vec, + bind_fixups: &mut Vec, + import_names: &mut Vec>, +) -> Result { + use object::read::macho::Section as MachOSection; + + for (sec_idx, _slot) in obj.sections.iter().enumerate() { + let section_res = &obj.section_resolutions[sec_idx]; + let Some(output_addr) = section_res.address() else { continue }; + let Some(file_offset) = vm_addr_to_file_offset(output_addr, mappings) else { continue }; + + let input_section = match obj.object.sections.get(sec_idx) { + Some(s) => s, + None => continue, + }; + + let sec_type = input_section.flags(le) & 0xFF; + if sec_type == 0x01 || sec_type == 0x0C || sec_type == 0x12 { continue; } + + let input_offset = input_section.offset(le) as usize; + let input_size = input_section.size(le) as usize; + if input_size == 0 || input_offset == 0 { continue; } + + let input_data = match obj.object.data.get(input_offset..input_offset + input_size) { + Some(d) => d, + None => continue, + }; + + if file_offset + input_size <= out.len() { + out[file_offset..file_offset + input_size].copy_from_slice(input_data); + } + + if let Ok(relocs) = input_section.relocations(le, obj.object.data) { + apply_relocations(out, file_offset, output_addr, relocs, obj, layout, le, + rebase_fixups, bind_fixups, import_names)?; + } + } + Ok(()) } -impl<'a> Writer<'a> { - fn new(buf: &'a mut Vec) -> Self { - Writer { buf, pos: 0 } +/// Apply relocations for a section. +fn apply_relocations( + out: &mut [u8], + section_file_offset: usize, + section_vm_addr: u64, + relocs: &[object::macho::Relocation], + obj: &ObjectLayout<'_, MachO>, + layout: &Layout<'_, MachO>, + le: object::Endianness, + rebase_fixups: &mut Vec, + bind_fixups: &mut Vec, + import_names: &mut Vec>, +) -> Result { + let mut pending_addend: i64 = 0; + + for reloc_raw in relocs { + let reloc = reloc_raw.info(le); + + if reloc.r_type == 10 { pending_addend = reloc.r_symbolnum as i64; continue; } + if reloc.r_type == 1 { continue; } + + let addend = pending_addend; + pending_addend = 0; + + let patch_file_offset = section_file_offset + reloc.r_address as usize; + let pc_addr = section_vm_addr + reloc.r_address as u64; + if patch_file_offset + 4 > out.len() { continue; } + + let (target_addr, got_addr, plt_addr) = if reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let sym_id = obj.symbol_id_range.input_to_id(sym_idx); + match layout.merged_symbol_resolution(sym_id) { + Some(res) => (res.raw_value, res.format_specific.got_address, res.format_specific.plt_address), + None => continue, + } + } else { + // Non-extern: r_symbolnum is 1-based section ordinal. + // target = output_section_address + addend + let sec_ord = reloc.r_symbolnum as usize; + if sec_ord == 0 { continue; } + let sec_idx = sec_ord - 1; + let Some(output_sec_addr) = obj.section_resolutions.get(sec_idx).and_then(|r| r.address()) else { + continue; + }; + // Return section base; addend is added below along with extern path. + (output_sec_addr, None, None) + }; + + let target_addr = (target_addr as i64 + addend) as u64; + + match reloc.r_type { + 2 => { // ARM64_RELOC_BRANCH26 + let branch_target = plt_addr.unwrap_or(target_addr); + let offset = branch_target.wrapping_sub(pc_addr) as i64; + let imm26 = ((offset >> 2) & 0x03FF_FFFF) as u32; + let insn = read_u32(out, patch_file_offset); + write_u32_at(out, patch_file_offset, (insn & 0xFC00_0000) | imm26); + } + 3 => { write_adrp(out, patch_file_offset, pc_addr, target_addr); } + 4 => { write_pageoff12(out, patch_file_offset, target_addr); } + 5 => { // ARM64_RELOC_GOT_LOAD_PAGE21 + if let Some(got) = got_addr { + write_adrp(out, patch_file_offset, pc_addr, got); + } else { + write_adrp(out, patch_file_offset, pc_addr, target_addr); + } + } + 6 => { // ARM64_RELOC_GOT_LOAD_PAGEOFF12 + if let Some(got) = got_addr { + let page_off = (got & 0xFFF) as u32; + let insn = read_u32(out, patch_file_offset); + let imm12 = (page_off >> 3) & 0xFFF; + write_u32_at(out, patch_file_offset, (insn & 0xFFC0_03FF) | (imm12 << 10)); + } else { + let page_off = (target_addr & 0xFFF) as u32; + let insn = read_u32(out, patch_file_offset); + let rd = insn & 0x1F; + let rn = (insn >> 5) & 0x1F; + write_u32_at(out, patch_file_offset, 0x9100_0000 | (page_off << 10) | (rn << 5) | rd); + } + } + 8 => { write_adrp(out, patch_file_offset, pc_addr, target_addr); } + 9 => { // ARM64_RELOC_TLVP_LOAD_PAGEOFF12 -> relax to ADD + let page_off = (target_addr & 0xFFF) as u32; + let insn = read_u32(out, patch_file_offset); + let rd = insn & 0x1F; + let rn = (insn >> 5) & 0x1F; + write_u32_at(out, patch_file_offset, 0x9100_0000 | (page_off << 10) | (rn << 5) | rd); + } + 0 if reloc.r_length == 3 => { // ARM64_RELOC_UNSIGNED 64-bit + if patch_file_offset + 8 <= out.len() { + if reloc.r_extern && target_addr == 0 { + // Extern undefined symbol (e.g. _tlv_bootstrap): bind fixup + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let sym_id = obj.symbol_id_range.input_to_id(sym_idx); + let name = match layout.symbol_db.symbol_name(sym_id) { + Ok(n) => n.bytes().to_vec(), + Err(_) => b"".to_vec(), + }; + let import_index = import_names.len() as u32; + import_names.push(name); + bind_fixups.push(BindFixup { file_offset: patch_file_offset, import_index }); + } else { + // Check if target is in TLS data — write offset, not rebase + let tdata = layout.section_layouts.get(output_section_id::TDATA); + let tbss = layout.section_layouts.get(output_section_id::TBSS); + let in_tdata = tdata.mem_size > 0 + && target_addr >= tdata.mem_offset + && target_addr < tdata.mem_offset + tdata.mem_size; + let in_tbss = tbss.mem_size > 0 + && target_addr >= tbss.mem_offset + && target_addr < tbss.mem_offset + tbss.mem_size; + if in_tdata || in_tbss { + // TLS offset relative to the init template start. + // Template = init data in TDATA (after descriptors) + TBSS. + // Compute descriptor size by scanning for type 0x13 sections in this object. + // Find the init data template start: minimum address of + // type 0x11 (S_THREAD_LOCAL_REGULAR) sections in this object. + let mut tls_init_start = u64::MAX; + let mut tls_init_size = 0u64; + for (si, _) in obj.sections.iter().enumerate() { + if let Some(s) = obj.object.sections.get(si) { + use object::read::macho::Section as _; + let stype = s.flags(le) & 0xFF; + if stype == 0x11 { // S_THREAD_LOCAL_REGULAR + if let Some(addr) = obj.section_resolutions[si].address() { + tls_init_start = tls_init_start.min(addr); + tls_init_size += s.size(le); + } + } + } + } + // If no init data sections, use TDATA end as base + if tls_init_start == u64::MAX { + tls_init_start = tdata.mem_offset + tdata.mem_size; + } + let tls_offset = if in_tbss { + tls_init_size + target_addr.saturating_sub(tbss.mem_offset) + } else { + target_addr.saturating_sub(tls_init_start) + }; + out[patch_file_offset..patch_file_offset + 8] + .copy_from_slice(&tls_offset.to_le_bytes()); + } else { + rebase_fixups.push(RebaseFixup { + file_offset: patch_file_offset, target: target_addr, + }); + } + } + } + } + _ => {} + } } + Ok(()) +} - fn write_u8(&mut self, val: u8) { - self.buf[self.pos] = val; - self.pos += 1; +/// Write full chained fixups header with imports and symbol names. +fn write_chained_fixups_header( + out: &mut [u8], + cf_offset: usize, + all_fixups: &[(usize, u64)], + n_imports: u32, + import_name_offsets: &[u32], + symbols_pool: &[u8], + mappings: &[SegmentMapping], +) -> Result { + let has_data = mappings.len() > 1 && (mappings[1].vm_end > mappings[1].vm_start); + let seg_count = if has_data { 4u32 } else { 3u32 }; + let data_seg_idx: usize = 2; + let starts_offset: u32 = 32; + let starts_in_image_size = 4 + 4 * seg_count as usize; + + let (data_seg_file_offset, page_count) = if mappings.len() > 1 { + let m = &mappings[1]; + let mem_size = m.vm_end - m.vm_start; + (m.file_offset, ((mem_size + PAGE_SIZE - 1) / PAGE_SIZE) as u16) + } else { + (0, 0) + }; + + let seg_starts_size = 22 + 2 * page_count as usize; + let seg_starts_offset_in_image = starts_in_image_size as u32; + + let imports_table_offset = starts_offset + starts_in_image_size as u32 + seg_starts_size as u32; + let imports_size = 4 * n_imports; + let symbols_offset = imports_table_offset + imports_size; + + let w = &mut out[cf_offset..]; + + w[0..4].copy_from_slice(&0u32.to_le_bytes()); + w[4..8].copy_from_slice(&starts_offset.to_le_bytes()); + w[8..12].copy_from_slice(&imports_table_offset.to_le_bytes()); + w[12..16].copy_from_slice(&symbols_offset.to_le_bytes()); + w[16..20].copy_from_slice(&n_imports.to_le_bytes()); + w[20..24].copy_from_slice(&1u32.to_le_bytes()); + w[24..28].copy_from_slice(&0u32.to_le_bytes()); + + let si = starts_offset as usize; + w[si..si+4].copy_from_slice(&seg_count.to_le_bytes()); + for seg in 0..seg_count as usize { + let off: u32 = if seg == data_seg_idx { seg_starts_offset_in_image } else { 0 }; + w[si + 4 + seg * 4..si + 4 + seg * 4 + 4].copy_from_slice(&off.to_le_bytes()); } - fn write_u32(&mut self, val: u32) { - self.buf[self.pos..self.pos + 4].copy_from_slice(&val.to_le_bytes()); - self.pos += 4; + let ss = si + seg_starts_offset_in_image as usize; + w[ss..ss+4].copy_from_slice(&(seg_starts_size as u32).to_le_bytes()); + w[ss+4..ss+6].copy_from_slice(&(PAGE_SIZE as u16).to_le_bytes()); + w[ss+6..ss+8].copy_from_slice(&6u16.to_le_bytes()); + let seg_offset_val: u64 = if mappings.len() > 1 { mappings[1].vm_start - PAGEZERO_SIZE } else { 0 }; + w[ss+8..ss+16].copy_from_slice(&seg_offset_val.to_le_bytes()); + w[ss+16..ss+20].copy_from_slice(&0u32.to_le_bytes()); + w[ss+20..ss+22].copy_from_slice(&page_count.to_le_bytes()); + + let mut page_starts = vec![0xFFFFu16; page_count as usize]; + for &(file_off, _) in all_fixups { + if data_seg_file_offset == 0 || (file_off as u64) < data_seg_file_offset { continue; } + let offset_in_seg = file_off as u64 - data_seg_file_offset; + let page_idx = (offset_in_seg / PAGE_SIZE) as usize; + let offset_in_page = (offset_in_seg % PAGE_SIZE) as u16; + if page_idx < page_starts.len() && page_starts[page_idx] == 0xFFFF { + page_starts[page_idx] = offset_in_page; + } + } + for (p, &ps) in page_starts.iter().enumerate() { + w[ss + 22 + p * 2..ss + 22 + p * 2 + 2].copy_from_slice(&ps.to_le_bytes()); } - fn write_u64(&mut self, val: u64) { - self.buf[self.pos..self.pos + 8].copy_from_slice(&val.to_le_bytes()); - self.pos += 8; + let it = imports_table_offset as usize; + for (i, &name_off) in import_name_offsets.iter().enumerate() { + let import_val: u32 = 1u32 | ((name_off & 0x7F_FFFF) << 9); // lib_ordinal=1 + w[it + i * 4..it + i * 4 + 4].copy_from_slice(&import_val.to_le_bytes()); } - fn write_name16(&mut self, name: &[u8]) { - let mut padded = [0u8; 16]; - let len = name.len().min(16); - padded[..len].copy_from_slice(&name[..len]); - self.buf[self.pos..self.pos + 16].copy_from_slice(&padded); - self.pos += 16; + let sp = symbols_offset as usize; + if sp + symbols_pool.len() <= w.len() { + w[sp..sp + symbols_pool.len()].copy_from_slice(symbols_pool); } - fn write_bytes(&mut self, data: &[u8]) { - self.buf[self.pos..self.pos + data.len()].copy_from_slice(data); - self.pos += data.len(); + Ok(()) +} + +struct SegmentMapping { + vm_start: u64, + vm_end: u64, + file_offset: u64, +} + +fn vm_addr_to_file_offset(vm_addr: u64, mappings: &[SegmentMapping]) -> Option { + for m in mappings { + if vm_addr >= m.vm_start && vm_addr < m.vm_end { + return Some((m.file_offset + (vm_addr - m.vm_start)) as usize); + } } + None +} - fn pad_to_align(&mut self, alignment: usize) { - let aligned = (self.pos + alignment - 1) & !(alignment - 1); - while self.pos < aligned { - self.buf[self.pos] = 0; - self.pos += 1; +fn write_adrp(out: &mut [u8], offset: usize, pc: u64, target: u64) { + let page_off = (target & !0xFFF).wrapping_sub(pc & !0xFFF) as i64; + let imm = (page_off >> 12) as u32; + let insn = read_u32(out, offset); + write_u32_at(out, offset, (insn & 0x9F00_001F) | ((imm & 0x1F_FFFC) << 3) | ((imm & 0x3) << 29)); +} + +fn write_pageoff12(out: &mut [u8], offset: usize, target: u64) { + let page_off = (target & 0xFFF) as u32; + let insn = read_u32(out, offset); + let shift = if (insn & 0x3B00_0000) == 0x3900_0000 { (insn >> 30) & 0x3 } else { 0 }; + let imm12 = (page_off >> shift) & 0xFFF; + write_u32_at(out, offset, (insn & 0xFFC0_03FF) | (imm12 << 10)); +} + +/// Write Mach-O headers. Returns the chained fixups file offset. +fn write_headers( + out: &mut [u8], + offset: usize, + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], + chained_fixups_data_size: u32, +) -> Result> { + let text_vm_start = mappings.first().map_or(PAGEZERO_SIZE, |m| m.vm_start); + let text_vm_end = mappings.first().map_or(PAGEZERO_SIZE + PAGE_SIZE, |m| m.vm_end); + let text_filesize = align_to(text_vm_end - text_vm_start, PAGE_SIZE); + + let has_data = mappings.len() > 1; + let data_vmaddr = mappings.get(1).map_or(0, |m| m.vm_start); + let data_vm_end = mappings.iter().skip(1).map(|m| m.vm_end).max().unwrap_or(data_vmaddr); + let data_vmsize = align_to(data_vm_end - data_vmaddr, PAGE_SIZE); + let data_fileoff = mappings.get(1).map_or(0, |m| m.file_offset); + let data_filesize = if has_data { + align_to(mappings.iter().skip(1).map(|m| m.file_offset + (m.vm_end - m.vm_start)).max().unwrap() - data_fileoff, PAGE_SIZE) + } else { 0 }; + + let text_layout = layout.section_layouts.get(output_section_id::TEXT); + let entry_addr = layout.entry_symbol_address().unwrap_or(0); + let entry_offset = vm_addr_to_file_offset(entry_addr, mappings).unwrap_or(text_layout.file_offset); + + let tdata_layout = layout.section_layouts.get(output_section_id::TDATA); + let tbss_layout = layout.section_layouts.get(output_section_id::TBSS); + let has_tlv = tdata_layout.mem_size > 0 || tbss_layout.mem_size > 0; + let data_layout = layout.section_layouts.get(output_section_id::DATA); + let has_tvars = has_tlv; + + let mut w = Writer { buf: out, pos: offset }; + let dylinker_cmd_size = align8((12 + DYLD_PATH.len() + 1) as u32); + let dylib_cmd_size = align8((24 + LIBSYSTEM_PATH.len() + 1) as u32); + + let mut ncmds = 0u32; + let mut cmdsize = 0u32; + let add_cmd = |n: &mut u32, s: &mut u32, size: u32| { *n += 1; *s += size; }; + add_cmd(&mut ncmds, &mut cmdsize, 72); + add_cmd(&mut ncmds, &mut cmdsize, 72 + 80); + if has_data { + let data_nsects = if has_tvars { 2u32 } else { 0 }; + add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); + } + add_cmd(&mut ncmds, &mut cmdsize, 72); + add_cmd(&mut ncmds, &mut cmdsize, 24); + add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); + add_cmd(&mut ncmds, &mut cmdsize, dylib_cmd_size); + add_cmd(&mut ncmds, &mut cmdsize, 24); + add_cmd(&mut ncmds, &mut cmdsize, 80); + add_cmd(&mut ncmds, &mut cmdsize, 32); + add_cmd(&mut ncmds, &mut cmdsize, 16); + add_cmd(&mut ncmds, &mut cmdsize, 16); + + w.u32(MH_MAGIC_64); w.u32(CPU_TYPE_ARM64); w.u32(CPU_SUBTYPE_ARM64_ALL); + w.u32(MH_EXECUTE); w.u32(ncmds); w.u32(cmdsize); + let mut flags = MH_PIE | MH_TWOLEVEL | MH_DYLDLINK; + if has_tlv { flags |= 0x0080_0000; } // MH_HAS_TLV_DESCRIPTORS + w.u32(flags); w.u32(0); + + w.segment(b"__PAGEZERO", 0, PAGEZERO_SIZE, 0, 0, 0, 0, 0); + + // __TEXT + w.u32(LC_SEGMENT_64); w.u32(72 + 80); w.name16(b"__TEXT"); + w.u64(text_vm_start); w.u64(text_filesize); w.u64(0); w.u64(text_filesize); + w.u32(VM_PROT_READ | VM_PROT_EXECUTE); w.u32(VM_PROT_READ | VM_PROT_EXECUTE); + w.u32(1); w.u32(0); + w.name16(b"__text"); w.name16(b"__TEXT"); + w.u64(text_layout.mem_offset); w.u64(text_layout.mem_size); + w.u32(text_layout.file_offset as u32); w.u32(2); + w.u32(0); w.u32(0); w.u32(0x80000400); w.u32(0); w.u32(0); w.u32(0); + + if has_data { + let nsects = if has_tvars { 2u32 } else { 0 }; + let data_cmd_size = 72 + 80 * nsects; + w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); w.name16(b"__DATA"); + w.u64(data_vmaddr); w.u64(data_vmsize); w.u64(data_fileoff); w.u64(data_filesize); + w.u32(VM_PROT_READ | VM_PROT_WRITE); w.u32(VM_PROT_READ | VM_PROT_WRITE); + w.u32(nsects); w.u32(0); + if has_tvars { + // Section addresses must be within [data_vmaddr, data_vmaddr+data_vmsize). + // Clamp to segment range. + // Find actual __thread_vars address by scanning object sections + let le = object::Endianness::Little; + let mut tvars_addr = u64::MAX; + let mut tvars_size = 0u64; + let mut tdata_addr = u64::MAX; + let mut tdata_size = 0u64; + for group in &layout.group_layouts { + for file_layout in &group.files { + if let FileLayout::Object(obj) = file_layout { + for (sec_idx, _) in obj.sections.iter().enumerate() { + if let Some(s) = obj.object.sections.get(sec_idx) { + use object::read::macho::Section as _; + let sec_type = s.flags(le) & 0xFF; + if let Some(addr) = obj.section_resolutions[sec_idx].address() { + if sec_type == 0x13 { // S_THREAD_LOCAL_VARIABLES + tvars_addr = tvars_addr.min(addr); + tvars_size += s.size(le); + } else if sec_type == 0x11 { // S_THREAD_LOCAL_REGULAR + tdata_addr = tdata_addr.min(addr); + tdata_size += s.size(le); + } + } + } + } + } + } + } + tvars_size = (tvars_size / 24) * 24; + if tvars_addr == u64::MAX { tvars_addr = 0; } + // If no type-0x11 sections, use TDATA layout (may be empty but correctly positioned) + if tdata_addr == u64::MAX { + tdata_addr = tdata_layout.mem_offset; + } + let tvars_foff = vm_addr_to_file_offset(tvars_addr, mappings) + .unwrap_or(data_fileoff as usize) as u32; + w.name16(b"__thread_vars"); w.name16(b"__DATA"); + w.u64(tvars_addr); w.u64(tvars_size); + w.u32(tvars_foff); w.u32(3); + w.u32(0); w.u32(0); + w.u32(0x13); // S_THREAD_LOCAL_VARIABLES + w.u32(0); w.u32(0); w.u32(0); + + // __thread_data: init template. Size includes TBSS for dyld. + let tdata_init_addr = tdata_addr; + let tdata_init_size = tdata_size + tbss_layout.mem_size; + let tdata_init_foff = vm_addr_to_file_offset(tdata_init_addr, mappings) + .unwrap_or(data_fileoff as usize) as u32; + w.name16(b"__thread_data"); w.name16(b"__DATA"); + w.u64(tdata_init_addr); w.u64(tdata_init_size); + w.u32(tdata_init_foff); w.u32(2); + w.u32(0); w.u32(0); + w.u32(0x11); // S_THREAD_LOCAL_REGULAR + w.u32(0); w.u32(0); w.u32(0); } } + + let (last_file_end, linkedit_vm) = if has_data { + (data_fileoff + data_filesize, data_vmaddr + data_vmsize) + } else { + (text_filesize, align_to(text_vm_start + text_filesize, PAGE_SIZE)) + }; + let cf_offset = last_file_end; + let cf_size = chained_fixups_data_size as u64; + + w.segment(b"__LINKEDIT", linkedit_vm, PAGE_SIZE, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); + + w.u32(LC_MAIN); w.u32(24); w.u64(entry_offset as u64); w.u64(0); + + w.u32(LC_LOAD_DYLINKER); w.u32(dylinker_cmd_size); w.u32(12); + w.bytes(DYLD_PATH); w.u8(0); w.pad8(); + + w.u32(LC_LOAD_DYLIB); w.u32(dylib_cmd_size); w.u32(24); w.u32(2); + w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(LIBSYSTEM_PATH); w.u8(0); w.pad8(); + + w.u32(LC_SYMTAB); w.u32(24); w.u32(0); w.u32(0); w.u32(0); w.u32(0); + w.u32(LC_DYSYMTAB); w.u32(80); for _ in 0..18 { w.u32(0); } + + w.u32(LC_BUILD_VERSION); w.u32(32); w.u32(PLATFORM_MACOS); + w.u32(0x000E_0000); w.u32(0x000E_0000); w.u32(1); w.u32(3); w.u32(0x0300_0100); + + w.u32(LC_DYLD_CHAINED_FIXUPS); w.u32(16); w.u32(cf_offset as u32); w.u32(cf_size as u32); + w.u32(LC_DYLD_EXPORTS_TRIE); w.u32(16); w.u32(last_file_end as u32); w.u32(0); + + Ok(Some(cf_offset)) +} + +fn read_u32(buf: &[u8], offset: usize) -> u32 { + u32::from_le_bytes(buf[offset..offset + 4].try_into().unwrap()) +} + +fn write_u32_at(buf: &mut [u8], offset: usize, val: u32) { + buf[offset..offset + 4].copy_from_slice(&val.to_le_bytes()); } -fn align_to(value: u64, alignment: u64) -> u64 { - (value + alignment - 1) & !(alignment - 1) +fn align8(v: u32) -> u32 { (v + 7) & !7 } +fn align_to(value: u64, alignment: u64) -> u64 { (value + alignment - 1) & !(alignment - 1) } + +struct Writer<'a> { buf: &'a mut [u8], pos: usize } + +impl Writer<'_> { + fn u8(&mut self, v: u8) { self.buf[self.pos] = v; self.pos += 1; } + fn u32(&mut self, v: u32) { + self.buf[self.pos..self.pos + 4].copy_from_slice(&v.to_le_bytes()); self.pos += 4; + } + fn u64(&mut self, v: u64) { + self.buf[self.pos..self.pos + 8].copy_from_slice(&v.to_le_bytes()); self.pos += 8; + } + fn name16(&mut self, name: &[u8]) { + let mut buf = [0u8; 16]; + buf[..name.len().min(16)].copy_from_slice(&name[..name.len().min(16)]); + self.buf[self.pos..self.pos + 16].copy_from_slice(&buf); self.pos += 16; + } + fn bytes(&mut self, data: &[u8]) { + self.buf[self.pos..self.pos + data.len()].copy_from_slice(data); self.pos += data.len(); + } + fn pad8(&mut self) { + let aligned = (self.pos + 7) & !7; + while self.pos < aligned { self.buf[self.pos] = 0; self.pos += 1; } + } + fn segment(&mut self, name: &[u8], vmaddr: u64, vmsize: u64, + fileoff: u64, filesize: u64, maxprot: u32, initprot: u32, nsects: u32) { + self.u32(LC_SEGMENT_64); self.u32(72 + 80 * nsects); self.name16(name); + self.u64(vmaddr); self.u64(vmsize); self.u64(fileoff); self.u64(filesize); + self.u32(maxprot); self.u32(initprot); self.u32(nsects); self.u32(0); + } } diff --git a/libwild/src/platform.rs b/libwild/src/platform.rs index 332e4b038..e22d1e144 100644 --- a/libwild/src/platform.rs +++ b/libwild/src/platform.rs @@ -699,6 +699,16 @@ pub(crate) trait ObjectFile<'data>: Sized + Send + Sync + std::fmt::Debug + 'dat index: object::SymbolIndex, ) -> Result>; + /// Returns the symbol's offset within its section. For ELF, st_value is already + /// section-relative. For Mach-O, n_value is absolute so we subtract the section base. + fn symbol_value_in_section( + &self, + symbol: &::SymtabEntry, + _section_index: object::SectionIndex, + ) -> Result { + Ok(symbol.value()) + } + fn symbol_versions(&self) -> &[::SymbolVersionIndex]; fn dynamic_symbol_used( @@ -1165,6 +1175,12 @@ pub(crate) trait Args: std::fmt::Debug + Send + Sync + 'static { fn loadable_segment_alignment(&self) -> Alignment; + /// The base VM address for the output binary. Sections start at this address. + /// For ELF: 0x400000. For Mach-O: 0x100000000 (PAGEZERO size). + fn base_address(&self, output_kind: crate::output_kind::OutputKind) -> u64 { + output_kind.base_address() + } + fn should_merge_sections(&self) -> bool; fn dependency_file(&self) -> Option<&Path> { diff --git a/tests/macho_tests.sh b/tests/macho_tests.sh new file mode 100755 index 000000000..10bd49814 --- /dev/null +++ b/tests/macho_tests.sh @@ -0,0 +1,369 @@ +#!/bin/bash +# Integration tests for macOS Mach-O linking. +# Run from the repo root: bash tests/macho_tests.sh +set -euo pipefail + +WILD="$(cd "$(dirname "${1:-./target/debug/wild}")" && pwd)/$(basename "${1:-./target/debug/wild}")" +TMPDIR=$(mktemp -d) +PASS=0 +FAIL=0 + +cleanup() { rm -rf "$TMPDIR"; } +trap cleanup EXIT + +pass() { PASS=$((PASS + 1)); echo " PASS: $1"; } +fail() { FAIL=$((FAIL + 1)); echo " FAIL: $1"; } + +check_exit() { + local binary="$1" expected="$2" name="$3" + # wild now auto-signs binaries, no manual codesign needed + set +e + "$binary" + local got=$? + set -e + if [ "$got" -eq "$expected" ]; then + pass "$name (exit=$got)" + else + fail "$name (expected exit=$expected, got exit=$got)" + fi +} + +echo "=== Wild macOS Mach-O Tests ===" +echo "Linker: $WILD" +echo "" + +# --- Test 1: Single .o, return constant --- +echo "Test 1: Single object file, return 42" +cat > "$TMPDIR/t1.c" << 'EOF' +int main() { return 42; } +EOF +clang -c "$TMPDIR/t1.c" -o "$TMPDIR/t1.o" +"$WILD" "$TMPDIR/t1.o" -o "$TMPDIR/t1" +check_exit "$TMPDIR/t1" 42 "single-obj-return-42" + +# --- Test 2: Two .o files with cross-object call --- +echo "Test 2: Two object files, cross-object function call" +cat > "$TMPDIR/t2_add.c" << 'EOF' +int add(int a, int b) { return a + b; } +EOF +cat > "$TMPDIR/t2_main.c" << 'EOF' +int add(int a, int b); +int main() { return add(30, 12); } +EOF +clang -c "$TMPDIR/t2_add.c" -o "$TMPDIR/t2_add.o" +clang -c "$TMPDIR/t2_main.c" -o "$TMPDIR/t2_main.o" +"$WILD" "$TMPDIR/t2_main.o" "$TMPDIR/t2_add.o" -o "$TMPDIR/t2" +check_exit "$TMPDIR/t2" 42 "two-objs-cross-call" + +# --- Test 3: Three .o files --- +echo "Test 3: Three object files" +cat > "$TMPDIR/t3_a.c" << 'EOF' +int mul(int a, int b) { return a * b; } +EOF +cat > "$TMPDIR/t3_b.c" << 'EOF' +int mul(int a, int b); +int square(int x) { return mul(x, x); } +EOF +cat > "$TMPDIR/t3_main.c" << 'EOF' +int square(int x); +int main() { return square(5) - 25 + 7; } +EOF +clang -c "$TMPDIR/t3_a.c" -o "$TMPDIR/t3_a.o" +clang -c "$TMPDIR/t3_b.c" -o "$TMPDIR/t3_b.o" +clang -c "$TMPDIR/t3_main.c" -o "$TMPDIR/t3_main.o" +"$WILD" "$TMPDIR/t3_main.o" "$TMPDIR/t3_b.o" "$TMPDIR/t3_a.o" -o "$TMPDIR/t3" +check_exit "$TMPDIR/t3" 7 "three-objs-chain-calls" + +# --- Test 4: Global variable (data section) --- +echo "Test 4: Global variable access" +cat > "$TMPDIR/t4_data.c" << 'EOF' +int value = 42; +EOF +cat > "$TMPDIR/t4_main.c" << 'EOF' +extern int value; +int main() { return value; } +EOF +clang -c "$TMPDIR/t4_data.c" -o "$TMPDIR/t4_data.o" +clang -c "$TMPDIR/t4_main.c" -o "$TMPDIR/t4_main.o" +"$WILD" "$TMPDIR/t4_main.o" "$TMPDIR/t4_data.o" -o "$TMPDIR/t4" +check_exit "$TMPDIR/t4" 42 "global-variable-extern" + +# --- Test 4b: Static variable --- +echo "Test 4b: Static variable access" +cat > "$TMPDIR/t4b.c" << 'EOF' +static int value = 42; +int main() { return value; } +EOF +clang -c "$TMPDIR/t4b.c" -o "$TMPDIR/t4b.o" +"$WILD" "$TMPDIR/t4b.o" -o "$TMPDIR/t4b" +check_exit "$TMPDIR/t4b" 42 "global-variable-static" + +# --- Test 4c: Static archive (.a) --- +echo "Test 4c: Static archive linking" +cat > "$TMPDIR/t4c_add.c" << 'EOF' +int add(int a, int b) { return a + b; } +EOF +cat > "$TMPDIR/t4c_mul.c" << 'EOF' +int mul(int a, int b) { return a * b; } +EOF +cat > "$TMPDIR/t4c_main.c" << 'EOF' +int add(int a, int b); +int mul(int a, int b); +int main() { return add(mul(6, 7), 0); } +EOF +clang -c "$TMPDIR/t4c_add.c" -o "$TMPDIR/t4c_add.o" +clang -c "$TMPDIR/t4c_mul.c" -o "$TMPDIR/t4c_mul.o" +clang -c "$TMPDIR/t4c_main.c" -o "$TMPDIR/t4c_main.o" +ar rcs "$TMPDIR/t4c_lib.a" "$TMPDIR/t4c_add.o" "$TMPDIR/t4c_mul.o" +"$WILD" "$TMPDIR/t4c_main.o" "$TMPDIR/t4c_lib.a" -o "$TMPDIR/t4c" +check_exit "$TMPDIR/t4c" 42 "static-archive" + +# --- Test 4d: Dynamic symbol (printf) --- +echo "Test 4d: Dynamic symbol call (printf)" +cat > "$TMPDIR/t4d.c" << 'EOF' +#include +int main() { + printf("hello wild\n"); + return 7; +} +EOF +clang -c "$TMPDIR/t4d.c" -o "$TMPDIR/t4d.o" +"$WILD" "$TMPDIR/t4d.o" -o "$TMPDIR/t4d" +check_exit "$TMPDIR/t4d" 7 "dynamic-symbol-printf" + +# --- Test 4e: clang drop-in linker --- +echo "Test 4e: clang -fuse-ld=wild" +cat > "$TMPDIR/t4e.c" << 'EOF' +#include +void greet(const char *name) { printf("Hello, %s!\n", name); } +EOF +cat > "$TMPDIR/t4e_main.c" << 'EOF' +void greet(const char *name); +int main() { greet("wild"); return 3; } +EOF +if clang -fuse-ld="$WILD" "$TMPDIR/t4e.c" "$TMPDIR/t4e_main.c" -o "$TMPDIR/t4e" 2>/dev/null; then + check_exit "$TMPDIR/t4e" 3 "clang-drop-in-linker" +else + fail "clang-drop-in-linker (link failed)" +fi + +# --- Test 4f: Function pointer table (rebase fixups) --- +echo "Test 4f: Function pointer table with rebases" +cat > "$TMPDIR/t4f.c" << 'EOF' +#include +typedef int (*fn_t)(void); +int f0(void) { return 10; } +int f1(void) { return 20; } +int f2(void) { return 12; } +fn_t table[] = { f0, f1, f2 }; +int main() { + int sum = 0; + for (int i = 0; i < 3; i++) sum += table[i](); + return sum; +} +EOF +clang -c "$TMPDIR/t4f.c" -o "$TMPDIR/t4f.o" +"$WILD" "$TMPDIR/t4f.o" -o "$TMPDIR/t4f" +check_exit "$TMPDIR/t4f" 42 "function-pointer-rebase" + +# --- Test 4g: Rust no_std --- +echo "Test 4g: Rust no_std program" +cat > "$TMPDIR/t4g.rs" << 'EOF' +#![no_std] +#![no_main] +#[no_mangle] +pub extern "C" fn main() -> i32 { 42 } +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } +EOF +if rustc "$TMPDIR/t4g.rs" --emit=obj --target=aarch64-apple-darwin -C panic=abort -o "$TMPDIR/t4g.o" 2>/dev/null; then + "$WILD" "$TMPDIR/t4g.o" -o "$TMPDIR/t4g" + check_exit "$TMPDIR/t4g" 42 "rust-no-std" +else + echo " SKIP: rust-no-std (rustc not available)" +fi + +# --- Test 4h: Non-extern relocations (section-ordinal) --- +echo "Test 4h: Non-extern relocations" +cat > "$TMPDIR/t4h.c" << 'EOF' +static int helper(int x) { return x * 2; } +static int other(int x) { return x + 1; } +int main() { + int (*fns[])(int) = { helper, other }; + return fns[0](20) + fns[1](0); +} +EOF +clang -c "$TMPDIR/t4h.c" -o "$TMPDIR/t4h.o" +"$WILD" "$TMPDIR/t4h.o" -o "$TMPDIR/t4h" +check_exit "$TMPDIR/t4h" 41 "non-extern-relocs" + +# --- Test 4i: C TLS variable --- +echo "Test 4i: C thread-local variable" +cat > "$TMPDIR/t4i.c" << 'EOF' +__thread int x = 42; +int main() { return x; } +EOF +clang -c "$TMPDIR/t4i.c" -o "$TMPDIR/t4i.o" +"$WILD" "$TMPDIR/t4i.o" -o "$TMPDIR/t4i" +check_exit "$TMPDIR/t4i" 42 "c-tls-variable" + +# --- Test 4j: Multi-TLS across objects --- +echo "Test 4j: Multi-TLS across objects" +cat > "$TMPDIR/t4j_a.c" << 'EOF' +__thread int a = 10; +__thread int b = 20; +int get_tls_sum(void) { return a + b; } +EOF +cat > "$TMPDIR/t4j_b.c" << 'EOF' +int get_tls_sum(void); +int main() { return get_tls_sum() + 12; } +EOF +clang -c "$TMPDIR/t4j_a.c" -o "$TMPDIR/t4j_a.o" +clang -c "$TMPDIR/t4j_b.c" -o "$TMPDIR/t4j_b.o" +"$WILD" "$TMPDIR/t4j_a.o" "$TMPDIR/t4j_b.o" -o "$TMPDIR/t4j" +check_exit "$TMPDIR/t4j" 42 "multi-tls" + +# --- Test 4k: vtable + printf in archive (no TLS) --- +echo "Test 4k: Archive with vtable and printf" +cat > "$TMPDIR/t4k_lib.c" << 'EOF' +typedef int (*op_t)(int); +static int double_it(int x) { return x * 2; } +static int add_one(int x) { return x + 1; } +const op_t ops[] = { double_it, add_one }; +int apply_op(int i, int x) { return ops[i](x); } +EOF +cat > "$TMPDIR/t4k_main.c" << 'EOF' +#include +int apply_op(int i, int x); +int main() { + int result = apply_op(0, 10) + apply_op(1, 0); + printf("result=%d\n", result); + return result - 21 + 42; +} +EOF +clang -c "$TMPDIR/t4k_lib.c" -o "$TMPDIR/t4k_lib.o" +clang -c "$TMPDIR/t4k_main.c" -o "$TMPDIR/t4k_main.o" +ar rcs "$TMPDIR/t4k.a" "$TMPDIR/t4k_lib.o" +"$WILD" "$TMPDIR/t4k_main.o" "$TMPDIR/t4k.a" -o "$TMPDIR/t4k" +check_exit "$TMPDIR/t4k" 42 "archive-vtable-printf" + +# --- Test 4l: TLS + vtable + archive + printf --- +echo "Test 4l: Complex archive with TLS and vtable" +cat > "$TMPDIR/t4k_lib.c" << 'EOF' +#include +__thread int counter = 0; +typedef int (*op_t)(int); +static int double_it(int x) { return x * 2; } +static int add_one(int x) { return x + 1; } +const op_t ops[] = { double_it, add_one }; +int apply_op(int i, int x) { counter++; return ops[i](x); } +int get_counter(void) { return counter; } +EOF +cat > "$TMPDIR/t4k_main.c" << 'EOF' +#include +int apply_op(int i, int x); +int get_counter(void); +int main() { + int result = apply_op(0, 10) + apply_op(1, 0) + get_counter(); + printf("result=%d\n", result); + return result - 23 + 42; +} +EOF +clang -c "$TMPDIR/t4k_lib.c" -o "$TMPDIR/t4k_lib.o" +clang -c "$TMPDIR/t4k_main.c" -o "$TMPDIR/t4k_main.o" +ar rcs "$TMPDIR/t4k.a" "$TMPDIR/t4k_lib.o" +"$WILD" "$TMPDIR/t4k_main.o" "$TMPDIR/t4k.a" -o "$TMPDIR/t4k" +check_exit "$TMPDIR/t4k" 42 "complex-archive-tls-vtable" + +# --- Test 4m: Trait-like vtable dispatch with TLS + archive --- +echo "Test 4m: Trait dispatch with vtable, TLS, malloc" +cat > "$TMPDIR/t4m_lib.c" << 'EOF' +#include +__thread int depth = 0; +typedef struct { void (*drop)(void*); int (*call)(void*, int); } Vtable; +typedef struct { const Vtable *vtable; int value; } TraitObj; +static void a_drop(void *s) { depth++; } +static int a_call(void *s, int x) { depth++; return ((TraitObj*)s)->value + x; } +static const Vtable A_VT = { a_drop, a_call }; +static void m_drop(void *s) { depth++; } +static int m_call(void *s, int x) { depth++; return ((TraitObj*)s)->value * x; } +static const Vtable M_VT = { m_drop, m_call }; +TraitObj *make_adder(int v) { TraitObj *o=malloc(sizeof(*o)); o->vtable=&A_VT; o->value=v; return o; } +TraitObj *make_mul(int v) { TraitObj *o=malloc(sizeof(*o)); o->vtable=&M_VT; o->value=v; return o; } +int call_trait(TraitObj *o, int x) { return o->vtable->call(o, x); } +void drop_trait(TraitObj *o) { o->vtable->drop(o); free(o); } +int get_depth(void) { return depth; } +EOF +cat > "$TMPDIR/t4m_main.c" << 'EOF' +#include +typedef struct TraitObj TraitObj; +TraitObj *make_adder(int v); TraitObj *make_mul(int v); +int call_trait(TraitObj *o, int x); void drop_trait(TraitObj *o); int get_depth(void); +int main() { + TraitObj *a = make_adder(10), *m = make_mul(3); + int r = call_trait(a,5) + call_trait(m,7); + drop_trait(a); drop_trait(m); + printf("r=%d d=%d\n", r, get_depth()); + return r + get_depth() + 2; +} +EOF +clang -c "$TMPDIR/t4m_lib.c" -o "$TMPDIR/t4m_lib.o" +clang -c "$TMPDIR/t4m_main.c" -o "$TMPDIR/t4m_main.o" +ar rcs "$TMPDIR/t4m.a" "$TMPDIR/t4m_lib.o" +"$WILD" "$TMPDIR/t4m_main.o" "$TMPDIR/t4m.a" -o "$TMPDIR/t4m" +check_exit "$TMPDIR/t4m" 42 "trait-dispatch-tls-vtable" + +# --- Test 4n: Rust std links --- +echo "Test 4n: Rust std links" +cat > "$TMPDIR/t4i.rs" << 'EOF' +fn add(a: i32, b: i32) -> i32 { a + b } +fn main() { + let result = add(30, 12); + std::process::exit(result); +} +EOF +if rustc "$TMPDIR/t4i.rs" -Clinker=clang "-Clink-arg=-fuse-ld=$WILD" -o "$TMPDIR/t4i" 2>/dev/null; then + if [ -f "$TMPDIR/t4i" ] && file "$TMPDIR/t4i" | grep -q "Mach-O 64-bit executable arm64"; then + pass "rust-std-links" + else + fail "rust-std-links" + fi +else + echo " SKIP: rust-std-links (rustc not available or link failed)" +fi + +# --- Test 4j: Rust hello world runs --- +echo "Test 4o: Rust hello world runs" +cat > "$TMPDIR/t4k.rs" << 'EOF' +fn main() { + println!("Hello from wild!"); + std::process::exit(42); +} +EOF +if rustc "$TMPDIR/t4k.rs" -Clinker=clang "-Clink-arg=-fuse-ld=$WILD" -o "$TMPDIR/t4k" 2>/dev/null; then + check_exit "$TMPDIR/t4k" 42 "rust-hello-world" +else + echo " SKIP: rust-hello-world (rustc not available or link failed)" +fi + +# --- Test 5: Output flag --- +echo "Test 5: -o flag" +clang -c "$TMPDIR/t1.c" -o "$TMPDIR/t5.o" +"$WILD" "$TMPDIR/t5.o" -o "$TMPDIR/t5_out" +if [ -f "$TMPDIR/t5_out" ]; then + pass "output-flag" +else + fail "output-flag" +fi + +# --- Test 6: Valid Mach-O structure --- +echo "Test 6: Valid Mach-O structure" +if file "$TMPDIR/t1" | grep -q "Mach-O 64-bit executable arm64"; then + pass "valid-macho-structure" +else + fail "valid-macho-structure" +fi + +echo "" +echo "=== Results: $PASS passed, $FAIL failed ===" +[ "$FAIL" -eq 0 ] From 27446ebda4a88be4a6cc4f524a1d46b0b50891a5 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 6 Apr 2026 06:59:32 +0100 Subject: [PATCH 03/21] feat: expanded rust support Signed-off-by: Giles Cope --- libwild/src/args/macho.rs | 19 +- libwild/src/layout.rs | 8 +- libwild/src/macho.rs | 16 +- libwild/src/macho_writer.rs | 351 +++++++++++++++++++++++++++++++++--- tests/macho_tests.sh | 41 +++++ 5 files changed, 400 insertions(+), 35 deletions(-) diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index 5232a7c96..a40be5169 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -19,6 +19,8 @@ pub struct MachOArgs { pub(crate) lib_search_paths: Vec>, pub(crate) syslibroot: Option>, pub(crate) entry_symbol: Option>, + pub(crate) is_dylib: bool, + pub(crate) install_name: Option>, } impl MachOArgs { @@ -39,6 +41,8 @@ impl Default for MachOArgs { lib_search_paths: Vec::new(), syslibroot: None, entry_symbol: Some(b"_main".to_vec()), + is_dylib: false, + install_name: None, } } } @@ -76,7 +80,11 @@ impl platform::Args for MachOArgs { } fn base_address(&self, _output_kind: crate::output_kind::OutputKind) -> u64 { - 0x1_0000_0000 // PAGEZERO size -- Mach-O addresses start after 4GB null page + if self.is_dylib { + 0 // dylibs have no PAGEZERO + } else { + 0x1_0000_0000 // PAGEZERO size + } } fn should_merge_sections(&self) -> bool { false } @@ -85,7 +93,7 @@ impl platform::Args for MachOArgs { self.relocation_model } - fn should_output_executable(&self) -> bool { true } + fn should_output_executable(&self) -> bool { !self.is_dylib } } /// Parse macOS linker arguments. Handles the ld64-compatible flags that clang passes. @@ -174,7 +182,12 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( | "-mark_dead_strippable_dylib" | "-ObjC" | "-all_load" | "-no_implicit_dylibs" | "-search_paths_first" | "-two_levelnamespace" | "-flat_namespace" | "-bind_at_load" - | "-pie" | "-no_pie" | "-execute" | "-dylib" | "-bundle" => { + | "-pie" | "-no_pie" | "-execute" | "-bundle" => { + return Ok(()); + } + "-dylib" | "-dynamiclib" => { + args.is_dylib = true; + args.entry_symbol = None; // dylibs have no entry point return Ok(()); } _ => {} diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs index a62802688..6b8bf3c29 100644 --- a/libwild/src/layout.rs +++ b/libwild/src/layout.rs @@ -426,10 +426,10 @@ fn update_dynamic_symbol_resolutions<'data, P: Platform>( }; for (index, sym) in resources.dynamic_symbol_definitions.iter().enumerate() { - let dynamic_symbol_index = NonZeroU32::try_from(epilogue.dynsym_start_index + index as u32) - .expect("Dynamic symbol definitions should start > 0"); - if let Some(res) = &mut resolutions[sym.symbol_id.as_usize()] { - res.dynamic_symbol_index = Some(dynamic_symbol_index); + if let Some(dynamic_symbol_index) = NonZeroU32::new(epilogue.dynsym_start_index + index as u32) { + if let Some(res) = &mut resolutions[sym.symbol_id.as_usize()] { + res.dynamic_symbol_index = Some(dynamic_symbol_index); + } } } } diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index b563bddd2..01d5903d0 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -948,7 +948,9 @@ impl platform::Platform for MachO { crate::layout::OutputRecordLayout, >, ) -> crate::error::Result { - Ok(0) + // Mach-O doesn't use dynsym indices. Return 1 to satisfy NonZeroU32. + // The value is unused in the Mach-O writer. + Ok(1) } fn compute_object_addresses<'data>( @@ -1011,10 +1013,15 @@ impl platform::Platform for MachO { } fn create_dynamic_symbol_definition<'data>( - _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, - _symbol_id: crate::symbol_db::SymbolId, + symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, + symbol_id: crate::symbol_db::SymbolId, ) -> crate::error::Result> { - Err(error!("Dynamic symbols not yet supported for Mach-O")) + let name = symbol_db.symbol_name(symbol_id)?.bytes(); + Ok(crate::layout::DynamicSymbolDefinition { + symbol_id, + name, + format_specific: (), + }) } fn update_segment_keep_list( @@ -1399,6 +1406,7 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__thread_vars", output_section_id::GOT), SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), + SectionRule::exact_section(b".rustc", output_section_id::DATA), SectionRule::exact_section(b"__bss", output_section_id::BSS), SectionRule::exact_section(b"__common", output_section_id::BSS), SectionRule::exact_section(b"__unwind_info", output_section_id::RODATA), diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index ed6813297..81475b7c4 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -171,10 +171,11 @@ fn write_macho>( mappings[1].file_offset as usize + (mappings[1].vm_end - mappings[1].vm_start) as usize } else { 0 }; + let image_base = if layout.symbol_db.args.is_dylib { 0u64 } else { PAGEZERO_SIZE }; let mut all_data_fixups: Vec<(usize, u64)> = Vec::new(); for f in &rebase_fixups { if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } - let target_offset = f.target.wrapping_sub(PAGEZERO_SIZE); + let target_offset = f.target.wrapping_sub(image_base); all_data_fixups.push((f.file_offset, target_offset & 0xF_FFFF_FFFF)); } for f in &bind_fixups { @@ -217,7 +218,9 @@ fn write_macho>( // Compute chained fixups data size let has_data = mappings.len() > 1 && (mappings[1].vm_end > mappings[1].vm_start); - let seg_count = if has_data { 4u32 } else { 3u32 }; + let is_dylib = layout.symbol_db.args.is_dylib; + let base_segs = if is_dylib { 2u32 } else { 3u32 }; // TEXT+LINKEDIT or PAGEZERO+TEXT+LINKEDIT + let seg_count = if has_data { base_segs + 1 } else { base_segs }; let starts_in_image_size = 4 + 4 * seg_count; let page_count = if has_fixups && has_data { let data_mem_size = mappings[1].vm_end - mappings[1].vm_start; @@ -256,6 +259,7 @@ fn write_macho>( write_chained_fixups_header( out, cf_off as usize, &all_data_fixups, n_imports, &import_name_offsets, &symbols_pool, mappings, + layout.symbol_db.args.is_dylib, )?; cf_off as usize + cf_data_size as usize } @@ -263,9 +267,217 @@ fn write_macho>( out.len() }; + // For dylibs: write symbol table with exported symbols + let final_size = if layout.symbol_db.args.is_dylib { + write_dylib_symtab(out, final_size, layout, mappings)? + } else { + final_size + }; + Ok(final_size) } +/// Write a minimal symbol table for dylib exports. +fn write_dylib_symtab( + out: &mut [u8], + start: usize, + layout: &Layout<'_, MachO>, + mappings: &[SegmentMapping], +) -> Result { + + // Collect exported symbols from dynamic_symbol_definitions + let mut entries: Vec<(Vec, u64)> = Vec::new(); + for def in &layout.dynamic_symbol_definitions { + let sym_id = def.symbol_id; + if let Some(res) = layout.symbol_resolutions.iter().nth(sym_id.as_usize()).and_then(|r| r.as_ref()) { + entries.push((def.name.to_vec(), res.raw_value)); + } + } + + if entries.is_empty() { + return Ok(start); + } + + // Build string table: starts with \0 + let mut strtab = vec![0u8]; + let mut str_offsets = Vec::new(); + for (name, _) in &entries { + str_offsets.push(strtab.len() as u32); + strtab.extend_from_slice(name); + strtab.push(0); + } + + // Write nlist64 entries (16 bytes each, must be 8-byte aligned) + let symoff = (start + 7) & !7; // align to 8 + let nsyms = entries.len(); + let mut pos = symoff; + for (i, (_, value)) in entries.iter().enumerate() { + if pos + 16 > out.len() { break; } + // nlist64: n_strx (4), n_type (1), n_sect (1), n_desc (2), n_value (8) + out[pos..pos+4].copy_from_slice(&str_offsets[i].to_le_bytes()); + out[pos+4] = 0x0F; // N_SECT | N_EXT + out[pos+5] = 1; // n_sect: section 1 (__text) + out[pos+6..pos+8].copy_from_slice(&0u16.to_le_bytes()); // n_desc + out[pos+8..pos+16].copy_from_slice(&value.to_le_bytes()); + pos += 16; + } + + // Write string table + let stroff = pos; + if stroff + strtab.len() <= out.len() { + out[stroff..stroff + strtab.len()].copy_from_slice(&strtab); + } + pos = stroff + strtab.len(); + + // Patch LC_SYMTAB in the header + // Find LC_SYMTAB command and update it + let mut off = 32u32; // after header + let ncmds = u32::from_le_bytes(out[16..20].try_into().unwrap()); + for _ in 0..ncmds { + let cmd = u32::from_le_bytes(out[off as usize..off as usize+4].try_into().unwrap()); + let cmdsize = u32::from_le_bytes(out[off as usize+4..off as usize+8].try_into().unwrap()); + if cmd == LC_SYMTAB { + out[off as usize+8..off as usize+12].copy_from_slice(&(symoff as u32).to_le_bytes()); + out[off as usize+12..off as usize+16].copy_from_slice(&(nsyms as u32).to_le_bytes()); + out[off as usize+16..off as usize+20].copy_from_slice(&(stroff as u32).to_le_bytes()); + out[off as usize+20..off as usize+24].copy_from_slice(&(strtab.len() as u32).to_le_bytes()); + break; + } + off += cmdsize; + } + + // Build export trie for dlsym (must be aligned) + let trie_off = (pos + 7) & !7; + let trie = build_export_trie(&entries); + if trie_off + trie.len() <= out.len() { + out[trie_off..trie_off + trie.len()].copy_from_slice(&trie); + } + pos = trie_off + trie.len(); + + // Patch LC_SYMTAB and LC_DYLD_EXPORTS_TRIE in headers + off = 32; + for _ in 0..ncmds { + let cmd = u32::from_le_bytes(out[off as usize..off as usize+4].try_into().unwrap()); + let cmdsize = u32::from_le_bytes(out[off as usize+4..off as usize+8].try_into().unwrap()); + match cmd { + 0x19 => { // LC_SEGMENT_64 + let segname = &out[off as usize+8..off as usize+24]; + if segname.starts_with(b"__LINKEDIT") { + let linkedit_fileoff = u64::from_le_bytes( + out[off as usize+40..off as usize+48].try_into().unwrap()); + let new_filesize = pos as u64 - linkedit_fileoff; + out[off as usize+48..off as usize+56].copy_from_slice(&new_filesize.to_le_bytes()); + } + } + LC_DYSYMTAB => { + // DYSYMTAB: ilocalsym nlocalsym iextdefsym nextdefsym iundefsym nundefsym + let o = off as usize + 8; + out[o..o+4].copy_from_slice(&0u32.to_le_bytes()); // ilocalsym + out[o+4..o+8].copy_from_slice(&0u32.to_le_bytes()); // nlocalsym + out[o+8..o+12].copy_from_slice(&0u32.to_le_bytes()); // iextdefsym + out[o+12..o+16].copy_from_slice(&(nsyms as u32).to_le_bytes()); // nextdefsym + out[o+16..o+20].copy_from_slice(&(nsyms as u32).to_le_bytes()); // iundefsym + out[o+20..o+24].copy_from_slice(&0u32.to_le_bytes()); // nundefsym + } + 0x8000_0033 => { // LC_DYLD_EXPORTS_TRIE + out[off as usize+8..off as usize+12].copy_from_slice(&(trie_off as u32).to_le_bytes()); + out[off as usize+12..off as usize+16].copy_from_slice(&(trie.len() as u32).to_le_bytes()); + } + _ => {} + } + off += cmdsize; + } + + Ok(pos) +} + +/// Build a Mach-O export trie for the given symbols. +fn build_export_trie(entries: &[(Vec, u64)]) -> Vec { + if entries.is_empty() { return vec![0, 0]; } // empty root + + // Build child nodes first to know their sizes + let mut children: Vec> = Vec::new(); + for (_, addr) in entries { + let mut node = Vec::new(); + let mut info = Vec::new(); + uleb128_encode(&mut info, 0); // flags: regular + uleb128_encode(&mut info, *addr); + uleb128_encode(&mut node, info.len() as u64); // terminal size + node.extend_from_slice(&info); + node.push(0); // 0 child edges + children.push(node); + } + + // Build edge labels (symbol name bytes + NUL) + let mut labels: Vec> = Vec::new(); + for (name, _) in entries { + let mut label = Vec::new(); + label.extend_from_slice(name); + label.push(0); + labels.push(label); + } + + // Compute root node size to determine child offsets. + // Root = terminal_size(1) + edge_count(1) + edges + // Each edge = label + ULEB128(child_offset) + // We need to know root size to compute offsets, but offsets depend on their ULEB encoding size. + // Use two passes: estimate then fix. + let n = entries.len(); + // Estimate: each offset ULEB is ~2 bytes for typical small tries + let mut root_size_estimate = 2usize; // terminal_size(0) + edge_count + for label in &labels { + root_size_estimate += label.len() + 3; // label + ~3 byte offset + } + + // Compute exact child offsets from root_size_estimate + let mut child_offsets = Vec::new(); + let mut off = root_size_estimate; + for child in &children { + child_offsets.push(off); + off += child.len(); + } + + // Now build root with exact offsets + let mut root = Vec::new(); + root.push(0); // not terminal + root.push(n as u8); // edge count + for (i, label) in labels.iter().enumerate() { + root.extend_from_slice(label); + uleb128_encode(&mut root, child_offsets[i] as u64); + } + + // Check if root size matches estimate; if not, recompute + if root.len() != root_size_estimate { + let actual_root_size = root.len(); + let delta = actual_root_size as isize - root_size_estimate as isize; + // Recompute with corrected offsets + root.clear(); + root.push(0); + root.push(n as u8); + for (i, label) in labels.iter().enumerate() { + root.extend_from_slice(label); + uleb128_encode(&mut root, (child_offsets[i] as isize + delta) as u64); + } + } + + // Assemble trie + let mut trie = root; + for child in &children { + trie.extend_from_slice(child); + } + trie +} + +fn uleb128_encode(buf: &mut Vec, mut val: u64) { + loop { + let mut byte = (val & 0x7F) as u8; + val >>= 7; + if val != 0 { byte |= 0x80; } + buf.push(byte); + if val == 0 { break; } + } +} + /// Write PLT stubs and GOT bind entries for imported symbols. fn write_stubs_and_got>( out: &mut [u8], @@ -504,7 +716,9 @@ fn apply_relocations( tls_init_start = tdata.mem_offset + tdata.mem_size; } let tls_offset = if in_tbss { - tls_init_size + target_addr.saturating_sub(tbss.mem_offset) + // Align init_size to BSS alignment (8 bytes) + let aligned_init = (tls_init_size + 7) & !7; + aligned_init + target_addr.saturating_sub(tbss.mem_offset) } else { target_addr.saturating_sub(tls_init_start) }; @@ -533,10 +747,12 @@ fn write_chained_fixups_header( import_name_offsets: &[u32], symbols_pool: &[u8], mappings: &[SegmentMapping], + is_dylib: bool, ) -> Result { let has_data = mappings.len() > 1 && (mappings[1].vm_end > mappings[1].vm_start); - let seg_count = if has_data { 4u32 } else { 3u32 }; - let data_seg_idx: usize = 2; + let base_segs = if is_dylib { 2u32 } else { 3u32 }; + let seg_count = if has_data { base_segs + 1 } else { base_segs }; + let data_seg_idx: usize = if is_dylib { 1 } else { 2 }; let starts_offset: u32 = 32; let starts_in_image_size = 4 + 4 * seg_count as usize; @@ -576,7 +792,8 @@ fn write_chained_fixups_header( w[ss..ss+4].copy_from_slice(&(seg_starts_size as u32).to_le_bytes()); w[ss+4..ss+6].copy_from_slice(&(PAGE_SIZE as u16).to_le_bytes()); w[ss+6..ss+8].copy_from_slice(&6u16.to_le_bytes()); - let seg_offset_val: u64 = if mappings.len() > 1 { mappings[1].vm_start - PAGEZERO_SIZE } else { 0 }; + let image_base = if mappings.first().map_or(false, |m| m.vm_start >= PAGEZERO_SIZE) { PAGEZERO_SIZE } else { 0 }; + let seg_offset_val: u64 = if mappings.len() > 1 { mappings[1].vm_start.wrapping_sub(image_base) } else { 0 }; w[ss+8..ss+16].copy_from_slice(&seg_offset_val.to_le_bytes()); w[ss+16..ss+20].copy_from_slice(&0u32.to_le_bytes()); w[ss+20..ss+22].copy_from_slice(&page_count.to_le_bytes()); @@ -670,49 +887,101 @@ fn write_headers( let data_layout = layout.section_layouts.get(output_section_id::DATA); let has_tvars = has_tlv; + // Scan for .rustc section (proc-macro metadata) before computing cmd sizes + let mut rustc_addr = 0u64; + let mut rustc_size = 0u64; + { + use object::read::macho::Section as _; + let le = object::Endianness::Little; + for group in &layout.group_layouts { + for file_layout in &group.files { + if let FileLayout::Object(obj) = file_layout { + for (sec_idx, _) in obj.sections.iter().enumerate() { + if let Some(s) = obj.object.sections.get(sec_idx) { + let name = crate::macho::trim_nul(s.sectname()); + if name == b".rustc" { + if let Some(addr) = obj.section_resolutions[sec_idx].address() { + if rustc_addr == 0 || addr < rustc_addr { rustc_addr = addr; } + rustc_size += s.size(le); + } + } + } + } + } + } + } + } + let has_rustc = rustc_addr > 0 && rustc_size > 0; + let mut w = Writer { buf: out, pos: offset }; let dylinker_cmd_size = align8((12 + DYLD_PATH.len() + 1) as u32); let dylib_cmd_size = align8((24 + LIBSYSTEM_PATH.len() + 1) as u32); + let is_dylib = layout.symbol_db.args.is_dylib; + let install_name = if is_dylib { + layout.symbol_db.args.output().to_string_lossy().into_owned() + } else { String::new() }; + let id_dylib_cmd_size = if is_dylib { align8((24 + install_name.len() as u32 + 1)) } else { 0 }; + let mut ncmds = 0u32; let mut cmdsize = 0u32; let add_cmd = |n: &mut u32, s: &mut u32, size: u32| { *n += 1; *s += size; }; - add_cmd(&mut ncmds, &mut cmdsize, 72); - add_cmd(&mut ncmds, &mut cmdsize, 72 + 80); + if !is_dylib { add_cmd(&mut ncmds, &mut cmdsize, 72); } // PAGEZERO (exe only) + let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; + let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 }; + add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT if has_data { - let data_nsects = if has_tvars { 2u32 } else { 0 }; + let mut data_nsects = if has_tvars { 2u32 } else { 0 }; + if has_rustc { data_nsects += 1; } add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); } - add_cmd(&mut ncmds, &mut cmdsize, 72); - add_cmd(&mut ncmds, &mut cmdsize, 24); - add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); + add_cmd(&mut ncmds, &mut cmdsize, 72); // LINKEDIT + if is_dylib { + add_cmd(&mut ncmds, &mut cmdsize, id_dylib_cmd_size); // LC_ID_DYLIB + add_cmd(&mut ncmds, &mut cmdsize, 24); // LC_UUID + } else { + add_cmd(&mut ncmds, &mut cmdsize, 24); // LC_MAIN + } + if !is_dylib { add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); } add_cmd(&mut ncmds, &mut cmdsize, dylib_cmd_size); - add_cmd(&mut ncmds, &mut cmdsize, 24); - add_cmd(&mut ncmds, &mut cmdsize, 80); + add_cmd(&mut ncmds, &mut cmdsize, 24); // SYMTAB + add_cmd(&mut ncmds, &mut cmdsize, 80); // DYSYMTAB add_cmd(&mut ncmds, &mut cmdsize, 32); add_cmd(&mut ncmds, &mut cmdsize, 16); add_cmd(&mut ncmds, &mut cmdsize, 16); + let filetype = if is_dylib { 6u32 } else { MH_EXECUTE }; // MH_DYLIB = 6 w.u32(MH_MAGIC_64); w.u32(CPU_TYPE_ARM64); w.u32(CPU_SUBTYPE_ARM64_ALL); - w.u32(MH_EXECUTE); w.u32(ncmds); w.u32(cmdsize); + w.u32(filetype); w.u32(ncmds); w.u32(cmdsize); let mut flags = MH_PIE | MH_TWOLEVEL | MH_DYLDLINK; if has_tlv { flags |= 0x0080_0000; } // MH_HAS_TLV_DESCRIPTORS w.u32(flags); w.u32(0); - w.segment(b"__PAGEZERO", 0, PAGEZERO_SIZE, 0, 0, 0, 0, 0); + if !is_dylib { + w.segment(b"__PAGEZERO", 0, PAGEZERO_SIZE, 0, 0, 0, 0, 0); + } - // __TEXT - w.u32(LC_SEGMENT_64); w.u32(72 + 80); w.name16(b"__TEXT"); + // __TEXT — include .rustc section if it falls in TEXT range + w.u32(LC_SEGMENT_64); w.u32(72 + 80 * text_nsects); w.name16(b"__TEXT"); w.u64(text_vm_start); w.u64(text_filesize); w.u64(0); w.u64(text_filesize); w.u32(VM_PROT_READ | VM_PROT_EXECUTE); w.u32(VM_PROT_READ | VM_PROT_EXECUTE); - w.u32(1); w.u32(0); + w.u32(text_nsects); w.u32(0); w.name16(b"__text"); w.name16(b"__TEXT"); w.u64(text_layout.mem_offset); w.u64(text_layout.mem_size); w.u32(text_layout.file_offset as u32); w.u32(2); w.u32(0); w.u32(0); w.u32(0x80000400); w.u32(0); w.u32(0); w.u32(0); + if rustc_in_text { + let rustc_foff = vm_addr_to_file_offset(rustc_addr, mappings) + .unwrap_or(0) as u32; + w.name16(b".rustc"); w.name16(b"__TEXT"); + w.u64(rustc_addr); w.u64(rustc_size); + w.u32(rustc_foff); w.u32(0); + w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); + } if has_data { - let nsects = if has_tvars { 2u32 } else { 0 }; + let mut nsects = if has_tvars { 2u32 } else { 0 }; + if has_rustc { nsects += 1; } let data_cmd_size = 72 + 80 * nsects; w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); w.name16(b"__DATA"); w.u64(data_vmaddr); w.u64(data_vmsize); w.u64(data_fileoff); w.u64(data_filesize); @@ -765,7 +1034,7 @@ fn write_headers( // __thread_data: init template. Size includes TBSS for dyld. let tdata_init_addr = tdata_addr; - let tdata_init_size = tdata_size + tbss_layout.mem_size; + let tdata_init_size = ((tdata_size + 7) & !7) + tbss_layout.mem_size; let tdata_init_foff = vm_addr_to_file_offset(tdata_init_addr, mappings) .unwrap_or(data_fileoff as usize) as u32; w.name16(b"__thread_data"); w.name16(b"__DATA"); @@ -775,6 +1044,18 @@ fn write_headers( w.u32(0x11); // S_THREAD_LOCAL_REGULAR w.u32(0); w.u32(0); w.u32(0); } + if has_rustc { + // Always emit .rustc in __DATA for rustc to find metadata. + let rc_addr = if rustc_in_text { data_vmaddr } else { rustc_addr.max(data_vmaddr) }; + let rc_foff = if rustc_in_text { data_fileoff as u32 } else { + vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(data_fileoff as usize) as u32 + }; + w.name16(b".rustc"); w.name16(b"__DATA"); + w.u64(rc_addr); w.u64(rustc_size); + w.u32(rc_foff); w.u32(0); + w.u32(0); w.u32(0); + w.u32(0); w.u32(0); w.u32(0); w.u32(0); + } } let (last_file_end, linkedit_vm) = if has_data { @@ -787,10 +1068,32 @@ fn write_headers( w.segment(b"__LINKEDIT", linkedit_vm, PAGE_SIZE, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); - w.u32(LC_MAIN); w.u32(24); w.u64(entry_offset as u64); w.u64(0); + if is_dylib { + // LC_ID_DYLIB = 0x0D + w.u32(0x0D); w.u32(id_dylib_cmd_size); w.u32(24); w.u32(2); + w.u32(0x01_0000); w.u32(0x01_0000); + w.bytes(install_name.as_bytes()); w.u8(0); w.pad8(); + // LC_UUID = 0x1B (required for dlopen) + w.u32(0x1B); w.u32(24); + // Generate a deterministic UUID from the output path + let uuid_bytes: [u8; 16] = { + let mut h = [0u8; 16]; + for (i, b) in install_name.bytes().enumerate() { + h[i % 16] ^= b; + } + h[6] = (h[6] & 0x0F) | 0x40; // version 4 + h[8] = (h[8] & 0x3F) | 0x80; // variant 1 + h + }; + w.bytes(&uuid_bytes); + } else { + w.u32(LC_MAIN); w.u32(24); w.u64(entry_offset as u64); w.u64(0); + } - w.u32(LC_LOAD_DYLINKER); w.u32(dylinker_cmd_size); w.u32(12); - w.bytes(DYLD_PATH); w.u8(0); w.pad8(); + if !is_dylib { + w.u32(LC_LOAD_DYLINKER); w.u32(dylinker_cmd_size); w.u32(12); + w.bytes(DYLD_PATH); w.u8(0); w.pad8(); + } w.u32(LC_LOAD_DYLIB); w.u32(dylib_cmd_size); w.u32(24); w.u32(2); w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(LIBSYSTEM_PATH); w.u8(0); w.pad8(); diff --git a/tests/macho_tests.sh b/tests/macho_tests.sh index 10bd49814..7d7d09b0f 100755 --- a/tests/macho_tests.sh +++ b/tests/macho_tests.sh @@ -346,6 +346,47 @@ else echo " SKIP: rust-hello-world (rustc not available or link failed)" fi +# --- Test 4p: Rust proc-macro (requires dylib .rustc section) --- +echo "Test 4p: Rust proc-macro crate" +PROC_DIR="$TMPDIR/procmacro" +mkdir -p "$PROC_DIR/my_macro/src" "$PROC_DIR/my_app/src" +cat > "$PROC_DIR/Cargo.toml" << 'EOF' +[workspace] +members = ["my_macro", "my_app"] +resolver = "2" +EOF +cat > "$PROC_DIR/my_macro/Cargo.toml" << 'EOF' +[package] +name = "my_macro" +version = "0.1.0" +edition = "2021" +[lib] +proc-macro = true +EOF +cat > "$PROC_DIR/my_macro/src/lib.rs" << 'EOF' +extern crate proc_macro; +use proc_macro::TokenStream; +#[proc_macro] +pub fn answer(_input: TokenStream) -> TokenStream { "42i32".parse().unwrap() } +EOF +cat > "$PROC_DIR/my_app/Cargo.toml" << 'EOF' +[package] +name = "my_app" +version = "0.1.0" +edition = "2021" +[dependencies] +my_macro = { path = "../my_macro" } +EOF +cat > "$PROC_DIR/my_app/src/main.rs" << 'EOF' +fn main() { let v: i32 = my_macro::answer!(); std::process::exit(v); } +EOF +if cd "$PROC_DIR" && RUSTFLAGS="-Clinker=clang -Clink-arg=-fuse-ld=$WILD" cargo build 2>/dev/null; then + check_exit "$PROC_DIR/target/debug/my_app" 42 "rust-proc-macro" +else + fail "rust-proc-macro" +fi +cd "$TMPDIR" + # --- Test 5: Output flag --- echo "Test 5: -o flag" clang -c "$TMPDIR/t1.c" -o "$TMPDIR/t5.o" From c7d1d770e8991983299c05b0ad0e05dc24c12820 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 6 Apr 2026 19:26:21 +0100 Subject: [PATCH 04/21] chore: merge main Signed-off-by: Giles Cope --- Cargo.lock | 13 ++++++-- libwild/src/macho.rs | 19 ++++++++++-- libwild/src/macho_writer.rs | 61 +++++++++++++++++++------------------ 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 390f4b1bf..22803858c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ dependencies = [ "cc", "cfg-if", "constant_time_eq", - "cpufeatures", + "cpufeatures 0.3.0", "rayon-core", ] @@ -397,6 +397,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + [[package]] name = "cpufeatures" version = "0.3.0" @@ -1705,7 +1714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 01d5903d0..0a2138c27 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -849,6 +849,7 @@ impl platform::Platform for MachO { type RawSymbolName<'data> = RawSymbolName<'data>; type VersionNames<'data> = (); type VerneedTable<'data> = VerneedTable<'data>; + type SymtabShndxEntry = u32; fn link_for_arch<'data>( linker: &'data crate::Linker, @@ -861,7 +862,11 @@ impl platform::Platform for MachO { output: &crate::file_writer::Output, layout: &crate::layout::Layout<'data, Self>, ) -> crate::error::Result { - crate::macho_writer::write::(output, layout) + // Mach-O writer bypasses SizedOutput but we still need to go through + // Output::write to satisfy the file creation lifecycle. + output.write(layout, |_sized_output, lay| { + crate::macho_writer::write_direct::(lay) + }) } fn section_attributes(header: &Self::SectionHeader) -> Self::SectionAttributes { @@ -898,6 +903,14 @@ impl platform::Platform for MachO { 0 } + fn start_memory_address(output_kind: crate::output_kind::OutputKind) -> u64 { + if output_kind == crate::output_kind::OutputKind::SharedObject { + 0 // dylibs have no PAGEZERO + } else { + 0x1_0000_0000 // PAGEZERO size for executables + } + } + fn finalise_find_required_sections(_groups: &[crate::layout::GroupState]) { } @@ -1258,6 +1271,7 @@ impl platform::Platform for MachO { flags: crate::value_flags::ValueFlags, mem_sizes: &mut crate::output_section_part_map::OutputSectionPartMap, _output_kind: crate::output_kind::OutputKind, + _args: &Self::Args, ) { if flags.needs_plt() { // Mach-O stubs are 12 bytes (adrp + ldr + br) @@ -1273,7 +1287,8 @@ impl platform::Platform for MachO { _common: &mut crate::layout::CommonGroupState<'data, Self>, _symbol_db: &crate::symbol_db::SymbolDb<'data, Self>, _per_symbol_flags: &crate::value_flags::AtomicPerSymbolFlags, - ) { + ) -> crate::error::Result { + Ok(()) } fn allocate_internal_symbol( diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 81475b7c4..ace8c966c 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -40,8 +40,7 @@ const PLATFORM_MACOS: u32 = 1; const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; const LIBSYSTEM_PATH: &[u8] = b"/usr/lib/libSystem.B.dylib"; -pub(crate) fn write>( - _output: &crate::file_writer::Output, +pub(crate) fn write_direct>( layout: &Layout<'_, MachO>, ) -> Result { let (mappings, alloc_size) = build_mappings_and_size(layout); @@ -116,7 +115,9 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, } else { text_filesize }; - let total = linkedit_offset as usize + 8192; + let n_exports = layout.dynamic_symbol_definitions.len(); + let linkedit_estimate = 8192 + n_exports * 256; + let total = linkedit_offset as usize + linkedit_estimate.max(8192); (mappings, total) } @@ -160,7 +161,7 @@ fn write_macho>( write_stubs_and_got::(out, layout, mappings, &mut bind_fixups, &mut import_names)?; // Populate GOT entries for non-import symbols - write_got_entries(out, layout, mappings)?; + write_got_entries(out, layout, mappings, &mut rebase_fixups)?; // Build chained fixup data: merge rebase + bind, encode per-page chains rebase_fixups.sort_by_key(|f| f.file_offset); @@ -367,6 +368,8 @@ fn write_dylib_symtab( out[off as usize+40..off as usize+48].try_into().unwrap()); let new_filesize = pos as u64 - linkedit_fileoff; out[off as usize+48..off as usize+56].copy_from_slice(&new_filesize.to_le_bytes()); + let new_vmsize = align_to(new_filesize, PAGE_SIZE); + out[off as usize+32..off as usize+40].copy_from_slice(&new_vmsize.to_le_bytes()); } } LC_DYSYMTAB => { @@ -518,6 +521,7 @@ fn write_got_entries( out: &mut [u8], layout: &Layout<'_, MachO>, mappings: &[SegmentMapping], + rebase_fixups: &mut Vec, ) -> Result { for res in layout.symbol_resolutions.iter().flatten() { if res.format_specific.plt_address.is_some() { continue; } // handled by stubs @@ -526,6 +530,9 @@ fn write_got_entries( if file_off + 8 <= out.len() { out[file_off..file_off + 8].copy_from_slice(&res.raw_value.to_le_bytes()); } + if res.raw_value != 0 { + rebase_fixups.push(RebaseFixup { file_offset: file_off, target: res.raw_value }); + } } } } @@ -692,31 +699,12 @@ fn apply_relocations( && target_addr >= tbss.mem_offset && target_addr < tbss.mem_offset + tbss.mem_size; if in_tdata || in_tbss { - // TLS offset relative to the init template start. - // Template = init data in TDATA (after descriptors) + TBSS. - // Compute descriptor size by scanning for type 0x13 sections in this object. - // Find the init data template start: minimum address of - // type 0x11 (S_THREAD_LOCAL_REGULAR) sections in this object. - let mut tls_init_start = u64::MAX; - let mut tls_init_size = 0u64; - for (si, _) in obj.sections.iter().enumerate() { - if let Some(s) = obj.object.sections.get(si) { - use object::read::macho::Section as _; - let stype = s.flags(le) & 0xFF; - if stype == 0x11 { // S_THREAD_LOCAL_REGULAR - if let Some(addr) = obj.section_resolutions[si].address() { - tls_init_start = tls_init_start.min(addr); - tls_init_size += s.size(le); - } - } - } - } - // If no init data sections, use TDATA end as base - if tls_init_start == u64::MAX { - tls_init_start = tdata.mem_offset + tdata.mem_size; - } + // TLS offset relative to the GLOBAL TLS template start. + // The template is the TDATA section content (init data for + // all TLS variables across all objects) followed by TBSS. + let tls_init_start = tdata.mem_offset; + let tls_init_size = tdata.mem_size; let tls_offset = if in_tbss { - // Align init_size to BSS alignment (8 bytes) let aligned_init = (tls_init_size + 7) & !7; aligned_init + target_addr.saturating_sub(tbss.mem_offset) } else { @@ -732,6 +720,19 @@ fn apply_relocations( } } } + 7 if reloc.r_length == 2 && reloc.r_pcrel => { // ARM64_RELOC_POINTER_TO_GOT + if let Some(got) = got_addr { + let delta = (got as i64 - pc_addr as i64) as i32; + if patch_file_offset + 4 <= out.len() { + out[patch_file_offset..patch_file_offset + 4].copy_from_slice(&delta.to_le_bytes()); + } + } else { + let delta = (target_addr as i64 - pc_addr as i64) as i32; + if patch_file_offset + 4 <= out.len() { + out[patch_file_offset..patch_file_offset + 4].copy_from_slice(&delta.to_le_bytes()); + } + } + } _ => {} } } @@ -913,6 +914,7 @@ fn write_headers( } let has_rustc = rustc_addr > 0 && rustc_size > 0; + let buf_len = out.len(); let mut w = Writer { buf: out, pos: offset }; let dylinker_cmd_size = align8((12 + DYLD_PATH.len() + 1) as u32); let dylib_cmd_size = align8((24 + LIBSYSTEM_PATH.len() + 1) as u32); @@ -1066,7 +1068,8 @@ fn write_headers( let cf_offset = last_file_end; let cf_size = chained_fixups_data_size as u64; - w.segment(b"__LINKEDIT", linkedit_vm, PAGE_SIZE, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); + let linkedit_vmsize = align_to((buf_len as u64).saturating_sub(last_file_end).max(PAGE_SIZE), PAGE_SIZE); + w.segment(b"__LINKEDIT", linkedit_vm, linkedit_vmsize, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); if is_dylib { // LC_ID_DYLIB = 0x0D From 17726c6cf10f76c3d775083ba1daa6c5c76064c3 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 6 Apr 2026 22:40:43 +0100 Subject: [PATCH 05/21] feat: try and match test structure Signed-off-by: Giles Cope --- .claude/settings.local.json | 58 ++++ libwild/src/args/macho.rs | 27 +- libwild/src/macho.rs | 49 ++- libwild/src/macho_writer.rs | 105 ++++-- libwild/src/output_section_id.rs | 8 +- libwild/src/platform.rs | 9 + libwild/src/resolution.rs | 7 +- linker-diff/src/utils.rs | 25 +- main | Bin 0 -> 438472 bytes tests/macho_tests.sh | 36 ++ wild/Cargo.toml | 5 + wild/tests/integration_tests.rs | 8 + wild/tests/macho_integration_tests.rs | 317 ++++++++++++++++++ .../tests/sources/macho/alignment/alignment.c | 10 + wild/tests/sources/macho/bss/bss.c | 9 + .../macho/common-symbol/common-symbol.c | 6 + .../macho/common-symbol/common-symbol1.c | 2 + .../sources/macho/constructors/constructors.c | 7 + .../sources/macho/cpp-basic/cpp-basic.cc | 19 ++ wild/tests/sources/macho/data/data.c | 8 + .../macho/string-constants/string-constants.c | 15 + wild/tests/sources/macho/trivial/trivial.c | 1 + wild/tests/sources/macho/weak-fns/weak-fns.c | 4 + wild/tests/sources/macho/weak-fns/weak-fns1.c | 2 + .../tests/sources/macho/weak-vars/weak-vars.c | 4 + .../sources/macho/weak-vars/weak-vars1.c | 2 + 26 files changed, 710 insertions(+), 33 deletions(-) create mode 100644 .claude/settings.local.json create mode 100755 main create mode 100644 wild/tests/macho_integration_tests.rs create mode 100644 wild/tests/sources/macho/alignment/alignment.c create mode 100644 wild/tests/sources/macho/bss/bss.c create mode 100644 wild/tests/sources/macho/common-symbol/common-symbol.c create mode 100644 wild/tests/sources/macho/common-symbol/common-symbol1.c create mode 100644 wild/tests/sources/macho/constructors/constructors.c create mode 100644 wild/tests/sources/macho/cpp-basic/cpp-basic.cc create mode 100644 wild/tests/sources/macho/data/data.c create mode 100644 wild/tests/sources/macho/string-constants/string-constants.c create mode 100644 wild/tests/sources/macho/trivial/trivial.c create mode 100644 wild/tests/sources/macho/weak-fns/weak-fns.c create mode 100644 wild/tests/sources/macho/weak-fns/weak-fns1.c create mode 100644 wild/tests/sources/macho/weak-vars/weak-vars.c create mode 100644 wild/tests/sources/macho/weak-vars/weak-vars1.c diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..7ca8445d2 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,58 @@ +{ + "permissions": { + "allow": [ + "Bash(wc -l /Users/gilescope/git/gilescope/wild/libwild/src/*.rs)", + "WebSearch", + "WebFetch(domain:crates.io)", + "WebFetch(domain:lib.rs)", + "WebFetch(domain:docs.rs)", + "Bash(rustup install:*)", + "Bash(rustup override:*)", + "Bash(grep -A5 \"fn is_undefined\" ~/.cargo/registry/src/*/object-0.39.*/src/read/macho/symbol.rs)", + "Bash(grep -n \"S_REGULAR\\\\|S_ZEROFILL\\\\|S_CSTRING_LITERALS\\\\|S_NON_LAZY\\\\|S_LAZY\\\\|SECTION_TYPE\\\\|S_ATTR_PURE_INST\\\\|S_ATTR_SOME_INST\\\\|SECTION_ATTRIBUTES\" ~/.cargo/registry/src/*/object-0.39.*/src/macho.rs)", + "Bash(grep -n \"ARM64_RELOC\" ~/.cargo/registry/src/*/object-0.39.*/src/macho.rs)", + "Bash(clang -v -c /tmp/test_macho.c -o /tmp/test_macho.o)", + "Bash(clang -### /tmp/test_macho.o -o /tmp/test_macho)", + "Bash(./target/debug/wild:*)", + "Bash(otool -l /tmp/test_macho.o)", + "Read(//private/tmp/**)", + "Bash(otool -l /tmp/test_wild_macho)", + "Bash(/tmp/test_wild_macho)", + "Bash(codesign:*)", + "Bash(clang /tmp/test_macho.o -o /tmp/test_ref_macho -Wl,-no_compact_unwind)", + "Bash(otool -l /tmp/test_ref_macho)", + "Bash(clang -c /tmp/test_add.c -o /tmp/test_add.o)", + "Bash(clang -c /tmp/test_main2.c -o /tmp/test_main2.o)", + "Bash(/tmp/test_multi)", + "Bash(otool:*)", + "Bash(grep:*)", + "Bash(/tmp/test_multi2)", + "Bash(chmod +x /Users/gilescope/git/gilescope/wild/tests/macho_tests.sh)", + "Bash(bash /Users/gilescope/git/gilescope/wild/tests/macho_tests.sh)", + "Bash(clang -c /tmp/t4_data.c -o /tmp/t4_data.o)", + "Bash(clang -c /tmp/t4_main.c -o /tmp/t4_main.o)", + "Bash(nm:*)", + "Bash(clang -x c -c -o /tmp/dbg.o -)", + "Bash(/tmp/dbg_out)", + "Bash(clang -x c -c -o /tmp/ref.o -)", + "Bash(clang /tmp/ref.o -o /tmp/ref_out -Wl,-no_compact_unwind)", + "Bash(git stash:*)", + "Bash(clang -x c -c -o /tmp/old.o -)", + "Bash(/tmp/old_out)", + "Bash(clang -x c -c -o /tmp/new.o -)", + "Bash(DYLD_PRINT_APIS=1 /tmp/new_out)", + "Bash(/tmp/new_out)", + "Bash(clang -x c -c -o /tmp/fix.o -)", + "Bash(/tmp/fix_out)", + "Bash(clang -c /tmp/t_static.c -o /tmp/t_static.o)", + "Bash(/tmp/t_static_out)", + "Bash(clang -c /tmp/t_extern_data.c -o /tmp/t_extern_data.o)", + "Bash(clang -c /tmp/t_extern_main.c -o /tmp/t_extern_main.o)", + "Bash(clang -c /tmp/t_ext_data.c -o /tmp/t_ext_data.o)", + "Bash(clang -c /tmp/t_ext_main.c -o /tmp/t_ext_main.o)", + "Bash(/tmp/t_ext_out)", + "Bash(RUST_BACKTRACE=1 /tmp/rust_map)", + "Bash(RUST_BACKTRACE=full /tmp/rust_map)" + ] + } +} diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index 80cd1b611..2842d1e9a 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -20,7 +20,10 @@ pub struct MachOArgs { pub(crate) syslibroot: Option>, pub(crate) entry_symbol: Option>, pub(crate) is_dylib: bool, + #[allow(dead_code)] pub(crate) install_name: Option>, + /// Additional dylibs to emit LC_LOAD_DYLIB for (from -l flags resolving to .tbd stubs). + pub(crate) extra_dylibs: Vec>, } impl MachOArgs { @@ -43,6 +46,7 @@ impl Default for MachOArgs { entry_symbol: Some(b"_main".to_vec()), is_dylib: false, install_name: None, + extra_dylibs: Vec::new(), } } } @@ -228,8 +232,14 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( for dir in &search_paths { let path = dir.join(&filename); if path.exists() { - // For .tbd files, skip (text-based stubs, dylib references) + // .tbd files are text-based dylib stubs. Parse the + // install-name so we can emit LC_LOAD_DYLIB for it. if *ext == ".tbd" { + if let Some(dylib_path) = parse_tbd_install_name(&path) { + if !args.extra_dylibs.contains(&dylib_path) { + args.extra_dylibs.push(dylib_path); + } + } found = true; break; } @@ -268,3 +278,18 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( Ok(()) } + +/// Extract `install-name` from a .tbd (text-based dylib stub) file. +fn parse_tbd_install_name(path: &Path) -> Option> { + let content = std::fs::read_to_string(path).ok()?; + for line in content.lines() { + let trimmed = line.trim(); + if let Some(rest) = trimmed.strip_prefix("install-name:") { + let name = rest.trim().trim_matches('\'').trim_matches('"'); + if !name.is_empty() { + return Some(name.as_bytes().to_vec()); + } + } + } + None +} diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 0a2138c27..1cf7b5886 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -305,6 +305,22 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } } + fn is_symbol_in_common_section(&self, symbol: &SymtabEntry) -> bool { + let n_type = symbol.n_type() & macho::N_TYPE; + if n_type != macho::N_SECT { + return false; + } + let sect = symbol.n_sect(); + if sect == 0 { + return false; + } + if let Some(section) = self.sections.get(sect as usize - 1) { + trim_nul(section.sectname()) == b"__common" + } else { + false + } + } + fn dynamic_tag_values(&self) -> Option> { None } @@ -389,14 +405,30 @@ impl platform::SectionHeader for SectionHeader { } fn should_retain(&self) -> bool { - false + let sec_type = self.0.flags(LE) & macho::SECTION_TYPE; + // __mod_init_func / __mod_term_func must always be retained — + // they contain constructor/destructor pointers called by dyld. + sec_type == macho::S_MOD_INIT_FUNC_POINTERS || sec_type == macho::S_MOD_TERM_FUNC_POINTERS } fn should_exclude(&self) -> bool { + let segname = trim_nul(self.0.segname()); let sectname = trim_nul(self.0.sectname()); // Debug sections in __DWARF segment are not loaded - let segname = trim_nul(self.0.segname()); - segname == b"__DWARF" + if segname == b"__DWARF" { + return true; + } + // __LD segment contains linker-private data (e.g. __compact_unwind) + // that must be consumed by the linker, not emitted to output. + if segname == b"__LD" { + return true; + } + // __eh_frame has SUBTRACTOR relocation pairs we don't process yet; + // exclude until we generate proper __unwind_info. + if sectname == b"__eh_frame" { + return true; + } + false } fn is_group(&self) -> bool { @@ -877,9 +909,12 @@ impl platform::Platform for MachO { } fn apply_force_keep_sections( - _keep_sections: &mut crate::output_section_map::OutputSectionMap, + keep_sections: &mut crate::output_section_map::OutputSectionMap, _args: &Self::Args, ) { + // Constructor/destructor function pointer arrays must always be kept. + *keep_sections.get_mut(crate::output_section_id::INIT_ARRAY) = true; + *keep_sections.get_mut(crate::output_section_id::FINI_ARRAY) = true; } fn is_zero_sized_section_content( @@ -1383,6 +1418,8 @@ impl platform::Platform for MachO { // __DATA segment (rw-): writable data, GOT, BSS builder.add_section(output_section_id::DATA); + builder.add_section(output_section_id::INIT_ARRAY); // __mod_init_func + builder.add_section(output_section_id::FINI_ARRAY); // __mod_term_func builder.add_sections(&custom.data); builder.add_section(output_section_id::GOT); builder.add_section(output_section_id::TDATA); @@ -1421,6 +1458,10 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__thread_vars", output_section_id::GOT), SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), + // Constructor/destructor function pointer arrays (Mach-O equivalent of .init_array/.fini_array) + SectionRule::exact_section(b"__mod_init_func", output_section_id::INIT_ARRAY), + SectionRule::exact_section(b"__mod_term_func", output_section_id::FINI_ARRAY), + SectionRule::exact_section(b"__gcc_except_tab", output_section_id::GCC_EXCEPT_TABLE), SectionRule::exact_section(b".rustc", output_section_id::DATA), SectionRule::exact_section(b"__bss", output_section_id::BSS), SectionRule::exact_section(b"__common", output_section_id::BSS), diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index ace8c966c..2996ddb18 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -115,6 +115,8 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, } else { text_filesize }; + // Estimate LINKEDIT size: chained fixups + symtab + strtab + exports trie. + // For dylibs with many exports, 8KB is not enough. let n_exports = layout.dynamic_symbol_definitions.len(); let linkedit_estimate = 8192 + n_exports * 256; let total = linkedit_offset as usize + linkedit_estimate.max(8192); @@ -133,6 +135,20 @@ struct BindFixup { import_index: u32, } +/// An imported symbol name and its dylib ordinal. +struct ImportEntry { + name: Vec, + /// 1 = libSystem, 2+ = extra dylibs, 0xFE = flat lookup (search all dylibs). + lib_ordinal: u8, +} + +/// Determine the lib ordinal for a symbol name. +/// If there are extra dylibs (beyond libSystem), we use flat lookup (0xFE) +/// since we don't yet track which dylib exports which symbol. +fn lib_ordinal_for_symbol(has_extra_dylibs: bool) -> u8 { + if has_extra_dylibs { 0xFE } else { 1 } +} + /// Returns the actual final file size. fn write_macho>( out: &mut [u8], @@ -145,20 +161,21 @@ fn write_macho>( // Collect fixups during section writing and stub generation let mut rebase_fixups: Vec = Vec::new(); let mut bind_fixups: Vec = Vec::new(); - let mut import_names: Vec> = Vec::new(); + let mut imports: Vec = Vec::new(); + let has_extra_dylibs = !layout.symbol_db.args.extra_dylibs.is_empty(); // Copy section data and apply relocations for group in &layout.group_layouts { for file_layout in &group.files { if let FileLayout::Object(obj) = file_layout { write_object_sections(out, obj, layout, mappings, le, - &mut rebase_fixups, &mut bind_fixups, &mut import_names)?; + &mut rebase_fixups, &mut bind_fixups, &mut imports, has_extra_dylibs)?; } } } // Write PLT stubs and collect bind fixups for imported symbols - write_stubs_and_got::(out, layout, mappings, &mut bind_fixups, &mut import_names)?; + write_stubs_and_got::(out, layout, mappings, &mut bind_fixups, &mut imports, has_extra_dylibs)?; // Populate GOT entries for non-import symbols write_got_entries(out, layout, mappings, &mut rebase_fixups)?; @@ -206,14 +223,14 @@ fn write_macho>( } let has_fixups = !all_data_fixups.is_empty(); - let n_imports = import_names.len() as u32; + let n_imports = imports.len() as u32; // Build symbol name pool for imports let mut symbols_pool = vec![0u8]; let mut import_name_offsets: Vec = Vec::new(); - for name in &import_names { + for entry in &imports { import_name_offsets.push(symbols_pool.len() as u32); - symbols_pool.extend_from_slice(name); + symbols_pool.extend_from_slice(&entry.name); symbols_pool.push(0); } @@ -257,9 +274,10 @@ fn write_macho>( } cf + cf_data_size as usize } else { + let ordinals: Vec = imports.iter().map(|e| e.lib_ordinal).collect(); write_chained_fixups_header( out, cf_off as usize, &all_data_fixups, n_imports, - &import_name_offsets, &symbols_pool, mappings, + &import_name_offsets, &ordinals, &symbols_pool, mappings, layout.symbol_db.args.is_dylib, )?; cf_off as usize + cf_data_size as usize @@ -283,7 +301,7 @@ fn write_dylib_symtab( out: &mut [u8], start: usize, layout: &Layout<'_, MachO>, - mappings: &[SegmentMapping], + _mappings: &[SegmentMapping], ) -> Result { // Collect exported symbols from dynamic_symbol_definitions @@ -368,6 +386,7 @@ fn write_dylib_symtab( out[off as usize+40..off as usize+48].try_into().unwrap()); let new_filesize = pos as u64 - linkedit_fileoff; out[off as usize+48..off as usize+56].copy_from_slice(&new_filesize.to_le_bytes()); + // Update vmsize to cover the content let new_vmsize = align_to(new_filesize, PAGE_SIZE); out[off as usize+32..off as usize+40].copy_from_slice(&new_vmsize.to_le_bytes()); } @@ -487,7 +506,8 @@ fn write_stubs_and_got>( layout: &Layout<'_, MachO>, mappings: &[SegmentMapping], bind_fixups: &mut Vec, - import_names: &mut Vec>, + imports: &mut Vec, + has_extra_dylibs: bool, ) -> Result { use crate::symbol_db::SymbolId; @@ -503,13 +523,16 @@ fn write_stubs_and_got>( } if let Some(got_file_off) = vm_addr_to_file_offset(got_addr, mappings) { - let import_index = import_names.len() as u32; + let import_index = imports.len() as u32; let symbol_id = SymbolId::from_usize(sym_idx); let name = match layout.symbol_db.symbol_name(symbol_id) { Ok(n) => n.bytes().to_vec(), Err(_) => b"".to_vec(), }; - import_names.push(name); + imports.push(ImportEntry { + name, + lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), + }); bind_fixups.push(BindFixup { file_offset: got_file_off, import_index }); } } @@ -517,6 +540,7 @@ fn write_stubs_and_got>( } /// Fill GOT entries with target symbol addresses (for non-import symbols). +/// Also registers rebase fixups so dyld can adjust for ASLR. fn write_got_entries( out: &mut [u8], layout: &Layout<'_, MachO>, @@ -529,6 +553,13 @@ fn write_got_entries( if let Some(file_off) = vm_addr_to_file_offset(got_vm_addr, mappings) { if file_off + 8 <= out.len() { out[file_off..file_off + 8].copy_from_slice(&res.raw_value.to_le_bytes()); + // Register a rebase fixup so dyld adjusts for ASLR + if res.raw_value != 0 { + rebase_fixups.push(RebaseFixup { + file_offset: file_off, + target: res.raw_value, + }); + } } if res.raw_value != 0 { rebase_fixups.push(RebaseFixup { file_offset: file_off, target: res.raw_value }); @@ -548,7 +579,8 @@ fn write_object_sections( le: object::Endianness, rebase_fixups: &mut Vec, bind_fixups: &mut Vec, - import_names: &mut Vec>, + imports: &mut Vec, + has_extra_dylibs: bool, ) -> Result { use object::read::macho::Section as MachOSection; @@ -580,7 +612,7 @@ fn write_object_sections( if let Ok(relocs) = input_section.relocations(le, obj.object.data) { apply_relocations(out, file_offset, output_addr, relocs, obj, layout, le, - rebase_fixups, bind_fixups, import_names)?; + rebase_fixups, bind_fixups, imports, has_extra_dylibs)?; } } Ok(()) @@ -597,7 +629,8 @@ fn apply_relocations( le: object::Endianness, rebase_fixups: &mut Vec, bind_fixups: &mut Vec, - import_names: &mut Vec>, + imports: &mut Vec, + has_extra_dylibs: bool, ) -> Result { let mut pending_addend: i64 = 0; @@ -685,8 +718,11 @@ fn apply_relocations( Ok(n) => n.bytes().to_vec(), Err(_) => b"".to_vec(), }; - let import_index = import_names.len() as u32; - import_names.push(name); + let import_index = imports.len() as u32; + imports.push(ImportEntry { + name, + lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), + }); bind_fixups.push(BindFixup { file_offset: patch_file_offset, import_index }); } else { // Check if target is in TLS data — write offset, not rebase @@ -746,6 +782,7 @@ fn write_chained_fixups_header( all_fixups: &[(usize, u64)], n_imports: u32, import_name_offsets: &[u32], + import_ordinals: &[u8], symbols_pool: &[u8], mappings: &[SegmentMapping], is_dylib: bool, @@ -815,7 +852,8 @@ fn write_chained_fixups_header( let it = imports_table_offset as usize; for (i, &name_off) in import_name_offsets.iter().enumerate() { - let import_val: u32 = 1u32 | ((name_off & 0x7F_FFFF) << 9); // lib_ordinal=1 + let ordinal = import_ordinals[i] as u32; + let import_val: u32 = ordinal | ((name_off & 0x7F_FFFF) << 9); w[it + i * 4..it + i * 4 + 4].copy_from_slice(&import_val.to_le_bytes()); } @@ -885,7 +923,6 @@ fn write_headers( let tdata_layout = layout.section_layouts.get(output_section_id::TDATA); let tbss_layout = layout.section_layouts.get(output_section_id::TBSS); let has_tlv = tdata_layout.mem_size > 0 || tbss_layout.mem_size > 0; - let data_layout = layout.section_layouts.get(output_section_id::DATA); let has_tvars = has_tlv; // Scan for .rustc section (proc-macro metadata) before computing cmd sizes @@ -923,7 +960,7 @@ fn write_headers( let install_name = if is_dylib { layout.symbol_db.args.output().to_string_lossy().into_owned() } else { String::new() }; - let id_dylib_cmd_size = if is_dylib { align8((24 + install_name.len() as u32 + 1)) } else { 0 }; + let id_dylib_cmd_size = if is_dylib { align8(24 + install_name.len() as u32 + 1) } else { 0 }; let mut ncmds = 0u32; let mut cmdsize = 0u32; @@ -932,9 +969,12 @@ fn write_headers( let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT + let init_array_layout = layout.section_layouts.get(output_section_id::INIT_ARRAY); + let has_init_array = init_array_layout.mem_size > 0; if has_data { let mut data_nsects = if has_tvars { 2u32 } else { 0 }; if has_rustc { data_nsects += 1; } + if has_init_array { data_nsects += 1; } add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); } add_cmd(&mut ncmds, &mut cmdsize, 72); // LINKEDIT @@ -945,7 +985,15 @@ fn write_headers( add_cmd(&mut ncmds, &mut cmdsize, 24); // LC_MAIN } if !is_dylib { add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); } - add_cmd(&mut ncmds, &mut cmdsize, dylib_cmd_size); + add_cmd(&mut ncmds, &mut cmdsize, dylib_cmd_size); // libSystem + let extra_dylibs = &layout.symbol_db.args.extra_dylibs; + let extra_dylib_sizes: Vec = extra_dylibs + .iter() + .map(|p| align8(24 + p.len() as u32 + 1)) + .collect(); + for &sz in &extra_dylib_sizes { + add_cmd(&mut ncmds, &mut cmdsize, sz); + } add_cmd(&mut ncmds, &mut cmdsize, 24); // SYMTAB add_cmd(&mut ncmds, &mut cmdsize, 80); // DYSYMTAB add_cmd(&mut ncmds, &mut cmdsize, 32); @@ -984,6 +1032,7 @@ fn write_headers( if has_data { let mut nsects = if has_tvars { 2u32 } else { 0 }; if has_rustc { nsects += 1; } + if has_init_array { nsects += 1; } let data_cmd_size = 72 + 80 * nsects; w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); w.name16(b"__DATA"); w.u64(data_vmaddr); w.u64(data_vmsize); w.u64(data_fileoff); w.u64(data_filesize); @@ -1058,6 +1107,16 @@ fn write_headers( w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); } + if has_init_array { + let ia_foff = vm_addr_to_file_offset(init_array_layout.mem_offset, mappings) + .unwrap_or(data_fileoff as usize) as u32; + w.name16(b"__mod_init_func"); w.name16(b"__DATA"); + w.u64(init_array_layout.mem_offset); w.u64(init_array_layout.mem_size); + w.u32(ia_foff); w.u32(3); // align 2^3 = 8 + w.u32(0); w.u32(0); + w.u32(0x09); // S_MOD_INIT_FUNC_POINTERS + w.u32(0); w.u32(0); w.u32(0); + } } let (last_file_end, linkedit_vm) = if has_data { @@ -1068,6 +1127,7 @@ fn write_headers( let cf_offset = last_file_end; let cf_size = chained_fixups_data_size as u64; + // LINKEDIT vmsize must cover the full content (fixups + symtab + exports). let linkedit_vmsize = align_to((buf_len as u64).saturating_sub(last_file_end).max(PAGE_SIZE), PAGE_SIZE); w.segment(b"__LINKEDIT", linkedit_vm, linkedit_vmsize, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); @@ -1101,6 +1161,11 @@ fn write_headers( w.u32(LC_LOAD_DYLIB); w.u32(dylib_cmd_size); w.u32(24); w.u32(2); w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(LIBSYSTEM_PATH); w.u8(0); w.pad8(); + for (i, dylib_path) in extra_dylibs.iter().enumerate() { + w.u32(LC_LOAD_DYLIB); w.u32(extra_dylib_sizes[i]); w.u32(24); w.u32(2); + w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(dylib_path); w.u8(0); w.pad8(); + } + w.u32(LC_SYMTAB); w.u32(24); w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(LC_DYSYMTAB); w.u32(80); for _ in 0..18 { w.u32(0); } diff --git a/libwild/src/output_section_id.rs b/libwild/src/output_section_id.rs index c622caaef..de2dae369 100644 --- a/libwild/src/output_section_id.rs +++ b/libwild/src/output_section_id.rs @@ -100,11 +100,16 @@ pub(crate) const SYMTAB_SHNDX_LOCAL: OutputSectionId = part_id::SYMTAB_SHNDX_LOCAL.output_section_id(); pub(crate) const SYMTAB_SHNDX_GLOBAL: OutputSectionId = part_id::SYMTAB_SHNDX_GLOBAL.output_section_id(); -// Mach-O specific sections +// Mach-O specific sections (used by the Mach-O writer pipeline) +#[allow(dead_code)] pub(crate) const PAGEZERO_SEGMENT: OutputSectionId = part_id::PAGEZERO_SEGMENT.output_section_id(); +#[allow(dead_code)] pub(crate) const TEXT_SEGMENT: OutputSectionId = part_id::TEXT_SEGMENT.output_section_id(); +#[allow(dead_code)] pub(crate) const DATA_SEGMENT: OutputSectionId = part_id::DATA_SEGMENT.output_section_id(); +#[allow(dead_code)] pub(crate) const ENTRY_POINT: OutputSectionId = part_id::ENTRY_POINT.output_section_id(); +#[allow(dead_code)] pub(crate) const LINK_EDIT_SEGMENT: OutputSectionId = part_id::LINK_EDIT_SEGMENT.output_section_id(); @@ -125,6 +130,7 @@ pub(crate) const GCC_EXCEPT_TABLE: OutputSectionId = OutputSectionId::regular(12 pub(crate) const NOTE_ABI_TAG: OutputSectionId = OutputSectionId::regular(13); pub(crate) const DATA_REL_RO: OutputSectionId = OutputSectionId::regular(14); // Mach-O specific sections +#[allow(dead_code)] pub(crate) const CSTRING: OutputSectionId = OutputSectionId::regular(15); pub(crate) const NUM_BUILT_IN_REGULAR_SECTIONS: usize = 16; diff --git a/libwild/src/platform.rs b/libwild/src/platform.rs index 3c68517b6..7257979a4 100644 --- a/libwild/src/platform.rs +++ b/libwild/src/platform.rs @@ -798,6 +798,15 @@ pub(crate) trait ObjectFile<'data>: Sized + Send + Sync + std::fmt::Debug + 'dat fn section_display_name(&self, index: object::SectionIndex) -> Cow<'data, str>; + /// Returns true if the given symbol is in a common/tentative section (e.g. + /// Mach-O `__common`). Default returns false; Mach-O overrides this. + fn is_symbol_in_common_section( + &self, + _symbol: &::SymtabEntry, + ) -> bool { + false + } + fn dynamic_tag_values(&self) -> Option<::DynamicTagValues<'data>>; fn get_version_names(&self) -> Result<::VersionNames<'data>>; diff --git a/libwild/src/resolution.rs b/libwild/src/resolution.rs index 8df98866f..9ab917f15 100644 --- a/libwild/src/resolution.rs +++ b/libwild/src/resolution.rs @@ -1006,9 +1006,14 @@ impl<'data, P: Platform> ResolvedCommon<'data, P> { pub(crate) fn symbol_strength(&self, symbol_id: SymbolId) -> SymbolStrength { let local_index = symbol_id.to_input(self.symbol_id_range); let Ok(obj_symbol) = self.object.symbol(local_index) else { - // Errors from this function should have been reported elsewhere. return SymbolStrength::Undefined; }; + // Mach-O __common section symbols are tentative definitions (like ELF + // SHN_COMMON) but appear as N_SECT in the nlist. Check the section + // name to classify them correctly. + if self.object.is_symbol_in_common_section(obj_symbol) { + return SymbolStrength::Common(obj_symbol.size()); + } SymbolStrength::of(obj_symbol) } } diff --git a/linker-diff/src/utils.rs b/linker-diff/src/utils.rs index 8be62a248..9d8976631 100644 --- a/linker-diff/src/utils.rs +++ b/linker-diff/src/utils.rs @@ -22,8 +22,19 @@ pub fn decode_insn_with_objdump(insn: &[u8], address: u64, arch: ArchKind) -> Re let objdump = objdump_bin_candidates .iter() - .find(|bin| which::which(bin).is_ok()) - .unwrap(); + .find(|bin| { + if which::which(bin).is_ok() { + // macOS ships llvm-objdump as "objdump" which doesn't support -b binary. + // Only accept objdump if it supports the -b flag (GNU objdump). + if **bin == "objdump" && cfg!(target_os = "macos") { + return false; + } + true + } else { + false + } + }) + .context("No suitable objdump found")?; let command = Command::new(objdump) .arg("-b") @@ -61,10 +72,12 @@ fn test_align_up() { || std::env::var("WILD_TEST_CROSS") .is_ok_and(|v| v == "all" || v.split(',').any(|a| a == "aarch64")) { - assert_eq!( - decode_insn_with_objdump(&[0xe3, 0x93, 0x44, 0xa9], 0x1000, ArchKind::Aarch64).unwrap(), - "ldp\tx3, x4, [sp, #72]" - ); + // Skip if no suitable (GNU) objdump is available (e.g. macOS ships llvm-objdump). + if let Ok(result) = + decode_insn_with_objdump(&[0xe3, 0x93, 0x44, 0xa9], 0x1000, ArchKind::Aarch64) + { + assert_eq!(result, "ldp\tx3, x4, [sp, #72]"); + } } if cfg!(target_arch = "riscv64") diff --git a/main b/main new file mode 100755 index 0000000000000000000000000000000000000000..b6e3ee291317ad8280ff911efd0fa18d47f2e386 GIT binary patch literal 438472 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

n+ru#kf*vV=FY0`3c?6$vR{bb4b1x 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 Sc@osV8kO0uU*Y}#*!mYjXAd0! literal 0 HcmV?d00001 diff --git a/tests/macho_tests.sh b/tests/macho_tests.sh index 7d7d09b0f..80c88ab65 100755 --- a/tests/macho_tests.sh +++ b/tests/macho_tests.sh @@ -346,6 +346,42 @@ else echo " SKIP: rust-hello-world (rustc not available or link failed)" fi +# --- Test 4o2: Rust dylib with complex std (HashMap, Vec, format) --- +echo "Test 4o2: Rust dylib with complex std usage" +cat > "$TMPDIR/t4o2.rs" << 'EOF' +use std::collections::HashMap; +#[no_mangle] +pub extern "C" fn complex_test() -> i32 { + let mut map = HashMap::new(); + map.insert("hello".to_string(), 10); + map.insert("world".to_string(), 32); + let sum: i32 = map.values().sum(); + let msg = format!("sum={}", sum); + if msg.contains("42") { sum } else { -1 } +} +EOF +if rustc "$TMPDIR/t4o2.rs" --crate-type dylib -Clinker=clang "-Clink-arg=-fuse-ld=$WILD" -o "$TMPDIR/t4o2.dylib" 2>/dev/null; then + # Test via dlopen + cat > "$TMPDIR/t4o2_test.c" << 'LOADEOF' +#include +#include +int main() { + void *h = dlopen("DYLIB_PATH", RTLD_NOW); + if (!h) { fprintf(stderr, "dlopen: %s\n", dlerror()); return 1; } + int (*fn)(void) = dlsym(h, "complex_test"); + if (!fn) { fprintf(stderr, "dlsym: %s\n", dlerror()); dlclose(h); return 1; } + int r = fn(); + dlclose(h); + return r == 42 ? 42 : 1; +} +LOADEOF + sed -i '' "s|DYLIB_PATH|$TMPDIR/t4o2.dylib|" "$TMPDIR/t4o2_test.c" + clang "$TMPDIR/t4o2_test.c" -o "$TMPDIR/t4o2_test" + check_exit "$TMPDIR/t4o2_test" 42 "rust-dylib-complex-std" +else + echo " SKIP: rust-dylib-complex-std (rustc not available or link failed)" +fi + # --- Test 4p: Rust proc-macro (requires dylib .rustc section) --- echo "Test 4p: Rust proc-macro crate" PROC_DIR="$TMPDIR/procmacro" diff --git a/wild/Cargo.toml b/wild/Cargo.toml index 4c2303199..343cf0303 100644 --- a/wild/Cargo.toml +++ b/wild/Cargo.toml @@ -17,6 +17,11 @@ name = "integration_tests" path = "tests/integration_tests.rs" harness = false +[[test]] +name = "macho_integration_tests" +path = "tests/macho_integration_tests.rs" +harness = false + [dependencies] libwild = { path = "../libwild", version = "0.8.0" } diff --git a/wild/tests/integration_tests.rs b/wild/tests/integration_tests.rs index c385a212f..665d5df73 100644 --- a/wild/tests/integration_tests.rs +++ b/wild/tests/integration_tests.rs @@ -3912,6 +3912,14 @@ fn run_integration_test( mut config: Config, test_config: &TestConfig, ) -> Result { + // ELF tests require a Linux toolchain (GNU ld, ELF-compatible compiler). + // On macOS, the system linker is ld64 which doesn't support ELF flags. + if cfg!(target_os = "macos") && config.platform == PlatformKind::Elf { + return Ok(libtest_mimic::Completion::ignored_with( + "ELF tests require Linux toolchain", + )); + } + setup_symlink(); let linkers = available_linkers()?; diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs new file mode 100644 index 000000000..7679be9b7 --- /dev/null +++ b/wild/tests/macho_integration_tests.rs @@ -0,0 +1,317 @@ +//! Integration tests for macOS Mach-O linking. +//! +//! Mirrors the structure of the ELF integration tests (`integration_tests.rs`) but for Mach-O. +//! Test sources live in `tests/sources/macho/{test_name}/{test_name}.{c,cc,rs}`. +//! +//! Supported directives (in `//#Directive:Args` format): +//! +//! Object:{filename} Extra object file to compile and link. +//! CompArgs:... Extra compiler flags. +//! LinkArgs:... Extra linker flags. +//! ExpectError:{regex} Link must fail; stderr must match regex. +//! RunEnabled:{bool} Whether to execute the output (default: true). +//! Contains:{string} Output binary must contain this string. +//! DoesNotContain:{string} Output binary must NOT contain this string. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn main() -> Result<(), Box> { + // Only run on macOS. + if cfg!(not(target_os = "macos")) { + eprintln!("Mach-O integration tests only run on macOS — skipping."); + let args = libtest_mimic::Arguments::from_args(); + let _ = libtest_mimic::run(&args, Vec::new()); + return Ok(()); + } + + let args = libtest_mimic::Arguments::from_args(); + let mut tests = Vec::new(); + collect_tests(&mut tests)?; + let _ = libtest_mimic::run(&args, tests).exit_code(); + Ok(()) +} + +// --------------------------------------------------------------------------- +// Test collection +// --------------------------------------------------------------------------- + +fn collect_tests(tests: &mut Vec) -> Result<(), Box> { + let wild_bin = wild_binary_path(); + let src_root = macho_sources_dir(); + + for entry in std::fs::read_dir(&src_root)? { + let entry = entry?; + let dir = entry.path(); + if !dir.is_dir() { + continue; + } + let test_name = dir.file_name().unwrap().to_string_lossy().to_string(); + + // Find primary source: {test_name}.{c,cc,rs} + let primary = identify_primary_source(&dir, &test_name); + let Some(primary) = primary else { continue }; + + let config = parse_config(&dir, &primary)?; + let wild = wild_bin.clone(); + + let ignored = config.ignore_reason.is_some(); + tests.push( + libtest_mimic::Trial::test( + format!("macho::{test_name}"), + move || run_test(&wild, &dir, &test_name, &primary, &config).map_err(Into::into), + ) + .with_ignored_flag(ignored), + ); + } + Ok(()) +} + +fn identify_primary_source(dir: &Path, test_name: &str) -> Option { + for ext in &["c", "cc", "rs"] { + let p = dir.join(format!("{test_name}.{ext}")); + if p.exists() { + return Some(p); + } + } + None +} + +// --------------------------------------------------------------------------- +// Config parsing +// --------------------------------------------------------------------------- + +#[derive(Default)] +struct TestConfig { + extra_objects: Vec, + comp_args: Vec, + link_args: Vec, + expect_error: Option, + run_enabled: bool, + use_clang_driver: bool, + contains: Vec, + does_not_contain: Vec, + ignore_reason: Option, +} + +fn parse_config( + test_dir: &Path, + primary: &Path, +) -> Result> { + let mut cfg = TestConfig { + run_enabled: true, + ..Default::default() + }; + + let src = std::fs::read_to_string(primary)?; + for line in src.lines() { + let Some(directive) = line.strip_prefix("//#") else { + continue; + }; + let (key, value) = match directive.split_once(':') { + Some((k, v)) => (k, v), + None => (directive, ""), + }; + match key { + "Object" => cfg.extra_objects.push(value.to_string()), + "CompArgs" => cfg.comp_args.extend(shell_words(value)), + "LinkArgs" => cfg.link_args.extend(shell_words(value)), + "ExpectError" => cfg.expect_error = Some(value.to_string()), + "RunEnabled" => cfg.run_enabled = value.trim() != "false", + "LinkerDriver" if value.trim().starts_with("clang") => cfg.use_clang_driver = true, + "Contains" => cfg.contains.push(value.to_string()), + "DoesNotContain" => cfg.does_not_contain.push(value.to_string()), + "Ignore" => cfg.ignore_reason = Some(value.to_string()), + _ => {} // Ignore unknown directives for forward-compatibility. + } + } + + // Also parse directives from extra object files (they might have CompArgs etc.) + for obj_name in &cfg.extra_objects { + let obj_path = test_dir.join(obj_name); + if obj_path.exists() { + let obj_src = std::fs::read_to_string(&obj_path)?; + for line in obj_src.lines() { + if let Some(directive) = line.strip_prefix("//#") { + if let Some(("CompArgs", v)) = directive.split_once(':').map(|(k, v)| (k, v)) { + // CompArgs in extra objects only apply to that object — ignored here. + let _ = v; + } + } + } + } + } + + Ok(cfg) +} + +fn shell_words(s: &str) -> Vec { + s.split_whitespace().map(|w| w.to_string()).collect() +} + +// --------------------------------------------------------------------------- +// Test execution +// --------------------------------------------------------------------------- + +fn run_test( + wild_bin: &Path, + test_dir: &Path, + test_name: &str, + primary: &Path, + config: &TestConfig, +) -> Result<(), String> { + let build_dir = build_dir(test_name); + std::fs::create_dir_all(&build_dir).map_err(|e| format!("mkdir: {e}"))?; + + // Compile all source files. + let mut objects = Vec::new(); + let is_cpp = primary.extension().map_or(false, |e| e == "cc"); + + compile_source(primary, &build_dir, &config.comp_args, is_cpp)?; + objects.push(object_path(&build_dir, primary)); + + for obj_name in &config.extra_objects { + let src = test_dir.join(obj_name); + let extra_cpp = src.extension().map_or(false, |e| e == "cc"); + compile_source(&src, &build_dir, &config.comp_args, extra_cpp)?; + objects.push(object_path(&build_dir, &src)); + } + + // Link with wild. + let output = build_dir.join(test_name); + let mut cmd = if config.use_clang_driver { + // Use clang as driver (passes -syslibroot, -L paths, etc.) + let compiler = if is_cpp { "clang++" } else { "clang" }; + let mut c = Command::new(compiler); + c.arg(format!("-fuse-ld={}", wild_bin.display())); + for obj in &objects { + c.arg(obj); + } + c.arg("-o").arg(&output); + for arg in &config.link_args { + c.arg(arg); + } + c + } else { + let mut c = Command::new(wild_bin); + for obj in &objects { + c.arg(obj); + } + c.arg("-o").arg(&output); + for arg in &config.link_args { + c.arg(arg); + } + c + }; + + let link_result = cmd.output().map_err(|e| format!("wild: {e}"))?; + + // Check for expected errors. + if let Some(ref pattern) = config.expect_error { + if link_result.status.success() { + return Err(format!("Expected link failure matching '{pattern}', but link succeeded")); + } + let stderr = String::from_utf8_lossy(&link_result.stderr); + if !stderr.contains(pattern) { + return Err(format!( + "Expected error matching '{pattern}', got:\n{stderr}" + )); + } + return Ok(()); + } + + if !link_result.status.success() { + let stderr = String::from_utf8_lossy(&link_result.stderr); + return Err(format!("Link failed:\n{stderr}")); + } + + // Binary content checks. + let binary = std::fs::read(&output).map_err(|e| format!("read output: {e}"))?; + for needle in &config.contains { + if !binary_contains(&binary, needle.as_bytes()) { + return Err(format!("Output binary does not contain '{needle}'")); + } + } + for needle in &config.does_not_contain { + if binary_contains(&binary, needle.as_bytes()) { + return Err(format!("Output binary unexpectedly contains '{needle}'")); + } + } + + // Run the binary and check exit code. + if config.run_enabled { + let run = Command::new(&output) + .output() + .map_err(|e| format!("run: {e}"))?; + let code = run.status.code().unwrap_or(-1); + if code != 42 { + return Err(format!("Expected exit code 42, got {code}")); + } + } + + Ok(()) +} + +fn compile_source( + src: &Path, + build_dir: &Path, + extra_args: &[String], + is_cpp: bool, +) -> Result<(), String> { + let out = object_path(build_dir, src); + let compiler = if is_cpp { "clang++" } else { "clang" }; + + let mut cmd = Command::new(compiler); + cmd.arg("-c").arg(src).arg("-o").arg(&out); + for arg in extra_args { + cmd.arg(arg); + } + + let result = cmd.output().map_err(|e| format!("{compiler}: {e}"))?; + if !result.status.success() { + let stderr = String::from_utf8_lossy(&result.stderr); + return Err(format!("Compilation of {} failed:\n{stderr}", src.display())); + } + Ok(()) +} + +fn object_path(build_dir: &Path, src: &Path) -> PathBuf { + let stem = src.file_stem().unwrap().to_string_lossy(); + build_dir.join(format!("{stem}.o")) +} + +fn binary_contains(haystack: &[u8], needle: &[u8]) -> bool { + haystack + .windows(needle.len()) + .any(|window| window == needle) +} + +// --------------------------------------------------------------------------- +// Paths +// --------------------------------------------------------------------------- + +fn wild_binary_path() -> PathBuf { + let mut path = std::env::current_exe().expect("current_exe"); + path.pop(); // remove test binary name + path.pop(); // remove `deps/` + path.push("wild"); + if !path.exists() { + path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join("target/debug/wild"); + } + // clang -fuse-ld= requires an absolute path. + std::fs::canonicalize(&path).unwrap_or(path) +} + +fn macho_sources_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/sources/macho") +} + +fn build_dir(test_name: &str) -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .unwrap() + .join(format!("target/macho-test-build/{test_name}")) +} diff --git a/wild/tests/sources/macho/alignment/alignment.c b/wild/tests/sources/macho/alignment/alignment.c new file mode 100644 index 000000000..e87654c78 --- /dev/null +++ b/wild/tests/sources/macho/alignment/alignment.c @@ -0,0 +1,10 @@ +// Test that the linker respects large alignment requirements. +struct __attribute__((aligned(16384))) S { + int x; +}; +struct S obj = {.x = 42}; + +int main() { + if ((unsigned long long)&obj & 0x3FFF) return 1; + return obj.x; +} diff --git a/wild/tests/sources/macho/bss/bss.c b/wild/tests/sources/macho/bss/bss.c new file mode 100644 index 000000000..cc8071e77 --- /dev/null +++ b/wild/tests/sources/macho/bss/bss.c @@ -0,0 +1,9 @@ +// Test that uninitialised globals are zero-filled (BSS). +int uninit_global; +static int uninit_static; + +int main() { + if (uninit_global != 0) return 1; + if (uninit_static != 0) return 2; + return 42; +} diff --git a/wild/tests/sources/macho/common-symbol/common-symbol.c b/wild/tests/sources/macho/common-symbol/common-symbol.c new file mode 100644 index 000000000..95e8cacc2 --- /dev/null +++ b/wild/tests/sources/macho/common-symbol/common-symbol.c @@ -0,0 +1,6 @@ +//#Object:common-symbol1.c + +// Test that tentative (common) definitions from multiple objects merge +// correctly. +int shared_var; +int main() { return shared_var == 0 ? 42 : 1; } diff --git a/wild/tests/sources/macho/common-symbol/common-symbol1.c b/wild/tests/sources/macho/common-symbol/common-symbol1.c new file mode 100644 index 000000000..db7a202cc --- /dev/null +++ b/wild/tests/sources/macho/common-symbol/common-symbol1.c @@ -0,0 +1,2 @@ +// Another tentative definition of the same symbol. +int shared_var; diff --git a/wild/tests/sources/macho/constructors/constructors.c b/wild/tests/sources/macho/constructors/constructors.c new file mode 100644 index 000000000..0fa294ff0 --- /dev/null +++ b/wild/tests/sources/macho/constructors/constructors.c @@ -0,0 +1,7 @@ +//#LinkerDriver:clang +// Test that __attribute__((constructor)) functions run before main. +static int init_val = 0; + +__attribute__((constructor)) void my_init(void) { init_val = 42; } + +int main() { return init_val; } diff --git a/wild/tests/sources/macho/cpp-basic/cpp-basic.cc b/wild/tests/sources/macho/cpp-basic/cpp-basic.cc new file mode 100644 index 000000000..6272407e6 --- /dev/null +++ b/wild/tests/sources/macho/cpp-basic/cpp-basic.cc @@ -0,0 +1,19 @@ +//#CompArgs:-std=c++17 +//#LinkerDriver:clang++ +//#LinkArgs:-lc++ + +// Test basic C++ linking: virtual dispatch, new/delete. +struct Base { + virtual int value() { return 1; } + virtual ~Base() = default; +}; + +struct Derived : Base { + int value() override { return 42; } +}; + +int main() { + Derived d; + Base* b = &d; + return b->value(); +} diff --git a/wild/tests/sources/macho/data/data.c b/wild/tests/sources/macho/data/data.c new file mode 100644 index 000000000..a9216cae6 --- /dev/null +++ b/wild/tests/sources/macho/data/data.c @@ -0,0 +1,8 @@ +static char data1[] = "Hello"; +char data2[] = "World"; + +int main() { + if (data1[0] != 'H') return 1; + if (data2[0] != 'W') return 2; + return 42; +} diff --git a/wild/tests/sources/macho/string-constants/string-constants.c b/wild/tests/sources/macho/string-constants/string-constants.c new file mode 100644 index 000000000..d93d3a9c2 --- /dev/null +++ b/wild/tests/sources/macho/string-constants/string-constants.c @@ -0,0 +1,15 @@ +//#Contains:Hello World + +// Test that string literals are present and the binary links correctly. +const char* get_str1(void) { return "Hello World"; } +const char* get_str2(void) { return "Hello World"; } + +int main() { + // Whether the linker merges identical strings is an optimisation choice. + // We just verify the values are correct. + const char* a = get_str1(); + const char* b = get_str2(); + if (a[0] != 'H') return 1; + if (b[0] != 'H') return 2; + return 42; +} diff --git a/wild/tests/sources/macho/trivial/trivial.c b/wild/tests/sources/macho/trivial/trivial.c new file mode 100644 index 000000000..dbff7309f --- /dev/null +++ b/wild/tests/sources/macho/trivial/trivial.c @@ -0,0 +1 @@ +int main() { return 42; } diff --git a/wild/tests/sources/macho/weak-fns/weak-fns.c b/wild/tests/sources/macho/weak-fns/weak-fns.c new file mode 100644 index 000000000..728692595 --- /dev/null +++ b/wild/tests/sources/macho/weak-fns/weak-fns.c @@ -0,0 +1,4 @@ +//#Object:weak-fns1.c + +int __attribute__((weak)) get_value(void) { return 1; } +int main() { return get_value(); } diff --git a/wild/tests/sources/macho/weak-fns/weak-fns1.c b/wild/tests/sources/macho/weak-fns/weak-fns1.c new file mode 100644 index 000000000..9a4805937 --- /dev/null +++ b/wild/tests/sources/macho/weak-fns/weak-fns1.c @@ -0,0 +1,2 @@ +// Strong override of the weak get_value in weak-fns.c +int get_value(void) { return 42; } diff --git a/wild/tests/sources/macho/weak-vars/weak-vars.c b/wild/tests/sources/macho/weak-vars/weak-vars.c new file mode 100644 index 000000000..291752e34 --- /dev/null +++ b/wild/tests/sources/macho/weak-vars/weak-vars.c @@ -0,0 +1,4 @@ +//#Object:weak-vars1.c + +int __attribute__((weak)) value = 1; +int main() { return value; } diff --git a/wild/tests/sources/macho/weak-vars/weak-vars1.c b/wild/tests/sources/macho/weak-vars/weak-vars1.c new file mode 100644 index 000000000..f4cfe23c3 --- /dev/null +++ b/wild/tests/sources/macho/weak-vars/weak-vars1.c @@ -0,0 +1,2 @@ +// Strong override of the weak value in weak-vars.c +int value = 42; From e6835f512e0f6b22fd5306314b9dfa19a9dbe20a Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Mon, 6 Apr 2026 22:41:32 +0100 Subject: [PATCH 06/21] chore: fmt Signed-off-by: Giles Cope --- libwild/src/args/macho.rs | 110 +++- libwild/src/layout.rs | 8 +- libwild/src/macho.rs | 111 ++-- libwild/src/macho_aarch64.rs | 4 +- libwild/src/macho_writer.rs | 832 +++++++++++++++++++------- wild/tests/macho_integration_tests.rs | 24 +- 6 files changed, 783 insertions(+), 306 deletions(-) diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index 2842d1e9a..bfae4f9e7 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -60,8 +60,12 @@ impl platform::Args for MachOArgs { parse(self, input) } - fn should_strip_debug(&self) -> bool { false } - fn should_strip_all(&self) -> bool { false } + fn should_strip_debug(&self) -> bool { + false + } + fn should_strip_all(&self) -> bool { + false + } fn entry_symbol_name<'a>(&'a self, linker_script_entry: Option<&'a [u8]>) -> &'a [u8] { linker_script_entry @@ -73,23 +77,37 @@ impl platform::Args for MachOArgs { &self.lib_search_paths } - fn output(&self) -> &std::sync::Arc { &self.output } - fn common(&self) -> &crate::args::CommonArgs { &self.common } - fn common_mut(&mut self) -> &mut crate::args::CommonArgs { &mut self.common } - fn should_export_all_dynamic_symbols(&self) -> bool { false } - fn should_export_dynamic(&self, _lib_name: &[u8]) -> bool { false } + fn output(&self) -> &std::sync::Arc { + &self.output + } + fn common(&self) -> &crate::args::CommonArgs { + &self.common + } + fn common_mut(&mut self) -> &mut crate::args::CommonArgs { + &mut self.common + } + fn should_export_all_dynamic_symbols(&self) -> bool { + false + } + fn should_export_dynamic(&self, _lib_name: &[u8]) -> bool { + false + } fn loadable_segment_alignment(&self) -> crate::alignment::Alignment { crate::alignment::Alignment { exponent: 14 } // 16KB pages } - fn should_merge_sections(&self) -> bool { false } + fn should_merge_sections(&self) -> bool { + false + } fn relocation_model(&self) -> crate::args::RelocationModel { self.relocation_model } - fn should_output_executable(&self) -> bool { !self.is_dylib } + fn should_output_executable(&self) -> bool { + !self.is_dylib + } } /// Parse macOS linker arguments. Handles the ld64-compatible flags that clang passes. @@ -137,7 +155,10 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( args.common.time_phase_options = Some(Vec::new()); return Ok(()); } - "-arch" => { input.next(); return Ok(()); } // consume and ignore + "-arch" => { + input.next(); + return Ok(()); + } // consume and ignore "-syslibroot" => { if let Some(val) = input.next() { args.syslibroot = Some(Box::from(Path::new(val.as_ref()))); @@ -151,13 +172,28 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( return Ok(()); } // Flags that take 1 argument, ignored - "-lto_library" | "-mllvm" | "-headerpad" | "-install_name" - | "-compatibility_version" | "-current_version" | "-rpath" - | "-object_path_lto" | "-order_file" | "-exported_symbols_list" - | "-unexported_symbols_list" | "-filelist" | "-sectcreate" - | "-framework" | "-weak_framework" | "-weak_library" - | "-reexport_library" | "-umbrella" | "-allowable_client" - | "-client_name" | "-sub_library" | "-sub_umbrella" + "-lto_library" + | "-mllvm" + | "-headerpad" + | "-install_name" + | "-compatibility_version" + | "-current_version" + | "-rpath" + | "-object_path_lto" + | "-order_file" + | "-exported_symbols_list" + | "-unexported_symbols_list" + | "-filelist" + | "-sectcreate" + | "-framework" + | "-weak_framework" + | "-weak_library" + | "-reexport_library" + | "-umbrella" + | "-allowable_client" + | "-client_name" + | "-sub_library" + | "-sub_umbrella" | "-objc_abi_version" => { input.next(); // consume the argument return Ok(()); @@ -170,19 +206,34 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( return Ok(()); } // Flags that take 1 argument, ignored (group 2) - "-undefined" | "-multiply_defined" | "-force_load" | "-weak-l" - | "-needed-l" | "-reexport-l" | "-upward-l" | "-alignment" => { + "-undefined" | "-multiply_defined" | "-force_load" | "-weak-l" | "-needed-l" + | "-reexport-l" | "-upward-l" | "-alignment" => { input.next(); return Ok(()); } // No-argument flags, ignored - "-demangle" | "-dynamic" | "-no_deduplicate" | "-no_compact_unwind" - | "-dead_strip" | "-dead_strip_dylibs" | "-headerpad_max_install_names" - | "-export_dynamic" | "-application_extension" | "-no_objc_category_merging" - | "-mark_dead_strippable_dylib" | "-ObjC" | "-all_load" - | "-no_implicit_dylibs" | "-search_paths_first" | "-two_levelnamespace" - | "-flat_namespace" | "-bind_at_load" - | "-pie" | "-no_pie" | "-execute" | "-bundle" => { + "-demangle" + | "-dynamic" + | "-no_deduplicate" + | "-no_compact_unwind" + | "-dead_strip" + | "-dead_strip_dylibs" + | "-headerpad_max_install_names" + | "-export_dynamic" + | "-application_extension" + | "-no_objc_category_merging" + | "-mark_dead_strippable_dylib" + | "-ObjC" + | "-all_load" + | "-no_implicit_dylibs" + | "-search_paths_first" + | "-two_levelnamespace" + | "-flat_namespace" + | "-bind_at_load" + | "-pie" + | "-no_pie" + | "-execute" + | "-bundle" => { return Ok(()); } "-dylib" | "-dynamiclib" => { @@ -203,7 +254,8 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( if let Some(path) = arg.strip_prefix("-L") { if path.is_empty() { if let Some(val) = input.next() { - args.lib_search_paths.push(Box::from(Path::new(val.as_ref()))); + args.lib_search_paths + .push(Box::from(Path::new(val.as_ref()))); } } else { args.lib_search_paths.push(Box::from(Path::new(path))); @@ -252,7 +304,9 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( break; } } - if found { break; } + if found { + break; + } } // If not found, warn but don't error (might be a system dylib we handle implicitly) if !found { diff --git a/libwild/src/layout.rs b/libwild/src/layout.rs index e86646864..8728225fd 100644 --- a/libwild/src/layout.rs +++ b/libwild/src/layout.rs @@ -426,7 +426,9 @@ fn update_dynamic_symbol_resolutions<'data, P: Platform>( }; for (index, sym) in resources.dynamic_symbol_definitions.iter().enumerate() { - if let Some(dynamic_symbol_index) = NonZeroU32::new(epilogue.dynsym_start_index + index as u32) { + if let Some(dynamic_symbol_index) = + NonZeroU32::new(epilogue.dynsym_start_index + index as u32) + { if let Some(res) = &mut resolutions[sym.symbol_id.as_usize()] { res.dynamic_symbol_index = Some(dynamic_symbol_index); } @@ -3909,7 +3911,9 @@ impl<'data, P: Platform> ObjectLayoutState<'data, P> { .symbol_section(local_symbol, local_symbol_index)? { if let Some(section_address) = section_resolutions[section_index.0].address() { - let input_offset = self.object.symbol_value_in_section(local_symbol, section_index)?; + let input_offset = self + .object + .symbol_value_in_section(local_symbol, section_index)?; let output_offset = opt_input_to_output( self.section_relax_deltas.get(section_index.0), input_offset, diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 1cf7b5886..b6bb94a6a 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -88,10 +88,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { self.symbols.iter() } - fn symbol( - &self, - index: object::SymbolIndex, - ) -> crate::error::Result<&'data SymtabEntry> { + fn symbol(&self, index: object::SymbolIndex) -> crate::error::Result<&'data SymtabEntry> { self.symbols .symbol(index) .map_err(|e| error!("Symbol index {} out of range: {e}", index.0)) @@ -119,42 +116,31 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { fn enumerate_sections( &self, - ) -> impl Iterator< - Item = ( - object::SectionIndex, - &'data SectionHeader, - ), - > { - self.sections - .iter() - .enumerate() - .map(|(i, section)| { - // Safety: SectionHeader is #[repr(transparent)] over Section64 - let header: &'data SectionHeader = - unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }; - (object::SectionIndex(i), header) - }) + ) -> impl Iterator { + self.sections.iter().enumerate().map(|(i, section)| { + // Safety: SectionHeader is #[repr(transparent)] over Section64 + let header: &'data SectionHeader = unsafe { + &*(section as *const macho::Section64 as *const SectionHeader) + }; + (object::SectionIndex(i), header) + }) } - fn section( - &self, - index: object::SectionIndex, - ) -> crate::error::Result<&'data SectionHeader> { - let section = self.sections.get(index.0).ok_or_else(|| { - error!("Section index {} out of range", index.0) - })?; + fn section(&self, index: object::SectionIndex) -> crate::error::Result<&'data SectionHeader> { + let section = self + .sections + .get(index.0) + .ok_or_else(|| error!("Section index {} out of range", index.0))?; Ok(unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }) } - fn section_by_name( - &self, - name: &str, - ) -> Option<(object::SectionIndex, &'data SectionHeader)> { + fn section_by_name(&self, name: &str) -> Option<(object::SectionIndex, &'data SectionHeader)> { for (i, section) in self.sections.iter().enumerate() { let sectname = trim_nul(section.sectname()); if sectname == name.as_bytes() { - let header: &'data SectionHeader = - unsafe { &*(section as *const macho::Section64 as *const SectionHeader) }; + let header: &'data SectionHeader = unsafe { + &*(section as *const macho::Section64 as *const SectionHeader) + }; return Some((object::SectionIndex(i), header)); } } @@ -190,7 +176,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { Ok(sym_value.wrapping_sub(section_addr)) } - fn symbol_versions(&self) -> &[()]{ + fn symbol_versions(&self) -> &[()] { // Mach-O doesn't have symbol versioning &[] } @@ -277,13 +263,16 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { index: object::SectionIndex, _relocations: &(), ) -> crate::error::Result> { - let section = self.sections.get(index.0).ok_or_else(|| { - error!("Section index {} out of range for relocations", index.0) - })?; + let section = self + .sections + .get(index.0) + .ok_or_else(|| error!("Section index {} out of range for relocations", index.0))?; let relocs = section .relocations(LE, self.data) .map_err(|e| error!("Failed to read relocations: {e}"))?; - Ok(RelocationList { relocations: relocs }) + Ok(RelocationList { + relocations: relocs, + }) } fn parse_relocations(&self) -> crate::error::Result<()> { @@ -349,9 +338,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } fn verneed_table(&self) -> crate::error::Result> { - Ok(VerneedTable { - _phantom: &[], - }) + Ok(VerneedTable { _phantom: &[] }) } fn process_gnu_note_section( @@ -389,9 +376,7 @@ impl platform::SectionHeader for SectionHeader { fn is_tls(&self) -> bool { let sectname = trim_nul(self.0.sectname()); - sectname == b"__thread_vars" - || sectname == b"__thread_data" - || sectname == b"__thread_bss" + sectname == b"__thread_vars" || sectname == b"__thread_data" || sectname == b"__thread_bss" } fn is_merge_section(&self) -> bool { @@ -496,9 +481,12 @@ impl platform::Symbol for SymtabEntry { && self.n_value(LE) > 0 { let alignment_val = u64::from(self.n_desc(LE)); - let alignment = - crate::alignment::Alignment::new(if alignment_val > 0 { 1u64 << alignment_val } else { 1 }) - .unwrap_or(crate::alignment::MIN); + let alignment = crate::alignment::Alignment::new(if alignment_val > 0 { + 1u64 << alignment_val + } else { + 1 + }) + .unwrap_or(crate::alignment::MIN); let size = alignment.align_up(self.n_value(LE)); let output_section_id = crate::output_section_id::BSS; let part_id = output_section_id.part_id_with_alignment(alignment); @@ -840,8 +828,8 @@ impl<'data> Iterator for MachOSectionIter<'data> { type Item = &'data SectionHeader; fn next(&mut self) -> Option { - self.inner.next().map(|s| { - unsafe { &*(s as *const macho::Section64 as *const SectionHeader) } + self.inner.next().map(|s| unsafe { + &*(s as *const macho::Section64 as *const SectionHeader) }) } } @@ -946,8 +934,7 @@ impl platform::Platform for MachO { } } - fn finalise_find_required_sections(_groups: &[crate::layout::GroupState]) { - } + fn finalise_find_required_sections(_groups: &[crate::layout::GroupState]) {} fn activate_dynamic<'data>( _state: &mut crate::layout::DynamicLayoutState<'data, Self>, @@ -1023,7 +1010,10 @@ impl platform::Platform for MachO { // Scan relocations to discover referenced symbols and trigger loading // of their containing sections. let le = object::Endianness::Little; - let input_section = state.object.sections.get(section.index.0) + let input_section = state + .object + .sections + .get(section.index.0) .ok_or_else(|| crate::error!("Section index out of range"))?; let relocs = match input_section.relocations(le, state.object.data) { Ok(r) => r, @@ -1031,9 +1021,13 @@ impl platform::Platform for MachO { }; for reloc_raw in relocs { let reloc = reloc_raw.info(le); - if !reloc.r_extern { continue; } + if !reloc.r_extern { + continue; + } // Skip ADDEND (type 10) and SUBTRACTOR (type 1) - if reloc.r_type == 10 || reloc.r_type == 1 { continue; } + if reloc.r_type == 10 || reloc.r_type == 1 { + continue; + } let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); let local_symbol_id = state.symbol_id_range.input_to_id(sym_idx); @@ -1103,7 +1097,8 @@ impl platform::Platform for MachO { use crate::output_section_id::SectionName; use crate::output_section_id::SectionOutputInfo; - let mut infos: Vec> = Vec::with_capacity(NUM_BUILT_IN_SECTIONS); + let mut infos: Vec> = + Vec::with_capacity(NUM_BUILT_IN_SECTIONS); for _ in 0..NUM_BUILT_IN_SECTIONS { infos.push(SectionOutputInfo { kind: SectionKind::Primary(SectionName(b"")), @@ -1372,7 +1367,10 @@ impl platform::Platform for MachO { raw_value, dynamic_symbol_index, flags, - format_specific: MachOResolutionExt { got_address, plt_address }, + format_specific: MachOResolutionExt { + got_address, + plt_address, + }, } } @@ -1458,7 +1456,8 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__thread_vars", output_section_id::GOT), SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), - // Constructor/destructor function pointer arrays (Mach-O equivalent of .init_array/.fini_array) + // Constructor/destructor function pointer arrays (Mach-O equivalent of + // .init_array/.fini_array) SectionRule::exact_section(b"__mod_init_func", output_section_id::INIT_ARRAY), SectionRule::exact_section(b"__mod_term_func", output_section_id::FINI_ARRAY), SectionRule::exact_section(b"__gcc_except_tab", output_section_id::GCC_EXCEPT_TABLE), diff --git a/libwild/src/macho_aarch64.rs b/libwild/src/macho_aarch64.rs index 4190371e0..28a7511c4 100644 --- a/libwild/src/macho_aarch64.rs +++ b/libwild/src/macho_aarch64.rs @@ -150,9 +150,7 @@ impl crate::platform::Arch for MachOAArch64 { // Mach-O doesn't use ELF-style arch identifiers } - fn get_dynamic_relocation_type( - _relocation: linker_utils::elf::DynamicRelocationKind, - ) -> u32 { + fn get_dynamic_relocation_type(_relocation: linker_utils::elf::DynamicRelocationKind) -> u32 { 0 } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 2996ddb18..1c036644e 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -40,9 +40,7 @@ const PLATFORM_MACOS: u32 = 1; const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; const LIBSYSTEM_PATH: &[u8] = b"/usr/lib/libSystem.B.dylib"; -pub(crate) fn write_direct>( - layout: &Layout<'_, MachO>, -) -> Result { +pub(crate) fn write_direct>(layout: &Layout<'_, MachO>) -> Result { let (mappings, alloc_size) = build_mappings_and_size(layout); let mut buf = vec![0u8; alloc_size]; let final_size = write_macho::(&mut buf, layout, &mappings)?; @@ -55,8 +53,8 @@ pub(crate) fn write_direct>( #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; - let _ = std::fs::set_permissions(output_path.as_ref(), - std::fs::Permissions::from_mode(0o755)); + let _ = + std::fs::set_permissions(output_path.as_ref(), std::fs::Permissions::from_mode(0o755)); } #[cfg(target_os = "macos")] { @@ -78,16 +76,30 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, let mut raw: Vec<(u64, u64, u64)> = Vec::new(); let mut file_cursor: u64 = 0; for seg in &layout.segment_layouts.segments { - if seg.sizes.file_size == 0 && seg.sizes.mem_size == 0 { continue; } - let file_off = if raw.is_empty() { 0 } else { align_to(file_cursor, PAGE_SIZE) }; + if seg.sizes.file_size == 0 && seg.sizes.mem_size == 0 { + continue; + } + let file_off = if raw.is_empty() { + 0 + } else { + align_to(file_cursor, PAGE_SIZE) + }; let file_sz = align_to(seg.sizes.file_size as u64, PAGE_SIZE); - raw.push((seg.sizes.mem_offset, seg.sizes.mem_offset + seg.sizes.mem_size, file_off)); + raw.push(( + seg.sizes.mem_offset, + seg.sizes.mem_offset + seg.sizes.mem_size, + file_off, + )); file_cursor = file_off + file_sz; } let mut mappings = Vec::new(); if let Some(&(vm_start, vm_end, file_off)) = raw.first() { - mappings.push(SegmentMapping { vm_start, vm_end, file_offset: file_off }); + mappings.push(SegmentMapping { + vm_start, + vm_end, + file_offset: file_off, + }); } if raw.len() > 1 { // Merge all non-TEXT segments into one DATA mapping. @@ -104,13 +116,21 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, // Compute LINKEDIT offset the same way write_headers does: // TEXT filesize is page-aligned, DATA filesize is page-aligned from its file_offset. - let text_filesize = mappings.first().map_or(PAGE_SIZE, |m| - align_to(m.vm_end - m.vm_start, PAGE_SIZE)); + let text_filesize = mappings + .first() + .map_or(PAGE_SIZE, |m| align_to(m.vm_end - m.vm_start, PAGE_SIZE)); let linkedit_offset = if mappings.len() > 1 { let data_fileoff = mappings[1].file_offset; let data_filesize = align_to( - mappings.iter().skip(1).map(|m| m.file_offset + (m.vm_end - m.vm_start)) - .max().unwrap() - data_fileoff, PAGE_SIZE); + mappings + .iter() + .skip(1) + .map(|m| m.file_offset + (m.vm_end - m.vm_start)) + .max() + .unwrap() + - data_fileoff, + PAGE_SIZE, + ); data_fileoff + data_filesize } else { text_filesize @@ -168,14 +188,30 @@ fn write_macho>( for group in &layout.group_layouts { for file_layout in &group.files { if let FileLayout::Object(obj) = file_layout { - write_object_sections(out, obj, layout, mappings, le, - &mut rebase_fixups, &mut bind_fixups, &mut imports, has_extra_dylibs)?; + write_object_sections( + out, + obj, + layout, + mappings, + le, + &mut rebase_fixups, + &mut bind_fixups, + &mut imports, + has_extra_dylibs, + )?; } } } // Write PLT stubs and collect bind fixups for imported symbols - write_stubs_and_got::(out, layout, mappings, &mut bind_fixups, &mut imports, has_extra_dylibs)?; + write_stubs_and_got::( + out, + layout, + mappings, + &mut bind_fixups, + &mut imports, + has_extra_dylibs, + )?; // Populate GOT entries for non-import symbols write_got_entries(out, layout, mappings, &mut rebase_fixups)?; @@ -184,27 +220,45 @@ fn write_macho>( rebase_fixups.sort_by_key(|f| f.file_offset); bind_fixups.sort_by_key(|f| f.file_offset); - let data_seg_start = if mappings.len() > 1 { mappings[1].file_offset as usize } else { usize::MAX }; + let data_seg_start = if mappings.len() > 1 { + mappings[1].file_offset as usize + } else { + usize::MAX + }; let data_seg_end = if mappings.len() > 1 { mappings[1].file_offset as usize + (mappings[1].vm_end - mappings[1].vm_start) as usize - } else { 0 }; + } else { + 0 + }; - let image_base = if layout.symbol_db.args.is_dylib { 0u64 } else { PAGEZERO_SIZE }; + let image_base = if layout.symbol_db.args.is_dylib { + 0u64 + } else { + PAGEZERO_SIZE + }; let mut all_data_fixups: Vec<(usize, u64)> = Vec::new(); for f in &rebase_fixups { - if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { + continue; + } let target_offset = f.target.wrapping_sub(image_base); all_data_fixups.push((f.file_offset, target_offset & 0xF_FFFF_FFFF)); } for f in &bind_fixups { - if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { + continue; + } let encoded = (1u64 << 63) | (f.import_index as u64 & 0xFF_FFFF); all_data_fixups.push((f.file_offset, encoded)); } all_data_fixups.sort_by_key(|&(off, _)| off); // Encode per-page chains - let data_seg_file_off = if mappings.len() > 1 { mappings[1].file_offset } else { 0 }; + let data_seg_file_off = if mappings.len() > 1 { + mappings[1].file_offset + } else { + 0 + }; for i in 0..all_data_fixups.len() { let (file_off, mut encoded) = all_data_fixups[i]; let next_stride = if i + 1 < all_data_fixups.len() { @@ -212,8 +266,12 @@ fn write_macho>( let next_page = (all_data_fixups[i + 1].0 as u64 - data_seg_file_off) / PAGE_SIZE; if cur_page == next_page { ((all_data_fixups[i + 1].0 - file_off) / 4) as u64 - } else { 0 } - } else { 0 }; + } else { + 0 + } + } else { + 0 + }; // Both bind and rebase use bits 51-62 for next (12 bits, 4-byte stride) encoded |= (next_stride & 0xFFF) << 51; @@ -243,7 +301,9 @@ fn write_macho>( let page_count = if has_fixups && has_data { let data_mem_size = mappings[1].vm_end - mappings[1].vm_start; ((data_mem_size + PAGE_SIZE - 1) / PAGE_SIZE) as u32 - } else { 0 }; + } else { + 0 + }; let cf_data_size = if !has_fixups { (32 + 4 + 4 * seg_count + 8).max(48) @@ -276,8 +336,14 @@ fn write_macho>( } else { let ordinals: Vec = imports.iter().map(|e| e.lib_ordinal).collect(); write_chained_fixups_header( - out, cf_off as usize, &all_data_fixups, n_imports, - &import_name_offsets, &ordinals, &symbols_pool, mappings, + out, + cf_off as usize, + &all_data_fixups, + n_imports, + &import_name_offsets, + &ordinals, + &symbols_pool, + mappings, layout.symbol_db.args.is_dylib, )?; cf_off as usize + cf_data_size as usize @@ -303,12 +369,16 @@ fn write_dylib_symtab( layout: &Layout<'_, MachO>, _mappings: &[SegmentMapping], ) -> Result { - // Collect exported symbols from dynamic_symbol_definitions let mut entries: Vec<(Vec, u64)> = Vec::new(); for def in &layout.dynamic_symbol_definitions { let sym_id = def.symbol_id; - if let Some(res) = layout.symbol_resolutions.iter().nth(sym_id.as_usize()).and_then(|r| r.as_ref()) { + if let Some(res) = layout + .symbol_resolutions + .iter() + .nth(sym_id.as_usize()) + .and_then(|r| r.as_ref()) + { entries.push((def.name.to_vec(), res.raw_value)); } } @@ -331,13 +401,15 @@ fn write_dylib_symtab( let nsyms = entries.len(); let mut pos = symoff; for (i, (_, value)) in entries.iter().enumerate() { - if pos + 16 > out.len() { break; } + if pos + 16 > out.len() { + break; + } // nlist64: n_strx (4), n_type (1), n_sect (1), n_desc (2), n_value (8) - out[pos..pos+4].copy_from_slice(&str_offsets[i].to_le_bytes()); - out[pos+4] = 0x0F; // N_SECT | N_EXT - out[pos+5] = 1; // n_sect: section 1 (__text) - out[pos+6..pos+8].copy_from_slice(&0u16.to_le_bytes()); // n_desc - out[pos+8..pos+16].copy_from_slice(&value.to_le_bytes()); + out[pos..pos + 4].copy_from_slice(&str_offsets[i].to_le_bytes()); + out[pos + 4] = 0x0F; // N_SECT | N_EXT + out[pos + 5] = 1; // n_sect: section 1 (__text) + out[pos + 6..pos + 8].copy_from_slice(&0u16.to_le_bytes()); // n_desc + out[pos + 8..pos + 16].copy_from_slice(&value.to_le_bytes()); pos += 16; } @@ -353,13 +425,18 @@ fn write_dylib_symtab( let mut off = 32u32; // after header let ncmds = u32::from_le_bytes(out[16..20].try_into().unwrap()); for _ in 0..ncmds { - let cmd = u32::from_le_bytes(out[off as usize..off as usize+4].try_into().unwrap()); - let cmdsize = u32::from_le_bytes(out[off as usize+4..off as usize+8].try_into().unwrap()); + let cmd = u32::from_le_bytes(out[off as usize..off as usize + 4].try_into().unwrap()); + let cmdsize = + u32::from_le_bytes(out[off as usize + 4..off as usize + 8].try_into().unwrap()); if cmd == LC_SYMTAB { - out[off as usize+8..off as usize+12].copy_from_slice(&(symoff as u32).to_le_bytes()); - out[off as usize+12..off as usize+16].copy_from_slice(&(nsyms as u32).to_le_bytes()); - out[off as usize+16..off as usize+20].copy_from_slice(&(stroff as u32).to_le_bytes()); - out[off as usize+20..off as usize+24].copy_from_slice(&(strtab.len() as u32).to_le_bytes()); + out[off as usize + 8..off as usize + 12] + .copy_from_slice(&(symoff as u32).to_le_bytes()); + out[off as usize + 12..off as usize + 16] + .copy_from_slice(&(nsyms as u32).to_le_bytes()); + out[off as usize + 16..off as usize + 20] + .copy_from_slice(&(stroff as u32).to_le_bytes()); + out[off as usize + 20..off as usize + 24] + .copy_from_slice(&(strtab.len() as u32).to_le_bytes()); break; } off += cmdsize; @@ -376,34 +453,44 @@ fn write_dylib_symtab( // Patch LC_SYMTAB and LC_DYLD_EXPORTS_TRIE in headers off = 32; for _ in 0..ncmds { - let cmd = u32::from_le_bytes(out[off as usize..off as usize+4].try_into().unwrap()); - let cmdsize = u32::from_le_bytes(out[off as usize+4..off as usize+8].try_into().unwrap()); + let cmd = u32::from_le_bytes(out[off as usize..off as usize + 4].try_into().unwrap()); + let cmdsize = + u32::from_le_bytes(out[off as usize + 4..off as usize + 8].try_into().unwrap()); match cmd { - 0x19 => { // LC_SEGMENT_64 - let segname = &out[off as usize+8..off as usize+24]; + 0x19 => { + // LC_SEGMENT_64 + let segname = &out[off as usize + 8..off as usize + 24]; if segname.starts_with(b"__LINKEDIT") { let linkedit_fileoff = u64::from_le_bytes( - out[off as usize+40..off as usize+48].try_into().unwrap()); + out[off as usize + 40..off as usize + 48] + .try_into() + .unwrap(), + ); let new_filesize = pos as u64 - linkedit_fileoff; - out[off as usize+48..off as usize+56].copy_from_slice(&new_filesize.to_le_bytes()); + out[off as usize + 48..off as usize + 56] + .copy_from_slice(&new_filesize.to_le_bytes()); // Update vmsize to cover the content let new_vmsize = align_to(new_filesize, PAGE_SIZE); - out[off as usize+32..off as usize+40].copy_from_slice(&new_vmsize.to_le_bytes()); + out[off as usize + 32..off as usize + 40] + .copy_from_slice(&new_vmsize.to_le_bytes()); } } LC_DYSYMTAB => { // DYSYMTAB: ilocalsym nlocalsym iextdefsym nextdefsym iundefsym nundefsym let o = off as usize + 8; - out[o..o+4].copy_from_slice(&0u32.to_le_bytes()); // ilocalsym - out[o+4..o+8].copy_from_slice(&0u32.to_le_bytes()); // nlocalsym - out[o+8..o+12].copy_from_slice(&0u32.to_le_bytes()); // iextdefsym - out[o+12..o+16].copy_from_slice(&(nsyms as u32).to_le_bytes()); // nextdefsym - out[o+16..o+20].copy_from_slice(&(nsyms as u32).to_le_bytes()); // iundefsym - out[o+20..o+24].copy_from_slice(&0u32.to_le_bytes()); // nundefsym + out[o..o + 4].copy_from_slice(&0u32.to_le_bytes()); // ilocalsym + out[o + 4..o + 8].copy_from_slice(&0u32.to_le_bytes()); // nlocalsym + out[o + 8..o + 12].copy_from_slice(&0u32.to_le_bytes()); // iextdefsym + out[o + 12..o + 16].copy_from_slice(&(nsyms as u32).to_le_bytes()); // nextdefsym + out[o + 16..o + 20].copy_from_slice(&(nsyms as u32).to_le_bytes()); // iundefsym + out[o + 20..o + 24].copy_from_slice(&0u32.to_le_bytes()); // nundefsym } - 0x8000_0033 => { // LC_DYLD_EXPORTS_TRIE - out[off as usize+8..off as usize+12].copy_from_slice(&(trie_off as u32).to_le_bytes()); - out[off as usize+12..off as usize+16].copy_from_slice(&(trie.len() as u32).to_le_bytes()); + 0x8000_0033 => { + // LC_DYLD_EXPORTS_TRIE + out[off as usize + 8..off as usize + 12] + .copy_from_slice(&(trie_off as u32).to_le_bytes()); + out[off as usize + 12..off as usize + 16] + .copy_from_slice(&(trie.len() as u32).to_le_bytes()); } _ => {} } @@ -415,7 +502,9 @@ fn write_dylib_symtab( /// Build a Mach-O export trie for the given symbols. fn build_export_trie(entries: &[(Vec, u64)]) -> Vec { - if entries.is_empty() { return vec![0, 0]; } // empty root + if entries.is_empty() { + return vec![0, 0]; + } // empty root // Build child nodes first to know their sizes let mut children: Vec> = Vec::new(); @@ -494,9 +583,13 @@ fn uleb128_encode(buf: &mut Vec, mut val: u64) { loop { let mut byte = (val & 0x7F) as u8; val >>= 7; - if val != 0 { byte |= 0x80; } + if val != 0 { + byte |= 0x80; + } buf.push(byte); - if val == 0 { break; } + if val == 0 { + break; + } } } @@ -513,12 +606,20 @@ fn write_stubs_and_got>( for (sym_idx, res) in layout.symbol_resolutions.iter().enumerate() { let Some(res) = res else { continue }; - let Some(plt_addr) = res.format_specific.plt_address else { continue }; - let Some(got_addr) = res.format_specific.got_address else { continue }; + let Some(plt_addr) = res.format_specific.plt_address else { + continue; + }; + let Some(got_addr) = res.format_specific.got_address else { + continue; + }; if let Some(plt_file_off) = vm_addr_to_file_offset(plt_addr, mappings) { if plt_file_off + 12 <= out.len() { - A::write_plt_entry(&mut out[plt_file_off..plt_file_off + 12], got_addr, plt_addr)?; + A::write_plt_entry( + &mut out[plt_file_off..plt_file_off + 12], + got_addr, + plt_addr, + )?; } } @@ -533,7 +634,10 @@ fn write_stubs_and_got>( name, lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), }); - bind_fixups.push(BindFixup { file_offset: got_file_off, import_index }); + bind_fixups.push(BindFixup { + file_offset: got_file_off, + import_index, + }); } } Ok(()) @@ -548,7 +652,9 @@ fn write_got_entries( rebase_fixups: &mut Vec, ) -> Result { for res in layout.symbol_resolutions.iter().flatten() { - if res.format_specific.plt_address.is_some() { continue; } // handled by stubs + if res.format_specific.plt_address.is_some() { + continue; + } // handled by stubs if let Some(got_vm_addr) = res.format_specific.got_address { if let Some(file_off) = vm_addr_to_file_offset(got_vm_addr, mappings) { if file_off + 8 <= out.len() { @@ -562,7 +668,10 @@ fn write_got_entries( } } if res.raw_value != 0 { - rebase_fixups.push(RebaseFixup { file_offset: file_off, target: res.raw_value }); + rebase_fixups.push(RebaseFixup { + file_offset: file_off, + target: res.raw_value, + }); } } } @@ -586,8 +695,12 @@ fn write_object_sections( for (sec_idx, _slot) in obj.sections.iter().enumerate() { let section_res = &obj.section_resolutions[sec_idx]; - let Some(output_addr) = section_res.address() else { continue }; - let Some(file_offset) = vm_addr_to_file_offset(output_addr, mappings) else { continue }; + let Some(output_addr) = section_res.address() else { + continue; + }; + let Some(file_offset) = vm_addr_to_file_offset(output_addr, mappings) else { + continue; + }; let input_section = match obj.object.sections.get(sec_idx) { Some(s) => s, @@ -595,11 +708,15 @@ fn write_object_sections( }; let sec_type = input_section.flags(le) & 0xFF; - if sec_type == 0x01 || sec_type == 0x0C || sec_type == 0x12 { continue; } + if sec_type == 0x01 || sec_type == 0x0C || sec_type == 0x12 { + continue; + } let input_offset = input_section.offset(le) as usize; let input_size = input_section.size(le) as usize; - if input_size == 0 || input_offset == 0 { continue; } + if input_size == 0 || input_offset == 0 { + continue; + } let input_data = match obj.object.data.get(input_offset..input_offset + input_size) { Some(d) => d, @@ -611,8 +728,19 @@ fn write_object_sections( } if let Ok(relocs) = input_section.relocations(le, obj.object.data) { - apply_relocations(out, file_offset, output_addr, relocs, obj, layout, le, - rebase_fixups, bind_fixups, imports, has_extra_dylibs)?; + apply_relocations( + out, + file_offset, + output_addr, + relocs, + obj, + layout, + le, + rebase_fixups, + bind_fixups, + imports, + has_extra_dylibs, + )?; } } Ok(()) @@ -637,30 +765,47 @@ fn apply_relocations( for reloc_raw in relocs { let reloc = reloc_raw.info(le); - if reloc.r_type == 10 { pending_addend = reloc.r_symbolnum as i64; continue; } - if reloc.r_type == 1 { continue; } + if reloc.r_type == 10 { + pending_addend = reloc.r_symbolnum as i64; + continue; + } + if reloc.r_type == 1 { + continue; + } let addend = pending_addend; pending_addend = 0; let patch_file_offset = section_file_offset + reloc.r_address as usize; let pc_addr = section_vm_addr + reloc.r_address as u64; - if patch_file_offset + 4 > out.len() { continue; } + if patch_file_offset + 4 > out.len() { + continue; + } let (target_addr, got_addr, plt_addr) = if reloc.r_extern { let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); let sym_id = obj.symbol_id_range.input_to_id(sym_idx); match layout.merged_symbol_resolution(sym_id) { - Some(res) => (res.raw_value, res.format_specific.got_address, res.format_specific.plt_address), + Some(res) => ( + res.raw_value, + res.format_specific.got_address, + res.format_specific.plt_address, + ), None => continue, } } else { // Non-extern: r_symbolnum is 1-based section ordinal. // target = output_section_address + addend let sec_ord = reloc.r_symbolnum as usize; - if sec_ord == 0 { continue; } + if sec_ord == 0 { + continue; + } let sec_idx = sec_ord - 1; - let Some(output_sec_addr) = obj.section_resolutions.get(sec_idx).and_then(|r| r.address()) else { + let Some(output_sec_addr) = obj + .section_resolutions + .get(sec_idx) + .and_then(|r| r.address()) + else { continue; }; // Return section base; addend is added below along with extern path. @@ -670,23 +815,30 @@ fn apply_relocations( let target_addr = (target_addr as i64 + addend) as u64; match reloc.r_type { - 2 => { // ARM64_RELOC_BRANCH26 + 2 => { + // ARM64_RELOC_BRANCH26 let branch_target = plt_addr.unwrap_or(target_addr); let offset = branch_target.wrapping_sub(pc_addr) as i64; let imm26 = ((offset >> 2) & 0x03FF_FFFF) as u32; let insn = read_u32(out, patch_file_offset); write_u32_at(out, patch_file_offset, (insn & 0xFC00_0000) | imm26); } - 3 => { write_adrp(out, patch_file_offset, pc_addr, target_addr); } - 4 => { write_pageoff12(out, patch_file_offset, target_addr); } - 5 => { // ARM64_RELOC_GOT_LOAD_PAGE21 + 3 => { + write_adrp(out, patch_file_offset, pc_addr, target_addr); + } + 4 => { + write_pageoff12(out, patch_file_offset, target_addr); + } + 5 => { + // ARM64_RELOC_GOT_LOAD_PAGE21 if let Some(got) = got_addr { write_adrp(out, patch_file_offset, pc_addr, got); } else { write_adrp(out, patch_file_offset, pc_addr, target_addr); } } - 6 => { // ARM64_RELOC_GOT_LOAD_PAGEOFF12 + 6 => { + // ARM64_RELOC_GOT_LOAD_PAGEOFF12 if let Some(got) = got_addr { let page_off = (got & 0xFFF) as u32; let insn = read_u32(out, patch_file_offset); @@ -697,18 +849,30 @@ fn apply_relocations( let insn = read_u32(out, patch_file_offset); let rd = insn & 0x1F; let rn = (insn >> 5) & 0x1F; - write_u32_at(out, patch_file_offset, 0x9100_0000 | (page_off << 10) | (rn << 5) | rd); + write_u32_at( + out, + patch_file_offset, + 0x9100_0000 | (page_off << 10) | (rn << 5) | rd, + ); } } - 8 => { write_adrp(out, patch_file_offset, pc_addr, target_addr); } - 9 => { // ARM64_RELOC_TLVP_LOAD_PAGEOFF12 -> relax to ADD + 8 => { + write_adrp(out, patch_file_offset, pc_addr, target_addr); + } + 9 => { + // ARM64_RELOC_TLVP_LOAD_PAGEOFF12 -> relax to ADD let page_off = (target_addr & 0xFFF) as u32; let insn = read_u32(out, patch_file_offset); let rd = insn & 0x1F; let rn = (insn >> 5) & 0x1F; - write_u32_at(out, patch_file_offset, 0x9100_0000 | (page_off << 10) | (rn << 5) | rd); + write_u32_at( + out, + patch_file_offset, + 0x9100_0000 | (page_off << 10) | (rn << 5) | rd, + ); } - 0 if reloc.r_length == 3 => { // ARM64_RELOC_UNSIGNED 64-bit + 0 if reloc.r_length == 3 => { + // ARM64_RELOC_UNSIGNED 64-bit if patch_file_offset + 8 <= out.len() { if reloc.r_extern && target_addr == 0 { // Extern undefined symbol (e.g. _tlv_bootstrap): bind fixup @@ -723,7 +887,10 @@ fn apply_relocations( name, lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), }); - bind_fixups.push(BindFixup { file_offset: patch_file_offset, import_index }); + bind_fixups.push(BindFixup { + file_offset: patch_file_offset, + import_index, + }); } else { // Check if target is in TLS data — write offset, not rebase let tdata = layout.section_layouts.get(output_section_id::TDATA); @@ -750,22 +917,26 @@ fn apply_relocations( .copy_from_slice(&tls_offset.to_le_bytes()); } else { rebase_fixups.push(RebaseFixup { - file_offset: patch_file_offset, target: target_addr, + file_offset: patch_file_offset, + target: target_addr, }); } } } } - 7 if reloc.r_length == 2 && reloc.r_pcrel => { // ARM64_RELOC_POINTER_TO_GOT + 7 if reloc.r_length == 2 && reloc.r_pcrel => { + // ARM64_RELOC_POINTER_TO_GOT if let Some(got) = got_addr { let delta = (got as i64 - pc_addr as i64) as i32; if patch_file_offset + 4 <= out.len() { - out[patch_file_offset..patch_file_offset + 4].copy_from_slice(&delta.to_le_bytes()); + out[patch_file_offset..patch_file_offset + 4] + .copy_from_slice(&delta.to_le_bytes()); } } else { let delta = (target_addr as i64 - pc_addr as i64) as i32; if patch_file_offset + 4 <= out.len() { - out[patch_file_offset..patch_file_offset + 4].copy_from_slice(&delta.to_le_bytes()); + out[patch_file_offset..patch_file_offset + 4] + .copy_from_slice(&delta.to_le_bytes()); } } } @@ -797,7 +968,10 @@ fn write_chained_fixups_header( let (data_seg_file_offset, page_count) = if mappings.len() > 1 { let m = &mappings[1]; let mem_size = m.vm_end - m.vm_start; - (m.file_offset, ((mem_size + PAGE_SIZE - 1) / PAGE_SIZE) as u16) + ( + m.file_offset, + ((mem_size + PAGE_SIZE - 1) / PAGE_SIZE) as u16, + ) } else { (0, 0) }; @@ -820,25 +994,42 @@ fn write_chained_fixups_header( w[24..28].copy_from_slice(&0u32.to_le_bytes()); let si = starts_offset as usize; - w[si..si+4].copy_from_slice(&seg_count.to_le_bytes()); + w[si..si + 4].copy_from_slice(&seg_count.to_le_bytes()); for seg in 0..seg_count as usize { - let off: u32 = if seg == data_seg_idx { seg_starts_offset_in_image } else { 0 }; + let off: u32 = if seg == data_seg_idx { + seg_starts_offset_in_image + } else { + 0 + }; w[si + 4 + seg * 4..si + 4 + seg * 4 + 4].copy_from_slice(&off.to_le_bytes()); } let ss = si + seg_starts_offset_in_image as usize; - w[ss..ss+4].copy_from_slice(&(seg_starts_size as u32).to_le_bytes()); - w[ss+4..ss+6].copy_from_slice(&(PAGE_SIZE as u16).to_le_bytes()); - w[ss+6..ss+8].copy_from_slice(&6u16.to_le_bytes()); - let image_base = if mappings.first().map_or(false, |m| m.vm_start >= PAGEZERO_SIZE) { PAGEZERO_SIZE } else { 0 }; - let seg_offset_val: u64 = if mappings.len() > 1 { mappings[1].vm_start.wrapping_sub(image_base) } else { 0 }; - w[ss+8..ss+16].copy_from_slice(&seg_offset_val.to_le_bytes()); - w[ss+16..ss+20].copy_from_slice(&0u32.to_le_bytes()); - w[ss+20..ss+22].copy_from_slice(&page_count.to_le_bytes()); + w[ss..ss + 4].copy_from_slice(&(seg_starts_size as u32).to_le_bytes()); + w[ss + 4..ss + 6].copy_from_slice(&(PAGE_SIZE as u16).to_le_bytes()); + w[ss + 6..ss + 8].copy_from_slice(&6u16.to_le_bytes()); + let image_base = if mappings + .first() + .map_or(false, |m| m.vm_start >= PAGEZERO_SIZE) + { + PAGEZERO_SIZE + } else { + 0 + }; + let seg_offset_val: u64 = if mappings.len() > 1 { + mappings[1].vm_start.wrapping_sub(image_base) + } else { + 0 + }; + w[ss + 8..ss + 16].copy_from_slice(&seg_offset_val.to_le_bytes()); + w[ss + 16..ss + 20].copy_from_slice(&0u32.to_le_bytes()); + w[ss + 20..ss + 22].copy_from_slice(&page_count.to_le_bytes()); let mut page_starts = vec![0xFFFFu16; page_count as usize]; for &(file_off, _) in all_fixups { - if data_seg_file_offset == 0 || (file_off as u64) < data_seg_file_offset { continue; } + if data_seg_file_offset == 0 || (file_off as u64) < data_seg_file_offset { + continue; + } let offset_in_seg = file_off as u64 - data_seg_file_offset; let page_idx = (offset_in_seg / PAGE_SIZE) as usize; let offset_in_page = (offset_in_seg % PAGE_SIZE) as u16; @@ -884,13 +1075,21 @@ fn write_adrp(out: &mut [u8], offset: usize, pc: u64, target: u64) { let page_off = (target & !0xFFF).wrapping_sub(pc & !0xFFF) as i64; let imm = (page_off >> 12) as u32; let insn = read_u32(out, offset); - write_u32_at(out, offset, (insn & 0x9F00_001F) | ((imm & 0x1F_FFFC) << 3) | ((imm & 0x3) << 29)); + write_u32_at( + out, + offset, + (insn & 0x9F00_001F) | ((imm & 0x1F_FFFC) << 3) | ((imm & 0x3) << 29), + ); } fn write_pageoff12(out: &mut [u8], offset: usize, target: u64) { let page_off = (target & 0xFFF) as u32; let insn = read_u32(out, offset); - let shift = if (insn & 0x3B00_0000) == 0x3900_0000 { (insn >> 30) & 0x3 } else { 0 }; + let shift = if (insn & 0x3B00_0000) == 0x3900_0000 { + (insn >> 30) & 0x3 + } else { + 0 + }; let imm12 = (page_off >> shift) & 0xFFF; write_u32_at(out, offset, (insn & 0xFFC0_03FF) | (imm12 << 10)); } @@ -904,21 +1103,40 @@ fn write_headers( chained_fixups_data_size: u32, ) -> Result> { let text_vm_start = mappings.first().map_or(PAGEZERO_SIZE, |m| m.vm_start); - let text_vm_end = mappings.first().map_or(PAGEZERO_SIZE + PAGE_SIZE, |m| m.vm_end); + let text_vm_end = mappings + .first() + .map_or(PAGEZERO_SIZE + PAGE_SIZE, |m| m.vm_end); let text_filesize = align_to(text_vm_end - text_vm_start, PAGE_SIZE); let has_data = mappings.len() > 1; let data_vmaddr = mappings.get(1).map_or(0, |m| m.vm_start); - let data_vm_end = mappings.iter().skip(1).map(|m| m.vm_end).max().unwrap_or(data_vmaddr); + let data_vm_end = mappings + .iter() + .skip(1) + .map(|m| m.vm_end) + .max() + .unwrap_or(data_vmaddr); let data_vmsize = align_to(data_vm_end - data_vmaddr, PAGE_SIZE); let data_fileoff = mappings.get(1).map_or(0, |m| m.file_offset); let data_filesize = if has_data { - align_to(mappings.iter().skip(1).map(|m| m.file_offset + (m.vm_end - m.vm_start)).max().unwrap() - data_fileoff, PAGE_SIZE) - } else { 0 }; + align_to( + mappings + .iter() + .skip(1) + .map(|m| m.file_offset + (m.vm_end - m.vm_start)) + .max() + .unwrap() + - data_fileoff, + PAGE_SIZE, + ) + } else { + 0 + }; let text_layout = layout.section_layouts.get(output_section_id::TEXT); let entry_addr = layout.entry_symbol_address().unwrap_or(0); - let entry_offset = vm_addr_to_file_offset(entry_addr, mappings).unwrap_or(text_layout.file_offset); + let entry_offset = + vm_addr_to_file_offset(entry_addr, mappings).unwrap_or(text_layout.file_offset); let tdata_layout = layout.section_layouts.get(output_section_id::TDATA); let tbss_layout = layout.section_layouts.get(output_section_id::TBSS); @@ -939,7 +1157,9 @@ fn write_headers( let name = crate::macho::trim_nul(s.sectname()); if name == b".rustc" { if let Some(addr) = obj.section_resolutions[sec_idx].address() { - if rustc_addr == 0 || addr < rustc_addr { rustc_addr = addr; } + if rustc_addr == 0 || addr < rustc_addr { + rustc_addr = addr; + } rustc_size += s.size(le); } } @@ -952,20 +1172,39 @@ fn write_headers( let has_rustc = rustc_addr > 0 && rustc_size > 0; let buf_len = out.len(); - let mut w = Writer { buf: out, pos: offset }; + let mut w = Writer { + buf: out, + pos: offset, + }; let dylinker_cmd_size = align8((12 + DYLD_PATH.len() + 1) as u32); let dylib_cmd_size = align8((24 + LIBSYSTEM_PATH.len() + 1) as u32); let is_dylib = layout.symbol_db.args.is_dylib; let install_name = if is_dylib { - layout.symbol_db.args.output().to_string_lossy().into_owned() - } else { String::new() }; - let id_dylib_cmd_size = if is_dylib { align8(24 + install_name.len() as u32 + 1) } else { 0 }; + layout + .symbol_db + .args + .output() + .to_string_lossy() + .into_owned() + } else { + String::new() + }; + let id_dylib_cmd_size = if is_dylib { + align8(24 + install_name.len() as u32 + 1) + } else { + 0 + }; let mut ncmds = 0u32; let mut cmdsize = 0u32; - let add_cmd = |n: &mut u32, s: &mut u32, size: u32| { *n += 1; *s += size; }; - if !is_dylib { add_cmd(&mut ncmds, &mut cmdsize, 72); } // PAGEZERO (exe only) + let add_cmd = |n: &mut u32, s: &mut u32, size: u32| { + *n += 1; + *s += size; + }; + if !is_dylib { + add_cmd(&mut ncmds, &mut cmdsize, 72); + } // PAGEZERO (exe only) let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT @@ -973,8 +1212,12 @@ fn write_headers( let has_init_array = init_array_layout.mem_size > 0; if has_data { let mut data_nsects = if has_tvars { 2u32 } else { 0 }; - if has_rustc { data_nsects += 1; } - if has_init_array { data_nsects += 1; } + if has_rustc { + data_nsects += 1; + } + if has_init_array { + data_nsects += 1; + } add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); } add_cmd(&mut ncmds, &mut cmdsize, 72); // LINKEDIT @@ -984,7 +1227,9 @@ fn write_headers( } else { add_cmd(&mut ncmds, &mut cmdsize, 24); // LC_MAIN } - if !is_dylib { add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); } + if !is_dylib { + add_cmd(&mut ncmds, &mut cmdsize, dylinker_cmd_size); + } add_cmd(&mut ncmds, &mut cmdsize, dylib_cmd_size); // libSystem let extra_dylibs = &layout.symbol_db.args.extra_dylibs; let extra_dylib_sizes: Vec = extra_dylibs @@ -1001,43 +1246,83 @@ fn write_headers( add_cmd(&mut ncmds, &mut cmdsize, 16); let filetype = if is_dylib { 6u32 } else { MH_EXECUTE }; // MH_DYLIB = 6 - w.u32(MH_MAGIC_64); w.u32(CPU_TYPE_ARM64); w.u32(CPU_SUBTYPE_ARM64_ALL); - w.u32(filetype); w.u32(ncmds); w.u32(cmdsize); + w.u32(MH_MAGIC_64); + w.u32(CPU_TYPE_ARM64); + w.u32(CPU_SUBTYPE_ARM64_ALL); + w.u32(filetype); + w.u32(ncmds); + w.u32(cmdsize); let mut flags = MH_PIE | MH_TWOLEVEL | MH_DYLDLINK; - if has_tlv { flags |= 0x0080_0000; } // MH_HAS_TLV_DESCRIPTORS - w.u32(flags); w.u32(0); + if has_tlv { + flags |= 0x0080_0000; + } // MH_HAS_TLV_DESCRIPTORS + w.u32(flags); + w.u32(0); if !is_dylib { w.segment(b"__PAGEZERO", 0, PAGEZERO_SIZE, 0, 0, 0, 0, 0); } // __TEXT — include .rustc section if it falls in TEXT range - w.u32(LC_SEGMENT_64); w.u32(72 + 80 * text_nsects); w.name16(b"__TEXT"); - w.u64(text_vm_start); w.u64(text_filesize); w.u64(0); w.u64(text_filesize); - w.u32(VM_PROT_READ | VM_PROT_EXECUTE); w.u32(VM_PROT_READ | VM_PROT_EXECUTE); - w.u32(text_nsects); w.u32(0); - w.name16(b"__text"); w.name16(b"__TEXT"); - w.u64(text_layout.mem_offset); w.u64(text_layout.mem_size); - w.u32(text_layout.file_offset as u32); w.u32(2); - w.u32(0); w.u32(0); w.u32(0x80000400); w.u32(0); w.u32(0); w.u32(0); + w.u32(LC_SEGMENT_64); + w.u32(72 + 80 * text_nsects); + w.name16(b"__TEXT"); + w.u64(text_vm_start); + w.u64(text_filesize); + w.u64(0); + w.u64(text_filesize); + w.u32(VM_PROT_READ | VM_PROT_EXECUTE); + w.u32(VM_PROT_READ | VM_PROT_EXECUTE); + w.u32(text_nsects); + w.u32(0); + w.name16(b"__text"); + w.name16(b"__TEXT"); + w.u64(text_layout.mem_offset); + w.u64(text_layout.mem_size); + w.u32(text_layout.file_offset as u32); + w.u32(2); + w.u32(0); + w.u32(0); + w.u32(0x80000400); + w.u32(0); + w.u32(0); + w.u32(0); if rustc_in_text { - let rustc_foff = vm_addr_to_file_offset(rustc_addr, mappings) - .unwrap_or(0) as u32; - w.name16(b".rustc"); w.name16(b"__TEXT"); - w.u64(rustc_addr); w.u64(rustc_size); - w.u32(rustc_foff); w.u32(0); - w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); w.u32(0); + let rustc_foff = vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(0) as u32; + w.name16(b".rustc"); + w.name16(b"__TEXT"); + w.u64(rustc_addr); + w.u64(rustc_size); + w.u32(rustc_foff); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); } if has_data { let mut nsects = if has_tvars { 2u32 } else { 0 }; - if has_rustc { nsects += 1; } - if has_init_array { nsects += 1; } + if has_rustc { + nsects += 1; + } + if has_init_array { + nsects += 1; + } let data_cmd_size = 72 + 80 * nsects; - w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); w.name16(b"__DATA"); - w.u64(data_vmaddr); w.u64(data_vmsize); w.u64(data_fileoff); w.u64(data_filesize); - w.u32(VM_PROT_READ | VM_PROT_WRITE); w.u32(VM_PROT_READ | VM_PROT_WRITE); - w.u32(nsects); w.u32(0); + w.u32(LC_SEGMENT_64); + w.u32(data_cmd_size); + w.name16(b"__DATA"); + w.u64(data_vmaddr); + w.u64(data_vmsize); + w.u64(data_fileoff); + w.u64(data_filesize); + w.u32(VM_PROT_READ | VM_PROT_WRITE); + w.u32(VM_PROT_READ | VM_PROT_WRITE); + w.u32(nsects); + w.u32(0); if has_tvars { // Section addresses must be within [data_vmaddr, data_vmaddr+data_vmsize). // Clamp to segment range. @@ -1055,10 +1340,12 @@ fn write_headers( use object::read::macho::Section as _; let sec_type = s.flags(le) & 0xFF; if let Some(addr) = obj.section_resolutions[sec_idx].address() { - if sec_type == 0x13 { // S_THREAD_LOCAL_VARIABLES + if sec_type == 0x13 { + // S_THREAD_LOCAL_VARIABLES tvars_addr = tvars_addr.min(addr); tvars_size += s.size(le); - } else if sec_type == 0x11 { // S_THREAD_LOCAL_REGULAR + } else if sec_type == 0x11 { + // S_THREAD_LOCAL_REGULAR tdata_addr = tdata_addr.min(addr); tdata_size += s.size(le); } @@ -1069,75 +1356,132 @@ fn write_headers( } } tvars_size = (tvars_size / 24) * 24; - if tvars_addr == u64::MAX { tvars_addr = 0; } + if tvars_addr == u64::MAX { + tvars_addr = 0; + } // If no type-0x11 sections, use TDATA layout (may be empty but correctly positioned) if tdata_addr == u64::MAX { tdata_addr = tdata_layout.mem_offset; } let tvars_foff = vm_addr_to_file_offset(tvars_addr, mappings) .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__thread_vars"); w.name16(b"__DATA"); - w.u64(tvars_addr); w.u64(tvars_size); - w.u32(tvars_foff); w.u32(3); - w.u32(0); w.u32(0); + w.name16(b"__thread_vars"); + w.name16(b"__DATA"); + w.u64(tvars_addr); + w.u64(tvars_size); + w.u32(tvars_foff); + w.u32(3); + w.u32(0); + w.u32(0); w.u32(0x13); // S_THREAD_LOCAL_VARIABLES - w.u32(0); w.u32(0); w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); // __thread_data: init template. Size includes TBSS for dyld. let tdata_init_addr = tdata_addr; let tdata_init_size = ((tdata_size + 7) & !7) + tbss_layout.mem_size; let tdata_init_foff = vm_addr_to_file_offset(tdata_init_addr, mappings) .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__thread_data"); w.name16(b"__DATA"); - w.u64(tdata_init_addr); w.u64(tdata_init_size); - w.u32(tdata_init_foff); w.u32(2); - w.u32(0); w.u32(0); + w.name16(b"__thread_data"); + w.name16(b"__DATA"); + w.u64(tdata_init_addr); + w.u64(tdata_init_size); + w.u32(tdata_init_foff); + w.u32(2); + w.u32(0); + w.u32(0); w.u32(0x11); // S_THREAD_LOCAL_REGULAR - w.u32(0); w.u32(0); w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); } if has_rustc { // Always emit .rustc in __DATA for rustc to find metadata. - let rc_addr = if rustc_in_text { data_vmaddr } else { rustc_addr.max(data_vmaddr) }; - let rc_foff = if rustc_in_text { data_fileoff as u32 } else { + let rc_addr = if rustc_in_text { + data_vmaddr + } else { + rustc_addr.max(data_vmaddr) + }; + let rc_foff = if rustc_in_text { + data_fileoff as u32 + } else { vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(data_fileoff as usize) as u32 }; - w.name16(b".rustc"); w.name16(b"__DATA"); - w.u64(rc_addr); w.u64(rustc_size); - w.u32(rc_foff); w.u32(0); - w.u32(0); w.u32(0); - w.u32(0); w.u32(0); w.u32(0); w.u32(0); + w.name16(b".rustc"); + w.name16(b"__DATA"); + w.u64(rc_addr); + w.u64(rustc_size); + w.u32(rc_foff); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); } if has_init_array { let ia_foff = vm_addr_to_file_offset(init_array_layout.mem_offset, mappings) .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__mod_init_func"); w.name16(b"__DATA"); - w.u64(init_array_layout.mem_offset); w.u64(init_array_layout.mem_size); - w.u32(ia_foff); w.u32(3); // align 2^3 = 8 - w.u32(0); w.u32(0); + w.name16(b"__mod_init_func"); + w.name16(b"__DATA"); + w.u64(init_array_layout.mem_offset); + w.u64(init_array_layout.mem_size); + w.u32(ia_foff); + w.u32(3); // align 2^3 = 8 + w.u32(0); + w.u32(0); w.u32(0x09); // S_MOD_INIT_FUNC_POINTERS - w.u32(0); w.u32(0); w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); } } let (last_file_end, linkedit_vm) = if has_data { (data_fileoff + data_filesize, data_vmaddr + data_vmsize) } else { - (text_filesize, align_to(text_vm_start + text_filesize, PAGE_SIZE)) + ( + text_filesize, + align_to(text_vm_start + text_filesize, PAGE_SIZE), + ) }; let cf_offset = last_file_end; let cf_size = chained_fixups_data_size as u64; // LINKEDIT vmsize must cover the full content (fixups + symtab + exports). - let linkedit_vmsize = align_to((buf_len as u64).saturating_sub(last_file_end).max(PAGE_SIZE), PAGE_SIZE); - w.segment(b"__LINKEDIT", linkedit_vm, linkedit_vmsize, last_file_end, cf_size, VM_PROT_READ, VM_PROT_READ, 0); + let linkedit_vmsize = align_to( + (buf_len as u64) + .saturating_sub(last_file_end) + .max(PAGE_SIZE), + PAGE_SIZE, + ); + w.segment( + b"__LINKEDIT", + linkedit_vm, + linkedit_vmsize, + last_file_end, + cf_size, + VM_PROT_READ, + VM_PROT_READ, + 0, + ); if is_dylib { // LC_ID_DYLIB = 0x0D - w.u32(0x0D); w.u32(id_dylib_cmd_size); w.u32(24); w.u32(2); - w.u32(0x01_0000); w.u32(0x01_0000); - w.bytes(install_name.as_bytes()); w.u8(0); w.pad8(); + w.u32(0x0D); + w.u32(id_dylib_cmd_size); + w.u32(24); + w.u32(2); + w.u32(0x01_0000); + w.u32(0x01_0000); + w.bytes(install_name.as_bytes()); + w.u8(0); + w.pad8(); // LC_UUID = 0x1B (required for dlopen) - w.u32(0x1B); w.u32(24); + w.u32(0x1B); + w.u32(24); // Generate a deterministic UUID from the output path let uuid_bytes: [u8; 16] = { let mut h = [0u8; 16]; @@ -1150,30 +1494,72 @@ fn write_headers( }; w.bytes(&uuid_bytes); } else { - w.u32(LC_MAIN); w.u32(24); w.u64(entry_offset as u64); w.u64(0); + w.u32(LC_MAIN); + w.u32(24); + w.u64(entry_offset as u64); + w.u64(0); } if !is_dylib { - w.u32(LC_LOAD_DYLINKER); w.u32(dylinker_cmd_size); w.u32(12); - w.bytes(DYLD_PATH); w.u8(0); w.pad8(); + w.u32(LC_LOAD_DYLINKER); + w.u32(dylinker_cmd_size); + w.u32(12); + w.bytes(DYLD_PATH); + w.u8(0); + w.pad8(); } - w.u32(LC_LOAD_DYLIB); w.u32(dylib_cmd_size); w.u32(24); w.u32(2); - w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(LIBSYSTEM_PATH); w.u8(0); w.pad8(); + w.u32(LC_LOAD_DYLIB); + w.u32(dylib_cmd_size); + w.u32(24); + w.u32(2); + w.u32(0x01_0000); + w.u32(0x01_0000); + w.bytes(LIBSYSTEM_PATH); + w.u8(0); + w.pad8(); for (i, dylib_path) in extra_dylibs.iter().enumerate() { - w.u32(LC_LOAD_DYLIB); w.u32(extra_dylib_sizes[i]); w.u32(24); w.u32(2); - w.u32(0x01_0000); w.u32(0x01_0000); w.bytes(dylib_path); w.u8(0); w.pad8(); + w.u32(LC_LOAD_DYLIB); + w.u32(extra_dylib_sizes[i]); + w.u32(24); + w.u32(2); + w.u32(0x01_0000); + w.u32(0x01_0000); + w.bytes(dylib_path); + w.u8(0); + w.pad8(); } - w.u32(LC_SYMTAB); w.u32(24); w.u32(0); w.u32(0); w.u32(0); w.u32(0); - w.u32(LC_DYSYMTAB); w.u32(80); for _ in 0..18 { w.u32(0); } - - w.u32(LC_BUILD_VERSION); w.u32(32); w.u32(PLATFORM_MACOS); - w.u32(0x000E_0000); w.u32(0x000E_0000); w.u32(1); w.u32(3); w.u32(0x0300_0100); + w.u32(LC_SYMTAB); + w.u32(24); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(0); + w.u32(LC_DYSYMTAB); + w.u32(80); + for _ in 0..18 { + w.u32(0); + } - w.u32(LC_DYLD_CHAINED_FIXUPS); w.u32(16); w.u32(cf_offset as u32); w.u32(cf_size as u32); - w.u32(LC_DYLD_EXPORTS_TRIE); w.u32(16); w.u32(last_file_end as u32); w.u32(0); + w.u32(LC_BUILD_VERSION); + w.u32(32); + w.u32(PLATFORM_MACOS); + w.u32(0x000E_0000); + w.u32(0x000E_0000); + w.u32(1); + w.u32(3); + w.u32(0x0300_0100); + + w.u32(LC_DYLD_CHAINED_FIXUPS); + w.u32(16); + w.u32(cf_offset as u32); + w.u32(cf_size as u32); + w.u32(LC_DYLD_EXPORTS_TRIE); + w.u32(16); + w.u32(last_file_end as u32); + w.u32(0); Ok(Some(cf_offset)) } @@ -1186,35 +1572,69 @@ fn write_u32_at(buf: &mut [u8], offset: usize, val: u32) { buf[offset..offset + 4].copy_from_slice(&val.to_le_bytes()); } -fn align8(v: u32) -> u32 { (v + 7) & !7 } -fn align_to(value: u64, alignment: u64) -> u64 { (value + alignment - 1) & !(alignment - 1) } +fn align8(v: u32) -> u32 { + (v + 7) & !7 +} +fn align_to(value: u64, alignment: u64) -> u64 { + (value + alignment - 1) & !(alignment - 1) +} -struct Writer<'a> { buf: &'a mut [u8], pos: usize } +struct Writer<'a> { + buf: &'a mut [u8], + pos: usize, +} impl Writer<'_> { - fn u8(&mut self, v: u8) { self.buf[self.pos] = v; self.pos += 1; } + fn u8(&mut self, v: u8) { + self.buf[self.pos] = v; + self.pos += 1; + } fn u32(&mut self, v: u32) { - self.buf[self.pos..self.pos + 4].copy_from_slice(&v.to_le_bytes()); self.pos += 4; + self.buf[self.pos..self.pos + 4].copy_from_slice(&v.to_le_bytes()); + self.pos += 4; } fn u64(&mut self, v: u64) { - self.buf[self.pos..self.pos + 8].copy_from_slice(&v.to_le_bytes()); self.pos += 8; + self.buf[self.pos..self.pos + 8].copy_from_slice(&v.to_le_bytes()); + self.pos += 8; } fn name16(&mut self, name: &[u8]) { let mut buf = [0u8; 16]; buf[..name.len().min(16)].copy_from_slice(&name[..name.len().min(16)]); - self.buf[self.pos..self.pos + 16].copy_from_slice(&buf); self.pos += 16; + self.buf[self.pos..self.pos + 16].copy_from_slice(&buf); + self.pos += 16; } fn bytes(&mut self, data: &[u8]) { - self.buf[self.pos..self.pos + data.len()].copy_from_slice(data); self.pos += data.len(); + self.buf[self.pos..self.pos + data.len()].copy_from_slice(data); + self.pos += data.len(); } fn pad8(&mut self) { let aligned = (self.pos + 7) & !7; - while self.pos < aligned { self.buf[self.pos] = 0; self.pos += 1; } + while self.pos < aligned { + self.buf[self.pos] = 0; + self.pos += 1; + } } - fn segment(&mut self, name: &[u8], vmaddr: u64, vmsize: u64, - fileoff: u64, filesize: u64, maxprot: u32, initprot: u32, nsects: u32) { - self.u32(LC_SEGMENT_64); self.u32(72 + 80 * nsects); self.name16(name); - self.u64(vmaddr); self.u64(vmsize); self.u64(fileoff); self.u64(filesize); - self.u32(maxprot); self.u32(initprot); self.u32(nsects); self.u32(0); + fn segment( + &mut self, + name: &[u8], + vmaddr: u64, + vmsize: u64, + fileoff: u64, + filesize: u64, + maxprot: u32, + initprot: u32, + nsects: u32, + ) { + self.u32(LC_SEGMENT_64); + self.u32(72 + 80 * nsects); + self.name16(name); + self.u64(vmaddr); + self.u64(vmsize); + self.u64(fileoff); + self.u64(filesize); + self.u32(maxprot); + self.u32(initprot); + self.u32(nsects); + self.u32(0); } } diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 7679be9b7..4037ee9b2 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -13,7 +13,8 @@ //! Contains:{string} Output binary must contain this string. //! DoesNotContain:{string} Output binary must NOT contain this string. -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::path::PathBuf; use std::process::Command; fn main() -> Result<(), Box> { @@ -57,10 +58,9 @@ fn collect_tests(tests: &mut Vec) -> Result<(), Box, } -fn parse_config( - test_dir: &Path, - primary: &Path, -) -> Result> { +fn parse_config(test_dir: &Path, primary: &Path) -> Result> { let mut cfg = TestConfig { run_enabled: true, ..Default::default() @@ -209,7 +206,9 @@ fn run_test( // Check for expected errors. if let Some(ref pattern) = config.expect_error { if link_result.status.success() { - return Err(format!("Expected link failure matching '{pattern}', but link succeeded")); + return Err(format!( + "Expected link failure matching '{pattern}', but link succeeded" + )); } let stderr = String::from_utf8_lossy(&link_result.stderr); if !stderr.contains(pattern) { @@ -270,7 +269,10 @@ fn compile_source( let result = cmd.output().map_err(|e| format!("{compiler}: {e}"))?; if !result.status.success() { let stderr = String::from_utf8_lossy(&result.stderr); - return Err(format!("Compilation of {} failed:\n{stderr}", src.display())); + return Err(format!( + "Compilation of {} failed:\n{stderr}", + src.display() + )); } Ok(()) } From f43d9cb966776610e0270ca3631430f131387e3b Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Tue, 7 Apr 2026 08:59:11 +0100 Subject: [PATCH 07/21] equivilent tests Signed-off-by: Giles Cope --- libwild/src/macho.rs | 27 +++++---- libwild/src/macho_writer.rs | 57 +++++++++++++++++-- wild/tests/macho_integration_tests.rs | 44 +++++++++++++- .../macho/absolute-symbol/absolute-symbol.c | 10 ++++ .../archive-activation/archive-activation.c | 4 ++ .../archive-activation/archive-activation1.c | 1 + .../macho/backtrace-test/backtrace-test.rs | 15 +++++ .../cross-object-call/cross-object-call.c | 4 ++ .../cross-object-call/cross-object-call1.c | 1 + .../macho/custom-section/custom-section.c | 10 ++++ .../macho/custom-section/custom-section1.c | 3 + .../macho/data-pointers/data-pointers.c | 17 ++++++ .../macho/data-pointers/data-pointers1.c | 8 +++ .../macho/duplicate-strong-error/dup1.c | 1 + .../duplicate-strong-error.c | 5 ++ .../tests/sources/macho/entry-arg/entry-arg.c | 5 ++ .../sources/macho/exception/exception.cc | 15 +++++ .../macho/force-undefined/force-undefined.c | 6 ++ .../macho/force-undefined/force-undefined1.c | 1 + .../global-definitions/global-definitions.c | 4 ++ .../global-definitions/global-definitions1.c | 1 + .../macho/got-ref-to-local/got-ref-to-local.c | 14 +++++ .../sources/macho/hidden-ref/hidden-ref.c | 12 ++++ .../sources/macho/hidden-ref/hidden-ref1.c | 1 + .../sources/macho/hidden-ref/hidden-ref2.c | 3 + .../sources/macho/init-order/init-order.c | 12 ++++ .../input-does-not-exist.c | 4 ++ .../local-symbol-refs/local-symbol-refs.c | 5 ++ .../local-symbol-refs/local-symbol-refs1.c | 2 + .../sources/macho/relocatables/relocatables.c | 12 ++++ .../macho/relocatables/relocatables1.c | 1 + .../macho/relocatables/relocatables2.c | 1 + .../rust-integration/rust-integration.rs | 7 +++ .../rust-panic-unwind/rust-panic-unwind.rs | 6 ++ .../macho/shared-basic/shared-basic-lib.c | 1 + .../sources/macho/shared-basic/shared-basic.c | 5 ++ .../macho/string-merging/string-merging.c | 13 +++++ .../macho/string-merging/string-merging1.c | 1 + wild/tests/sources/macho/tls/tls.c | 5 ++ wild/tests/sources/macho/tls/tls1.c | 2 + .../macho/trivial-dynamic/trivial-dynamic.c | 5 ++ .../macho/trivial-dynamic/trivial-dynamic1.c | 1 + .../sources/macho/trivial-main/trivial-main.c | 3 + .../undefined-symbol-error.c | 5 ++ .../undefined-weak-and-strong.c | 11 ++++ .../undefined-weak-and-strong1.c | 2 + .../undefined-weak-sym/undefined-weak-sym.c | 8 +++ .../visibility-merging/visibility-merging.c | 12 ++++ .../visibility-merging/visibility-merging1.c | 2 + .../macho/weak-fns-archive/weak-fns-archive.c | 5 ++ .../weak-fns-archive/weak-fns-archive1.c | 2 + .../weak-vars-archive/weak-vars-archive.c | 5 ++ .../weak-vars-archive/weak-vars-archive1.c | 2 + .../macho/whole-archive/whole-archive.c | 4 ++ .../macho/whole-archive/whole-archive1.c | 1 + 55 files changed, 398 insertions(+), 16 deletions(-) create mode 100644 wild/tests/sources/macho/absolute-symbol/absolute-symbol.c create mode 100644 wild/tests/sources/macho/archive-activation/archive-activation.c create mode 100644 wild/tests/sources/macho/archive-activation/archive-activation1.c create mode 100644 wild/tests/sources/macho/backtrace-test/backtrace-test.rs create mode 100644 wild/tests/sources/macho/cross-object-call/cross-object-call.c create mode 100644 wild/tests/sources/macho/cross-object-call/cross-object-call1.c create mode 100644 wild/tests/sources/macho/custom-section/custom-section.c create mode 100644 wild/tests/sources/macho/custom-section/custom-section1.c create mode 100644 wild/tests/sources/macho/data-pointers/data-pointers.c create mode 100644 wild/tests/sources/macho/data-pointers/data-pointers1.c create mode 100644 wild/tests/sources/macho/duplicate-strong-error/dup1.c create mode 100644 wild/tests/sources/macho/duplicate-strong-error/duplicate-strong-error.c create mode 100644 wild/tests/sources/macho/entry-arg/entry-arg.c create mode 100644 wild/tests/sources/macho/exception/exception.cc create mode 100644 wild/tests/sources/macho/force-undefined/force-undefined.c create mode 100644 wild/tests/sources/macho/force-undefined/force-undefined1.c create mode 100644 wild/tests/sources/macho/global-definitions/global-definitions.c create mode 100644 wild/tests/sources/macho/global-definitions/global-definitions1.c create mode 100644 wild/tests/sources/macho/got-ref-to-local/got-ref-to-local.c create mode 100644 wild/tests/sources/macho/hidden-ref/hidden-ref.c create mode 100644 wild/tests/sources/macho/hidden-ref/hidden-ref1.c create mode 100644 wild/tests/sources/macho/hidden-ref/hidden-ref2.c create mode 100644 wild/tests/sources/macho/init-order/init-order.c create mode 100644 wild/tests/sources/macho/input-does-not-exist/input-does-not-exist.c create mode 100644 wild/tests/sources/macho/local-symbol-refs/local-symbol-refs.c create mode 100644 wild/tests/sources/macho/local-symbol-refs/local-symbol-refs1.c create mode 100644 wild/tests/sources/macho/relocatables/relocatables.c create mode 100644 wild/tests/sources/macho/relocatables/relocatables1.c create mode 100644 wild/tests/sources/macho/relocatables/relocatables2.c create mode 100644 wild/tests/sources/macho/rust-integration/rust-integration.rs create mode 100644 wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs create mode 100644 wild/tests/sources/macho/shared-basic/shared-basic-lib.c create mode 100644 wild/tests/sources/macho/shared-basic/shared-basic.c create mode 100644 wild/tests/sources/macho/string-merging/string-merging.c create mode 100644 wild/tests/sources/macho/string-merging/string-merging1.c create mode 100644 wild/tests/sources/macho/tls/tls.c create mode 100644 wild/tests/sources/macho/tls/tls1.c create mode 100644 wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c create mode 100644 wild/tests/sources/macho/trivial-dynamic/trivial-dynamic1.c create mode 100644 wild/tests/sources/macho/trivial-main/trivial-main.c create mode 100644 wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c create mode 100644 wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c create mode 100644 wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong1.c create mode 100644 wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c create mode 100644 wild/tests/sources/macho/visibility-merging/visibility-merging.c create mode 100644 wild/tests/sources/macho/visibility-merging/visibility-merging1.c create mode 100644 wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c create mode 100644 wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c create mode 100644 wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c create mode 100644 wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c create mode 100644 wild/tests/sources/macho/whole-archive/whole-archive.c create mode 100644 wild/tests/sources/macho/whole-archive/whole-archive1.c diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index b6bb94a6a..32c1513ff 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -391,9 +391,18 @@ impl platform::SectionHeader for SectionHeader { fn should_retain(&self) -> bool { let sec_type = self.0.flags(LE) & macho::SECTION_TYPE; - // __mod_init_func / __mod_term_func must always be retained — - // they contain constructor/destructor pointers called by dyld. - sec_type == macho::S_MOD_INIT_FUNC_POINTERS || sec_type == macho::S_MOD_TERM_FUNC_POINTERS + let sectname = trim_nul(self.0.sectname()); + // Constructor/destructor function pointer arrays. + if sec_type == macho::S_MOD_INIT_FUNC_POINTERS + || sec_type == macho::S_MOD_TERM_FUNC_POINTERS + { + return true; + } + // Exception handling sections needed for unwinding. + if sectname == b"__eh_frame" || sectname == b"__gcc_except_tab" { + return true; + } + false } fn should_exclude(&self) -> bool { @@ -408,11 +417,7 @@ impl platform::SectionHeader for SectionHeader { if segname == b"__LD" { return true; } - // __eh_frame has SUBTRACTOR relocation pairs we don't process yet; - // exclude until we generate proper __unwind_info. - if sectname == b"__eh_frame" { - return true; - } + // __eh_frame is included — SUBTRACTOR relocation pairs are now handled. false } @@ -900,9 +905,11 @@ impl platform::Platform for MachO { keep_sections: &mut crate::output_section_map::OutputSectionMap, _args: &Self::Args, ) { - // Constructor/destructor function pointer arrays must always be kept. *keep_sections.get_mut(crate::output_section_id::INIT_ARRAY) = true; *keep_sections.get_mut(crate::output_section_id::FINI_ARRAY) = true; + // Exception handling sections needed for stack unwinding. + *keep_sections.get_mut(crate::output_section_id::EH_FRAME) = true; + *keep_sections.get_mut(crate::output_section_id::GCC_EXCEPT_TABLE) = true; } fn is_zero_sized_section_content( @@ -1465,7 +1472,7 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__bss", output_section_id::BSS), SectionRule::exact_section(b"__common", output_section_id::BSS), SectionRule::exact_section(b"__unwind_info", output_section_id::RODATA), - SectionRule::exact_section(b"__eh_frame", output_section_id::RODATA), + SectionRule::exact_section(b"__eh_frame", output_section_id::EH_FRAME), SectionRule::exact_section(b"__compact_unwind", output_section_id::RODATA), ] }; diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 1c036644e..45777c4d4 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -761,15 +761,32 @@ fn apply_relocations( has_extra_dylibs: bool, ) -> Result { let mut pending_addend: i64 = 0; + let mut pending_subtrahend: Option = None; for reloc_raw in relocs { let reloc = reloc_raw.info(le); - if reloc.r_type == 10 { + if reloc.r_type == 10 { // ARM64_RELOC_ADDEND pending_addend = reloc.r_symbolnum as i64; continue; } - if reloc.r_type == 1 { + if reloc.r_type == 1 { // ARM64_RELOC_SUBTRACTOR (part of a pair) + // Store the subtrahend symbol address for the next UNSIGNED reloc. + let sub_addr = if reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let sym_id = obj.symbol_id_range.input_to_id(sym_idx); + layout.merged_symbol_resolution(sym_id) + .map(|r| r.raw_value) + .unwrap_or(0) + } else { + let sec_ord = reloc.r_symbolnum as usize; + if sec_ord > 0 { + obj.section_resolutions.get(sec_ord - 1) + .and_then(|r| r.address()) + .unwrap_or(0) + } else { 0 } + }; + pending_subtrahend = Some(sub_addr); continue; } @@ -872,8 +889,18 @@ fn apply_relocations( ); } 0 if reloc.r_length == 3 => { - // ARM64_RELOC_UNSIGNED 64-bit - if patch_file_offset + 8 <= out.len() { + // ARM64_RELOC_UNSIGNED 64-bit. + // If preceded by a SUBTRACTOR, compute difference: + // result = target_addr - subtrahend + existing_content + if let Some(sub_addr) = pending_subtrahend.take() { + if patch_file_offset + 8 <= out.len() { + let existing = i64::from_le_bytes( + out[patch_file_offset..patch_file_offset + 8].try_into().unwrap()); + let val = target_addr as i64 - sub_addr as i64 + existing; + out[patch_file_offset..patch_file_offset + 8] + .copy_from_slice(&val.to_le_bytes()); + } + } else if patch_file_offset + 8 <= out.len() { if reloc.r_extern && target_addr == 0 { // Extern undefined symbol (e.g. _tlv_bootstrap): bind fixup let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); @@ -1206,7 +1233,11 @@ fn write_headers( add_cmd(&mut ncmds, &mut cmdsize, 72); } // PAGEZERO (exe only) let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; - let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 }; + let eh_frame_layout = layout.section_layouts.get(output_section_id::EH_FRAME); + let has_eh_frame = eh_frame_layout.mem_size > 0; + let text_nsects = 1 + + if rustc_in_text { 1u32 } else { 0 } + + if has_eh_frame { 1 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT let init_array_layout = layout.section_layouts.get(output_section_id::INIT_ARRAY); let has_init_array = init_array_layout.mem_size > 0; @@ -1302,6 +1333,22 @@ fn write_headers( w.u32(0); w.u32(0); } + if has_eh_frame { + let eh_foff = vm_addr_to_file_offset(eh_frame_layout.mem_offset, mappings) + .unwrap_or(0) as u32; + w.name16(b"__eh_frame"); + w.name16(b"__TEXT"); + w.u64(eh_frame_layout.mem_offset); + w.u64(eh_frame_layout.mem_size); + w.u32(eh_foff); + w.u32(3); // align 2^3 = 8 + w.u32(0); + w.u32(0); + w.u32(0x6800_000B); // S_COALESCED | S_ATTR_NO_TOC | S_ATTR_STRIP_STATIC_SYMS | S_ATTR_LIVE_SUPPORT + w.u32(0); + w.u32(0); + w.u32(0); + } if has_data { let mut nsects = if has_tvars { 2u32 } else { 0 }; diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 4037ee9b2..d8e8f0083 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -56,9 +56,14 @@ fn collect_tests(tests: &mut Vec) -> Result<(), Box String { + let bt = std::backtrace::Backtrace::force_capture(); + format!("{bt}") +} + +fn main() { + let bt = inner(); + if bt.contains("inner") { + std::process::exit(42); + } + std::process::exit(1); +} diff --git a/wild/tests/sources/macho/cross-object-call/cross-object-call.c b/wild/tests/sources/macho/cross-object-call/cross-object-call.c new file mode 100644 index 000000000..15b9ac229 --- /dev/null +++ b/wild/tests/sources/macho/cross-object-call/cross-object-call.c @@ -0,0 +1,4 @@ +//#Object:cross-object-call1.c + +int add(int, int); +int main() { return add(30, 12); } diff --git a/wild/tests/sources/macho/cross-object-call/cross-object-call1.c b/wild/tests/sources/macho/cross-object-call/cross-object-call1.c new file mode 100644 index 000000000..1f7644834 --- /dev/null +++ b/wild/tests/sources/macho/cross-object-call/cross-object-call1.c @@ -0,0 +1 @@ +int add(int a, int b) { return a + b; } diff --git a/wild/tests/sources/macho/custom-section/custom-section.c b/wild/tests/sources/macho/custom-section/custom-section.c new file mode 100644 index 000000000..d621eb0cf --- /dev/null +++ b/wild/tests/sources/macho/custom-section/custom-section.c @@ -0,0 +1,10 @@ +//#Object:custom-section1.c + +// Tests that data placed in custom sections via __attribute__((section)) +// is correctly linked and accessible at runtime. + +extern int get_custom_value(void); + +static int my_data __attribute__((used, section("__DATA,__custom"))) = 30; + +int main() { return my_data + get_custom_value(); } diff --git a/wild/tests/sources/macho/custom-section/custom-section1.c b/wild/tests/sources/macho/custom-section/custom-section1.c new file mode 100644 index 000000000..4d9da3c67 --- /dev/null +++ b/wild/tests/sources/macho/custom-section/custom-section1.c @@ -0,0 +1,3 @@ +static int other __attribute__((used, section("__DATA,__custom"))) = 12; + +int get_custom_value(void) { return other; } diff --git a/wild/tests/sources/macho/data-pointers/data-pointers.c b/wild/tests/sources/macho/data-pointers/data-pointers.c new file mode 100644 index 000000000..271ce29c7 --- /dev/null +++ b/wild/tests/sources/macho/data-pointers/data-pointers.c @@ -0,0 +1,17 @@ +//#Object:data-pointers1.c + +// Tests that data pointers (function pointers and data addresses) in the +// DATA section are correctly rebased for ASLR. + +extern int values[4]; +extern int (*get_fn(void))(void); + +int main() { + // Check data array values + if (values[0] != 10) return 1; + if (values[1] != 20) return 2; + + // Check function pointer from another object + int (*fn)(void) = get_fn(); + return fn(); +} diff --git a/wild/tests/sources/macho/data-pointers/data-pointers1.c b/wild/tests/sources/macho/data-pointers/data-pointers1.c new file mode 100644 index 000000000..9eef489d4 --- /dev/null +++ b/wild/tests/sources/macho/data-pointers/data-pointers1.c @@ -0,0 +1,8 @@ +int values[4] = {10, 20, 30, 40}; + +static int return_42(void) { return 42; } + +// Function pointer in the data section — requires rebase fixup. +static int (*fn_ptr)(void) = return_42; + +int (*get_fn(void))(void) { return fn_ptr; } diff --git a/wild/tests/sources/macho/duplicate-strong-error/dup1.c b/wild/tests/sources/macho/duplicate-strong-error/dup1.c new file mode 100644 index 000000000..3167837f0 --- /dev/null +++ b/wild/tests/sources/macho/duplicate-strong-error/dup1.c @@ -0,0 +1 @@ +int foo(void) { return 2; } diff --git a/wild/tests/sources/macho/duplicate-strong-error/duplicate-strong-error.c b/wild/tests/sources/macho/duplicate-strong-error/duplicate-strong-error.c new file mode 100644 index 000000000..f6c029498 --- /dev/null +++ b/wild/tests/sources/macho/duplicate-strong-error/duplicate-strong-error.c @@ -0,0 +1,5 @@ +//#Object:dup1.c +//#ExpectError:Duplicate + +int foo(void) { return 1; } +int main() { return foo(); } diff --git a/wild/tests/sources/macho/entry-arg/entry-arg.c b/wild/tests/sources/macho/entry-arg/entry-arg.c new file mode 100644 index 000000000..a200f11e2 --- /dev/null +++ b/wild/tests/sources/macho/entry-arg/entry-arg.c @@ -0,0 +1,5 @@ +//#LinkArgs:-e _custom_entry +//#Ignore:custom entry point via -e not yet verified +//#RunEnabled:false + +void custom_entry(void) {} diff --git a/wild/tests/sources/macho/exception/exception.cc b/wild/tests/sources/macho/exception/exception.cc new file mode 100644 index 000000000..7c6909561 --- /dev/null +++ b/wild/tests/sources/macho/exception/exception.cc @@ -0,0 +1,15 @@ +//#LinkerDriver:clang++ +//#LinkArgs:-lc++ +//#CompArgs:-std=c++17 +//#Ignore:C++ exceptions need __compact_unwind → __unwind_info conversion + +#include + +int main() { + try { + throw std::runtime_error("test"); + } catch (const std::runtime_error& e) { + return 42; + } + return 1; +} diff --git a/wild/tests/sources/macho/force-undefined/force-undefined.c b/wild/tests/sources/macho/force-undefined/force-undefined.c new file mode 100644 index 000000000..b2db9e4fb --- /dev/null +++ b/wild/tests/sources/macho/force-undefined/force-undefined.c @@ -0,0 +1,6 @@ +//#Ignore:-u flag not implemented for Mach-O +//#Object:force-undefined1.c +//#LinkArgs:-u _forced_sym + +extern int forced_sym; +int main() { return forced_sym; } diff --git a/wild/tests/sources/macho/force-undefined/force-undefined1.c b/wild/tests/sources/macho/force-undefined/force-undefined1.c new file mode 100644 index 000000000..eaa4f34b9 --- /dev/null +++ b/wild/tests/sources/macho/force-undefined/force-undefined1.c @@ -0,0 +1 @@ +int forced_sym = 42; diff --git a/wild/tests/sources/macho/global-definitions/global-definitions.c b/wild/tests/sources/macho/global-definitions/global-definitions.c new file mode 100644 index 000000000..0912e4d65 --- /dev/null +++ b/wild/tests/sources/macho/global-definitions/global-definitions.c @@ -0,0 +1,4 @@ +//#Object:global-definitions1.c + +extern int g1, g2, g3; +int main() { return g1 + g2 + g3; } diff --git a/wild/tests/sources/macho/global-definitions/global-definitions1.c b/wild/tests/sources/macho/global-definitions/global-definitions1.c new file mode 100644 index 000000000..4d4b8cc45 --- /dev/null +++ b/wild/tests/sources/macho/global-definitions/global-definitions1.c @@ -0,0 +1 @@ +int g1 = 10, g2 = 20, g3 = 12; diff --git a/wild/tests/sources/macho/got-ref-to-local/got-ref-to-local.c b/wild/tests/sources/macho/got-ref-to-local/got-ref-to-local.c new file mode 100644 index 000000000..d29157530 --- /dev/null +++ b/wild/tests/sources/macho/got-ref-to-local/got-ref-to-local.c @@ -0,0 +1,14 @@ +// Tests that GOT references to local (static) functions work correctly. +// The compiler may generate GOT-indirect references for function pointers. + +static int local_fn1(void) { return 20; } +static int local_fn2(void) { return 22; } + +typedef int (*fnptr)(void); + +// Force GOT-indirect references by taking addresses in a volatile context. +int main() { + volatile fnptr f1 = local_fn1; + volatile fnptr f2 = local_fn2; + return f1() + f2(); +} diff --git a/wild/tests/sources/macho/hidden-ref/hidden-ref.c b/wild/tests/sources/macho/hidden-ref/hidden-ref.c new file mode 100644 index 000000000..734201011 --- /dev/null +++ b/wild/tests/sources/macho/hidden-ref/hidden-ref.c @@ -0,0 +1,12 @@ +//#Object:hidden-ref1.c +//#Object:hidden-ref2.c +//#Ignore:dylib creation needed to test hidden symbol visibility + +// Tests that a hidden symbol is not exported from a dylib. +// hidden-ref1.c defines foo() as default visibility. +// hidden-ref2.c defines foo() as hidden. +// When linked into a dylib, hidden should win and foo should not be exported. + +__attribute__((visibility(("hidden")))) int foo(void); + +int main() { return foo(); } diff --git a/wild/tests/sources/macho/hidden-ref/hidden-ref1.c b/wild/tests/sources/macho/hidden-ref/hidden-ref1.c new file mode 100644 index 000000000..464a23056 --- /dev/null +++ b/wild/tests/sources/macho/hidden-ref/hidden-ref1.c @@ -0,0 +1 @@ +int foo(void) { return 42; } diff --git a/wild/tests/sources/macho/hidden-ref/hidden-ref2.c b/wild/tests/sources/macho/hidden-ref/hidden-ref2.c new file mode 100644 index 000000000..f173e05c2 --- /dev/null +++ b/wild/tests/sources/macho/hidden-ref/hidden-ref2.c @@ -0,0 +1,3 @@ +// Hidden definition — when merged with the default-visibility declaration +// in the main file, hidden should take precedence. +__attribute__((visibility("hidden"))) int foo(void); diff --git a/wild/tests/sources/macho/init-order/init-order.c b/wild/tests/sources/macho/init-order/init-order.c new file mode 100644 index 000000000..947c53107 --- /dev/null +++ b/wild/tests/sources/macho/init-order/init-order.c @@ -0,0 +1,12 @@ +//#LinkerDriver:clang +//#Ignore:constructor priority ordering not yet verified + +static int order = 0; +static int first_val = 0, second_val = 0; + +__attribute__((constructor(101))) void first(void) { first_val = ++order; } +__attribute__((constructor(102))) void second(void) { second_val = ++order; } + +int main() { + return (first_val == 1 && second_val == 2) ? 42 : first_val * 10 + second_val; +} diff --git a/wild/tests/sources/macho/input-does-not-exist/input-does-not-exist.c b/wild/tests/sources/macho/input-does-not-exist/input-does-not-exist.c new file mode 100644 index 000000000..f7ac7aaec --- /dev/null +++ b/wild/tests/sources/macho/input-does-not-exist/input-does-not-exist.c @@ -0,0 +1,4 @@ +//#Object:/does/not/exist.o +//#ExpectError:/does/not/exist.o + +int main() { return 0; } diff --git a/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs.c b/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs.c new file mode 100644 index 000000000..a28077e2c --- /dev/null +++ b/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs.c @@ -0,0 +1,5 @@ +//#Object:local-symbol-refs1.c + +static int local_val = 42; +int* get_local_ptr(void); +int main() { return *get_local_ptr() == local_val ? local_val : 1; } diff --git a/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs1.c b/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs1.c new file mode 100644 index 000000000..f1c3060c2 --- /dev/null +++ b/wild/tests/sources/macho/local-symbol-refs/local-symbol-refs1.c @@ -0,0 +1,2 @@ +static int other_val = 42; +int* get_local_ptr(void) { return &other_val; } diff --git a/wild/tests/sources/macho/relocatables/relocatables.c b/wild/tests/sources/macho/relocatables/relocatables.c new file mode 100644 index 000000000..04bb32127 --- /dev/null +++ b/wild/tests/sources/macho/relocatables/relocatables.c @@ -0,0 +1,12 @@ +//#Object:relocatables1.c +//#Object:relocatables2.c +//#Ignore:partial linking (-r) not yet implemented for Mach-O + +// Tests -r (partial link / relocatable output). +// Link relocatables1.c and relocatables2.c into a single .o via -r, +// then link that combined .o into the final executable. + +int add(int, int); +int multiply(int, int); + +int main() { return add(30, 12) == 42 && multiply(6, 7) == 42 ? 42 : 1; } diff --git a/wild/tests/sources/macho/relocatables/relocatables1.c b/wild/tests/sources/macho/relocatables/relocatables1.c new file mode 100644 index 000000000..1f7644834 --- /dev/null +++ b/wild/tests/sources/macho/relocatables/relocatables1.c @@ -0,0 +1 @@ +int add(int a, int b) { return a + b; } diff --git a/wild/tests/sources/macho/relocatables/relocatables2.c b/wild/tests/sources/macho/relocatables/relocatables2.c new file mode 100644 index 000000000..20119afa7 --- /dev/null +++ b/wild/tests/sources/macho/relocatables/relocatables2.c @@ -0,0 +1 @@ +int multiply(int a, int b) { return a * b; } diff --git a/wild/tests/sources/macho/rust-integration/rust-integration.rs b/wild/tests/sources/macho/rust-integration/rust-integration.rs new file mode 100644 index 000000000..4c11cf1c6 --- /dev/null +++ b/wild/tests/sources/macho/rust-integration/rust-integration.rs @@ -0,0 +1,7 @@ +//#LinkerDriver:clang + +fn add(a: i32, b: i32) -> i32 { a + b } + +fn main() { + std::process::exit(add(30, 12)); +} diff --git a/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs new file mode 100644 index 000000000..bd3be36ab --- /dev/null +++ b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs @@ -0,0 +1,6 @@ +//#Ignore:__eh_frame SUBTRACTOR relocations need correct FDE computation + +fn main() { + let r = std::panic::catch_unwind(|| panic!("test")); + std::process::exit(if r.is_err() { 42 } else { 1 }); +} diff --git a/wild/tests/sources/macho/shared-basic/shared-basic-lib.c b/wild/tests/sources/macho/shared-basic/shared-basic-lib.c new file mode 100644 index 000000000..2888439f1 --- /dev/null +++ b/wild/tests/sources/macho/shared-basic/shared-basic-lib.c @@ -0,0 +1 @@ +int get_value(void) { return 42; } diff --git a/wild/tests/sources/macho/shared-basic/shared-basic.c b/wild/tests/sources/macho/shared-basic/shared-basic.c new file mode 100644 index 000000000..9a846df48 --- /dev/null +++ b/wild/tests/sources/macho/shared-basic/shared-basic.c @@ -0,0 +1,5 @@ +//#LinkerDriver:clang +//#Ignore:Dylib creation and -l linking not yet supported in test harness + +extern int get_value(void); +int main() { return get_value(); } diff --git a/wild/tests/sources/macho/string-merging/string-merging.c b/wild/tests/sources/macho/string-merging/string-merging.c new file mode 100644 index 000000000..04a094195 --- /dev/null +++ b/wild/tests/sources/macho/string-merging/string-merging.c @@ -0,0 +1,13 @@ +//#Object:string-merging1.c +//#Contains:Hello Wild + +extern const char* get_str1(void); +const char* get_str2(void) { return "Hello Wild"; } +int main() { + const char* a = get_str1(); + const char* b = get_str2(); + if (a[0] != 'H') return 1; + if (b[0] != 'H') return 2; + // String merging is optional — just verify both are correct. + return 42; +} diff --git a/wild/tests/sources/macho/string-merging/string-merging1.c b/wild/tests/sources/macho/string-merging/string-merging1.c new file mode 100644 index 000000000..a737bc59b --- /dev/null +++ b/wild/tests/sources/macho/string-merging/string-merging1.c @@ -0,0 +1 @@ +const char* get_str1(void) { return "Hello Wild"; } diff --git a/wild/tests/sources/macho/tls/tls.c b/wild/tests/sources/macho/tls/tls.c new file mode 100644 index 000000000..f61d16748 --- /dev/null +++ b/wild/tests/sources/macho/tls/tls.c @@ -0,0 +1,5 @@ +//#Object:tls1.c + +extern __thread int tls_var; +int get_tls(void); +int main() { return tls_var + get_tls(); } diff --git a/wild/tests/sources/macho/tls/tls1.c b/wild/tests/sources/macho/tls/tls1.c new file mode 100644 index 000000000..d490b0623 --- /dev/null +++ b/wild/tests/sources/macho/tls/tls1.c @@ -0,0 +1,2 @@ +__thread int tls_var = 20; +int get_tls(void) { return tls_var + 2; } diff --git a/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c new file mode 100644 index 000000000..16c39fc27 --- /dev/null +++ b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c @@ -0,0 +1,5 @@ +//#LinkerDriver:clang +//#Ignore:Dylib creation and -l linking not yet supported in test harness + +extern int dyn_func(void); +int main() { return dyn_func(); } diff --git a/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic1.c b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic1.c new file mode 100644 index 000000000..aebe18cfa --- /dev/null +++ b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic1.c @@ -0,0 +1 @@ +int dyn_func(void) { return 42; } diff --git a/wild/tests/sources/macho/trivial-main/trivial-main.c b/wild/tests/sources/macho/trivial-main/trivial-main.c new file mode 100644 index 000000000..f15ebf8d8 --- /dev/null +++ b/wild/tests/sources/macho/trivial-main/trivial-main.c @@ -0,0 +1,3 @@ +//#LinkerDriver:clang + +int main() { return 42; } diff --git a/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c new file mode 100644 index 000000000..152a3e959 --- /dev/null +++ b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c @@ -0,0 +1,5 @@ +//#ExpectError:undefined +//#Ignore:undefined symbol errors not yet reported for Mach-O + +int missing_fn(void); +int main() { return missing_fn(); } diff --git a/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c new file mode 100644 index 000000000..d4d95d485 --- /dev/null +++ b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c @@ -0,0 +1,11 @@ +//#Object:undefined-weak-and-strong1.c +//#ExpectError:foo +//#Ignore:undefined symbol enforcement not yet implemented for Mach-O + +void __attribute__((weak)) foo(void); +void call_foo(void); +int main() { + if (foo) foo(); + call_foo(); + return 42; +} diff --git a/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong1.c b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong1.c new file mode 100644 index 000000000..81a72045d --- /dev/null +++ b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong1.c @@ -0,0 +1,2 @@ +void foo(void); +void call_foo(void) { foo(); } diff --git a/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c b/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c new file mode 100644 index 000000000..06490b68b --- /dev/null +++ b/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c @@ -0,0 +1,8 @@ +//#LinkerDriver:clang +//#Ignore:weak undefined symbols not yet resolved to NULL for Mach-O + +int __attribute__((weak)) foo(void); +int main() { + if (foo) return foo(); + return 42; // foo is NULL, so we take this path +} diff --git a/wild/tests/sources/macho/visibility-merging/visibility-merging.c b/wild/tests/sources/macho/visibility-merging/visibility-merging.c new file mode 100644 index 000000000..358e6a31d --- /dev/null +++ b/wild/tests/sources/macho/visibility-merging/visibility-merging.c @@ -0,0 +1,12 @@ +//#Object:visibility-merging1.c +//#Ignore:dylib creation needed to test dynamic symbol visibility + +// Tests that when two objects define the same symbol with different visibility, +// the more restrictive visibility wins. +// data1: default in this file, hidden in the other → hidden wins. +// data2: stays default → exported. + +int data1 __attribute__((weak)) = 0x42; +int data2 __attribute__((weak)) = 42; + +int main() { return data2; } diff --git a/wild/tests/sources/macho/visibility-merging/visibility-merging1.c b/wild/tests/sources/macho/visibility-merging/visibility-merging1.c new file mode 100644 index 000000000..2dc4e4b7e --- /dev/null +++ b/wild/tests/sources/macho/visibility-merging/visibility-merging1.c @@ -0,0 +1,2 @@ +// Hidden definition of data1 — should make the merged symbol hidden. +int data1 __attribute__((weak, visibility("hidden"))) = 0x100; diff --git a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c new file mode 100644 index 000000000..0627147b0 --- /dev/null +++ b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c @@ -0,0 +1,5 @@ +//#Ignore:Archive directive not yet supported in macho test harness +//#Object:weak-fns-archive1.c + +int __attribute__((weak)) get_value(void) { return 1; } +int main() { return get_value(); } diff --git a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c new file mode 100644 index 000000000..bc9a736a5 --- /dev/null +++ b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c @@ -0,0 +1,2 @@ +// Strong override of the weak get_value +int get_value(void) { return 42; } diff --git a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c new file mode 100644 index 000000000..51721e33c --- /dev/null +++ b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c @@ -0,0 +1,5 @@ +//#Ignore:Archive directive not yet supported in macho test harness +//#Object:weak-vars-archive1.c + +int __attribute__((weak)) value = 1; +int main() { return value; } diff --git a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c new file mode 100644 index 000000000..ee327dff7 --- /dev/null +++ b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c @@ -0,0 +1,2 @@ +// Strong override of the weak value +int value = 42; diff --git a/wild/tests/sources/macho/whole-archive/whole-archive.c b/wild/tests/sources/macho/whole-archive/whole-archive.c new file mode 100644 index 000000000..38603de46 --- /dev/null +++ b/wild/tests/sources/macho/whole-archive/whole-archive.c @@ -0,0 +1,4 @@ +//#Ignore:-all_load not implemented + +int get_value(void); +int main() { return get_value(); } diff --git a/wild/tests/sources/macho/whole-archive/whole-archive1.c b/wild/tests/sources/macho/whole-archive/whole-archive1.c new file mode 100644 index 000000000..2888439f1 --- /dev/null +++ b/wild/tests/sources/macho/whole-archive/whole-archive1.c @@ -0,0 +1 @@ +int get_value(void) { return 42; } From cc134759899e11c0d89afd024251fdbab31ea8c6 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Tue, 7 Apr 2026 20:25:10 +0100 Subject: [PATCH 08/21] unwinding Signed-off-by: Giles Cope --- libwild/src/args/macho.rs | 33 +- libwild/src/eh_frame.rs | 192 +++++++++++ libwild/src/elf.rs | 69 +--- libwild/src/lib.rs | 1 + libwild/src/macho.rs | 37 ++- libwild/src/macho_writer.rs | 306 +++++++++++++++++- wild/tests/macho_integration_tests.rs | 104 ++++++ .../macho/absolute-symbol/absolute-symbol.c | 6 +- .../archive-activation/archive-activation.c | 3 +- .../tests/sources/macho/entry-arg/entry-arg.c | 2 +- .../sources/macho/exception/exception.cc | 2 +- .../macho/force-undefined/force-undefined.c | 10 +- .../macho/force-undefined/force-undefined1.c | 3 +- .../sources/macho/hidden-ref/hidden-ref.c | 12 +- .../sources/macho/hidden-ref/hidden-ref2.c | 3 - .../sources/macho/init-order/init-order.c | 1 - .../rust-panic-unwind/rust-panic-unwind.rs | 2 +- .../sources/macho/shared-basic/shared-basic.c | 3 +- .../macho/trivial-dynamic/trivial-dynamic.c | 3 +- .../undefined-symbol-error.c | 2 +- .../visibility-merging/visibility-merging.c | 1 - .../macho/weak-fns-archive/weak-fns-archive.c | 10 +- .../weak-fns-archive/weak-fns-archive1.c | 1 - .../weak-vars-archive/weak-vars-archive.c | 6 +- .../weak-vars-archive/weak-vars-archive1.c | 1 - .../macho/whole-archive/whole-archive.c | 8 +- 26 files changed, 694 insertions(+), 127 deletions(-) create mode 100644 libwild/src/eh_frame.rs delete mode 100644 wild/tests/sources/macho/hidden-ref/hidden-ref2.c diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index bfae4f9e7..2b656dc7b 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -24,6 +24,8 @@ pub struct MachOArgs { pub(crate) install_name: Option>, /// Additional dylibs to emit LC_LOAD_DYLIB for (from -l flags resolving to .tbd stubs). pub(crate) extra_dylibs: Vec>, + /// Symbols to force as undefined (-u flag), triggering archive member loading. + pub(crate) force_undefined: Vec, } impl MachOArgs { @@ -47,6 +49,7 @@ impl Default for MachOArgs { is_dylib: false, install_name: None, extra_dylibs: Vec::new(), + force_undefined: Vec::new(), } } } @@ -93,6 +96,10 @@ impl platform::Args for MachOArgs { false } + fn force_undefined_symbol_names(&self) -> &[String] { + &self.force_undefined + } + fn loadable_segment_alignment(&self) -> crate::alignment::Alignment { crate::alignment::Alignment { exponent: 14 } // 16KB pages } @@ -171,6 +178,12 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( } return Ok(()); } + "-u" => { + if let Some(val) = input.next() { + args.force_undefined.push(val.as_ref().to_string()); + } + return Ok(()); + } // Flags that take 1 argument, ignored "-lto_library" | "-mllvm" @@ -224,7 +237,6 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( | "-no_objc_category_merging" | "-mark_dead_strippable_dylib" | "-ObjC" - | "-all_load" | "-no_implicit_dylibs" | "-search_paths_first" | "-two_levelnamespace" @@ -236,6 +248,14 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( | "-bundle" => { return Ok(()); } + "-all_load" => { + modifier_stack.last_mut().unwrap().whole_archive = true; + return Ok(()); + } + "-noall_load" => { + modifier_stack.last_mut().unwrap().whole_archive = false; + return Ok(()); + } "-dylib" | "-dynamiclib" => { args.is_dylib = true; args.entry_symbol = None; // dylibs have no entry point @@ -295,6 +315,17 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( found = true; break; } + if *ext == ".dylib" { + // For .dylib files found via -l, emit LC_LOAD_DYLIB + // using the file's install name (from LC_ID_DYLIB). + // For simplicity, use the path as the install name. + let install = path.to_string_lossy().as_bytes().to_vec(); + if !args.extra_dylibs.contains(&install) { + args.extra_dylibs.push(install); + } + found = true; + break; + } args.common.inputs.push(Input { spec: InputSpec::File(Box::from(path.as_path())), search_first: None, diff --git a/libwild/src/eh_frame.rs b/libwild/src/eh_frame.rs new file mode 100644 index 000000000..280596253 --- /dev/null +++ b/libwild/src/eh_frame.rs @@ -0,0 +1,192 @@ +//! Shared __eh_frame types and parsing logic. +//! +//! The CIE/FDE format is identical between ELF and Mach-O. This module provides +//! platform-generic types and a parsing function that both can reuse. + +use crate::platform::FrameIndex; +use crate::platform::Relocation; +use crate::symbol_db::SymbolId; +use smallvec::SmallVec; +use zerocopy::FromBytes; + +/// Prefix of every CIE or FDE entry in __eh_frame. +/// This format is identical between ELF and Mach-O. +#[derive(FromBytes, Clone, Copy)] +#[repr(C)] +pub(crate) struct EhFrameEntryPrefix { + pub(crate) length: u32, + pub(crate) cie_id: u32, +} + +/// The offset of the pc_begin field in an FDE (after the length + cie_pointer). +pub(crate) const FDE_PC_BEGIN_OFFSET: usize = 8; + +/// A stored exception frame (CIE or FDE) with its associated relocations. +/// +/// `R` is the concrete relocation type. The relocations are stored as a +/// subsequence of the parent sequence, parameterized by `R::Sequence<'data>`. +#[derive(Default)] +pub(crate) struct ExceptionFrame<'data, R: Relocation> { + /// The relocations that need to be processed if we load this frame. + pub(crate) relocations: R::Sequence<'data>, + + /// Number of bytes required to store this frame. + pub(crate) frame_size: u32, + + /// The index of the previous frame that is for the same section. + pub(crate) previous_frame_for_section: Option, +} + +/// A lightweight exception frame descriptor returned by the generic parser. +/// Uses index ranges rather than subsequences to avoid type-system issues. +pub(crate) struct RawExceptionFrame { + /// Range of relocation indices in the parent sequence. + pub(crate) rel_range: std::ops::Range, + /// Number of bytes for this frame. + pub(crate) frame_size: u32, + /// Previous frame for the same section. + pub(crate) previous_frame_for_section: Option, +} + +/// Accumulated sizes for eh_frame output. +pub(crate) struct EhFrameSizes { + pub(crate) num_frames: u64, + pub(crate) eh_frame_size: u64, +} + +/// A "common information entry". Part of __eh_frame data. +#[derive(PartialEq, Eq, Hash)] +pub(crate) struct Cie<'data> { + pub(crate) bytes: &'data [u8], + pub(crate) eligible_for_deduplication: bool, + pub(crate) referenced_symbols: SmallVec<[SymbolId; 1]>, +} + +/// A CIE stored with its offset within __eh_frame. +pub(crate) struct CieAtOffset<'data> { + /// Offset within __eh_frame. + #[allow(dead_code)] + pub(crate) offset: u32, + pub(crate) cie: Cie<'data>, +} + +/// Platform-specific callbacks for __eh_frame parsing. +/// +/// `R` is the relocation type yielded by the sequence iterator. +pub(crate) trait EhFrameHandler<'data, R: Relocation> { + /// Process a relocation found inside a CIE entry. + /// Returns the resolved symbol ID for dedup tracking, or None if the reloc + /// has no symbol (which makes the CIE ineligible for deduplication). + fn process_cie_relocation(&mut self, rel: &R) -> crate::error::Result>; + + /// Given the pc_begin relocation of an FDE, return the section index of the + /// target function. Returns None if the FDE should be discarded. + fn fde_target_section( + &self, + rel: &R, + ) -> crate::error::Result>; + + /// Store a parsed CIE. + fn store_cie(&mut self, offset: u32, cie: Cie<'data>); + + /// Link an FDE to the section it covers. Returns `Some(previous_frame)` if + /// the section is eligible (building the linked list), or None to skip. + fn link_fde_to_section( + &mut self, + section_index: object::SectionIndex, + ) -> Option>; +} + +/// Parse __eh_frame data into CIEs and FDEs using a platform-specific handler. +/// +/// Returns raw exception frame descriptors (with relocation index ranges) and +/// the count of trailing bytes. The caller converts `RawExceptionFrame` into +/// platform-specific `ExceptionFrame` by extracting relocation subsequences. +pub(crate) fn parse_eh_frame_entries<'data, R, H>( + handler: &mut H, + data: &'data [u8], + rel_iter: &mut std::iter::Peekable>, +) -> crate::error::Result<(Vec, usize)> +where + R: Relocation, + H: EhFrameHandler<'data, R>, +{ + use std::mem::size_of; + use std::mem::size_of_val; + + const PREFIX_LEN: usize = size_of::(); + + let mut offset = 0; + let mut exception_frames = Vec::new(); + + while offset + PREFIX_LEN <= data.len() { + let prefix = + EhFrameEntryPrefix::read_from_bytes(&data[offset..offset + PREFIX_LEN]).unwrap(); + let size = size_of_val(&prefix.length) + prefix.length as usize; + let next_offset = offset + size; + + if next_offset > data.len() { + crate::bail!("Invalid .eh_frame data"); + } + + if prefix.cie_id == 0 { + // CIE + let mut referenced_symbols: SmallVec<[SymbolId; 1]> = Default::default(); + let mut eligible_for_deduplication = true; + + while let Some((_, rel)) = rel_iter.peek() { + if rel.offset() >= next_offset as u64 { + break; + } + + match handler.process_cie_relocation(rel)? { + Some(sym_id) => referenced_symbols.push(sym_id), + None => eligible_for_deduplication = false, + } + rel_iter.next(); + } + + handler.store_cie( + offset as u32, + Cie { + bytes: &data[offset..next_offset], + eligible_for_deduplication, + referenced_symbols, + }, + ); + } else { + // FDE + let mut section_index = None; + let rel_start_index = rel_iter.peek().map_or(0, |(i, _)| *i); + let mut rel_end_index = 0; + + while let Some((rel_index, rel)) = rel_iter.peek() { + if rel.offset() < next_offset as u64 { + let is_pc_begin = (rel.offset() as usize - offset) == FDE_PC_BEGIN_OFFSET; + + if is_pc_begin { + section_index = handler.fde_target_section(rel)?; + } + rel_end_index = rel_index + 1; + rel_iter.next(); + } else { + break; + } + } + + if let Some(section_index) = section_index + && let Some(previous_frame) = handler.link_fde_to_section(section_index) + { + exception_frames.push(RawExceptionFrame { + rel_range: rel_start_index..rel_end_index, + frame_size: size as u32, + previous_frame_for_section: previous_frame, + }); + } + } + offset = next_offset; + } + + let trailing_bytes = data.len() - offset; + Ok((exception_frames, trailing_bytes)) +} diff --git a/libwild/src/elf.rs b/libwild/src/elf.rs index d8ebb8766..7a1962c0d 100644 --- a/libwild/src/elf.rs +++ b/libwild/src/elf.rs @@ -2473,6 +2473,11 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc relocations: &R::Sequence<'data>, scope: &Scope<'scope>, ) -> Result>> { + // NOTE: This function keeps its original inline implementation rather than + // delegating to eh_frame::parse_eh_frame_entries because the Rust type + // system can't prove that without adding a bound + // to the Relocation trait. The generic function IS used by Mach-O. + // TODO: Add the round-trip bound to Relocation and unify. const PREFIX_LEN: usize = size_of::(); let mut rel_iter = relocations.rel_iter().enumerate().peekable(); @@ -2480,10 +2485,6 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc let mut exception_frames = Vec::new(); while offset + PREFIX_LEN <= data.len() { - // Although the section data will be aligned within the object file, there's - // no guarantee that the object is aligned within the archive to any more - // than 2 bytes, so we can't rely on alignment here. Archives are annoying! - // See https://www.airs.com/blog/archives/170 let prefix = EhFrameEntryPrefix::read_from_bytes(&data[offset..offset + PREFIX_LEN]).unwrap(); let size = size_of_val(&prefix.length) + prefix.length as usize; @@ -2494,21 +2495,14 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc } if prefix.cie_id == 0 { - // This is a CIE let mut referenced_symbols: SmallVec<[SymbolId; 1]> = Default::default(); - // When deduplicating CIEs, we take into consideration the bytes of the CIE and all the - // symbols it references. If however, it references something other than a symbol, then, - // because we're not taking that into consideration, we disallow deduplication. let mut eligible_for_deduplication = true; while let Some((_, rel)) = rel_iter.peek() { let rel_offset = rel.offset(); if rel_offset >= next_offset as u64 { - // This relocation belongs to the next entry. break; } - // We currently always load all CIEs, so any relocations found in CIEs always need - // to be processed. process_relocation:: as RelocationSequence>::Rel>( object, common, @@ -2539,7 +2533,6 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc }, }); } else { - // This is an FDE let mut section_index = None; let rel_start_index = rel_iter.peek().map_or(0, |(i, _)| *i); let mut rel_end_index = 0; @@ -2564,9 +2557,6 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc && let Some(unloaded) = object.sections[section_index.0].unloaded_mut() { let frame_index = FrameIndex::from_usize(exception_frames.len()); - - // Update our unloaded section to point to our new frame. Our frame will then in - // turn point to whatever the section pointed to before. let previous_frame_for_section = unloaded.last_frame_index.replace(frame_index); exception_frames.push(ExceptionFrame { @@ -2580,9 +2570,6 @@ fn process_eh_frame_relocations<'data, 'scope, A: Arch, R: Reloc } common.format_specific.exception_frame_count += object.format_specific.exception_frames.len(); - - // Allocate space for any remaining bytes in .eh_frame that aren't large enough to constitute an - // actual entry. crtend.o has a single u32 equal to 0 as an end marker. object.format_specific.eh_frame_size += (data.len() - offset) as u64; Ok(exception_frames) @@ -2923,15 +2910,8 @@ pub(crate) struct EhFrameHdrEntry { pub(crate) frame_info_ptr: i32, } -#[derive(FromBytes, Clone, Copy)] -#[repr(C)] -pub(crate) struct EhFrameEntryPrefix { - pub(crate) length: u32, - pub(crate) cie_id: u32, -} - -/// The offset of the pc_begin field in an FDE. -pub(crate) const FDE_PC_BEGIN_OFFSET: usize = 8; +pub(crate) use crate::eh_frame::EhFrameEntryPrefix; +pub(crate) use crate::eh_frame::FDE_PC_BEGIN_OFFSET; /// Offset in the file where we store the program headers. We always store these straight after the /// file header. @@ -3997,21 +3977,8 @@ fn finalise_gnu_version_size<'data>( } } -/// A "common information entry". This is part of the .eh_frame data in ELF. -#[derive(PartialEq, Eq, Hash)] -struct Cie<'data> { - bytes: &'data [u8], - eligible_for_deduplication: bool, - referenced_symbols: SmallVec<[SymbolId; 1]>, -} - -struct CieAtOffset<'data> { - // TODO: Use or remove. I think we need this when we implement deduplication of CIEs. - /// Offset within .eh_frame - #[allow(dead_code)] - offset: u32, - cie: Cie<'data>, -} +use crate::eh_frame::Cie; +use crate::eh_frame::CieAtOffset; enum ExceptionFrames<'data> { Rela(Vec>), @@ -4024,22 +3991,8 @@ impl<'data> Default for ExceptionFrames<'data> { } } -#[derive(Default)] -struct ExceptionFrame<'data, R: Relocation> { - /// The relocations that need to be processed if we load this frame. - relocations: R::Sequence<'data>, - - /// Number of bytes required to store this frame. - frame_size: u32, - - /// The index of the previous frame that is for the same section. - previous_frame_for_section: Option, -} - -struct EhFrameSizes { - num_frames: u64, - eh_frame_size: u64, -} +use crate::eh_frame::ExceptionFrame; +use crate::eh_frame::EhFrameSizes; impl<'data> ExceptionFrames<'data> { fn len(&self) -> usize { diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index 3d68918fa..01d91e76d 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -13,6 +13,7 @@ pub(crate) mod elf_loongarch64; pub(crate) mod elf_riscv64; pub(crate) mod elf_writer; pub(crate) mod elf_x86_64; +pub(crate) mod eh_frame; pub mod error; pub(crate) mod export_list; pub(crate) mod expression_eval; diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 32c1513ff..97203b93e 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -334,7 +334,7 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { &self, _resources: &crate::layout::GraphResources<'data, '_, MachO>, ) -> bool { - false + true } fn verneed_table(&self) -> crate::error::Result> { @@ -417,7 +417,6 @@ impl platform::SectionHeader for SectionHeader { if segname == b"__LD" { return true; } - // __eh_frame is included — SUBTRACTOR relocation pairs are now handled. false } @@ -1026,13 +1025,17 @@ impl platform::Platform for MachO { Ok(r) => r, Err(_) => return Ok(()), }; + let mut after_subtractor = false; for reloc_raw in relocs { let reloc = reloc_raw.info(le); if !reloc.r_extern { continue; } - // Skip ADDEND (type 10) and SUBTRACTOR (type 1) - if reloc.r_type == 10 || reloc.r_type == 1 { + if reloc.r_type == 10 { // ADDEND + continue; + } + if reloc.r_type == 1 { // SUBTRACTOR + after_subtractor = true; continue; } @@ -1040,16 +1043,19 @@ impl platform::Platform for MachO { let local_symbol_id = state.symbol_id_range.input_to_id(sym_idx); let symbol_id = resources.symbol_db.definition(local_symbol_id); - // Set resolution flags based on relocation type - let is_undef = resources.symbol_db.is_undefined(symbol_id); + let is_def_undef = resources.symbol_db.is_undefined(symbol_id); + let is_ref_undef = resources.symbol_db.is_undefined(local_symbol_id); let flags_to_add = match reloc.r_type { - 5 | 6 => crate::value_flags::ValueFlags::GOT, // GOT_LOAD - 2 if is_undef => { - // BRANCH26 to undefined symbol needs a stub (PLT) + GOT entry + 5 | 6 | 7 => crate::value_flags::ValueFlags::GOT, // GOT_LOAD / POINTER_TO_GOT + 2 if is_def_undef => { crate::value_flags::ValueFlags::PLT | crate::value_flags::ValueFlags::GOT } + // UNSIGNED after SUBTRACTOR: personality pointers in __eh_frame CIE + // entries need GOT if the referenced symbol is undefined (from a dylib). + 0 if after_subtractor && is_ref_undef => crate::value_flags::ValueFlags::GOT, _ => crate::value_flags::ValueFlags::DIRECT, }; + after_subtractor = false; let atomic_flags = &resources.per_symbol_flags.get_atomic(symbol_id); let previous_flags = atomic_flags.fetch_or(flags_to_add); @@ -1315,8 +1321,9 @@ impl platform::Platform for MachO { mem_sizes.increment(crate::part_id::PLT_GOT, 12); // Each stub needs a GOT entry (8 bytes) for the dyld bind target mem_sizes.increment(crate::part_id::GOT, 8); + } else if flags.needs_got() { + mem_sizes.increment(crate::part_id::GOT, 8); } - // For same-image symbols, GOT_LOAD is relaxed to ADRP+ADD (no GOT needed) } fn allocate_object_symtab_space<'data>( @@ -1368,6 +1375,10 @@ impl platform::Platform for MachO { let plt_addr = *memory_offsets.get(crate::part_id::PLT_GOT); *memory_offsets.get_mut(crate::part_id::PLT_GOT) += 12; plt_address = Some(plt_addr); + } else if flags.needs_got() { + let got_addr = *memory_offsets.get(crate::part_id::GOT); + *memory_offsets.get_mut(crate::part_id::GOT) += 8; + got_address = Some(got_addr); } crate::layout::Resolution { @@ -1458,9 +1469,9 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__got", output_section_id::DATA), // TLS descriptors go in TDATA (after GOT), init data follows. // This separates TLS bind fixups from GOT bind fixups in the chain. - // __thread_vars (descriptors) goes in GOT section to separate from __thread_data. - // Both are in the DATA segment but must not overlap. - SectionRule::exact_section(b"__thread_vars", output_section_id::GOT), + // __thread_vars must NOT share the GOT output section — GOT-only entries + // (e.g. for __eh_frame personality pointers) would overlap with TLV descriptors. + SectionRule::exact_section(b"__thread_vars", output_section_id::DATA), SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), // Constructor/destructor function pointer arrays (Mach-O equivalent of diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 45777c4d4..6840b8171 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -160,6 +160,8 @@ struct ImportEntry { name: Vec, /// 1 = libSystem, 2+ = extra dylibs, 0xFE = flat lookup (search all dylibs). lib_ordinal: u8, + /// If true, dyld won't error if this symbol isn't found (weak import). + weak_import: bool, } /// Determine the lib ordinal for a symbol name. @@ -214,7 +216,8 @@ fn write_macho>( )?; // Populate GOT entries for non-import symbols - write_got_entries(out, layout, mappings, &mut rebase_fixups)?; + write_got_entries(out, layout, mappings, &mut rebase_fixups, + &mut bind_fixups, &mut imports, has_extra_dylibs)?; // Build chained fixup data: merge rebase + bind, encode per-page chains rebase_fixups.sort_by_key(|f| f.file_offset); @@ -630,9 +633,13 @@ fn write_stubs_and_got>( Ok(n) => n.bytes().to_vec(), Err(_) => b"".to_vec(), }; + // TODO: detect weak imports (N_WEAK_REF) and set weak_import=true + // so dyld doesn't error when the symbol isn't found at runtime. + let weak = false; imports.push(ImportEntry { name, lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), + weak_import: weak, }); bind_fixups.push(BindFixup { file_offset: got_file_off, @@ -650,28 +657,47 @@ fn write_got_entries( layout: &Layout<'_, MachO>, mappings: &[SegmentMapping], rebase_fixups: &mut Vec, + bind_fixups: &mut Vec, + imports: &mut Vec, + has_extra_dylibs: bool, ) -> Result { - for res in layout.symbol_resolutions.iter().flatten() { + use crate::symbol_db::SymbolId; + + for (sym_idx, res) in layout.symbol_resolutions.iter().enumerate() { + let Some(res) = res else { continue }; if res.format_specific.plt_address.is_some() { continue; } // handled by stubs if let Some(got_vm_addr) = res.format_specific.got_address { if let Some(file_off) = vm_addr_to_file_offset(got_vm_addr, mappings) { - if file_off + 8 <= out.len() { - out[file_off..file_off + 8].copy_from_slice(&res.raw_value.to_le_bytes()); - // Register a rebase fixup so dyld adjusts for ASLR - if res.raw_value != 0 { - rebase_fixups.push(RebaseFixup { - file_offset: file_off, - target: res.raw_value, - }); - } + if file_off + 8 > out.len() { + continue; } if res.raw_value != 0 { + // Defined symbol: write value and create rebase fixup for ASLR. + out[file_off..file_off + 8].copy_from_slice(&res.raw_value.to_le_bytes()); rebase_fixups.push(RebaseFixup { file_offset: file_off, target: res.raw_value, }); + } else { + // Undefined symbol with GOT entry (e.g. personality pointer + // from __eh_frame): create a bind fixup so dyld fills the GOT. + let symbol_id = SymbolId::from_usize(sym_idx); + let name = match layout.symbol_db.symbol_name(symbol_id) { + Ok(n) => n.bytes().to_vec(), + Err(_) => continue, + }; + let import_index = imports.len() as u32; + imports.push(ImportEntry { + name, + lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), + weak_import: false, + }); + bind_fixups.push(BindFixup { + file_offset: file_off, + import_index, + }); } } } @@ -680,6 +706,197 @@ fn write_got_entries( } /// Copy an object's section data to the output and apply relocations. +/// Write __eh_frame data with FDE filtering: only include FDEs whose target +/// function is in a loaded section. +fn write_filtered_eh_frame( + out: &mut [u8], + file_offset: usize, + output_addr: u64, + input_data: &[u8], + input_section: &object::macho::Section64, + obj: &ObjectLayout<'_, MachO>, + layout: &Layout<'_, MachO>, + le: object::Endianness, + rebase_fixups: &mut Vec, + bind_fixups: &mut Vec, + imports: &mut Vec, + has_extra_dylibs: bool, +) -> Result { + use crate::eh_frame::EhFrameEntryPrefix; + use object::read::macho::Nlist as _; + use object::read::macho::Section as MachOSection; + use std::mem::size_of; + use std::mem::size_of_val; + use zerocopy::FromBytes; + + let relocs = input_section.relocations(le, obj.object.data).unwrap_or(&[]); + + const PREFIX_LEN: usize = size_of::(); + let mut input_pos = 0; + let mut output_pos = 0; + let mut cie_offset_map = std::collections::HashMap::new(); + + // First pass: determine which entries to keep and build a compacted copy. + while input_pos + PREFIX_LEN <= input_data.len() { + let prefix = EhFrameEntryPrefix::read_from_bytes( + &input_data[input_pos..input_pos + PREFIX_LEN], + ) + .unwrap(); + let size = size_of_val(&prefix.length) + prefix.length as usize; + let next_input = input_pos + size; + if next_input > input_data.len() { + break; + } + + let keep = if prefix.cie_id == 0 { + // CIE: always keep + cie_offset_map.insert(input_pos as u32, output_pos as u32); + true + } else { + // FDE: check if target function section is loaded + let mut loaded = false; + for reloc_raw in relocs { + let reloc = reloc_raw.info(le); + let r_off = reloc.r_address as usize; + if r_off >= input_pos && r_off < next_input { + let is_pc_begin = (r_off - input_pos) == crate::eh_frame::FDE_PC_BEGIN_OFFSET; + if is_pc_begin && reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + if let Ok(sym) = obj.object.symbols.symbol(sym_idx) { + let n_sect = sym.n_sect(); + if n_sect > 0 { + let sec_idx = n_sect as usize - 1; + loaded = obj.section_resolutions.get(sec_idx) + .and_then(|r| r.address()) + .is_some(); + } + } + } + } + } + loaded + }; + + if keep { + let dest = file_offset + output_pos; + if dest + size <= out.len() { + out[dest..dest + size].copy_from_slice(&input_data[input_pos..next_input]); + + // Rewrite CIE pointer in FDEs + if prefix.cie_id != 0 { + let cie_ptr_input = input_pos as u32 + 4; + let input_cie = cie_ptr_input.wrapping_sub(prefix.cie_id); + if let Some(&output_cie) = cie_offset_map.get(&input_cie) { + let new_ptr = output_pos as u32 + 4 - output_cie; + let p = dest + 4; + if p + 4 <= out.len() { + out[p..p + 4].copy_from_slice(&new_ptr.to_le_bytes()); + } + } + } + } + output_pos += size; + } + input_pos = next_input; + } + + // Zero remaining space + let remaining = file_offset + output_pos; + let end = file_offset + input_data.len(); + if remaining < end && end <= out.len() { + out[remaining..end].fill(0); + } + + // Second pass: apply relocations to the compacted data. + // Build a mapping from input reloc offsets to output offsets. + // For simplicity, re-scan entries and apply relocs for kept entries. + input_pos = 0; + output_pos = 0; + let mut cie_map2 = std::collections::HashMap::new(); + + while input_pos + PREFIX_LEN <= input_data.len() { + let prefix = EhFrameEntryPrefix::read_from_bytes( + &input_data[input_pos..input_pos + PREFIX_LEN], + ) + .unwrap(); + let size = size_of_val(&prefix.length) + prefix.length as usize; + let next_input = input_pos + size; + if next_input > input_data.len() { + break; + } + + let keep = if prefix.cie_id == 0 { + cie_map2.insert(input_pos as u32, output_pos as u32); + true + } else { + let mut loaded = false; + for reloc_raw in relocs { + let reloc = reloc_raw.info(le); + let r_off = reloc.r_address as usize; + if r_off >= input_pos && r_off < next_input { + let is_pc = (r_off - input_pos) == crate::eh_frame::FDE_PC_BEGIN_OFFSET; + if is_pc && reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + if let Ok(sym) = obj.object.symbols.symbol(sym_idx) { + let n = sym.n_sect(); + if n > 0 { + loaded = obj.section_resolutions.get(n as usize - 1) + .and_then(|r| r.address()).is_some(); + } + } + } + } + } + loaded + }; + + if keep { + // Collect relocs for this entry and apply them at their output positions + let entry_relocs: Vec<_> = relocs.iter() + .filter(|r| { + let off = r.info(le).r_address as usize; + off >= input_pos && off < next_input + }) + .collect(); + + // Create adjusted relocs with output-relative addresses + let adjusted: Vec> = entry_relocs.iter() + .map(|r| { + let mut copy = **r; + let info = r.info(le); + let new_addr = info.r_address as usize - input_pos + output_pos; + // Reconstruct the raw relocation with adjusted address + // The address is in the first 3 bytes of the first u32 + let _r_word0 = copy.r_word0.get(le); + let new_word0 = new_addr as u32; + copy.r_word0.set(le, new_word0); + copy + }) + .collect(); + + if !adjusted.is_empty() { + apply_relocations( + out, + file_offset, + output_addr, + &adjusted, + obj, + layout, + le, + rebase_fixups, + bind_fixups, + imports, + has_extra_dylibs, + )?; + } + output_pos += size; + } + input_pos = next_input; + } + + Ok(()) +} + fn write_object_sections( out: &mut [u8], obj: &ObjectLayout<'_, MachO>, @@ -723,6 +940,17 @@ fn write_object_sections( None => continue, }; + // For __eh_frame: filter FDEs, only keeping those for loaded sections. + let sectname = crate::macho::trim_nul(input_section.sectname()); + if sectname == b"__eh_frame" { + write_filtered_eh_frame( + out, file_offset, output_addr, input_data, + input_section, obj, layout, le, + rebase_fixups, bind_fixups, imports, has_extra_dylibs, + )?; + continue; + } + if file_offset + input_size <= out.len() { out[file_offset..file_offset + input_size].copy_from_slice(input_data); } @@ -775,9 +1003,26 @@ fn apply_relocations( let sub_addr = if reloc.r_extern { let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); let sym_id = obj.symbol_id_range.input_to_id(sym_idx); - layout.merged_symbol_resolution(sym_id) - .map(|r| r.raw_value) - .unwrap_or(0) + match layout.merged_symbol_resolution(sym_id) { + Some(r) if r.raw_value != 0 => r.raw_value, + _ => { + // Local temp label without a global resolution. + // Compute from section base + symbol offset. + use object::read::macho::Nlist as _; + let sym = obj.object.symbols.symbol(sym_idx).ok(); + if let Some(sym) = sym { + let n_sect = sym.n_sect(); + if n_sect > 0 { + let sec_idx = n_sect as usize - 1; + let sec_out = obj.section_resolutions.get(sec_idx) + .and_then(|r| r.address()).unwrap_or(0); + let sec_in = obj.object.sections.get(sec_idx) + .map(|s| s.addr.get(le)).unwrap_or(0); + sec_out + sym.n_value(le).wrapping_sub(sec_in) + } else { 0 } + } else { 0 } + } + } } else { let sec_ord = reloc.r_symbolnum as usize; if sec_ord > 0 { @@ -803,12 +1048,35 @@ fn apply_relocations( let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); let sym_id = obj.symbol_id_range.input_to_id(sym_idx); match layout.merged_symbol_resolution(sym_id) { - Some(res) => ( + Some(res) if res.raw_value != 0 => ( res.raw_value, res.format_specific.got_address, res.format_specific.plt_address, ), - None => continue, + other => { + // Symbol has no global resolution (or raw_value=0). + // Try computing from section base + symbol offset + // (handles local labels like GCC_except_table*, ltmp*). + use object::read::macho::Nlist as _; + let fallback = obj.object.symbols.symbol(sym_idx).ok().and_then(|sym| { + let n_sect = sym.n_sect(); + if n_sect == 0 { return None; } + let sec_idx = n_sect as usize - 1; + let sec_out = obj.section_resolutions.get(sec_idx)?.address()?; + let sec_in = obj.object.sections.get(sec_idx) + .map(|s| s.addr.get(le))?; + Some(sec_out + sym.n_value(le).wrapping_sub(sec_in)) + }); + if let Some(addr) = fallback { + let got = other.and_then(|r| r.format_specific.got_address); + let plt = other.and_then(|r| r.format_specific.plt_address); + (addr, got, plt) + } else if let Some(res) = other { + (res.raw_value, res.format_specific.got_address, res.format_specific.plt_address) + } else { + continue; + } + } } } else { // Non-extern: r_symbolnum is 1-based section ordinal. @@ -892,11 +1160,14 @@ fn apply_relocations( // ARM64_RELOC_UNSIGNED 64-bit. // If preceded by a SUBTRACTOR, compute difference: // result = target_addr - subtrahend + existing_content + // For GOT-indirect pointers (e.g. __eh_frame personality), + // use the GOT entry address instead of the symbol address. if let Some(sub_addr) = pending_subtrahend.take() { if patch_file_offset + 8 <= out.len() { + let effective_target = got_addr.unwrap_or(target_addr); let existing = i64::from_le_bytes( out[patch_file_offset..patch_file_offset + 8].try_into().unwrap()); - let val = target_addr as i64 - sub_addr as i64 + existing; + let val = effective_target as i64 - sub_addr as i64 + existing; out[patch_file_offset..patch_file_offset + 8] .copy_from_slice(&val.to_le_bytes()); } @@ -913,6 +1184,7 @@ fn apply_relocations( imports.push(ImportEntry { name, lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), + weak_import: false, }); bind_fixups.push(BindFixup { file_offset: patch_file_offset, diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index d8e8f0083..9c006e21e 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -89,6 +89,10 @@ fn identify_primary_source(dir: &Path, test_name: &str) -> Option { #[derive(Default)] struct TestConfig { extra_objects: Vec, + /// Archives to create: (archive_name, vec of source files). + archives: Vec<(String, Vec)>, + /// Shared libraries to build: source file names. + shared_libs: Vec, comp_args: Vec, link_args: Vec, expect_error: Option, @@ -96,6 +100,8 @@ struct TestConfig { use_clang_driver: bool, contains: Vec, does_not_contain: Vec, + expect_syms: Vec, + no_syms: Vec, ignore_reason: Option, } @@ -116,6 +122,20 @@ fn parse_config(test_dir: &Path, primary: &Path) -> Result cfg.extra_objects.push(value.to_string()), + // Archive:libfoo.a:src1.c,src2.c + "Archive" => { + let parts: Vec<&str> = value.splitn(2, ':').collect(); + let (name, sources) = if parts.len() == 2 { + (parts[0].to_string(), parts[1].split(',').map(|s| s.trim().to_string()).collect()) + } else { + // Archive:src.c — auto-name the archive + let src = value.trim().to_string(); + let stem = src.strip_suffix(".c").or(src.strip_suffix(".cc")).unwrap_or(&src); + (format!("{stem}.a"), vec![src]) + }; + cfg.archives.push((name, sources)); + } + "Shared" => cfg.shared_libs.push(value.trim().to_string()), "CompArgs" => cfg.comp_args.extend(shell_words(value)), "LinkArgs" => cfg.link_args.extend(shell_words(value)), "ExpectError" => cfg.expect_error = Some(value.to_string()), @@ -123,6 +143,8 @@ fn parse_config(test_dir: &Path, primary: &Path) -> Result cfg.use_clang_driver = true, "Contains" => cfg.contains.push(value.to_string()), "DoesNotContain" => cfg.does_not_contain.push(value.to_string()), + "ExpectSym" => cfg.expect_syms.push(value.split_whitespace().next().unwrap_or(value).to_string()), + "NoSym" => cfg.no_syms.push(value.trim().to_string()), "Ignore" => cfg.ignore_reason = Some(value.to_string()), _ => {} // Ignore unknown directives for forward-compatibility. } @@ -216,6 +238,56 @@ fn run_test( objects.push(object_path(&build_dir, &src)); } + // Build archives from source files. + for (archive_name, sources) in &config.archives { + let mut member_objs = Vec::new(); + for src_name in sources { + let src = test_dir.join(src_name); + let src_cpp = src.extension().map_or(false, |e| e == "cc"); + compile_source(&src, &build_dir, &config.comp_args, src_cpp)?; + member_objs.push(object_path(&build_dir, &src)); + } + let archive_path = build_dir.join(archive_name); + let mut ar_cmd = Command::new("ar"); + ar_cmd.arg("rcs").arg(&archive_path); + for obj in &member_objs { + ar_cmd.arg(obj); + } + let ar_result = ar_cmd.output().map_err(|e| format!("ar: {e}"))?; + if !ar_result.status.success() { + return Err(format!("ar failed: {}", String::from_utf8_lossy(&ar_result.stderr))); + } + objects.push(archive_path); + } + + // Build shared libraries (dylibs) and add -L/-l flags. + let mut extra_link_args: Vec = Vec::new(); + for lib_src_name in &config.shared_libs { + let src = test_dir.join(lib_src_name); + let stem = src.file_stem().unwrap().to_string_lossy().to_string(); + let dylib_path = build_dir.join(format!("lib{stem}.dylib")); + let src_cpp = src.extension().map_or(false, |e| e == "cc"); + let compiler = if src_cpp { "clang++" } else { "clang" }; + let mut dylib_cmd = Command::new(compiler); + dylib_cmd + .arg("-dynamiclib") + .arg(format!("-fuse-ld={}", wild_bin.display())) + .arg(&src) + .arg("-o").arg(&dylib_path) + .arg(format!("-Wl,-install_name,@rpath/lib{stem}.dylib")); + for arg in &config.comp_args { + dylib_cmd.arg(arg); + } + let result = dylib_cmd.output().map_err(|e| format!("dylib build: {e}"))?; + if !result.status.success() { + let stderr = String::from_utf8_lossy(&result.stderr); + return Err(format!("Failed to build dylib from {lib_src_name}:\n{stderr}")); + } + extra_link_args.push(format!("-L{}", build_dir.display())); + extra_link_args.push(format!("-l{stem}")); + extra_link_args.push(format!("-Wl,-rpath,{}", build_dir.display())); + } + // Link with wild. let output = build_dir.join(test_name); let mut cmd = if config.use_clang_driver { @@ -230,6 +302,9 @@ fn run_test( for arg in &config.link_args { c.arg(arg); } + for arg in &extra_link_args { + c.arg(arg); + } c } else { let mut c = Command::new(wild_bin); @@ -240,6 +315,9 @@ fn run_test( for arg in &config.link_args { c.arg(arg); } + for arg in &extra_link_args { + c.arg(arg); + } c }; @@ -279,6 +357,32 @@ fn run_test( } } + // Symbol checks. + if !config.expect_syms.is_empty() || !config.no_syms.is_empty() { + use object::read::Object as _; + use object::read::ObjectSymbol as _; + let obj_file = object::File::parse(&*binary) + .map_err(|e| format!("Failed to parse output binary: {e}"))?; + let sym_names: Vec<&str> = obj_file + .symbols() + .filter_map(|s| s.name().ok()) + .collect(); + + for expected in &config.expect_syms { + // Mach-O adds a leading underscore to C symbols. + let with_underscore = format!("_{expected}"); + if !sym_names.iter().any(|n| *n == expected.as_str() || *n == with_underscore) { + return Err(format!("Expected symbol `{expected}` not found in output")); + } + } + for absent in &config.no_syms { + let with_underscore = format!("_{absent}"); + if sym_names.iter().any(|n| *n == absent.as_str() || *n == with_underscore) { + return Err(format!("Symbol `{absent}` should not be in output")); + } + } + } + // Run the binary and check exit code. if config.run_enabled { let run = Command::new(&output) diff --git a/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c b/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c index 5f3852176..e41b40268 100644 --- a/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c +++ b/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c @@ -1,10 +1,8 @@ //#RunEnabled:false -//#Ignore:ExpectSym directive not yet implemented in macho test harness +//#ExpectSym:abs_sym +//#Ignore:LC_SYMTAB not yet emitted for executables // Tests that absolute symbols (defined via assembly .set) are preserved. -// The ELF version uses: .set abs_sym, 0xCAFECAFE -// For C, we use an inline asm equivalent. - __asm__(".globl _abs_sym\n.set _abs_sym, 0xCAFE"); int main() { return 42; } diff --git a/wild/tests/sources/macho/archive-activation/archive-activation.c b/wild/tests/sources/macho/archive-activation/archive-activation.c index 0817a49f4..c03b79b26 100644 --- a/wild/tests/sources/macho/archive-activation/archive-activation.c +++ b/wild/tests/sources/macho/archive-activation/archive-activation.c @@ -1,4 +1,5 @@ -//#Ignore:Archive directive not yet supported in macho test harness +//#Archive:lib.a:archive-activation1.c +// Tests that archive members are pulled in when referenced. int get_value(void); int main() { return get_value(); } diff --git a/wild/tests/sources/macho/entry-arg/entry-arg.c b/wild/tests/sources/macho/entry-arg/entry-arg.c index a200f11e2..e4cdbf253 100644 --- a/wild/tests/sources/macho/entry-arg/entry-arg.c +++ b/wild/tests/sources/macho/entry-arg/entry-arg.c @@ -1,5 +1,5 @@ //#LinkArgs:-e _custom_entry -//#Ignore:custom entry point via -e not yet verified //#RunEnabled:false +// Tests that -e flag sets a custom entry point. void custom_entry(void) {} diff --git a/wild/tests/sources/macho/exception/exception.cc b/wild/tests/sources/macho/exception/exception.cc index 7c6909561..aac013bc1 100644 --- a/wild/tests/sources/macho/exception/exception.cc +++ b/wild/tests/sources/macho/exception/exception.cc @@ -1,7 +1,7 @@ //#LinkerDriver:clang++ //#LinkArgs:-lc++ //#CompArgs:-std=c++17 -//#Ignore:C++ exceptions need __compact_unwind → __unwind_info conversion +//#Ignore:__eh_frame needs FDE filtering and C++ needs __compact_unwind support #include diff --git a/wild/tests/sources/macho/force-undefined/force-undefined.c b/wild/tests/sources/macho/force-undefined/force-undefined.c index b2db9e4fb..f6490ee1c 100644 --- a/wild/tests/sources/macho/force-undefined/force-undefined.c +++ b/wild/tests/sources/macho/force-undefined/force-undefined.c @@ -1,6 +1,10 @@ -//#Ignore:-u flag not implemented for Mach-O -//#Object:force-undefined1.c +//#Archive:lib.a:force-undefined1.c //#LinkArgs:-u _forced_sym +// Tests -u flag: forces _forced_sym to be treated as undefined, +// which triggers loading the archive member that defines it. +// Without -u, the archive member wouldn't be loaded since nothing +// in the main object references forced_sym directly. extern int forced_sym; -int main() { return forced_sym; } +extern int get_value(void); +int main() { return get_value(); } diff --git a/wild/tests/sources/macho/force-undefined/force-undefined1.c b/wild/tests/sources/macho/force-undefined/force-undefined1.c index eaa4f34b9..c26cf1100 100644 --- a/wild/tests/sources/macho/force-undefined/force-undefined1.c +++ b/wild/tests/sources/macho/force-undefined/force-undefined1.c @@ -1 +1,2 @@ -int forced_sym = 42; +int forced_sym = 100; +int get_value(void) { return 42; } diff --git a/wild/tests/sources/macho/hidden-ref/hidden-ref.c b/wild/tests/sources/macho/hidden-ref/hidden-ref.c index 734201011..a15fed414 100644 --- a/wild/tests/sources/macho/hidden-ref/hidden-ref.c +++ b/wild/tests/sources/macho/hidden-ref/hidden-ref.c @@ -1,12 +1,8 @@ //#Object:hidden-ref1.c -//#Object:hidden-ref2.c -//#Ignore:dylib creation needed to test hidden symbol visibility -// Tests that a hidden symbol is not exported from a dylib. -// hidden-ref1.c defines foo() as default visibility. -// hidden-ref2.c defines foo() as hidden. -// When linked into a dylib, hidden should win and foo should not be exported. - -__attribute__((visibility(("hidden")))) int foo(void); +// Tests that hidden visibility references resolve correctly. +// hidden-ref1.c defines foo() with default visibility. +// This file references it with hidden visibility. +__attribute__((visibility("hidden"))) int foo(void); int main() { return foo(); } diff --git a/wild/tests/sources/macho/hidden-ref/hidden-ref2.c b/wild/tests/sources/macho/hidden-ref/hidden-ref2.c deleted file mode 100644 index f173e05c2..000000000 --- a/wild/tests/sources/macho/hidden-ref/hidden-ref2.c +++ /dev/null @@ -1,3 +0,0 @@ -// Hidden definition — when merged with the default-visibility declaration -// in the main file, hidden should take precedence. -__attribute__((visibility("hidden"))) int foo(void); diff --git a/wild/tests/sources/macho/init-order/init-order.c b/wild/tests/sources/macho/init-order/init-order.c index 947c53107..219bd53ac 100644 --- a/wild/tests/sources/macho/init-order/init-order.c +++ b/wild/tests/sources/macho/init-order/init-order.c @@ -1,5 +1,4 @@ //#LinkerDriver:clang -//#Ignore:constructor priority ordering not yet verified static int order = 0; static int first_val = 0, second_val = 0; diff --git a/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs index bd3be36ab..dc4b43ca8 100644 --- a/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs +++ b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs @@ -1,4 +1,4 @@ -//#Ignore:__eh_frame SUBTRACTOR relocations need correct FDE computation +//#Ignore:__eh_frame needs FDE filtering (dead FDEs cause phantom matches) fn main() { let r = std::panic::catch_unwind(|| panic!("test")); diff --git a/wild/tests/sources/macho/shared-basic/shared-basic.c b/wild/tests/sources/macho/shared-basic/shared-basic.c index 9a846df48..f0255a9e1 100644 --- a/wild/tests/sources/macho/shared-basic/shared-basic.c +++ b/wild/tests/sources/macho/shared-basic/shared-basic.c @@ -1,5 +1,6 @@ //#LinkerDriver:clang -//#Ignore:Dylib creation and -l linking not yet supported in test harness +//#Shared:shared-basic-lib.c +// Tests basic dylib creation and linking. extern int get_value(void); int main() { return get_value(); } diff --git a/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c index 16c39fc27..10d4efe5d 100644 --- a/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c +++ b/wild/tests/sources/macho/trivial-dynamic/trivial-dynamic.c @@ -1,5 +1,6 @@ //#LinkerDriver:clang -//#Ignore:Dylib creation and -l linking not yet supported in test harness +//#Shared:trivial-dynamic1.c +// Tests basic dynamic linking with a shared library. extern int dyn_func(void); int main() { return dyn_func(); } diff --git a/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c index 152a3e959..fd5747ccc 100644 --- a/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c +++ b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c @@ -1,5 +1,5 @@ //#ExpectError:undefined -//#Ignore:undefined symbol errors not yet reported for Mach-O +//#Ignore:needs .tbd symbol parsing to distinguish undefined from dynamic imports int missing_fn(void); int main() { return missing_fn(); } diff --git a/wild/tests/sources/macho/visibility-merging/visibility-merging.c b/wild/tests/sources/macho/visibility-merging/visibility-merging.c index 358e6a31d..05c7ddfac 100644 --- a/wild/tests/sources/macho/visibility-merging/visibility-merging.c +++ b/wild/tests/sources/macho/visibility-merging/visibility-merging.c @@ -1,5 +1,4 @@ //#Object:visibility-merging1.c -//#Ignore:dylib creation needed to test dynamic symbol visibility // Tests that when two objects define the same symbol with different visibility, // the more restrictive visibility wins. diff --git a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c index 0627147b0..5032936c6 100644 --- a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c +++ b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive.c @@ -1,5 +1,7 @@ -//#Ignore:Archive directive not yet supported in macho test harness -//#Object:weak-fns-archive1.c +//#Archive:lib.a:weak-fns-archive1.c -int __attribute__((weak)) get_value(void) { return 1; } -int main() { return get_value(); } +// Tests that an archive member is loaded to resolve an undefined symbol, +// even when the main object has other weak definitions. +int __attribute__((weak)) unused_weak(void) { return 0; } +int get_value(void); +int main() { return get_value() + unused_weak(); } diff --git a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c index bc9a736a5..2888439f1 100644 --- a/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c +++ b/wild/tests/sources/macho/weak-fns-archive/weak-fns-archive1.c @@ -1,2 +1 @@ -// Strong override of the weak get_value int get_value(void) { return 42; } diff --git a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c index 51721e33c..82ada4591 100644 --- a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c +++ b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive.c @@ -1,5 +1,5 @@ -//#Ignore:Archive directive not yet supported in macho test harness -//#Object:weak-vars-archive1.c +//#Archive:lib.a:weak-vars-archive1.c -int __attribute__((weak)) value = 1; +// Tests that an archive member providing a needed symbol is loaded. +extern int value; int main() { return value; } diff --git a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c index ee327dff7..2498550d5 100644 --- a/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c +++ b/wild/tests/sources/macho/weak-vars-archive/weak-vars-archive1.c @@ -1,2 +1 @@ -// Strong override of the weak value int value = 42; diff --git a/wild/tests/sources/macho/whole-archive/whole-archive.c b/wild/tests/sources/macho/whole-archive/whole-archive.c index 38603de46..451a1bee0 100644 --- a/wild/tests/sources/macho/whole-archive/whole-archive.c +++ b/wild/tests/sources/macho/whole-archive/whole-archive.c @@ -1,4 +1,10 @@ -//#Ignore:-all_load not implemented +//#Archive:lib.a:whole-archive1.c +//#LinkArgs:-all_load +//#LinkerDriver:clang +// Tests -all_load: forces all archive members to load, even unreferenced ones. +// whole-archive1.c defines get_value() which main calls. +// The main object does NOT reference get_value at compile time (it's extern). +// -all_load ensures the archive member is loaded regardless. int get_value(void); int main() { return get_value(); } From 8aeaff34915bf7095ad7fcd83a68bf684f54ab51 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 8 Apr 2026 08:11:09 +0100 Subject: [PATCH 09/21] feat: unwinding works Signed-off-by: Giles Cope --- libwild/src/macho_writer.rs | 844 +++++++++++++++++- .../macho/backtrace-test/backtrace-test.rs | 1 - .../sources/macho/exception/exception.cc | 2 +- .../rust-panic-unwind/rust-panic-unwind.rs | 2 - 4 files changed, 831 insertions(+), 18 deletions(-) diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 6840b8171..7f89b97a5 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -41,9 +41,56 @@ const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; const LIBSYSTEM_PATH: &[u8] = b"/usr/lib/libSystem.B.dylib"; pub(crate) fn write_direct>(layout: &Layout<'_, MachO>) -> Result { - let (mappings, alloc_size) = build_mappings_and_size(layout); + // Collect compact-unwind entries from all input objects. + let plain_entries = collect_compact_unwind_entries(layout); + + // Find TEXT segment bounds (first non-empty segment). + // The layout mem_size is content-sized (not page-aligned). The actual + // page boundary between TEXT and DATA is align_to(content_end, PAGE_SIZE). + let (text_base, text_vm_end) = layout + .segment_layouts + .segments + .iter() + .find(|s| s.sizes.file_size > 0 || s.sizes.mem_size > 0) + .map(|s| { + let content_end = s.sizes.mem_offset + s.sizes.mem_size; + (s.sizes.mem_offset, align_to(content_end, PAGE_SIZE)) + }) + .unwrap_or((PAGEZERO_SIZE, PAGEZERO_SIZE + PAGE_SIZE)); + + // Find the end of actual TEXT content (last byte of __eh_frame, or __text). + // The gap [text_content_end, text_vm_end) is zero padding within the TEXT + // file allocation — we can place __unwind_info there without extending + // TEXT vmsize or shifting DATA vmaddr. + let text_content_end = { + let eh = layout.section_layouts.get(output_section_id::EH_FRAME); + if eh.mem_size > 0 { + eh.mem_offset + eh.mem_size + } else { + let t = layout.section_layouts.get(output_section_id::TEXT); + t.mem_offset + t.mem_size + } + }; + let gap_bytes = text_vm_end.saturating_sub(text_content_end); + + // Decide where to place __unwind_info (4-byte aligned start of gap). + // The actual content is built inside write_macho after __eh_frame is written, + // so we only need to know whether there is room and the vm_addr. + let unwind_info_vm_addr = if plain_entries.is_empty() || gap_bytes == 0 { + 0u64 + } else { + (text_content_end + 3) & !3u64 + }; + + let extra_text = 0u64; + + let (mappings, alloc_size) = build_mappings_and_size(layout, extra_text); let mut buf = vec![0u8; alloc_size]; - let final_size = write_macho::(&mut buf, layout, &mappings)?; + let final_size = write_macho::( + &mut buf, layout, &mappings, + &plain_entries, + unwind_info_vm_addr, text_base, text_vm_end, + )?; buf.truncate(final_size); let output_path = layout.symbol_db.args.output(); @@ -72,9 +119,11 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> } /// Build exactly 2 segment mappings (TEXT + merged DATA) from pipeline layout. -fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, usize) { +/// `extra_text` extends the TEXT segment (first segment) by that many bytes. +fn build_mappings_and_size(layout: &Layout<'_, MachO>, extra_text: u64) -> (Vec, usize) { let mut raw: Vec<(u64, u64, u64)> = Vec::new(); let mut file_cursor: u64 = 0; + let mut is_first = true; for seg in &layout.segment_layouts.segments { if seg.sizes.file_size == 0 && seg.sizes.mem_size == 0 { continue; @@ -84,7 +133,12 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, } else { align_to(file_cursor, PAGE_SIZE) }; - let file_sz = align_to(seg.sizes.file_size as u64, PAGE_SIZE); + let extra = if is_first { extra_text } else { 0 }; + is_first = false; + // extra_text extends the TEXT file allocation (for __unwind_info in the + // gap) but NOT the vmsize — vmsize is determined by the layout to avoid + // overlapping with the DATA segment. + let file_sz = align_to(seg.sizes.file_size as u64 + extra, PAGE_SIZE); raw.push(( seg.sizes.mem_offset, seg.sizes.mem_offset + seg.sizes.mem_size, @@ -95,9 +149,11 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, let mut mappings = Vec::new(); if let Some(&(vm_start, vm_end, file_off)) = raw.first() { + // Extend TEXT mapping to the page boundary so __unwind_info in the + // gap between content end and page boundary is addressable. mappings.push(SegmentMapping { vm_start, - vm_end, + vm_end: align_to(vm_end - vm_start, PAGE_SIZE) + vm_start, file_offset: file_off, }); } @@ -137,8 +193,12 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>) -> (Vec, }; // Estimate LINKEDIT size: chained fixups + symtab + strtab + exports trie. // For dylibs with many exports, 8KB is not enough. + // For executables, we write all defined symbols for backtrace symbolization. let n_exports = layout.dynamic_symbol_definitions.len(); - let linkedit_estimate = 8192 + n_exports * 256; + let n_syms = layout.symbol_resolutions.iter().filter(|r| r.is_some()).count(); + // Each nlist64 = 16 bytes, average symbol name ~60 bytes + NUL + let symtab_estimate = n_syms * (16 + 64); + let linkedit_estimate = 8192 + n_exports * 256 + symtab_estimate; let total = linkedit_offset as usize + linkedit_estimate.max(8192); (mappings, total) } @@ -176,6 +236,10 @@ fn write_macho>( out: &mut [u8], layout: &Layout<'_, MachO>, mappings: &[SegmentMapping], + plain_entries: &[CollectedUnwindEntry], + unwind_info_vm_addr: u64, + text_base: u64, + text_vm_end: u64, ) -> Result { let le = object::Endianness::Little; let header_layout = layout.section_layouts.get(output_section_id::FILE_HEADER); @@ -316,9 +380,56 @@ fn write_macho>( 32 + starts_in_image_size + seg_starts_size + imports_size + symbols_pool.len() as u32 }; + // Build and write __unwind_info now that __eh_frame is in the output buffer. + // Scan output __eh_frame to map func_vm_addr → EhFrameFdeInfo. + let unwind_info_size = if unwind_info_vm_addr != 0 { + let eh_layout = layout.section_layouts.get(output_section_id::EH_FRAME); + let fde_map: std::collections::HashMap = if eh_layout.mem_size > 0 { + if let Some(eh_foff) = vm_addr_to_file_offset(eh_layout.mem_offset, mappings) { + let m = scan_eh_frame_fde_offsets( + out, + eh_layout.mem_offset, + eh_foff, + eh_layout.mem_size as usize, + ); + m + } else { + Default::default() + } + } else { + Default::default() + }; + let available = text_vm_end.saturating_sub(unwind_info_vm_addr); + let content = build_unwind_info_section( + plain_entries, &fde_map, text_base, available, + ); + if !content.is_empty() && content.len() as u64 <= available { + if let Some(ui_foff) = vm_addr_to_file_offset(unwind_info_vm_addr, mappings) { + let end = ui_foff + content.len(); + if end <= out.len() { + out[ui_foff..end].copy_from_slice(&content); + } + } + content.len() as u64 + } else { + if !content.is_empty() { + tracing::debug!( + "compact_unwind: __unwind_info too large ({} bytes) for gap ({} bytes)", + content.len(), available + ); + } + 0 + } + } else { + 0 + }; + // Write headers let header_offset = header_layout.file_offset; - let chained_fixups_offset = write_headers(out, header_offset, layout, mappings, cf_data_size)?; + let chained_fixups_offset = write_headers( + out, header_offset, layout, mappings, cf_data_size, + unwind_info_vm_addr, unwind_info_size, + )?; // Write chained fixups let final_size = if let Some(cf_off) = chained_fixups_offset { @@ -355,11 +466,11 @@ fn write_macho>( out.len() }; - // For dylibs: write symbol table with exported symbols + // Write symbol table let final_size = if layout.symbol_db.args.is_dylib { write_dylib_symtab(out, final_size, layout, mappings)? } else { - final_size + write_exe_symtab(out, final_size, layout, mappings)? }; Ok(final_size) @@ -503,6 +614,132 @@ fn write_dylib_symtab( Ok(pos) } +/// Write a symbol table for executables so that backtraces can resolve function names. +fn write_exe_symtab( + out: &mut [u8], + start: usize, + layout: &Layout<'_, MachO>, + _mappings: &[SegmentMapping], +) -> Result { + use crate::symbol_db::SymbolId; + + // Collect all defined symbols with non-zero addresses. + let mut entries: Vec<(Vec, u64, u8)> = Vec::new(); // (name, value, n_type) + for (sym_idx, res) in layout.symbol_resolutions.iter().enumerate() { + let Some(res) = res else { continue }; + if res.raw_value == 0 { + continue; + } + if res.flags.contains(crate::value_flags::ValueFlags::DYNAMIC) { + continue; + } + let symbol_id = SymbolId::from_usize(sym_idx); + let name = match layout.symbol_db.symbol_name(symbol_id) { + Ok(n) => n.bytes().to_vec(), + Err(_) => continue, + }; + // Skip empty or internal names + if name.is_empty() { + continue; + } + // N_SECT=0x0e, N_EXT=0x01 for global, N_SECT for local + let n_type = 0x0e_u8; // N_SECT (defined in a section) + entries.push((name, res.raw_value, n_type)); + } + + if entries.is_empty() { + return Ok(start); + } + + // Sort by address for easier debugging + entries.sort_by_key(|e| e.1); + + // Build string table: starts with \0 + let mut strtab = vec![0u8]; + let mut str_offsets = Vec::new(); + for (name, _, _) in &entries { + str_offsets.push(strtab.len() as u32); + strtab.extend_from_slice(name); + strtab.push(0); + } + + // Write nlist64 entries (16 bytes each, must be 8-byte aligned) + let symoff = (start + 7) & !7; + let nsyms = entries.len(); + let mut pos = symoff; + for (i, (_, value, n_type)) in entries.iter().enumerate() { + if pos + 16 > out.len() { + break; + } + // nlist64: n_strx (4), n_type (1), n_sect (1), n_desc (2), n_value (8) + out[pos..pos + 4].copy_from_slice(&str_offsets[i].to_le_bytes()); + out[pos + 4] = *n_type; + out[pos + 5] = 1; // n_sect: section 1 (__text) + out[pos + 6..pos + 8].copy_from_slice(&0u16.to_le_bytes()); + out[pos + 8..pos + 16].copy_from_slice(&value.to_le_bytes()); + pos += 16; + } + + // Write string table + let stroff = pos; + if stroff + strtab.len() <= out.len() { + out[stroff..stroff + strtab.len()].copy_from_slice(&strtab); + } + pos = stroff + strtab.len(); + + // Patch LC_SYMTAB, LC_DYSYMTAB, and LINKEDIT segment in the header + let mut off = 32u32; + let ncmds = u32::from_le_bytes(out[16..20].try_into().unwrap()); + for _ in 0..ncmds { + let cmd = u32::from_le_bytes(out[off as usize..off as usize + 4].try_into().unwrap()); + let cmdsize = + u32::from_le_bytes(out[off as usize + 4..off as usize + 8].try_into().unwrap()); + match cmd { + LC_SYMTAB => { + out[off as usize + 8..off as usize + 12] + .copy_from_slice(&(symoff as u32).to_le_bytes()); + out[off as usize + 12..off as usize + 16] + .copy_from_slice(&(nsyms as u32).to_le_bytes()); + out[off as usize + 16..off as usize + 20] + .copy_from_slice(&(stroff as u32).to_le_bytes()); + out[off as usize + 20..off as usize + 24] + .copy_from_slice(&(strtab.len() as u32).to_le_bytes()); + } + 0x19 => { + // LC_SEGMENT_64 — update LINKEDIT filesize/vmsize + let segname = &out[off as usize + 8..off as usize + 24]; + if segname.starts_with(b"__LINKEDIT") { + let linkedit_fileoff = u64::from_le_bytes( + out[off as usize + 40..off as usize + 48] + .try_into() + .unwrap(), + ); + let new_filesize = pos as u64 - linkedit_fileoff; + out[off as usize + 48..off as usize + 56] + .copy_from_slice(&new_filesize.to_le_bytes()); + let new_vmsize = align_to(new_filesize, PAGE_SIZE); + out[off as usize + 32..off as usize + 40] + .copy_from_slice(&new_vmsize.to_le_bytes()); + } + } + LC_DYSYMTAB => { + // All symbols are local for executables + let o = off as usize + 8; + out[o..o + 4].copy_from_slice(&0u32.to_le_bytes()); // ilocalsym + out[o + 4..o + 8].copy_from_slice(&(nsyms as u32).to_le_bytes()); // nlocalsym + out[o + 8..o + 12].copy_from_slice(&(nsyms as u32).to_le_bytes()); // iextdefsym + out[o + 12..o + 16].copy_from_slice(&0u32.to_le_bytes()); // nextdefsym + out[o + 16..o + 20].copy_from_slice(&(nsyms as u32).to_le_bytes()); // iundefsym + out[o + 20..o + 24].copy_from_slice(&0u32.to_le_bytes()); // nundefsym + } + _ => {} + } + off += cmdsize; + } + + Ok(pos) +} + /// Build a Mach-O export trie for the given symbols. fn build_export_trie(entries: &[(Vec, u64)]) -> Vec { if entries.is_empty() { @@ -1160,14 +1397,14 @@ fn apply_relocations( // ARM64_RELOC_UNSIGNED 64-bit. // If preceded by a SUBTRACTOR, compute difference: // result = target_addr - subtrahend + existing_content - // For GOT-indirect pointers (e.g. __eh_frame personality), - // use the GOT entry address instead of the symbol address. if let Some(sub_addr) = pending_subtrahend.take() { if patch_file_offset + 8 <= out.len() { - let effective_target = got_addr.unwrap_or(target_addr); + // SUBTRACTOR+UNSIGNED encodes a pcrel difference (e.g. FDE pc_begin, + // LSDA pointer). Always use the direct symbol address, never the GOT + // address — GOT indirection is expressed via POINTER_TO_GOT (type 7). let existing = i64::from_le_bytes( out[patch_file_offset..patch_file_offset + 8].try_into().unwrap()); - let val = effective_target as i64 - sub_addr as i64 + existing; + let val = target_addr as i64 - sub_addr as i64 + existing; out[patch_file_offset..patch_file_offset + 8] .copy_from_slice(&val.to_le_bytes()); } @@ -1393,6 +1630,566 @@ fn write_pageoff12(out: &mut [u8], offset: usize, target: u64) { write_u32_at(out, offset, (insn & 0xFFC0_03FF) | (imm12 << 10)); } +// ── Compact unwind / __unwind_info generation ────────────────────────────── + +/// A per-function compact unwind entry collected from `__LD,__compact_unwind`. +struct CollectedUnwindEntry { + /// Output VM address of the function. + func_addr: u64, + /// Function size in bytes. + func_size: u32, + /// Compact unwind encoding (ARM64 mode + register mask). + encoding: u32, +} + +/// Scan all input objects for `__LD,__compact_unwind` sections and collect +/// frame-pointer entries that can be represented directly in `__unwind_info`. +/// Personality entries are handled separately by scanning output `__eh_frame`. +fn collect_compact_unwind_entries(layout: &Layout<'_, MachO>) -> Vec { + use object::read::macho::MachHeader as _; + use object::read::macho::Section as _; + use object::read::macho::Segment as _; + let le = object::Endianness::Little; + let mut entries: Vec = Vec::new(); + + let mut n_objects = 0usize; + let mut n_cu_entries = 0usize; + for group in &layout.group_layouts { + for file_layout in &group.files { + let FileLayout::Object(obj) = file_layout else { + continue; + }; + let _ = n_objects; // suppress unused warning + n_objects += 1; + // Parse raw load commands to reach __LD segment (not in obj.object.sections). + let Ok(header) = + object::macho::MachHeader64::::parse(obj.object.data, 0) + else { + continue; + }; + // Mach-O object files have a single unnamed LC_SEGMENT_64 containing + // ALL sections. Each section has its own segname field. Iterate all + // sections of the single segment to find __LD,__compact_unwind. + let Ok(mut cmds) = header.load_commands(le, obj.object.data, 0) else { + continue; + }; + while let Ok(Some(cmd)) = cmds.next() { + let Ok(Some((seg, seg_data))) = cmd.segment_64() else { + continue; + }; + let Ok(sections) = seg.sections(le, seg_data) else { + continue; + }; + for sec in sections { + let sec_segname = crate::macho::trim_nul(&sec.segname); + let sectname = crate::macho::trim_nul(&sec.sectname); + if sec_segname != b"__LD" || sectname != b"__compact_unwind" { + continue; + } + n_cu_entries += 1; + let sec_off = sec.offset.get(le) as usize; + let sec_size = sec.size.get(le) as usize; + if sec_size == 0 || sec_off == 0 { + continue; + } + let Some(data) = obj.object.data.get(sec_off..sec_off + sec_size) else { + continue; + }; + let relocs = sec.relocations(le, obj.object.data).unwrap_or(&[]); + let n = sec_size / 32; + for i in 0..n { + let base = i * 32; + if base + 32 > data.len() { + break; + } + let func_size = + u32::from_le_bytes(data[base + 8..base + 12].try_into().unwrap()); + let encoding = + u32::from_le_bytes(data[base + 12..base + 16].try_into().unwrap()); + if encoding == 0 { + continue; // no unwind info needed + } + // DWARF mode → handled via __eh_frame FDE scan, skip here. + if (encoding & 0x0F00_0000) == 0x0300_0000 { + continue; + } + // Plain frame-pointer / no-unwind entry. + let Some(func_addr) = + resolve_compact_unwind_addr(obj, layout, le, relocs, base, data) + else { + continue; + }; + entries.push(CollectedUnwindEntry { func_addr, func_size, encoding }); + } + } + } + } + } + + tracing::debug!( + "compact_unwind: {} raw entries, {} plain", + n_cu_entries, entries.len() + ); + entries.sort_by_key(|e| e.func_addr); + entries.dedup_by_key(|e| e.func_addr); + entries +} + +/// Resolve the VM address stored at `field_offset` within a compact-unwind entry. +/// `field_offset` is the absolute byte offset within the `__compact_unwind` section data. +/// `sec_data` is the raw section bytes (used to read the implicit 8-byte addend for +/// non-extern / section-relative relocations). +fn resolve_compact_unwind_addr( + obj: &ObjectLayout<'_, MachO>, + layout: &Layout<'_, MachO>, + le: object::Endianness, + relocs: &[object::macho::Relocation], + field_offset: usize, + sec_data: &[u8], +) -> Option { + use object::read::macho::Nlist as _; + for r in relocs { + let reloc = r.info(le); + if reloc.r_address as usize != field_offset { + continue; + } + if reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let sym_id = obj.symbol_id_range.input_to_id(sym_idx); + if let Some(res) = layout.merged_symbol_resolution(sym_id) { + if res.raw_value != 0 { + return Some(res.raw_value); + } + } + // Fallback: local symbol (compute from section base + symbol value). + let sym = obj.object.symbols.symbol(sym_idx).ok()?; + let n_sect = sym.n_sect(); + if n_sect == 0 { + return None; + } + let sec_idx = n_sect as usize - 1; + let sec_out = obj.section_resolutions.get(sec_idx)?.address()?; + let sec_in = obj.object.sections.get(sec_idx).map(|s| s.addr.get(le))?; + return Some(sec_out + sym.n_value(le).wrapping_sub(sec_in)); + } else { + // Non-extern (section-relative): r_symbolnum is 1-based section ordinal. + let sec_ord = reloc.r_symbolnum as usize; + if sec_ord == 0 { + return None; + } + let sec_idx = sec_ord - 1; + let sec_out = obj.section_resolutions.get(sec_idx)?.address()?; + let sec_in = obj.object.sections.get(sec_idx).map(|s| s.addr.get(le))?; + // Read the 8-byte implicit addend from the field. + let addend = u64::from_le_bytes( + sec_data.get(field_offset..field_offset + 8)?.try_into().ok()? + ); + return Some(sec_out + addend.wrapping_sub(sec_in)); + } + } + None +} + + +/// Build the binary content of the `__unwind_info` section from collected entries. +/// `text_base` is the VM address of the start of the `__TEXT` segment. +/// +/// Produces a version-1 unwind_info with regular second-level pages (kind=2). +/// Entries with personality/LSDA are not included (use DWARF FDEs for those). +/// Info extracted from a `__eh_frame` CIE augmentation string. +#[derive(Default, Clone)] +struct CieAugInfo { + /// Whether the CIE has a personality function ('P' in augstr). + has_personality: bool, + /// VM address of the GOT slot for the personality function, or 0. + pers_got_vm: u64, + /// Whether FDEs referencing this CIE carry an LSDA pointer ('L' in augstr). + has_lsda: bool, + /// Size of the FDE pc_begin / pc_range fields in bytes (from 'R' enc; 0 = unknown/8). + fde_ptr_size: u8, + /// Size of the LSDA pointer in FDE augmentation data (from 'L' enc; 0 = unknown/8). + lsda_ptr_size: u8, +} + +/// Per-FDE info extracted from the output `__eh_frame` buffer. +pub(crate) struct EhFrameFdeInfo { + /// Byte offset of the FDE within the `__eh_frame` section. + pub section_offset: u32, + /// VM address of the LSDA for this function, or 0. + pub lsda_vm: u64, + /// VM address of the GOT slot for the personality function, or 0. + pub pers_got_vm: u64, +} + +/// Read a ULEB128 value from `data` at `pos`, advancing `pos`. +fn read_uleb128(data: &[u8], pos: &mut usize) -> u64 { + let mut val = 0u64; + let mut shift = 0; + while *pos < data.len() { + let b = data[*pos]; + *pos += 1; + val |= ((b & 0x7F) as u64) << shift; + shift += 7; + if b & 0x80 == 0 { break; } + } + val +} + +/// Determine the byte size of an encoded pointer value from a DW_EH_PE encoding byte. +/// Returns 4 or 8; defaults to 8 (pointer-sized) for unknown formats. +fn eh_ptr_size(enc: u8) -> u8 { + match enc & 0x0F { + 0x00 => 8, // DW_EH_PE_absptr (pointer-sized = 8 on 64-bit) + 0x02 => 2, + 0x03 => 4, // DW_EH_PE_udata4 + 0x04 => 8, // DW_EH_PE_udata8 + 0x09 => 2, + 0x0A => 4, + 0x0B => 4, // DW_EH_PE_sdata4 + 0x0C => 8, // DW_EH_PE_sdata8 + _ => 8, + } +} + +/// Read a PC-relative signed value of `size` bytes from `data` at `pos`, +/// apply it relative to `field_vm_addr`, and return the target VM address. +fn read_pcrel(data: &[u8], pos: usize, size: usize, field_vm_addr: u64) -> u64 { + let bytes = match data.get(pos..pos + size) { + Some(b) => b, + None => return 0, + }; + let delta = match size { + 4 => i32::from_le_bytes(bytes.try_into().unwrap_or([0; 4])) as i64, + 8 => i64::from_le_bytes(bytes.try_into().unwrap_or([0; 8])), + _ => return 0, + }; + (field_vm_addr as i64 + delta) as u64 +} + +/// Parse a CIE at section offset `cie_pos` and return its augmentation info. +fn parse_cie_aug(data: &[u8], cie_pos: usize, eh_frame_vm_addr: u64) -> CieAugInfo { + let mut info = CieAugInfo::default(); + // Skip: length(4) + cie_id(4) + version(1) = 9 bytes. + let mut pos = cie_pos + 9; + // Find augmentation string (null-terminated). + let aug_start = pos; + while pos < data.len() && data[pos] != 0 { pos += 1; } + if pos >= data.len() { return info; } + let aug_bytes = &data[aug_start..pos]; + pos += 1; // skip null terminator + + let has_z = aug_bytes.contains(&b'z'); + let has_p = aug_bytes.contains(&b'P'); + let has_l = aug_bytes.contains(&b'L'); + let has_r = aug_bytes.contains(&b'R'); + info.has_lsda = has_l; + + // Skip code_alignment (ULEB128), data_alignment (SLEB128), ra_register (ULEB128). + read_uleb128(data, &mut pos); // code_alignment + // SLEB128 (just skip as if ULEB128 since we only care about the byte count) + loop { + if pos >= data.len() { return info; } + let b = data[pos]; pos += 1; + if b & 0x80 == 0 { break; } + } + read_uleb128(data, &mut pos); // ra_register + + if !has_z { return info; } + let aug_data_len = read_uleb128(data, &mut pos) as usize; + let aug_data_start = pos; + + // Augmentation data contains per-letter info in augstr order (skipping 'z'). + let mut ap = aug_data_start; + for &ch in aug_bytes { + if ap >= aug_data_start + aug_data_len { break; } + match ch { + b'P' if has_p => { + let pers_enc = data[ap]; ap += 1; + let sz = eh_ptr_size(pers_enc) as usize; + if ap + sz <= data.len() { + // Personality ptr is PC-relative from the field address. + let field_vm = eh_frame_vm_addr + ap as u64; + let target_vm = read_pcrel(data, ap, sz, field_vm); + if target_vm != 0 { + info.has_personality = true; + info.pers_got_vm = target_vm; + } + } + ap += sz; + } + b'L' if has_l => { + let lsda_enc = data[ap]; ap += 1; + info.lsda_ptr_size = eh_ptr_size(lsda_enc); + } + b'R' if has_r => { + let fde_enc = data[ap]; ap += 1; + info.fde_ptr_size = eh_ptr_size(fde_enc); + } + _ => {} + } + } + + // Default pointer size = 8 for 64-bit Mach-O. + if info.fde_ptr_size == 0 { info.fde_ptr_size = 8; } + if info.lsda_ptr_size == 0 { info.lsda_ptr_size = 8; } + info +} + +/// Scan the output `__eh_frame` buffer. +/// Returns a map: `func_vm_addr → EhFrameFdeInfo` for every FDE found. +/// FDEs without personality have `pers_got_vm = 0`. +fn scan_eh_frame_fde_offsets( + buf: &[u8], + eh_frame_vm_addr: u64, + eh_frame_file_offset: usize, + eh_frame_size: usize, +) -> std::collections::HashMap { + use crate::eh_frame::EhFrameEntryPrefix; + use std::mem::size_of; + use zerocopy::FromBytes; + + let mut map = std::collections::HashMap::new(); + // CIE map: section_offset → CieAugInfo + let mut cie_map: std::collections::HashMap = Default::default(); + + let Some(data) = buf.get(eh_frame_file_offset..eh_frame_file_offset + eh_frame_size) else { + return map; + }; + + const PREFIX_LEN: usize = size_of::(); + let mut pos = 0usize; + + while pos + PREFIX_LEN <= data.len() { + let Ok(prefix) = EhFrameEntryPrefix::read_from_bytes(&data[pos..pos + PREFIX_LEN]) else { + break; + }; + if prefix.length == 0 { break; } + let size = 4 + prefix.length as usize; + if pos + size > data.len() { break; } + + if prefix.cie_id == 0 { + // CIE: parse augmentation. + let cie_aug = parse_cie_aug(data, pos, eh_frame_vm_addr); + cie_map.insert(pos as u32, cie_aug); + } else { + // FDE: resolve CIE, then extract pc_begin, LSDA. + // cie_id = byte distance from the cie_ptr field to the CIE. + let cie_ptr_field_off = pos + 4; + let cie_off = (cie_ptr_field_off as u64).wrapping_sub(prefix.cie_id as u64) as u32; + let cie_aug = cie_map.get(&cie_off).cloned().unwrap_or_default(); + let ptr_size = cie_aug.fde_ptr_size.max(4) as usize; + + // pc_begin at byte 8, PC-relative signed value of ptr_size bytes. + let pc_begin_field_vm = eh_frame_vm_addr + pos as u64 + 8; + let func_vm = read_pcrel(data, pos + 8, ptr_size, pc_begin_field_vm); + if func_vm == 0 { pos += size; continue; } + + // pc_range at byte 8+ptr_size (absolute, not PC-relative). + // Skip it (we don't use pc_range for __unwind_info). + + // aug_data_length at byte 8 + 2*ptr_size. + let aug_len_pos = pos + 8 + 2 * ptr_size; + let mut ap = aug_len_pos; + let aug_len = read_uleb128(data, &mut ap) as usize; + + // LSDA pointer at start of aug_data (if CIE has 'L'). + let lsda_vm = if cie_aug.has_lsda && cie_aug.lsda_ptr_size > 0 && ap + cie_aug.lsda_ptr_size as usize <= data.len() { + let lsda_field_vm = eh_frame_vm_addr + ap as u64; + read_pcrel(data, ap, cie_aug.lsda_ptr_size as usize, lsda_field_vm) + } else { + 0 + }; + let _ = aug_len; + + map.insert(func_vm, EhFrameFdeInfo { + section_offset: pos as u32, + lsda_vm, + pers_got_vm: cie_aug.pers_got_vm, + }); + } + + pos += size; + } + + map +} + +/// Build the binary content of `__unwind_info` from collected compact-unwind entries +/// and FDE info from the output `__eh_frame`. +/// +/// `plain_entries`: ARM64 frame-pointer entries (from __compact_unwind). +/// `fde_map`: func_vm_addr → EhFrameFdeInfo (from scanning output __eh_frame). +/// `text_base`: VM address of the start of `__TEXT`. +/// +/// For each FDE with a personality function, emits a DWARF-mode entry +/// (`UNWIND_HAS_LSDA | pers_idx | UNWIND_ARM64_DWARF | fde_section_offset`). +/// Plain frame-pointer entries are also included. +fn build_unwind_info_section( + plain_entries: &[CollectedUnwindEntry], + fde_map: &std::collections::HashMap, + text_base: u64, + max_bytes: u64, +) -> Vec { + // ARM64 compact-unwind encoding constants. + const UNWIND_ARM64_DWARF: u32 = 0x0300_0000; + + // Build: (func_addr, func_size, encoding) sorted by func_addr. + let mut all_entries: Vec<(u64, u32, u32)> = Vec::new(); + + // Collect unique personality GOT slots (in encounter order). + let mut personalities: Vec = Vec::new(); + + // Emit DWARF-mode entries for FDEs that have a personality function. + // Each such FDE needs an __unwind_info entry so the unwinder can find it. + // + // For DWARF-mode entries the unwinder reads the LSDA from the FDE + // augmentation data in __eh_frame, NOT from the __unwind_info LSDA array. + // So we omit UNWIND_HAS_LSDA and the LSDA array to save space. + for (&func_vm, fde_info) in fde_map { + if fde_info.pers_got_vm == 0 { continue; } // no personality → skip + + // Personality index (1-based into the personality array we build). + let pers_idx = if let Some(pos) = personalities.iter().position(|&g| g == fde_info.pers_got_vm) { + pos + 1 + } else { + personalities.push(fde_info.pers_got_vm); + personalities.len() + }; + + let enc = UNWIND_ARM64_DWARF | fde_info.section_offset + | (((pers_idx as u32) & 3) << 28); + all_entries.push((func_vm, 0u32, enc)); + } + + let pers_count = all_entries.len(); + for e in plain_entries { + if fde_map.get(&e.func_addr).is_some_and(|f| f.pers_got_vm != 0) { continue; } + all_entries.push((e.func_addr, e.func_size, e.encoding)); + } + + if all_entries.is_empty() { + return Vec::new(); + } + + all_entries.sort_by_key(|e| e.0); + all_entries.dedup_by_key(|e| e.0); + + // Truncate if the full content would exceed max_bytes. + // Personality entries (pers_count) are critical; trim plain entries first. + let n_pers = personalities.len() as u32; + const ENTRIES_PER_PAGE: usize = 500; + loop { + let np = all_entries.len().div_ceil(ENTRIES_PER_PAGE); + // Estimate: header(28) + pers(n*4) + index((np+1)*12) + SL pages(np*8 + entries*8) + let est = 28 + (n_pers as usize) * 4 + (np + 1) * 12 + np * 8 + all_entries.len() * 8; + if est as u64 <= max_bytes || all_entries.len() <= pers_count { + break; + } + // Remove last plain entry (they're sorted, so the highest address is removed first). + all_entries.pop(); + } + + let num_pages = all_entries.len().div_ceil(ENTRIES_PER_PAGE); + + tracing::debug!( + "compact_unwind: building __unwind_info: {} entries ({} pers), {} personalities", + all_entries.len(), pers_count, personalities.len() + ); + + // DWARF-mode entries all have unique encodings (different FDE offsets) so + // common encodings provide no benefit — skip them to save space. + + // Section layout: + // [28] header (7 × u32) + // [P*4] personality array (GOT slot offsets from TEXT base) + // [(N+1)*12] first-level index (N pages + sentinel) + // [page data…] + // + // LSDA array is empty: DWARF-mode entries get LSDA from the FDE augmentation + // data in __eh_frame, so no separate LSDA index is needed. + let ce_off = 28u32; + let pers_off = ce_off; // no common encodings + let pers_bytes = n_pers * 4; + let idx_off = pers_off + pers_bytes; + let idx_bytes = (num_pages as u32 + 1) * 12; + let lsda_off = idx_off + idx_bytes; // empty LSDA array starts here + let sl_start = lsda_off; + + let mut sl_offsets = Vec::with_capacity(num_pages); + let mut cur = sl_start; + for i in 0..num_pages { + sl_offsets.push(cur); + let n = (all_entries.len() - i * ENTRIES_PER_PAGE).min(ENTRIES_PER_PAGE); + cur += 8 + n as u32 * 8; + } + let total = cur as usize; + + let mut out = vec![0u8; total]; + macro_rules! wu32 { + ($off:expr, $val:expr) => { + out[$off..$off + 4].copy_from_slice(&($val as u32).to_le_bytes()) + }; + } + macro_rules! wu16 { + ($off:expr, $val:expr) => { + out[$off..$off + 2].copy_from_slice(&($val as u16).to_le_bytes()) + }; + } + + // Header + wu32!(0, 1u32); // version + wu32!(4, ce_off); // commonEncodingsArraySectionOffset + wu32!(8, 0u32); // commonEncodingsArrayCount (none) + wu32!(12, pers_off); // personalityArraySectionOffset + wu32!(16, n_pers); // personalityArrayCount + wu32!(20, idx_off); // indexSectionOffset + wu32!(24, num_pages as u32 + 1); // indexCount (includes sentinel) + + // Personality array: 4-byte offsets from TEXT base to GOT slots. + for (i, &got_vm) in personalities.iter().enumerate() { + let offset_from_text = (got_vm - text_base) as u32; + wu32!(pers_off as usize + i * 4, offset_from_text); + } + + // First-level index entries + second-level regular pages + for page in 0..num_pages { + let start = page * ENTRIES_PER_PAGE; + let end = (start + ENTRIES_PER_PAGE).min(all_entries.len()); + let page_entries = &all_entries[start..end]; + + let first_fn_off = (page_entries[0].0 - text_base) as u32; + let sl_off = sl_offsets[page] as usize; + + // Index entry (12 bytes) + let ie = idx_off as usize + page * 12; + wu32!(ie, first_fn_off); + wu32!(ie + 4, sl_off as u32); // secondLevelPagesSectionOffset + wu32!(ie + 8, lsda_off); // lsdaIndexArraySectionOffset + + // Regular second-level page header (8 bytes) + wu32!(sl_off, 2u32); // kind = UNWIND_SECOND_LEVEL_REGULAR + wu16!(sl_off + 4, 8u16); // entryPageOffset + wu16!(sl_off + 6, page_entries.len() as u16); // entryCount + + // Entries (8 bytes each: funcOffset u32 + encoding u32) + for (j, &(fa, _, enc)) in page_entries.iter().enumerate() { + let eo = sl_off + 8 + j * 8; + wu32!(eo, (fa - text_base) as u32); + wu32!(eo + 4, enc); + } + } + + // Sentinel first-level index entry + let (last_fa, last_fs, _) = *all_entries.last().unwrap(); + let sentinel_fn_off = (last_fa - text_base + last_fs as u64) as u32; + let sie = idx_off as usize + num_pages * 12; + wu32!(sie, sentinel_fn_off); + wu32!(sie + 4, 0u32); // secondLevelPagesSectionOffset = 0 (sentinel) + wu32!(sie + 8, lsda_off); // lsdaIndexArraySectionOffset + + out +} + /// Write Mach-O headers. Returns the chained fixups file offset. fn write_headers( out: &mut [u8], @@ -1400,6 +2197,8 @@ fn write_headers( layout: &Layout<'_, MachO>, mappings: &[SegmentMapping], chained_fixups_data_size: u32, + unwind_info_vm_addr: u64, + unwind_info_size: u64, ) -> Result> { let text_vm_start = mappings.first().map_or(PAGEZERO_SIZE, |m| m.vm_start); let text_vm_end = mappings @@ -1507,9 +2306,11 @@ fn write_headers( let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; let eh_frame_layout = layout.section_layouts.get(output_section_id::EH_FRAME); let has_eh_frame = eh_frame_layout.mem_size > 0; + let has_unwind_info = unwind_info_size > 0; let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 } - + if has_eh_frame { 1 } else { 0 }; + + if has_eh_frame { 1 } else { 0 } + + if has_unwind_info { 1 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT let init_array_layout = layout.section_layouts.get(output_section_id::INIT_ARRAY); let has_init_array = init_array_layout.mem_size > 0; @@ -1621,6 +2422,21 @@ fn write_headers( w.u32(0); w.u32(0); } + if has_unwind_info { + let ui_foff = vm_addr_to_file_offset(unwind_info_vm_addr, mappings).unwrap_or(0) as u32; + w.name16(b"__unwind_info"); + w.name16(b"__TEXT"); + w.u64(unwind_info_vm_addr); + w.u64(unwind_info_size); + w.u32(ui_foff); + w.u32(2); // align 2^2 = 4 + w.u32(0); // reloff + w.u32(0); // nreloc + w.u32(0); // flags = S_REGULAR + w.u32(0); // reserved1 + w.u32(0); // reserved2 + w.u32(0); // reserved3 + } if has_data { let mut nsects = if has_tvars { 2u32 } else { 0 }; diff --git a/wild/tests/sources/macho/backtrace-test/backtrace-test.rs b/wild/tests/sources/macho/backtrace-test/backtrace-test.rs index 9fb28bb5d..30bc97bb0 100644 --- a/wild/tests/sources/macho/backtrace-test/backtrace-test.rs +++ b/wild/tests/sources/macho/backtrace-test/backtrace-test.rs @@ -1,5 +1,4 @@ //#LinkerDriver:clang -//#Ignore:__eh_frame SUBTRACTOR relocations need correct FDE computation fn inner() -> String { let bt = std::backtrace::Backtrace::force_capture(); diff --git a/wild/tests/sources/macho/exception/exception.cc b/wild/tests/sources/macho/exception/exception.cc index aac013bc1..e73006039 100644 --- a/wild/tests/sources/macho/exception/exception.cc +++ b/wild/tests/sources/macho/exception/exception.cc @@ -1,7 +1,7 @@ //#LinkerDriver:clang++ //#LinkArgs:-lc++ //#CompArgs:-std=c++17 -//#Ignore:__eh_frame needs FDE filtering and C++ needs __compact_unwind support +//#Ignore:C++ exceptions need __gcc_except_tab, __stubs, and __got sections #include diff --git a/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs index dc4b43ca8..31f40b65b 100644 --- a/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs +++ b/wild/tests/sources/macho/rust-panic-unwind/rust-panic-unwind.rs @@ -1,5 +1,3 @@ -//#Ignore:__eh_frame needs FDE filtering (dead FDEs cause phantom matches) - fn main() { let r = std::panic::catch_unwind(|| panic!("test")); std::process::exit(if r.is_err() { 42 } else { 1 }); From 41b9e0a49efb0eb52d85f877cecfbb2d58f29492 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 8 Apr 2026 08:31:34 +0100 Subject: [PATCH 10/21] fix: tests + fmt Signed-off-by: Giles Cope --- libwild/src/eh_frame.rs | 5 +- libwild/src/elf.rs | 2 +- libwild/src/lib.rs | 2 +- libwild/src/macho.rs | 6 +- libwild/src/macho_writer.rs | 352 +++++++++++++++++++------- wild/tests/macho_integration_tests.rs | 60 +++-- 6 files changed, 307 insertions(+), 120 deletions(-) diff --git a/libwild/src/eh_frame.rs b/libwild/src/eh_frame.rs index 280596253..c56870f9e 100644 --- a/libwild/src/eh_frame.rs +++ b/libwild/src/eh_frame.rs @@ -81,10 +81,7 @@ pub(crate) trait EhFrameHandler<'data, R: Relocation> { /// Given the pc_begin relocation of an FDE, return the section index of the /// target function. Returns None if the FDE should be discarded. - fn fde_target_section( - &self, - rel: &R, - ) -> crate::error::Result>; + fn fde_target_section(&self, rel: &R) -> crate::error::Result>; /// Store a parsed CIE. fn store_cie(&mut self, offset: u32, cie: Cie<'data>); diff --git a/libwild/src/elf.rs b/libwild/src/elf.rs index 7a1962c0d..cb98ac5c4 100644 --- a/libwild/src/elf.rs +++ b/libwild/src/elf.rs @@ -3991,8 +3991,8 @@ impl<'data> Default for ExceptionFrames<'data> { } } -use crate::eh_frame::ExceptionFrame; use crate::eh_frame::EhFrameSizes; +use crate::eh_frame::ExceptionFrame; impl<'data> ExceptionFrames<'data> { fn len(&self) -> usize { diff --git a/libwild/src/lib.rs b/libwild/src/lib.rs index 01d91e76d..0a0d13022 100644 --- a/libwild/src/lib.rs +++ b/libwild/src/lib.rs @@ -7,13 +7,13 @@ pub(crate) mod debug_trace; pub(crate) mod diagnostics; pub(crate) mod diff; pub(crate) mod dwarf_address_info; +pub(crate) mod eh_frame; pub(crate) mod elf; pub(crate) mod elf_aarch64; pub(crate) mod elf_loongarch64; pub(crate) mod elf_riscv64; pub(crate) mod elf_writer; pub(crate) mod elf_x86_64; -pub(crate) mod eh_frame; pub mod error; pub(crate) mod export_list; pub(crate) mod expression_eval; diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 97203b93e..02766bb0e 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -1031,10 +1031,12 @@ impl platform::Platform for MachO { if !reloc.r_extern { continue; } - if reloc.r_type == 10 { // ADDEND + if reloc.r_type == 10 { + // ADDEND continue; } - if reloc.r_type == 1 { // SUBTRACTOR + if reloc.r_type == 1 { + // SUBTRACTOR after_subtractor = true; continue; } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 7f89b97a5..38df7bbbb 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -63,11 +63,15 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> // file allocation — we can place __unwind_info there without extending // TEXT vmsize or shifting DATA vmaddr. let text_content_end = { + // Find the end of the last TEXT-segment section: EH_FRAME > PLT_GOT > TEXT let eh = layout.section_layouts.get(output_section_id::EH_FRAME); + let plt = layout.section_layouts.get(output_section_id::PLT_GOT); + let t = layout.section_layouts.get(output_section_id::TEXT); if eh.mem_size > 0 { eh.mem_offset + eh.mem_size + } else if plt.mem_size > 0 { + plt.mem_offset + plt.mem_size } else { - let t = layout.section_layouts.get(output_section_id::TEXT); t.mem_offset + t.mem_size } }; @@ -87,9 +91,13 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> let (mappings, alloc_size) = build_mappings_and_size(layout, extra_text); let mut buf = vec![0u8; alloc_size]; let final_size = write_macho::( - &mut buf, layout, &mappings, + &mut buf, + layout, + &mappings, &plain_entries, - unwind_info_vm_addr, text_base, text_vm_end, + unwind_info_vm_addr, + text_base, + text_vm_end, )?; buf.truncate(final_size); @@ -120,7 +128,10 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> /// Build exactly 2 segment mappings (TEXT + merged DATA) from pipeline layout. /// `extra_text` extends the TEXT segment (first segment) by that many bytes. -fn build_mappings_and_size(layout: &Layout<'_, MachO>, extra_text: u64) -> (Vec, usize) { +fn build_mappings_and_size( + layout: &Layout<'_, MachO>, + extra_text: u64, +) -> (Vec, usize) { let mut raw: Vec<(u64, u64, u64)> = Vec::new(); let mut file_cursor: u64 = 0; let mut is_first = true; @@ -195,7 +206,11 @@ fn build_mappings_and_size(layout: &Layout<'_, MachO>, extra_text: u64) -> (Vec< // For dylibs with many exports, 8KB is not enough. // For executables, we write all defined symbols for backtrace symbolization. let n_exports = layout.dynamic_symbol_definitions.len(); - let n_syms = layout.symbol_resolutions.iter().filter(|r| r.is_some()).count(); + let n_syms = layout + .symbol_resolutions + .iter() + .filter(|r| r.is_some()) + .count(); // Each nlist64 = 16 bytes, average symbol name ~60 bytes + NUL let symtab_estimate = n_syms * (16 + 64); let linkedit_estimate = 8192 + n_exports * 256 + symtab_estimate; @@ -280,8 +295,15 @@ fn write_macho>( )?; // Populate GOT entries for non-import symbols - write_got_entries(out, layout, mappings, &mut rebase_fixups, - &mut bind_fixups, &mut imports, has_extra_dylibs)?; + write_got_entries( + out, + layout, + mappings, + &mut rebase_fixups, + &mut bind_fixups, + &mut imports, + has_extra_dylibs, + )?; // Build chained fixup data: merge rebase + bind, encode per-page chains rebase_fixups.sort_by_key(|f| f.file_offset); @@ -400,9 +422,7 @@ fn write_macho>( Default::default() }; let available = text_vm_end.saturating_sub(unwind_info_vm_addr); - let content = build_unwind_info_section( - plain_entries, &fde_map, text_base, available, - ); + let content = build_unwind_info_section(plain_entries, &fde_map, text_base, available); if !content.is_empty() && content.len() as u64 <= available { if let Some(ui_foff) = vm_addr_to_file_offset(unwind_info_vm_addr, mappings) { let end = ui_foff + content.len(); @@ -415,7 +435,8 @@ fn write_macho>( if !content.is_empty() { tracing::debug!( "compact_unwind: __unwind_info too large ({} bytes) for gap ({} bytes)", - content.len(), available + content.len(), + available ); } 0 @@ -427,8 +448,13 @@ fn write_macho>( // Write headers let header_offset = header_layout.file_offset; let chained_fixups_offset = write_headers( - out, header_offset, layout, mappings, cf_data_size, - unwind_info_vm_addr, unwind_info_size, + out, + header_offset, + layout, + mappings, + cf_data_size, + unwind_info_vm_addr, + unwind_info_size, )?; // Write chained fixups @@ -966,7 +992,9 @@ fn write_filtered_eh_frame( use std::mem::size_of_val; use zerocopy::FromBytes; - let relocs = input_section.relocations(le, obj.object.data).unwrap_or(&[]); + let relocs = input_section + .relocations(le, obj.object.data) + .unwrap_or(&[]); const PREFIX_LEN: usize = size_of::(); let mut input_pos = 0; @@ -975,10 +1003,9 @@ fn write_filtered_eh_frame( // First pass: determine which entries to keep and build a compacted copy. while input_pos + PREFIX_LEN <= input_data.len() { - let prefix = EhFrameEntryPrefix::read_from_bytes( - &input_data[input_pos..input_pos + PREFIX_LEN], - ) - .unwrap(); + let prefix = + EhFrameEntryPrefix::read_from_bytes(&input_data[input_pos..input_pos + PREFIX_LEN]) + .unwrap(); let size = size_of_val(&prefix.length) + prefix.length as usize; let next_input = input_pos + size; if next_input > input_data.len() { @@ -1003,7 +1030,9 @@ fn write_filtered_eh_frame( let n_sect = sym.n_sect(); if n_sect > 0 { let sec_idx = n_sect as usize - 1; - loaded = obj.section_resolutions.get(sec_idx) + loaded = obj + .section_resolutions + .get(sec_idx) .and_then(|r| r.address()) .is_some(); } @@ -1052,10 +1081,9 @@ fn write_filtered_eh_frame( let mut cie_map2 = std::collections::HashMap::new(); while input_pos + PREFIX_LEN <= input_data.len() { - let prefix = EhFrameEntryPrefix::read_from_bytes( - &input_data[input_pos..input_pos + PREFIX_LEN], - ) - .unwrap(); + let prefix = + EhFrameEntryPrefix::read_from_bytes(&input_data[input_pos..input_pos + PREFIX_LEN]) + .unwrap(); let size = size_of_val(&prefix.length) + prefix.length as usize; let next_input = input_pos + size; if next_input > input_data.len() { @@ -1077,8 +1105,11 @@ fn write_filtered_eh_frame( if let Ok(sym) = obj.object.symbols.symbol(sym_idx) { let n = sym.n_sect(); if n > 0 { - loaded = obj.section_resolutions.get(n as usize - 1) - .and_then(|r| r.address()).is_some(); + loaded = obj + .section_resolutions + .get(n as usize - 1) + .and_then(|r| r.address()) + .is_some(); } } } @@ -1089,7 +1120,8 @@ fn write_filtered_eh_frame( if keep { // Collect relocs for this entry and apply them at their output positions - let entry_relocs: Vec<_> = relocs.iter() + let entry_relocs: Vec<_> = relocs + .iter() .filter(|r| { let off = r.info(le).r_address as usize; off >= input_pos && off < next_input @@ -1097,7 +1129,8 @@ fn write_filtered_eh_frame( .collect(); // Create adjusted relocs with output-relative addresses - let adjusted: Vec> = entry_relocs.iter() + let adjusted: Vec> = entry_relocs + .iter() .map(|r| { let mut copy = **r; let info = r.info(le); @@ -1181,9 +1214,18 @@ fn write_object_sections( let sectname = crate::macho::trim_nul(input_section.sectname()); if sectname == b"__eh_frame" { write_filtered_eh_frame( - out, file_offset, output_addr, input_data, - input_section, obj, layout, le, - rebase_fixups, bind_fixups, imports, has_extra_dylibs, + out, + file_offset, + output_addr, + input_data, + input_section, + obj, + layout, + le, + rebase_fixups, + bind_fixups, + imports, + has_extra_dylibs, )?; continue; } @@ -1231,11 +1273,13 @@ fn apply_relocations( for reloc_raw in relocs { let reloc = reloc_raw.info(le); - if reloc.r_type == 10 { // ARM64_RELOC_ADDEND + if reloc.r_type == 10 { + // ARM64_RELOC_ADDEND pending_addend = reloc.r_symbolnum as i64; continue; } - if reloc.r_type == 1 { // ARM64_RELOC_SUBTRACTOR (part of a pair) + if reloc.r_type == 1 { + // ARM64_RELOC_SUBTRACTOR (part of a pair) // Store the subtrahend symbol address for the next UNSIGNED reloc. let sub_addr = if reloc.r_extern { let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); @@ -1251,22 +1295,36 @@ fn apply_relocations( let n_sect = sym.n_sect(); if n_sect > 0 { let sec_idx = n_sect as usize - 1; - let sec_out = obj.section_resolutions.get(sec_idx) - .and_then(|r| r.address()).unwrap_or(0); - let sec_in = obj.object.sections.get(sec_idx) - .map(|s| s.addr.get(le)).unwrap_or(0); + let sec_out = obj + .section_resolutions + .get(sec_idx) + .and_then(|r| r.address()) + .unwrap_or(0); + let sec_in = obj + .object + .sections + .get(sec_idx) + .map(|s| s.addr.get(le)) + .unwrap_or(0); sec_out + sym.n_value(le).wrapping_sub(sec_in) - } else { 0 } - } else { 0 } + } else { + 0 + } + } else { + 0 + } } } } else { let sec_ord = reloc.r_symbolnum as usize; if sec_ord > 0 { - obj.section_resolutions.get(sec_ord - 1) + obj.section_resolutions + .get(sec_ord - 1) .and_then(|r| r.address()) .unwrap_or(0) - } else { 0 } + } else { + 0 + } }; pending_subtrahend = Some(sub_addr); continue; @@ -1285,7 +1343,7 @@ fn apply_relocations( let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); let sym_id = obj.symbol_id_range.input_to_id(sym_idx); match layout.merged_symbol_resolution(sym_id) { - Some(res) if res.raw_value != 0 => ( + Some(res) if res.raw_value != 0 || res.format_specific.plt_address.is_some() => ( res.raw_value, res.format_specific.got_address, res.format_specific.plt_address, @@ -1297,11 +1355,12 @@ fn apply_relocations( use object::read::macho::Nlist as _; let fallback = obj.object.symbols.symbol(sym_idx).ok().and_then(|sym| { let n_sect = sym.n_sect(); - if n_sect == 0 { return None; } + if n_sect == 0 { + return None; + } let sec_idx = n_sect as usize - 1; let sec_out = obj.section_resolutions.get(sec_idx)?.address()?; - let sec_in = obj.object.sections.get(sec_idx) - .map(|s| s.addr.get(le))?; + let sec_in = obj.object.sections.get(sec_idx).map(|s| s.addr.get(le))?; Some(sec_out + sym.n_value(le).wrapping_sub(sec_in)) }); if let Some(addr) = fallback { @@ -1309,7 +1368,11 @@ fn apply_relocations( let plt = other.and_then(|r| r.format_specific.plt_address); (addr, got, plt) } else if let Some(res) = other { - (res.raw_value, res.format_specific.got_address, res.format_specific.plt_address) + ( + res.raw_value, + res.format_specific.got_address, + res.format_specific.plt_address, + ) } else { continue; } @@ -1403,7 +1466,10 @@ fn apply_relocations( // LSDA pointer). Always use the direct symbol address, never the GOT // address — GOT indirection is expressed via POINTER_TO_GOT (type 7). let existing = i64::from_le_bytes( - out[patch_file_offset..patch_file_offset + 8].try_into().unwrap()); + out[patch_file_offset..patch_file_offset + 8] + .try_into() + .unwrap(), + ); let val = target_addr as i64 - sub_addr as i64 + existing; out[patch_file_offset..patch_file_offset + 8] .copy_from_slice(&val.to_le_bytes()); @@ -1719,7 +1785,11 @@ fn collect_compact_unwind_entries(layout: &Layout<'_, MachO>) -> Vec) -> Vec u64 { *pos += 1; val |= ((b & 0x7F) as u64) << shift; shift += 7; - if b & 0x80 == 0 { break; } + if b & 0x80 == 0 { + break; + } } val } @@ -1873,8 +1948,12 @@ fn parse_cie_aug(data: &[u8], cie_pos: usize, eh_frame_vm_addr: u64) -> CieAugIn let mut pos = cie_pos + 9; // Find augmentation string (null-terminated). let aug_start = pos; - while pos < data.len() && data[pos] != 0 { pos += 1; } - if pos >= data.len() { return info; } + while pos < data.len() && data[pos] != 0 { + pos += 1; + } + if pos >= data.len() { + return info; + } let aug_bytes = &data[aug_start..pos]; pos += 1; // skip null terminator @@ -1888,23 +1967,33 @@ fn parse_cie_aug(data: &[u8], cie_pos: usize, eh_frame_vm_addr: u64) -> CieAugIn read_uleb128(data, &mut pos); // code_alignment // SLEB128 (just skip as if ULEB128 since we only care about the byte count) loop { - if pos >= data.len() { return info; } - let b = data[pos]; pos += 1; - if b & 0x80 == 0 { break; } + if pos >= data.len() { + return info; + } + let b = data[pos]; + pos += 1; + if b & 0x80 == 0 { + break; + } } read_uleb128(data, &mut pos); // ra_register - if !has_z { return info; } + if !has_z { + return info; + } let aug_data_len = read_uleb128(data, &mut pos) as usize; let aug_data_start = pos; // Augmentation data contains per-letter info in augstr order (skipping 'z'). let mut ap = aug_data_start; for &ch in aug_bytes { - if ap >= aug_data_start + aug_data_len { break; } + if ap >= aug_data_start + aug_data_len { + break; + } match ch { b'P' if has_p => { - let pers_enc = data[ap]; ap += 1; + let pers_enc = data[ap]; + ap += 1; let sz = eh_ptr_size(pers_enc) as usize; if ap + sz <= data.len() { // Personality ptr is PC-relative from the field address. @@ -1918,11 +2007,13 @@ fn parse_cie_aug(data: &[u8], cie_pos: usize, eh_frame_vm_addr: u64) -> CieAugIn ap += sz; } b'L' if has_l => { - let lsda_enc = data[ap]; ap += 1; + let lsda_enc = data[ap]; + ap += 1; info.lsda_ptr_size = eh_ptr_size(lsda_enc); } b'R' if has_r => { - let fde_enc = data[ap]; ap += 1; + let fde_enc = data[ap]; + ap += 1; info.fde_ptr_size = eh_ptr_size(fde_enc); } _ => {} @@ -1930,8 +2021,12 @@ fn parse_cie_aug(data: &[u8], cie_pos: usize, eh_frame_vm_addr: u64) -> CieAugIn } // Default pointer size = 8 for 64-bit Mach-O. - if info.fde_ptr_size == 0 { info.fde_ptr_size = 8; } - if info.lsda_ptr_size == 0 { info.lsda_ptr_size = 8; } + if info.fde_ptr_size == 0 { + info.fde_ptr_size = 8; + } + if info.lsda_ptr_size == 0 { + info.lsda_ptr_size = 8; + } info } @@ -1963,9 +2058,13 @@ fn scan_eh_frame_fde_offsets( let Ok(prefix) = EhFrameEntryPrefix::read_from_bytes(&data[pos..pos + PREFIX_LEN]) else { break; }; - if prefix.length == 0 { break; } + if prefix.length == 0 { + break; + } let size = 4 + prefix.length as usize; - if pos + size > data.len() { break; } + if pos + size > data.len() { + break; + } if prefix.cie_id == 0 { // CIE: parse augmentation. @@ -1982,7 +2081,10 @@ fn scan_eh_frame_fde_offsets( // pc_begin at byte 8, PC-relative signed value of ptr_size bytes. let pc_begin_field_vm = eh_frame_vm_addr + pos as u64 + 8; let func_vm = read_pcrel(data, pos + 8, ptr_size, pc_begin_field_vm); - if func_vm == 0 { pos += size; continue; } + if func_vm == 0 { + pos += size; + continue; + } // pc_range at byte 8+ptr_size (absolute, not PC-relative). // Skip it (we don't use pc_range for __unwind_info). @@ -1993,7 +2095,10 @@ fn scan_eh_frame_fde_offsets( let aug_len = read_uleb128(data, &mut ap) as usize; // LSDA pointer at start of aug_data (if CIE has 'L'). - let lsda_vm = if cie_aug.has_lsda && cie_aug.lsda_ptr_size > 0 && ap + cie_aug.lsda_ptr_size as usize <= data.len() { + let lsda_vm = if cie_aug.has_lsda + && cie_aug.lsda_ptr_size > 0 + && ap + cie_aug.lsda_ptr_size as usize <= data.len() + { let lsda_field_vm = eh_frame_vm_addr + ap as u64; read_pcrel(data, ap, cie_aug.lsda_ptr_size as usize, lsda_field_vm) } else { @@ -2001,11 +2106,14 @@ fn scan_eh_frame_fde_offsets( }; let _ = aug_len; - map.insert(func_vm, EhFrameFdeInfo { - section_offset: pos as u32, - lsda_vm, - pers_got_vm: cie_aug.pers_got_vm, - }); + map.insert( + func_vm, + EhFrameFdeInfo { + section_offset: pos as u32, + lsda_vm, + pers_got_vm: cie_aug.pers_got_vm, + }, + ); } pos += size; @@ -2046,24 +2154,33 @@ fn build_unwind_info_section( // augmentation data in __eh_frame, NOT from the __unwind_info LSDA array. // So we omit UNWIND_HAS_LSDA and the LSDA array to save space. for (&func_vm, fde_info) in fde_map { - if fde_info.pers_got_vm == 0 { continue; } // no personality → skip + if fde_info.pers_got_vm == 0 { + continue; + } // no personality → skip // Personality index (1-based into the personality array we build). - let pers_idx = if let Some(pos) = personalities.iter().position(|&g| g == fde_info.pers_got_vm) { + let pers_idx = if let Some(pos) = personalities + .iter() + .position(|&g| g == fde_info.pers_got_vm) + { pos + 1 } else { personalities.push(fde_info.pers_got_vm); personalities.len() }; - let enc = UNWIND_ARM64_DWARF | fde_info.section_offset - | (((pers_idx as u32) & 3) << 28); + let enc = UNWIND_ARM64_DWARF | fde_info.section_offset | (((pers_idx as u32) & 3) << 28); all_entries.push((func_vm, 0u32, enc)); } let pers_count = all_entries.len(); for e in plain_entries { - if fde_map.get(&e.func_addr).is_some_and(|f| f.pers_got_vm != 0) { continue; } + if fde_map + .get(&e.func_addr) + .is_some_and(|f| f.pers_got_vm != 0) + { + continue; + } all_entries.push((e.func_addr, e.func_size, e.encoding)); } @@ -2093,7 +2210,9 @@ fn build_unwind_info_section( tracing::debug!( "compact_unwind: building __unwind_info: {} entries ({} pers), {} personalities", - all_entries.len(), pers_count, personalities.len() + all_entries.len(), + pers_count, + personalities.len() ); // DWARF-mode entries all have unique encodings (different FDE offsets) so @@ -2137,12 +2256,12 @@ fn build_unwind_info_section( } // Header - wu32!(0, 1u32); // version - wu32!(4, ce_off); // commonEncodingsArraySectionOffset - wu32!(8, 0u32); // commonEncodingsArrayCount (none) - wu32!(12, pers_off); // personalityArraySectionOffset - wu32!(16, n_pers); // personalityArrayCount - wu32!(20, idx_off); // indexSectionOffset + wu32!(0, 1u32); // version + wu32!(4, ce_off); // commonEncodingsArraySectionOffset + wu32!(8, 0u32); // commonEncodingsArrayCount (none) + wu32!(12, pers_off); // personalityArraySectionOffset + wu32!(16, n_pers); // personalityArrayCount + wu32!(20, idx_off); // indexSectionOffset wu32!(24, num_pages as u32 + 1); // indexCount (includes sentinel) // Personality array: 4-byte offsets from TEXT base to GOT slots. @@ -2162,19 +2281,19 @@ fn build_unwind_info_section( // Index entry (12 bytes) let ie = idx_off as usize + page * 12; - wu32!(ie, first_fn_off); - wu32!(ie + 4, sl_off as u32); // secondLevelPagesSectionOffset - wu32!(ie + 8, lsda_off); // lsdaIndexArraySectionOffset + wu32!(ie, first_fn_off); + wu32!(ie + 4, sl_off as u32); // secondLevelPagesSectionOffset + wu32!(ie + 8, lsda_off); // lsdaIndexArraySectionOffset // Regular second-level page header (8 bytes) - wu32!(sl_off, 2u32); // kind = UNWIND_SECOND_LEVEL_REGULAR - wu16!(sl_off + 4, 8u16); // entryPageOffset - wu16!(sl_off + 6, page_entries.len() as u16); // entryCount + wu32!(sl_off, 2u32); // kind = UNWIND_SECOND_LEVEL_REGULAR + wu16!(sl_off + 4, 8u16); // entryPageOffset + wu16!(sl_off + 6, page_entries.len() as u16); // entryCount // Entries (8 bytes each: funcOffset u32 + encoding u32) for (j, &(fa, _, enc)) in page_entries.iter().enumerate() { let eo = sl_off + 8 + j * 8; - wu32!(eo, (fa - text_base) as u32); + wu32!(eo, (fa - text_base) as u32); wu32!(eo + 4, enc); } } @@ -2183,8 +2302,8 @@ fn build_unwind_info_section( let (last_fa, last_fs, _) = *all_entries.last().unwrap(); let sentinel_fn_off = (last_fa - text_base + last_fs as u64) as u32; let sie = idx_off as usize + num_pages * 12; - wu32!(sie, sentinel_fn_off); - wu32!(sie + 4, 0u32); // secondLevelPagesSectionOffset = 0 (sentinel) + wu32!(sie, sentinel_fn_off); + wu32!(sie + 4, 0u32); // secondLevelPagesSectionOffset = 0 (sentinel) wu32!(sie + 8, lsda_off); // lsdaIndexArraySectionOffset out @@ -2240,6 +2359,10 @@ fn write_headers( let tbss_layout = layout.section_layouts.get(output_section_id::TBSS); let has_tlv = tdata_layout.mem_size > 0 || tbss_layout.mem_size > 0; let has_tvars = has_tlv; + let plt_layout = layout.section_layouts.get(output_section_id::PLT_GOT); + let has_stubs = plt_layout.mem_size > 0; + let got_layout = layout.section_layouts.get(output_section_id::GOT); + let has_got = got_layout.mem_size > 0; // Scan for .rustc section (proc-macro metadata) before computing cmd sizes let mut rustc_addr = 0u64; @@ -2309,6 +2432,7 @@ fn write_headers( let has_unwind_info = unwind_info_size > 0; let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 } + + if has_stubs { 1 } else { 0 } + if has_eh_frame { 1 } else { 0 } + if has_unwind_info { 1 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT @@ -2322,6 +2446,9 @@ fn write_headers( if has_init_array { data_nsects += 1; } + if has_got { + data_nsects += 1; + } add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); } add_cmd(&mut ncmds, &mut cmdsize, 72); // LINKEDIT @@ -2406,9 +2533,25 @@ fn write_headers( w.u32(0); w.u32(0); } + if has_stubs { + let stubs_foff = + vm_addr_to_file_offset(plt_layout.mem_offset, mappings).unwrap_or(0) as u32; + w.name16(b"__stubs"); + w.name16(b"__TEXT"); + w.u64(plt_layout.mem_offset); + w.u64(plt_layout.mem_size); + w.u32(stubs_foff); + w.u32(2); // align 2^2 = 4 + w.u32(0); // reloff + w.u32(0); // nreloc + w.u32(0x80000408); // S_SYMBOL_STUBS | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS + w.u32(0); // reserved1 (indirect symbol table index, 0 for now) + w.u32(12); // reserved2 = stub size + w.u32(0); // reserved3 + } if has_eh_frame { - let eh_foff = vm_addr_to_file_offset(eh_frame_layout.mem_offset, mappings) - .unwrap_or(0) as u32; + let eh_foff = + vm_addr_to_file_offset(eh_frame_layout.mem_offset, mappings).unwrap_or(0) as u32; w.name16(b"__eh_frame"); w.name16(b"__TEXT"); w.u64(eh_frame_layout.mem_offset); @@ -2446,6 +2589,9 @@ fn write_headers( if has_init_array { nsects += 1; } + if has_got { + nsects += 1; + } let data_cmd_size = 72 + 80 * nsects; w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); @@ -2556,6 +2702,22 @@ fn write_headers( w.u32(0); w.u32(0); } + if has_got { + let got_foff = vm_addr_to_file_offset(got_layout.mem_offset, mappings) + .unwrap_or(data_fileoff as usize) as u32; + w.name16(b"__got"); + w.name16(b"__DATA"); + w.u64(got_layout.mem_offset); + w.u64(got_layout.mem_size); + w.u32(got_foff); + w.u32(3); // align 2^3 = 8 + w.u32(0); // reloff + w.u32(0); // nreloc + w.u32(0x06); // S_NON_LAZY_SYMBOL_POINTERS + w.u32(0); // reserved1 + w.u32(0); // reserved2 + w.u32(0); // reserved3 + } if has_init_array { let ia_foff = vm_addr_to_file_offset(init_array_layout.mem_offset, mappings) .unwrap_or(data_fileoff as usize) as u32; diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 9c006e21e..3044fd0fe 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -126,11 +126,17 @@ fn parse_config(test_dir: &Path, primary: &Path) -> Result { let parts: Vec<&str> = value.splitn(2, ':').collect(); let (name, sources) = if parts.len() == 2 { - (parts[0].to_string(), parts[1].split(',').map(|s| s.trim().to_string()).collect()) + ( + parts[0].to_string(), + parts[1].split(',').map(|s| s.trim().to_string()).collect(), + ) } else { // Archive:src.c — auto-name the archive let src = value.trim().to_string(); - let stem = src.strip_suffix(".c").or(src.strip_suffix(".cc")).unwrap_or(&src); + let stem = src + .strip_suffix(".c") + .or(src.strip_suffix(".cc")) + .unwrap_or(&src); (format!("{stem}.a"), vec![src]) }; cfg.archives.push((name, sources)); @@ -143,7 +149,9 @@ fn parse_config(test_dir: &Path, primary: &Path) -> Result cfg.use_clang_driver = true, "Contains" => cfg.contains.push(value.to_string()), "DoesNotContain" => cfg.does_not_contain.push(value.to_string()), - "ExpectSym" => cfg.expect_syms.push(value.split_whitespace().next().unwrap_or(value).to_string()), + "ExpectSym" => cfg + .expect_syms + .push(value.split_whitespace().next().unwrap_or(value).to_string()), "NoSym" => cfg.no_syms.push(value.trim().to_string()), "Ignore" => cfg.ignore_reason = Some(value.to_string()), _ => {} // Ignore unknown directives for forward-compatibility. @@ -198,7 +206,8 @@ fn run_test( let output = build_dir.join(test_name); let mut cmd = Command::new("rustc"); cmd.arg(primary) - .arg("-o").arg(&output) + .arg("-o") + .arg(&output) .arg("-Clinker=clang") .arg(format!("-Clink-arg=-fuse-ld={}", wild_bin.display())); for arg in &config.link_args { @@ -208,13 +217,19 @@ fn run_test( if !result.status.success() { let stderr = String::from_utf8_lossy(&result.stderr); if let Some(ref pattern) = config.expect_error { - if stderr.contains(pattern) { return Ok(()); } - return Err(format!("Expected error matching '{pattern}', got:\n{stderr}")); + if stderr.contains(pattern) { + return Ok(()); + } + return Err(format!( + "Expected error matching '{pattern}', got:\n{stderr}" + )); } return Err(format!("rustc failed:\n{stderr}")); } if config.run_enabled { - let run = Command::new(&output).output().map_err(|e| format!("run: {e}"))?; + let run = Command::new(&output) + .output() + .map_err(|e| format!("run: {e}"))?; let code = run.status.code().unwrap_or(-1); if code != 42 { return Err(format!("Expected exit code 42, got {code}")); @@ -255,7 +270,10 @@ fn run_test( } let ar_result = ar_cmd.output().map_err(|e| format!("ar: {e}"))?; if !ar_result.status.success() { - return Err(format!("ar failed: {}", String::from_utf8_lossy(&ar_result.stderr))); + return Err(format!( + "ar failed: {}", + String::from_utf8_lossy(&ar_result.stderr) + )); } objects.push(archive_path); } @@ -273,15 +291,20 @@ fn run_test( .arg("-dynamiclib") .arg(format!("-fuse-ld={}", wild_bin.display())) .arg(&src) - .arg("-o").arg(&dylib_path) + .arg("-o") + .arg(&dylib_path) .arg(format!("-Wl,-install_name,@rpath/lib{stem}.dylib")); for arg in &config.comp_args { dylib_cmd.arg(arg); } - let result = dylib_cmd.output().map_err(|e| format!("dylib build: {e}"))?; + let result = dylib_cmd + .output() + .map_err(|e| format!("dylib build: {e}"))?; if !result.status.success() { let stderr = String::from_utf8_lossy(&result.stderr); - return Err(format!("Failed to build dylib from {lib_src_name}:\n{stderr}")); + return Err(format!( + "Failed to build dylib from {lib_src_name}:\n{stderr}" + )); } extra_link_args.push(format!("-L{}", build_dir.display())); extra_link_args.push(format!("-l{stem}")); @@ -363,21 +386,24 @@ fn run_test( use object::read::ObjectSymbol as _; let obj_file = object::File::parse(&*binary) .map_err(|e| format!("Failed to parse output binary: {e}"))?; - let sym_names: Vec<&str> = obj_file - .symbols() - .filter_map(|s| s.name().ok()) - .collect(); + let sym_names: Vec<&str> = obj_file.symbols().filter_map(|s| s.name().ok()).collect(); for expected in &config.expect_syms { // Mach-O adds a leading underscore to C symbols. let with_underscore = format!("_{expected}"); - if !sym_names.iter().any(|n| *n == expected.as_str() || *n == with_underscore) { + if !sym_names + .iter() + .any(|n| *n == expected.as_str() || *n == with_underscore) + { return Err(format!("Expected symbol `{expected}` not found in output")); } } for absent in &config.no_syms { let with_underscore = format!("_{absent}"); - if sym_names.iter().any(|n| *n == absent.as_str() || *n == with_underscore) { + if sym_names + .iter() + .any(|n| *n == absent.as_str() || *n == with_underscore) + { return Err(format!("Symbol `{absent}` should not be in output")); } } From 4ad80c69c0d8d326aa2242673e06bc8d3b09952a Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 8 Apr 2026 11:29:21 +0100 Subject: [PATCH 11/21] feat(macho): stubs, GOT, symtab, weak imports, .tbd parsing, exceptions, partial linking Enable remaining Mach-O integration tests (41/41 pass, 0 ignored): - Emit __stubs and __got section headers; fix PLT/GOT resolution for undefined symbols from dylibs - Populate LC_SYMTAB for executables (backtraces, absolute symbols) - Set weak_import bit in chained fixups for N_WEAK_REF symbols - Parse .tbd files via text-stub-library to detect truly undefined symbols vs dylib imports - Emit __gcc_except_tab section header; scan __compact_unwind for personality GOT entries; add LSDA descriptors to __unwind_info - Implement partial linking (-r) producing MH_OBJECT output with merged sections, remapped relocations and symbol tables - Remove unused eh_frame generic parsing abstraction Signed-off-by: Giles Cope --- Cargo.lock | 52 ++ libwild/Cargo.toml | 1 + libwild/src/args/macho.rs | 97 ++- libwild/src/eh_frame.rs | 128 ---- libwild/src/macho.rs | 87 ++- libwild/src/macho_writer.rs | 641 +++++++++++++++++- libwild/src/symbol_db.rs | 16 + wild/tests/macho_integration_tests.rs | 30 + .../macho/absolute-symbol/absolute-symbol.c | 2 - .../sources/macho/exception/exception.cc | 1 - .../sources/macho/relocatables/relocatables.c | 4 +- .../undefined-symbol-error.c | 2 +- .../undefined-weak-and-strong.c | 2 +- .../undefined-weak-sym/undefined-weak-sym.c | 1 - 14 files changed, 907 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22803858c..a1775846f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -925,6 +925,7 @@ dependencies = [ "symbolic-common", "symbolic-demangle", "tempfile", + "text-stub-library", "thread_local", "tracing", "tracing-subscriber", @@ -934,6 +935,12 @@ dependencies = [ "zstd", ] +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linker-diff" version = "0.8.0" @@ -1630,6 +1637,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1707,6 +1720,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1888,6 +1914,17 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "text-stub-library" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48070939e80c2662b5dd403a0b09cb97e8467a248d67e373e23f85dbdacd882" +dependencies = [ + "serde", + "serde_yaml", + "yaml-rust", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -2064,6 +2101,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "utf8parse" version = "0.2.2" @@ -2383,6 +2426,15 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "zerocopy" version = "0.8.48" diff --git a/libwild/Cargo.toml b/libwild/Cargo.toml index 94d4119c8..524396832 100644 --- a/libwild/Cargo.toml +++ b/libwild/Cargo.toml @@ -52,6 +52,7 @@ uuid = { workspace = true } winnow = { workspace = true } zerocopy = { workspace = true } zstd = { workspace = true } +text-stub-library = "0.9.0" [target.'cfg(all(target_os = "linux", any(target_arch = "x86_64", target_arch = "aarch64")))'.dependencies] perf-event = { workspace = true } diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index 2b656dc7b..37cb8457d 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -20,12 +20,16 @@ pub struct MachOArgs { pub(crate) syslibroot: Option>, pub(crate) entry_symbol: Option>, pub(crate) is_dylib: bool, + pub(crate) is_relocatable: bool, #[allow(dead_code)] pub(crate) install_name: Option>, /// Additional dylibs to emit LC_LOAD_DYLIB for (from -l flags resolving to .tbd stubs). pub(crate) extra_dylibs: Vec>, /// Symbols to force as undefined (-u flag), triggering archive member loading. pub(crate) force_undefined: Vec, + /// Symbols exported by linked dylibs (from .tbd parsing). Used to distinguish + /// undefined symbols that are dylib imports from truly missing symbols. + pub(crate) dylib_symbols: std::collections::HashSet>, } impl MachOArgs { @@ -47,9 +51,11 @@ impl Default for MachOArgs { syslibroot: None, entry_symbol: Some(b"_main".to_vec()), is_dylib: false, + is_relocatable: false, install_name: None, extra_dylibs: Vec::new(), force_undefined: Vec::new(), + dylib_symbols: Default::default(), } } } @@ -113,7 +119,11 @@ impl platform::Args for MachOArgs { } fn should_output_executable(&self) -> bool { - !self.is_dylib + !self.is_dylib && !self.is_relocatable + } + + fn should_output_partial_object(&self) -> bool { + self.is_relocatable } } @@ -261,6 +271,11 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( args.entry_symbol = None; // dylibs have no entry point return Ok(()); } + "-r" => { + args.is_relocatable = true; + args.entry_symbol = None; + return Ok(()); + } _ => {} } @@ -287,8 +302,34 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( if let Some(lib) = arg.strip_prefix("-l") { if !lib.is_empty() { // On macOS, libSystem is implicitly linked (we emit LC_LOAD_DYLIB for it). - // Skip it and other system dylibs that we handle implicitly. + // Skip it and other system dylibs that we handle implicitly, but still + // parse their .tbd to know which symbols they export. if lib == "System" || lib == "c" || lib == "m" || lib == "pthread" { + // Still parse .tbd for symbol resolution (including re-exported libs) + let mut search_paths: Vec> = args.lib_search_paths.clone(); + if let Some(ref root) = args.syslibroot { + search_paths.push(Box::from(root.join("usr/lib"))); + } + for dir in &search_paths { + let tbd_path = dir.join(format!("lib{lib}.tbd")); + if tbd_path.exists() { + collect_tbd_symbols(&tbd_path, &mut args.dylib_symbols); + // Also collect from re-exported libraries (e.g. libSystem + // re-exports libdyld, libsystem_c, etc. from system/ subdir) + let system_dir = dir.join("system"); + if system_dir.is_dir() { + if let Ok(entries) = std::fs::read_dir(&system_dir) { + for entry in entries.flatten() { + let p = entry.path(); + if p.extension().map_or(false, |e| e == "tbd") { + collect_tbd_symbols(&p, &mut args.dylib_symbols); + } + } + } + } + break; + } + } return Ok(()); } // Try to find the library on the search path, including syslibroot @@ -312,6 +353,7 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( args.extra_dylibs.push(dylib_path); } } + collect_tbd_symbols(&path, &mut args.dylib_symbols); found = true; break; } @@ -378,3 +420,54 @@ fn parse_tbd_install_name(path: &Path) -> Option> { } None } + +/// Collect exported symbols from a .tbd file into the given set. +fn collect_tbd_symbols(path: &Path, symbols: &mut std::collections::HashSet>) { + let content = match std::fs::read_to_string(path) { + Ok(c) => c, + Err(_) => return, + }; + let records = match text_stub_library::parse_str(&content) { + Ok(r) => r, + Err(_) => return, + }; + for record in &records { + match record { + text_stub_library::TbdVersionedRecord::V4(v4) => { + let is_arm64 = |targets: &[String]| -> bool { + targets.is_empty() + || targets + .iter() + .any(|t| t.starts_with("arm64-") || t.starts_with("arm64e-")) + }; + for exp in &v4.exports { + if !is_arm64(&exp.targets) { + continue; + } + for sym in &exp.symbols { + symbols.insert(sym.as_bytes().to_vec()); + } + for sym in &exp.weak_symbols { + symbols.insert(sym.as_bytes().to_vec()); + } + } + for exp in &v4.re_exports { + if !is_arm64(&exp.targets) { + continue; + } + for sym in &exp.symbols { + symbols.insert(sym.as_bytes().to_vec()); + } + } + } + text_stub_library::TbdVersionedRecord::V3(v3) => { + for exp in &v3.exports { + for sym in &exp.symbols { + symbols.insert(sym.as_bytes().to_vec()); + } + } + } + _ => {} + } + } +} diff --git a/libwild/src/eh_frame.rs b/libwild/src/eh_frame.rs index c56870f9e..18d181a13 100644 --- a/libwild/src/eh_frame.rs +++ b/libwild/src/eh_frame.rs @@ -37,17 +37,6 @@ pub(crate) struct ExceptionFrame<'data, R: Relocation> { pub(crate) previous_frame_for_section: Option, } -/// A lightweight exception frame descriptor returned by the generic parser. -/// Uses index ranges rather than subsequences to avoid type-system issues. -pub(crate) struct RawExceptionFrame { - /// Range of relocation indices in the parent sequence. - pub(crate) rel_range: std::ops::Range, - /// Number of bytes for this frame. - pub(crate) frame_size: u32, - /// Previous frame for the same section. - pub(crate) previous_frame_for_section: Option, -} - /// Accumulated sizes for eh_frame output. pub(crate) struct EhFrameSizes { pub(crate) num_frames: u64, @@ -70,120 +59,3 @@ pub(crate) struct CieAtOffset<'data> { pub(crate) cie: Cie<'data>, } -/// Platform-specific callbacks for __eh_frame parsing. -/// -/// `R` is the relocation type yielded by the sequence iterator. -pub(crate) trait EhFrameHandler<'data, R: Relocation> { - /// Process a relocation found inside a CIE entry. - /// Returns the resolved symbol ID for dedup tracking, or None if the reloc - /// has no symbol (which makes the CIE ineligible for deduplication). - fn process_cie_relocation(&mut self, rel: &R) -> crate::error::Result>; - - /// Given the pc_begin relocation of an FDE, return the section index of the - /// target function. Returns None if the FDE should be discarded. - fn fde_target_section(&self, rel: &R) -> crate::error::Result>; - - /// Store a parsed CIE. - fn store_cie(&mut self, offset: u32, cie: Cie<'data>); - - /// Link an FDE to the section it covers. Returns `Some(previous_frame)` if - /// the section is eligible (building the linked list), or None to skip. - fn link_fde_to_section( - &mut self, - section_index: object::SectionIndex, - ) -> Option>; -} - -/// Parse __eh_frame data into CIEs and FDEs using a platform-specific handler. -/// -/// Returns raw exception frame descriptors (with relocation index ranges) and -/// the count of trailing bytes. The caller converts `RawExceptionFrame` into -/// platform-specific `ExceptionFrame` by extracting relocation subsequences. -pub(crate) fn parse_eh_frame_entries<'data, R, H>( - handler: &mut H, - data: &'data [u8], - rel_iter: &mut std::iter::Peekable>, -) -> crate::error::Result<(Vec, usize)> -where - R: Relocation, - H: EhFrameHandler<'data, R>, -{ - use std::mem::size_of; - use std::mem::size_of_val; - - const PREFIX_LEN: usize = size_of::(); - - let mut offset = 0; - let mut exception_frames = Vec::new(); - - while offset + PREFIX_LEN <= data.len() { - let prefix = - EhFrameEntryPrefix::read_from_bytes(&data[offset..offset + PREFIX_LEN]).unwrap(); - let size = size_of_val(&prefix.length) + prefix.length as usize; - let next_offset = offset + size; - - if next_offset > data.len() { - crate::bail!("Invalid .eh_frame data"); - } - - if prefix.cie_id == 0 { - // CIE - let mut referenced_symbols: SmallVec<[SymbolId; 1]> = Default::default(); - let mut eligible_for_deduplication = true; - - while let Some((_, rel)) = rel_iter.peek() { - if rel.offset() >= next_offset as u64 { - break; - } - - match handler.process_cie_relocation(rel)? { - Some(sym_id) => referenced_symbols.push(sym_id), - None => eligible_for_deduplication = false, - } - rel_iter.next(); - } - - handler.store_cie( - offset as u32, - Cie { - bytes: &data[offset..next_offset], - eligible_for_deduplication, - referenced_symbols, - }, - ); - } else { - // FDE - let mut section_index = None; - let rel_start_index = rel_iter.peek().map_or(0, |(i, _)| *i); - let mut rel_end_index = 0; - - while let Some((rel_index, rel)) = rel_iter.peek() { - if rel.offset() < next_offset as u64 { - let is_pc_begin = (rel.offset() as usize - offset) == FDE_PC_BEGIN_OFFSET; - - if is_pc_begin { - section_index = handler.fde_target_section(rel)?; - } - rel_end_index = rel_index + 1; - rel_iter.next(); - } else { - break; - } - } - - if let Some(section_index) = section_index - && let Some(previous_frame) = handler.link_fde_to_section(section_index) - { - exception_frames.push(RawExceptionFrame { - rel_range: rel_start_index..rel_end_index, - frame_size: size as u32, - previous_frame_for_section: previous_frame, - }); - } - } - offset = next_offset; - } - - let trailing_bytes = data.len() - offset; - Ok((exception_frames, trailing_bytes)) -} diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 02766bb0e..67d181499 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -933,8 +933,10 @@ impl platform::Platform for MachO { } fn start_memory_address(output_kind: crate::output_kind::OutputKind) -> u64 { - if output_kind == crate::output_kind::OutputKind::SharedObject { - 0 // dylibs have no PAGEZERO + if output_kind == crate::output_kind::OutputKind::SharedObject + || output_kind.is_relocatable() + { + 0 // dylibs and relocatables have no PAGEZERO } else { 0x1_0000_0000 // PAGEZERO size for executables } @@ -1065,7 +1067,88 @@ impl platform::Platform for MachO { if !previous_flags.has_resolution() { queue.send_symbol_request::(symbol_id, resources, scope); } + + // Check for undefined symbol errors: strong references to symbols + // not found in any input or linked dylib. Only check when we have + // .tbd symbol data (meaning syslibroot was provided and we can + // distinguish dylib imports from truly missing symbols). + if is_def_undef && !resources.symbol_db.args.dylib_symbols.is_empty() { + use object::read::macho::Nlist as _; + let local_sym = state.object.symbols.symbol(sym_idx).ok(); + let is_weak = local_sym.map_or(false, |s| { + (s.n_desc(le) & (macho::N_WEAK_DEF | macho::N_WEAK_REF)) != 0 + }); + if !is_weak { + let sym_name = resources.symbol_db.symbol_name(symbol_id).ok(); + let in_dylib = sym_name.map_or(false, |n| { + resources.symbol_db.args.dylib_symbols.contains(n.bytes()) + }); + // If extra dylibs are linked (e.g. user .dylib files we don't + // parse symbols from), assume the symbol might come from them. + let has_unparsed_dylibs = !resources.symbol_db.args.extra_dylibs.is_empty(); + if !in_dylib && !has_unparsed_dylibs { + let sym_display = resources.symbol_db.symbol_name_for_display(symbol_id); + resources.report_error(crate::error!( + "Undefined symbol {sym_display}, referenced by {}", + state.input, + )); + } + } + } + } + + // Also scan __compact_unwind for personality function references that + // need GOT entries. The personality reloc is at offset 16 within each + // 32-byte entry. We request GOT for undefined personality symbols so + // they get GOT slots allocated during layout. + { + use object::read::macho::MachHeader as _; + use object::read::macho::Segment as _; + if let Ok(header) = + object::macho::MachHeader64::::parse(state.object.data, 0) + { + if let Ok(mut cmds) = header.load_commands(le, state.object.data, 0) { + while let Ok(Some(cmd)) = cmds.next() { + let Ok(Some((seg, seg_data))) = cmd.segment_64() else { + continue; + }; + let Ok(sections) = seg.sections(le, seg_data) else { + continue; + }; + for sec in sections { + let sec_segname = crate::macho::trim_nul(&sec.segname); + let sectname = crate::macho::trim_nul(&sec.sectname); + if sec_segname != b"__LD" || sectname != b"__compact_unwind" { + continue; + } + let relocs = match sec.relocations(le, state.object.data) { + Ok(r) => r, + Err(_) => continue, + }; + for r in relocs { + let ri = r.info(le); + if !ri.r_extern || ri.r_type != 0 { + continue; + } + // Personality is at offset 16 within each 32-byte entry. + if ri.r_address as usize % 32 != 16 { + continue; + } + let sym_idx = object::SymbolIndex(ri.r_symbolnum as usize); + let local_id = state.symbol_id_range.input_to_id(sym_idx); + let sym_id = resources.symbol_db.definition(local_id); + let atomic = &resources.per_symbol_flags.get_atomic(sym_id); + let prev = atomic.fetch_or(crate::value_flags::ValueFlags::GOT); + if !prev.has_resolution() { + queue.send_symbol_request::(sym_id, resources, scope); + } + } + } + } + } + } } + Ok(()) } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 38df7bbbb..c07ab60da 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -41,6 +41,10 @@ const DYLD_PATH: &[u8] = b"/usr/lib/dyld"; const LIBSYSTEM_PATH: &[u8] = b"/usr/lib/libSystem.B.dylib"; pub(crate) fn write_direct>(layout: &Layout<'_, MachO>) -> Result { + if layout.symbol_db.args.is_relocatable { + return write_relocatable_object(layout); + } + // Collect compact-unwind entries from all input objects. let plain_entries = collect_compact_unwind_entries(layout); @@ -63,12 +67,18 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> // file allocation — we can place __unwind_info there without extending // TEXT vmsize or shifting DATA vmaddr. let text_content_end = { - // Find the end of the last TEXT-segment section: EH_FRAME > PLT_GOT > TEXT + // Find the end of the last TEXT-segment section: + // EH_FRAME > GCC_EXCEPT_TABLE > PLT_GOT > TEXT let eh = layout.section_layouts.get(output_section_id::EH_FRAME); + let ge = layout + .section_layouts + .get(output_section_id::GCC_EXCEPT_TABLE); let plt = layout.section_layouts.get(output_section_id::PLT_GOT); let t = layout.section_layouts.get(output_section_id::TEXT); if eh.mem_size > 0 { eh.mem_offset + eh.mem_size + } else if ge.mem_size > 0 { + ge.mem_offset + ge.mem_size } else if plt.mem_size > 0 { plt.mem_offset + plt.mem_size } else { @@ -475,6 +485,7 @@ fn write_macho>( cf + cf_data_size as usize } else { let ordinals: Vec = imports.iter().map(|e| e.lib_ordinal).collect(); + let weak_flags: Vec = imports.iter().map(|e| e.weak_import).collect(); write_chained_fixups_header( out, cf_off as usize, @@ -482,6 +493,7 @@ fn write_macho>( n_imports, &import_name_offsets, &ordinals, + &weak_flags, &symbols_pool, mappings, layout.symbol_db.args.is_dylib, @@ -651,6 +663,7 @@ fn write_exe_symtab( // Collect all defined symbols with non-zero addresses. let mut entries: Vec<(Vec, u64, u8)> = Vec::new(); // (name, value, n_type) + let mut seen_names: std::collections::HashSet> = Default::default(); for (sym_idx, res) in layout.symbol_resolutions.iter().enumerate() { let Some(res) = res else { continue }; if res.raw_value == 0 { @@ -664,15 +677,52 @@ fn write_exe_symtab( Ok(n) => n.bytes().to_vec(), Err(_) => continue, }; - // Skip empty or internal names if name.is_empty() { continue; } - // N_SECT=0x0e, N_EXT=0x01 for global, N_SECT for local - let n_type = 0x0e_u8; // N_SECT (defined in a section) + let n_type = if res.flags.contains(crate::value_flags::ValueFlags::ABSOLUTE) { + 0x02_u8 // N_ABS + } else { + 0x0e_u8 // N_SECT + }; + seen_names.insert(name.clone()); entries.push((name, res.raw_value, n_type)); } + // Also collect absolute symbols from input objects that may lack resolutions + // (e.g. unreferenced .set symbols). + { + use object::read::macho::Nlist as _; + let le = object::Endianness::Little; + for group in &layout.group_layouts { + for file_layout in &group.files { + if let crate::layout::FileLayout::Object(obj) = file_layout { + for sym_idx in 0..obj.object.symbols.len() { + let Ok(sym) = obj.object.symbols.symbol(object::SymbolIndex(sym_idx)) + else { + continue; + }; + // N_ABS = 0x02, N_EXT = 0x01 + let n_type_raw = sym.n_type(); + if (n_type_raw & 0x0e) != 0x02 { + continue; // not absolute + } + let val = sym.n_value(le); + if val == 0 { + continue; + } + let name = sym.name(le, obj.object.symbols.strings()).unwrap_or(&[]); + if name.is_empty() || seen_names.contains(name) { + continue; + } + seen_names.insert(name.to_vec()); + entries.push((name.to_vec(), val, 0x02)); // N_ABS + } + } + } + } + } + if entries.is_empty() { return Ok(start); } @@ -896,9 +946,7 @@ fn write_stubs_and_got>( Ok(n) => n.bytes().to_vec(), Err(_) => b"".to_vec(), }; - // TODO: detect weak imports (N_WEAK_REF) and set weak_import=true - // so dyld doesn't error when the symbol isn't found at runtime. - let weak = false; + let weak = layout.symbol_db.is_weak_ref(symbol_id); imports.push(ImportEntry { name, lib_ordinal: lib_ordinal_for_symbol(has_extra_dylibs), @@ -1556,6 +1604,7 @@ fn write_chained_fixups_header( n_imports: u32, import_name_offsets: &[u32], import_ordinals: &[u8], + import_weak: &[bool], symbols_pool: &[u8], mappings: &[SegmentMapping], is_dylib: bool, @@ -1646,7 +1695,12 @@ fn write_chained_fixups_header( let it = imports_table_offset as usize; for (i, &name_off) in import_name_offsets.iter().enumerate() { let ordinal = import_ordinals[i] as u32; - let import_val: u32 = ordinal | ((name_off & 0x7F_FFFF) << 9); + let weak_bit = if import_weak.get(i).copied().unwrap_or(false) { + 1u32 << 8 + } else { + 0 + }; + let import_val: u32 = ordinal | weak_bit | ((name_off & 0x7F_FFFF) << 9); w[it + i * 4..it + i * 4 + 4].copy_from_slice(&import_val.to_le_bytes()); } @@ -1706,6 +1760,10 @@ struct CollectedUnwindEntry { func_size: u32, /// Compact unwind encoding (ARM64 mode + register mask). encoding: u32, + /// Personality function GOT address (if any). + personality_got: Option, + /// LSDA VM address (if any). + lsda_addr: Option, } /// Scan all input objects for `__LD,__compact_unwind` sections and collect @@ -1779,16 +1837,23 @@ fn collect_compact_unwind_entries(layout: &Layout<'_, MachO>) -> Vec, + layout: &Layout<'_, MachO>, + le: object::Endianness, + relocs: &[object::macho::Relocation], + field_offset: usize, +) -> Option { + for r in relocs { + let reloc = r.info(le); + if reloc.r_address as usize != field_offset { + continue; + } + if reloc.r_extern { + let sym_idx = object::SymbolIndex(reloc.r_symbolnum as usize); + let sym_id = obj.symbol_id_range.input_to_id(sym_idx); + if let Some(res) = layout.merged_symbol_resolution(sym_id) { + if let Some(got) = res.format_specific.got_address { + return Some(got); + } + if res.raw_value != 0 { + return Some(res.raw_value); + } + } + } + break; + } + None +} + /// Build the binary content of the `__unwind_info` section from collected entries. /// `text_base` is the VM address of the start of the `__TEXT` segment. /// /// Produces a version-1 unwind_info with regular second-level pages (kind=2). -/// Entries with personality/LSDA are not included (use DWARF FDEs for those). /// Info extracted from a `__eh_frame` CIE augmentation string. #[derive(Default, Clone)] struct CieAugInfo { @@ -2173,7 +2268,18 @@ fn build_unwind_info_section( all_entries.push((func_vm, 0u32, enc)); } + // Also collect personalities from compact_unwind entries. + for e in plain_entries { + if let Some(got) = e.personality_got { + if !personalities.contains(&got) { + personalities.push(got); + } + } + } + let pers_count = all_entries.len(); + // LSDA descriptors: (func_offset_from_text, lsda_offset_from_text) + let mut lsda_descriptors: Vec<(u32, u32)> = Vec::new(); for e in plain_entries { if fde_map .get(&e.func_addr) @@ -2181,8 +2287,22 @@ fn build_unwind_info_section( { continue; } - all_entries.push((e.func_addr, e.func_size, e.encoding)); + let mut enc = e.encoding; + // Set personality index in encoding bits [29:28] + if let Some(got) = e.personality_got { + if let Some(pos) = personalities.iter().position(|&g| g == got) { + let pers_idx = (pos + 1) as u32; + enc = (enc & !(0x3 << 28)) | ((pers_idx & 3) << 28); + } + } + // Set UNWIND_HAS_LSDA flag and record LSDA descriptor + if let Some(lsda) = e.lsda_addr { + enc |= 0x4000_0000; // UNWIND_HAS_LSDA + lsda_descriptors.push(((e.func_addr - text_base) as u32, (lsda - text_base) as u32)); + } + all_entries.push((e.func_addr, e.func_size, enc)); } + lsda_descriptors.sort_by_key(|d| d.0); if all_entries.is_empty() { return Vec::new(); @@ -2197,8 +2317,14 @@ fn build_unwind_info_section( const ENTRIES_PER_PAGE: usize = 500; loop { let np = all_entries.len().div_ceil(ENTRIES_PER_PAGE); - // Estimate: header(28) + pers(n*4) + index((np+1)*12) + SL pages(np*8 + entries*8) - let est = 28 + (n_pers as usize) * 4 + (np + 1) * 12 + np * 8 + all_entries.len() * 8; + // Estimate: header(28) + pers(n*4) + index((np+1)*12) + LSDA(n*8) + SL pages(np*8 + + // entries*8) + let est = 28 + + (n_pers as usize) * 4 + + (np + 1) * 12 + + lsda_descriptors.len() * 8 + + np * 8 + + all_entries.len() * 8; if est as u64 <= max_bytes || all_entries.len() <= pers_count { break; } @@ -2231,8 +2357,9 @@ fn build_unwind_info_section( let pers_bytes = n_pers * 4; let idx_off = pers_off + pers_bytes; let idx_bytes = (num_pages as u32 + 1) * 12; - let lsda_off = idx_off + idx_bytes; // empty LSDA array starts here - let sl_start = lsda_off; + let lsda_off = idx_off + idx_bytes; + let lsda_bytes = lsda_descriptors.len() as u32 * 8; // 8 bytes each: funcOffset + lsdaOffset + let sl_start = lsda_off + lsda_bytes; let mut sl_offsets = Vec::with_capacity(num_pages); let mut cur = sl_start; @@ -2270,6 +2397,13 @@ fn build_unwind_info_section( wu32!(pers_off as usize + i * 4, offset_from_text); } + // LSDA descriptors array (8 bytes each: funcOffset + lsdaOffset) + for (i, &(func_off, lsda_off_val)) in lsda_descriptors.iter().enumerate() { + let off = lsda_off as usize + i * 8; + wu32!(off, func_off); + wu32!(off + 4, lsda_off_val); + } + // First-level index entries + second-level regular pages for page in 0..num_pages { let start = page * ENTRIES_PER_PAGE; @@ -2304,7 +2438,7 @@ fn build_unwind_info_section( let sie = idx_off as usize + num_pages * 12; wu32!(sie, sentinel_fn_off); wu32!(sie + 4, 0u32); // secondLevelPagesSectionOffset = 0 (sentinel) - wu32!(sie + 8, lsda_off); // lsdaIndexArraySectionOffset + wu32!(sie + 8, lsda_off + lsda_bytes); // lsdaIndexArraySectionOffset (end) out } @@ -2363,6 +2497,10 @@ fn write_headers( let has_stubs = plt_layout.mem_size > 0; let got_layout = layout.section_layouts.get(output_section_id::GOT); let has_got = got_layout.mem_size > 0; + let gcc_except_layout = layout + .section_layouts + .get(output_section_id::GCC_EXCEPT_TABLE); + let has_gcc_except = gcc_except_layout.mem_size > 0; // Scan for .rustc section (proc-macro metadata) before computing cmd sizes let mut rustc_addr = 0u64; @@ -2433,6 +2571,7 @@ fn write_headers( let text_nsects = 1 + if rustc_in_text { 1u32 } else { 0 } + if has_stubs { 1 } else { 0 } + + if has_gcc_except { 1 } else { 0 } + if has_eh_frame { 1 } else { 0 } + if has_unwind_info { 1 } else { 0 }; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT @@ -2549,6 +2688,22 @@ fn write_headers( w.u32(12); // reserved2 = stub size w.u32(0); // reserved3 } + if has_gcc_except { + let ge_foff = + vm_addr_to_file_offset(gcc_except_layout.mem_offset, mappings).unwrap_or(0) as u32; + w.name16(b"__gcc_except_tab"); + w.name16(b"__TEXT"); + w.u64(gcc_except_layout.mem_offset); + w.u64(gcc_except_layout.mem_size); + w.u32(ge_foff); + w.u32(2); // align 2^2 = 4 + w.u32(0); // reloff + w.u32(0); // nreloc + w.u32(0); // flags = S_REGULAR + w.u32(0); // reserved1 + w.u32(0); // reserved2 + w.u32(0); // reserved3 + } if has_eh_frame { let eh_foff = vm_addr_to_file_offset(eh_frame_layout.mem_offset, mappings).unwrap_or(0) as u32; @@ -2935,3 +3090,457 @@ impl Writer<'_> { self.u32(0); } } + +/// Write a Mach-O relocatable object file (MH_OBJECT) for partial linking (-r). +fn write_relocatable_object(layout: &Layout<'_, MachO>) -> Result { + use crate::layout::FileLayout; + use object::read::macho::Nlist as _; + use object::read::macho::Section as MachOSec; + let le = object::Endianness::Little; + + // Phase 1: Collect sections and symbols from all input objects. + // Each output section aggregates data from matching input sections. + struct OutSection { + segname: [u8; 16], + sectname: [u8; 16], + data: Vec, + align: u32, + flags: u32, + relocs: Vec<[u8; 8]>, // raw Mach-O relocation entries + } + + // Symbol entry for the output nlist table. + struct OutSym { + name: Vec, + n_type: u8, + n_sect: u8, // 1-based section ordinal in output, 0 = NO_SECT + n_desc: u16, + n_value: u64, + } + + let mut sections: Vec = Vec::new(); + let mut symbols: Vec = Vec::new(); + + // Map: (segname, sectname) -> index in `sections` + let mut sec_map: std::collections::HashMap<([u8; 16], [u8; 16]), usize> = Default::default(); + + for group in &layout.group_layouts { + for file_layout in &group.files { + let FileLayout::Object(obj) = file_layout else { + continue; + }; + + // Build input symbol index -> output symbol index mapping for this object. + let n_input_syms = obj.object.symbols.len(); + let mut sym_remap: Vec = vec![0; n_input_syms]; + // Also track which input sections map to which output sections. + let n_input_secs = obj.object.sections.len(); + let mut sec_remap: Vec = vec![0; n_input_secs]; // 1-based output ordinal + let mut sec_value_adjust: Vec = vec![0; n_input_secs]; // offset adjustment per input section + + // Process sections: copy data and build section map. + for sec_idx in 0..n_input_secs { + let Some(sec) = obj.object.sections.get(sec_idx) else { + continue; + }; + let sec_segname = sec.segname; + let sec_sectname = sec.sectname; + let trimmed_seg = crate::macho::trim_nul(&sec_segname); + let _trimmed_name = crate::macho::trim_nul(&sec_sectname); + + // Skip __LD,__compact_unwind (linker-private metadata) + if trimmed_seg == b"__LD" { + continue; + } + + let sec_type = sec.flags(le) & 0xFF; + // Skip zerofill (BSS) sections' data + let has_data = sec_type != 0x01 && sec_type != 0x0C; + + let input_offset = sec.offset(le) as usize; + let input_size = sec.size(le) as usize; + + let out_sec_idx = if let Some(&idx) = sec_map.get(&(sec_segname, sec_sectname)) { + idx + } else { + let idx = sections.len(); + sec_map.insert((sec_segname, sec_sectname), idx); + sections.push(OutSection { + segname: sec_segname, + sectname: sec_sectname, + data: Vec::new(), + align: sec.align(le), + flags: sec.flags(le), + relocs: Vec::new(), + }); + idx + }; + sec_remap[sec_idx] = (out_sec_idx + 1) as u8; + + let out_sec = &mut sections[out_sec_idx]; + // Align the output position + let alignment = 1usize << out_sec.align.max(sec.align(le)); + out_sec.align = out_sec.align.max(sec.align(le)); + let padding = (alignment - (out_sec.data.len() % alignment)) % alignment; + out_sec.data.resize(out_sec.data.len() + padding, 0); + let output_offset_in_sec = out_sec.data.len(); + // Record the adjustment: symbols in this input section need their + // value increased by (output_offset_in_sec - input_section_addr). + let input_sec_addr = sec.addr.get(le); + sec_value_adjust[sec_idx] = output_offset_in_sec as u64 - input_sec_addr; + + if has_data && input_size > 0 && input_offset > 0 { + if let Some(data) = obj.object.data.get(input_offset..input_offset + input_size) + { + out_sec.data.extend_from_slice(data); + } else { + out_sec.data.resize(out_sec.data.len() + input_size, 0); + } + } else { + out_sec.data.resize(out_sec.data.len() + input_size, 0); + } + + // Copy and remap relocations (deferred until symbols are mapped) + // For now, store reloc info to process after symbol table is built. + // We'll handle this in a second pass. + } + + // Process symbols: add to output symbol table. + for sym_idx in 0..n_input_syms { + let Ok(sym) = obj.object.symbols.symbol(object::SymbolIndex(sym_idx)) else { + continue; + }; + let n_type = sym.n_type(); + // Skip debug symbols (N_STAB) + if n_type & 0xE0 != 0 { + continue; + } + let name = sym + .name(le, obj.object.symbols.strings()) + .unwrap_or(&[]) + .to_vec(); + // Remap n_sect + let n_sect_in = sym.n_sect(); + let n_sect_out = if n_sect_in > 0 && (n_sect_in as usize - 1) < sec_remap.len() { + sec_remap[n_sect_in as usize - 1] + } else { + 0 + }; + // Adjust n_value for merged section offset + let n_value = if n_sect_in > 0 + && n_sect_out > 0 + && (n_sect_in as usize - 1) < sec_value_adjust.len() + { + sym.n_value(le) + .wrapping_add(sec_value_adjust[n_sect_in as usize - 1]) + } else { + sym.n_value(le) + }; + let out_idx = symbols.len() as u32; + sym_remap[sym_idx] = out_idx; + symbols.push(OutSym { + name, + n_type, + n_sect: n_sect_out, + n_desc: sym.n_desc(le) as u16, + n_value, + }); + } + + // Second pass: copy and remap relocations. + for sec_idx in 0..n_input_secs { + let Some(sec) = obj.object.sections.get(sec_idx) else { + continue; + }; + let trimmed_seg = crate::macho::trim_nul(&sec.segname); + if trimmed_seg == b"__LD" { + continue; + } + let out_sec_ordinal = sec_remap[sec_idx]; + if out_sec_ordinal == 0 { + continue; + } + let out_sec_idx = out_sec_ordinal as usize - 1; + + let relocs = match sec.relocations(le, obj.object.data) { + Ok(r) => r, + Err(_) => continue, + }; + for r in relocs { + let ri = r.info(le); + // Build output relocation with remapped symbol/section index. + let new_symbolnum = if ri.r_extern { + let idx = ri.r_symbolnum as usize; + if idx < sym_remap.len() { + sym_remap[idx] + } else { + ri.r_symbolnum + } + } else { + // Non-extern: r_symbolnum is 1-based section ordinal. + let sec_ord = ri.r_symbolnum as usize; + if sec_ord > 0 + && sec_ord - 1 < sec_remap.len() + && sec_remap[sec_ord - 1] > 0 + { + sec_remap[sec_ord - 1] as u32 + } else { + ri.r_symbolnum + } + }; + // Encode relocation entry (Mach-O ARM64 format): + // word0 = r_address (adjusted for output section offset) + // word1 = packed(r_symbolnum, r_pcrel, r_length, r_extern, r_type) + let addr_adjust = sec_value_adjust[sec_idx] as u32; + let word0 = ri.r_address.wrapping_add(addr_adjust); + let word1: u32 = (new_symbolnum & 0x00FF_FFFF) + | (if ri.r_pcrel { 1 << 24 } else { 0 }) + | ((ri.r_length as u32 & 3) << 25) + | (if ri.r_extern { 1 << 27 } else { 0 }) + | ((ri.r_type as u32 & 0xF) << 28); + let mut entry = [0u8; 8]; + entry[0..4].copy_from_slice(&word0.to_le_bytes()); + entry[4..8].copy_from_slice(&word1.to_le_bytes()); + sections[out_sec_idx].relocs.push(entry); + } + } + } + } + + if sections.is_empty() { + // Nothing to output + let output_path = layout.symbol_db.args.output(); + std::fs::write(output_path.as_ref(), &[]) + .map_err(|e| crate::error!("Failed to write: {e}"))?; + return Ok(()); + } + + // Phase 2: Sort symbols (locals first, then defined externals, then undefined). + let mut local_syms: Vec = Vec::new(); + let mut ext_def_syms: Vec = Vec::new(); + let mut undef_syms: Vec = Vec::new(); + for (i, sym) in symbols.iter().enumerate() { + if sym.name.is_empty() && sym.n_type == 0 { + continue; // skip null symbol + } + let is_ext = (sym.n_type & 0x01) != 0; // N_EXT + let sym_type = sym.n_type & 0x0E; + if !is_ext { + local_syms.push(i); + } else if sym_type == 0 && sym.n_sect == 0 { + // N_UNDF + N_EXT = undefined external + undef_syms.push(i); + } else { + ext_def_syms.push(i); + } + } + let sorted_indices: Vec = local_syms + .iter() + .chain(ext_def_syms.iter()) + .chain(undef_syms.iter()) + .copied() + .collect(); + // Build reverse map: old index -> new index (for relocation fixup) + let mut new_sym_index = vec![0u32; symbols.len()]; + for (new_idx, &old_idx) in sorted_indices.iter().enumerate() { + new_sym_index[old_idx] = new_idx as u32; + } + + // Fixup relocations to use new symbol indices. + for sec in &mut sections { + for entry in &mut sec.relocs { + let word1 = u32::from_le_bytes(entry[4..8].try_into().unwrap()); + let old_symbolnum = word1 & 0x00FF_FFFF; + let is_extern = (word1 >> 27) & 1 != 0; + if is_extern { + let new_num = if (old_symbolnum as usize) < new_sym_index.len() { + new_sym_index[old_symbolnum as usize] + } else { + old_symbolnum + }; + let word1_new = (word1 & 0xFF00_0000) | (new_num & 0x00FF_FFFF); + entry[4..8].copy_from_slice(&word1_new.to_le_bytes()); + } + // Non-extern relocs reference section ordinals, already remapped. + } + } + + // Phase 3: Build string table and nlist entries. + let mut strtab = vec![0u8]; // starts with NUL + let mut nlist_data: Vec = Vec::new(); + for &old_idx in &sorted_indices { + let sym = &symbols[old_idx]; + let strx = strtab.len() as u32; + strtab.extend_from_slice(&sym.name); + strtab.push(0); + // nlist_64: n_strx(4) + n_type(1) + n_sect(1) + n_desc(2) + n_value(8) = 16 + nlist_data.extend_from_slice(&strx.to_le_bytes()); + nlist_data.push(sym.n_type); + nlist_data.push(sym.n_sect); + nlist_data.extend_from_slice(&sym.n_desc.to_le_bytes()); + nlist_data.extend_from_slice(&sym.n_value.to_le_bytes()); + } + + // Phase 4: Compute layout and write output. + let nsects = sections.len() as u32; + let ncmds = 3u32; // LC_SEGMENT_64 + LC_SYMTAB + LC_DYSYMTAB + let seg_cmdsize = 72 + 80 * nsects; + let symtab_cmdsize = 24u32; + let dysymtab_cmdsize = 80u32; + let header_size = 32; // Mach-O 64 header + let total_cmdsize = seg_cmdsize + symtab_cmdsize + dysymtab_cmdsize; + + let mut section_offset = header_size + total_cmdsize; + let mut sec_offsets: Vec = Vec::new(); + for sec in §ions { + // Align section data + let alignment = 1u32 << sec.align; + section_offset = (section_offset + alignment - 1) & !(alignment - 1); + sec_offsets.push(section_offset); + section_offset += sec.data.len() as u32; + } + + // Relocation entries follow section data + let mut reloc_offsets: Vec = Vec::new(); + let mut reloc_offset = section_offset; + for sec in §ions { + reloc_offsets.push(if sec.relocs.is_empty() { + 0 + } else { + reloc_offset + }); + reloc_offset += (sec.relocs.len() * 8) as u32; + } + + // Symbol table follows relocations + let symoff = (reloc_offset + 7) & !7; // 8-byte align + let nsyms = sorted_indices.len() as u32; + let stroff = symoff + nsyms * 16; + let total_size = stroff + strtab.len() as u32; + + let mut buf = vec![0u8; total_size as usize]; + + // Write header + let mut pos = 0usize; + let w = |buf: &mut Vec, pos: &mut usize, val: u32| { + buf[*pos..*pos + 4].copy_from_slice(&val.to_le_bytes()); + *pos += 4; + }; + w(&mut buf, &mut pos, MH_MAGIC_64); + w(&mut buf, &mut pos, CPU_TYPE_ARM64); + w(&mut buf, &mut pos, CPU_SUBTYPE_ARM64_ALL); + w(&mut buf, &mut pos, 1); // MH_OBJECT + w(&mut buf, &mut pos, ncmds); + w(&mut buf, &mut pos, total_cmdsize); + w(&mut buf, &mut pos, 0x2000); // MH_SUBSECTIONS_VIA_SYMBOLS + w(&mut buf, &mut pos, 0); // reserved + + // LC_SEGMENT_64 (unnamed, contains all sections) + w(&mut buf, &mut pos, LC_SEGMENT_64); + w(&mut buf, &mut pos, seg_cmdsize); + // segname: empty (16 NUL bytes) + buf[pos..pos + 16].fill(0); + pos += 16; + // vmaddr, vmsize + let seg_vmsize = sections + .iter() + .enumerate() + .map(|(i, s)| sec_offsets[i] as u64 - sec_offsets[0] as u64 + s.data.len() as u64) + .max() + .unwrap_or(0); + buf[pos..pos + 8].copy_from_slice(&0u64.to_le_bytes()); // vmaddr + pos += 8; + buf[pos..pos + 8].copy_from_slice(&seg_vmsize.to_le_bytes()); // vmsize + pos += 8; + buf[pos..pos + 8].copy_from_slice(&(sec_offsets[0] as u64).to_le_bytes()); // fileoff + pos += 8; + buf[pos..pos + 8] + .copy_from_slice(&(section_offset as u64 - sec_offsets[0] as u64).to_le_bytes()); // filesize + pos += 8; + w(&mut buf, &mut pos, 7); // maxprot: rwx + w(&mut buf, &mut pos, 7); // initprot: rwx + w(&mut buf, &mut pos, nsects); + w(&mut buf, &mut pos, 0); // flags + + // Section headers + for (i, sec) in sections.iter().enumerate() { + buf[pos..pos + 16].copy_from_slice(&sec.sectname); + pos += 16; + buf[pos..pos + 16].copy_from_slice(&sec.segname); + pos += 16; + buf[pos..pos + 8] + .copy_from_slice(&((sec_offsets[i] - sec_offsets[0]) as u64).to_le_bytes()); // addr (section-relative) + pos += 8; + buf[pos..pos + 8].copy_from_slice(&(sec.data.len() as u64).to_le_bytes()); // size + pos += 8; + w(&mut buf, &mut pos, sec_offsets[i]); // offset + w(&mut buf, &mut pos, sec.align); // align + w(&mut buf, &mut pos, reloc_offsets[i]); // reloff + w(&mut buf, &mut pos, sec.relocs.len() as u32); // nreloc + w(&mut buf, &mut pos, sec.flags); // flags + w(&mut buf, &mut pos, 0); // reserved1 + w(&mut buf, &mut pos, 0); // reserved2 + w(&mut buf, &mut pos, 0); // reserved3 + } + + // LC_SYMTAB + w(&mut buf, &mut pos, LC_SYMTAB); + w(&mut buf, &mut pos, symtab_cmdsize); + w(&mut buf, &mut pos, symoff); + w(&mut buf, &mut pos, nsyms); + w(&mut buf, &mut pos, stroff); + w(&mut buf, &mut pos, strtab.len() as u32); + + // LC_DYSYMTAB + w(&mut buf, &mut pos, LC_DYSYMTAB); + w(&mut buf, &mut pos, dysymtab_cmdsize); + let nlocalsym = local_syms.len() as u32; + let nextdefsym = ext_def_syms.len() as u32; + let nundefsym = undef_syms.len() as u32; + w(&mut buf, &mut pos, 0); // ilocalsym + w(&mut buf, &mut pos, nlocalsym); + w(&mut buf, &mut pos, nlocalsym); // iextdefsym + w(&mut buf, &mut pos, nextdefsym); + w(&mut buf, &mut pos, nlocalsym + nextdefsym); // iundefsym + w(&mut buf, &mut pos, nundefsym); + // Remaining DYSYMTAB fields are all zero + for _ in 0..14 { + w(&mut buf, &mut pos, 0); + } + + // Write section data + for (i, sec) in sections.iter().enumerate() { + let off = sec_offsets[i] as usize; + if off + sec.data.len() <= buf.len() { + buf[off..off + sec.data.len()].copy_from_slice(&sec.data); + } + } + + // Write relocations + for (i, sec) in sections.iter().enumerate() { + if sec.relocs.is_empty() { + continue; + } + let off = reloc_offsets[i] as usize; + for (j, entry) in sec.relocs.iter().enumerate() { + let p = off + j * 8; + if p + 8 <= buf.len() { + buf[p..p + 8].copy_from_slice(entry); + } + } + } + + // Write symbol table + if symoff as usize + nlist_data.len() <= buf.len() { + buf[symoff as usize..symoff as usize + nlist_data.len()].copy_from_slice(&nlist_data); + } + if stroff as usize + strtab.len() <= buf.len() { + buf[stroff as usize..stroff as usize + strtab.len()].copy_from_slice(&strtab); + } + + let output_path = layout.symbol_db.args.output(); + std::fs::write(output_path.as_ref(), &buf) + .map_err(|e| crate::error!("Failed to write: {e}"))?; + + Ok(()) +} diff --git a/libwild/src/symbol_db.rs b/libwild/src/symbol_db.rs index 8d0e9223e..fc5e21f36 100644 --- a/libwild/src/symbol_db.rs +++ b/libwild/src/symbol_db.rs @@ -1015,6 +1015,22 @@ impl<'data, P: Platform> SymbolDb<'data, P> { } } + /// Returns whether the symbol is a weak reference (N_WEAK_REF on Mach-O). + pub(crate) fn is_weak_ref(&self, symbol_id: SymbolId) -> bool { + let file_id = self.file_id_for_symbol(symbol_id); + match &self.groups[file_id.group()] { + Group::Objects(objects) => { + let file = &objects[file_id.file()]; + let local_index = file.symbol_id_range.id_to_input(symbol_id); + file.parsed + .object + .symbol(local_index) + .is_ok_and(|sym| sym.is_weak()) + } + _ => false, + } + } + pub(crate) fn warning(&self, message: impl Into) { self.args.warning(message); } diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 3044fd0fe..9fe7f9c95 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -93,6 +93,8 @@ struct TestConfig { archives: Vec<(String, Vec)>, /// Shared libraries to build: source file names. shared_libs: Vec, + /// Groups of sources to partial-link with -r. + relocatables: Vec>, comp_args: Vec, link_args: Vec, expect_error: Option, @@ -142,6 +144,10 @@ fn parse_config(test_dir: &Path, primary: &Path) -> Result cfg.shared_libs.push(value.trim().to_string()), + "Relocatable" => { + let sources: Vec = value.split(',').map(|s| s.trim().to_string()).collect(); + cfg.relocatables.push(sources); + } "CompArgs" => cfg.comp_args.extend(shell_words(value)), "LinkArgs" => cfg.link_args.extend(shell_words(value)), "ExpectError" => cfg.expect_error = Some(value.to_string()), @@ -278,6 +284,30 @@ fn run_test( objects.push(archive_path); } + // Build partial-linked relocatables with -r. + for (group_idx, sources) in config.relocatables.iter().enumerate() { + let mut member_objs = Vec::new(); + for src_name in sources { + let src = test_dir.join(src_name); + let src_cpp = src.extension().map_or(false, |e| e == "cc"); + compile_source(&src, &build_dir, &config.comp_args, src_cpp)?; + member_objs.push(object_path(&build_dir, &src)); + } + let reloc_path = build_dir.join(format!("relocatable{group_idx}.o")); + let mut reloc_cmd = Command::new(&wild_bin); + reloc_cmd.arg("-r"); + for obj in &member_objs { + reloc_cmd.arg(obj); + } + reloc_cmd.arg("-o").arg(&reloc_path); + let result = reloc_cmd.output().map_err(|e| format!("wild -r: {e}"))?; + if !result.status.success() { + let stderr = String::from_utf8_lossy(&result.stderr); + return Err(format!("Partial link (-r) failed:\n{stderr}")); + } + objects.push(reloc_path); + } + // Build shared libraries (dylibs) and add -L/-l flags. let mut extra_link_args: Vec = Vec::new(); for lib_src_name in &config.shared_libs { diff --git a/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c b/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c index e41b40268..e21e06d5d 100644 --- a/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c +++ b/wild/tests/sources/macho/absolute-symbol/absolute-symbol.c @@ -1,7 +1,5 @@ //#RunEnabled:false //#ExpectSym:abs_sym -//#Ignore:LC_SYMTAB not yet emitted for executables - // Tests that absolute symbols (defined via assembly .set) are preserved. __asm__(".globl _abs_sym\n.set _abs_sym, 0xCAFE"); diff --git a/wild/tests/sources/macho/exception/exception.cc b/wild/tests/sources/macho/exception/exception.cc index e73006039..d9c31461b 100644 --- a/wild/tests/sources/macho/exception/exception.cc +++ b/wild/tests/sources/macho/exception/exception.cc @@ -1,7 +1,6 @@ //#LinkerDriver:clang++ //#LinkArgs:-lc++ //#CompArgs:-std=c++17 -//#Ignore:C++ exceptions need __gcc_except_tab, __stubs, and __got sections #include diff --git a/wild/tests/sources/macho/relocatables/relocatables.c b/wild/tests/sources/macho/relocatables/relocatables.c index 04bb32127..e0a659d53 100644 --- a/wild/tests/sources/macho/relocatables/relocatables.c +++ b/wild/tests/sources/macho/relocatables/relocatables.c @@ -1,6 +1,4 @@ -//#Object:relocatables1.c -//#Object:relocatables2.c -//#Ignore:partial linking (-r) not yet implemented for Mach-O +//#Relocatable:relocatables1.c,relocatables2.c // Tests -r (partial link / relocatable output). // Link relocatables1.c and relocatables2.c into a single .o via -r, diff --git a/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c index fd5747ccc..e2e9aa4b9 100644 --- a/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c +++ b/wild/tests/sources/macho/undefined-symbol-error/undefined-symbol-error.c @@ -1,5 +1,5 @@ +//#LinkerDriver:clang //#ExpectError:undefined -//#Ignore:needs .tbd symbol parsing to distinguish undefined from dynamic imports int missing_fn(void); int main() { return missing_fn(); } diff --git a/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c index d4d95d485..419dfcc20 100644 --- a/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c +++ b/wild/tests/sources/macho/undefined-weak-and-strong/undefined-weak-and-strong.c @@ -1,6 +1,6 @@ //#Object:undefined-weak-and-strong1.c +//#LinkerDriver:clang //#ExpectError:foo -//#Ignore:undefined symbol enforcement not yet implemented for Mach-O void __attribute__((weak)) foo(void); void call_foo(void); diff --git a/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c b/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c index 06490b68b..8a983bf52 100644 --- a/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c +++ b/wild/tests/sources/macho/undefined-weak-sym/undefined-weak-sym.c @@ -1,5 +1,4 @@ //#LinkerDriver:clang -//#Ignore:weak undefined symbols not yet resolved to NULL for Mach-O int __attribute__((weak)) foo(void); int main() { From a7c3d6373d9d66ec66d32e74a24cbcf092099305 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 8 Apr 2026 12:05:47 +0100 Subject: [PATCH 12/21] more tests Signed-off-by: Giles Cope --- .../sources/macho/cpp-string/cpp-string.cc | 12 +++++++++ wild/tests/sources/macho/rust-tls/rust-tls.rs | 25 +++++++++++++++++++ .../sources/macho/weak-entry/weak-entry.c | 4 +++ .../sources/macho/weak-entry/weak-entry1.c | 1 + .../weak-override-archive.c | 16 ++++++++++++ .../weak-override-archive1.c | 1 + .../weak-override-archive2.c | 1 + 7 files changed, 60 insertions(+) create mode 100644 wild/tests/sources/macho/cpp-string/cpp-string.cc create mode 100644 wild/tests/sources/macho/rust-tls/rust-tls.rs create mode 100644 wild/tests/sources/macho/weak-entry/weak-entry.c create mode 100644 wild/tests/sources/macho/weak-entry/weak-entry1.c create mode 100644 wild/tests/sources/macho/weak-override-archive/weak-override-archive.c create mode 100644 wild/tests/sources/macho/weak-override-archive/weak-override-archive1.c create mode 100644 wild/tests/sources/macho/weak-override-archive/weak-override-archive2.c diff --git a/wild/tests/sources/macho/cpp-string/cpp-string.cc b/wild/tests/sources/macho/cpp-string/cpp-string.cc new file mode 100644 index 000000000..5ff09cf4e --- /dev/null +++ b/wild/tests/sources/macho/cpp-string/cpp-string.cc @@ -0,0 +1,12 @@ +//#LinkerDriver:clang++ +//#LinkArgs:-lc++ +//#CompArgs:-std=c++17 + +// Tests C++ std::string and basic stdlib linking. +#include + +int main() { + std::string s = "hello"; + s += " world"; + return s.length() == 11 ? 42 : 1; +} diff --git a/wild/tests/sources/macho/rust-tls/rust-tls.rs b/wild/tests/sources/macho/rust-tls/rust-tls.rs new file mode 100644 index 000000000..c29694fef --- /dev/null +++ b/wild/tests/sources/macho/rust-tls/rust-tls.rs @@ -0,0 +1,25 @@ +//#LinkerDriver:clang + +use std::cell::Cell; +use std::thread; + +thread_local!(static FOO: Cell = Cell::new(1)); + +fn main() { + assert_eq!(FOO.get(), 1); + FOO.set(2); + + // each thread starts out with the initial value of 1 + let t = thread::spawn(move || { + assert_eq!(FOO.get(), 1); + FOO.set(3); + }); + + // wait for the thread to complete and bail out on panic + t.join().unwrap(); + + // we retain our original value of 2 despite the child thread + assert_eq!(FOO.get(), 2); + + std::process::exit(42); +} diff --git a/wild/tests/sources/macho/weak-entry/weak-entry.c b/wild/tests/sources/macho/weak-entry/weak-entry.c new file mode 100644 index 000000000..38a0e98fc --- /dev/null +++ b/wild/tests/sources/macho/weak-entry/weak-entry.c @@ -0,0 +1,4 @@ +// Tests that a strong definition of main overrides a weak one. +//#Object:weak-entry1.c + +__attribute__((weak)) int main() { return 5; } diff --git a/wild/tests/sources/macho/weak-entry/weak-entry1.c b/wild/tests/sources/macho/weak-entry/weak-entry1.c new file mode 100644 index 000000000..dbff7309f --- /dev/null +++ b/wild/tests/sources/macho/weak-entry/weak-entry1.c @@ -0,0 +1 @@ +int main() { return 42; } diff --git a/wild/tests/sources/macho/weak-override-archive/weak-override-archive.c b/wild/tests/sources/macho/weak-override-archive/weak-override-archive.c new file mode 100644 index 000000000..16765196b --- /dev/null +++ b/wild/tests/sources/macho/weak-override-archive/weak-override-archive.c @@ -0,0 +1,16 @@ +// Tests that on Mach-O, archives only satisfy undefined references. +// A weak definition in an object is NOT overridden by a strong one in an +// archive. +//#Object:weak-override-archive1.c +//#Archive:weak-override-archive2.c + +__attribute__((weak)) int foo(void) { return 1; } +int bar(void); + +int main() { + // foo stays 1 (weak def in this TU; archive not pulled in since foo is + // defined) bar is 10 (from companion object) + if (foo() != 1) return foo(); + if (bar() != 10) return bar(); + return 42; +} diff --git a/wild/tests/sources/macho/weak-override-archive/weak-override-archive1.c b/wild/tests/sources/macho/weak-override-archive/weak-override-archive1.c new file mode 100644 index 000000000..8730c4af2 --- /dev/null +++ b/wild/tests/sources/macho/weak-override-archive/weak-override-archive1.c @@ -0,0 +1 @@ +int bar(void) { return 10; } diff --git a/wild/tests/sources/macho/weak-override-archive/weak-override-archive2.c b/wild/tests/sources/macho/weak-override-archive/weak-override-archive2.c new file mode 100644 index 000000000..3167837f0 --- /dev/null +++ b/wild/tests/sources/macho/weak-override-archive/weak-override-archive2.c @@ -0,0 +1 @@ +int foo(void) { return 2; } From 7ffcd13903127e72057f169cf947bf0084ec7219 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Wed, 8 Apr 2026 13:02:59 +0100 Subject: [PATCH 13/21] feat: honor validate output flag Signed-off-by: Giles Cope --- libwild/src/args/macho.rs | 4 + libwild/src/macho_writer.rs | 134 +++++++++++++++++ wild/tests/macho_integration_tests.rs | 141 +++++++++++++++++- .../sources/macho/const-data/const-data.c | 9 ++ .../sources/macho/cstring-data/cstring-data.c | 8 + .../macho/mixed-sections/mixed-sections.c | 26 ++++ .../macho/mixed-sections/mixed-sections1.c | 4 + .../macho/mutable-globals/mutable-globals.c | 12 ++ .../macho/mutable-globals/mutable-globals1.c | 2 + .../macho/rust-subprocess/rust-subprocess.rs | 28 ++++ 10 files changed, 367 insertions(+), 1 deletion(-) create mode 100644 wild/tests/sources/macho/const-data/const-data.c create mode 100644 wild/tests/sources/macho/cstring-data/cstring-data.c create mode 100644 wild/tests/sources/macho/mixed-sections/mixed-sections.c create mode 100644 wild/tests/sources/macho/mixed-sections/mixed-sections1.c create mode 100644 wild/tests/sources/macho/mutable-globals/mutable-globals.c create mode 100644 wild/tests/sources/macho/mutable-globals/mutable-globals1.c create mode 100644 wild/tests/sources/macho/rust-subprocess/rust-subprocess.rs diff --git a/libwild/src/args/macho.rs b/libwild/src/args/macho.rs index 37cb8457d..f90b867a7 100644 --- a/libwild/src/args/macho.rs +++ b/libwild/src/args/macho.rs @@ -276,6 +276,10 @@ fn parse_one_arg<'a, S: AsRef, I: Iterator>( args.entry_symbol = None; return Ok(()); } + "--validate-output" => { + args.common.validate_output = true; + return Ok(()); + } _ => {} } diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index c07ab60da..3c2d06fa3 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -111,6 +111,10 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> )?; buf.truncate(final_size); + if layout.symbol_db.args.common().validate_output { + validate_macho_output(&buf)?; + } + let output_path = layout.symbol_db.args.output(); std::fs::write(output_path.as_ref(), &buf) .map_err(|e| crate::error!("Failed to write: {e}"))?; @@ -3544,3 +3548,133 @@ fn write_relocatable_object(layout: &Layout<'_, MachO>) -> Result { Ok(()) } + +/// Validate structural invariants of a Mach-O output binary. +/// +/// Called when `WILD_VALIDATE_OUTPUT=1` is set. Parses the output back and checks: +/// +/// # Segment invariants +/// - Segment vmaddr is page-aligned (16KB on arm64) +/// - Segment fileoff is page-aligned (when filesize > 0) +/// - Segment file content fits within the file +/// +/// # Section invariants +/// - Section addr is within parent segment [vmaddr, vmaddr+vmsize) +/// - Section file offset is within parent segment [fileoff, fileoff+filesize) +/// - Section addr respects its declared alignment +/// - Sections within a segment do not overlap +/// +/// # Chained fixups invariants +/// - Page start offsets are within a page (< page_size) +fn validate_macho_output(buf: &[u8]) -> Result { + use object::read::macho::{MachHeader as _, Section as _, Segment as _}; + let le = object::Endianness::Little; + let header = object::macho::MachHeader64::::parse(buf, 0) + .map_err(|e| crate::error!("validate: bad Mach-O header: {e}"))?; + let mut cmds = header + .load_commands(le, buf, 0) + .map_err(|e| crate::error!("validate: bad load commands: {e}"))?; + + let file_len = buf.len() as u64; + + while let Ok(Some(cmd)) = cmds.next() { + if let Ok(Some((seg, seg_data))) = cmd.segment_64() { + let segname = crate::macho::trim_nul(&seg.segname); + let segname_str = String::from_utf8_lossy(segname); + + let vm_addr = seg.vmaddr.get(le); + let vm_size = seg.vmsize.get(le); + let file_off = seg.fileoff.get(le); + let file_size = seg.filesize.get(le); + + // Segment vmaddr page alignment + if vm_addr % PAGE_SIZE != 0 && !segname.is_empty() { + crate::bail!( + "validate: segment {segname_str} vmaddr {vm_addr:#x} not page-aligned" + ); + } + + // Segment fileoff page alignment + if file_size > 0 && file_off % PAGE_SIZE != 0 { + crate::bail!( + "validate: segment {segname_str} fileoff {file_off:#x} not page-aligned" + ); + } + + // Segment fits in file + if file_off + file_size > file_len { + crate::bail!( + "validate: segment {segname_str} extends beyond file \ + ({file_off:#x}+{file_size:#x} > {file_len:#x})" + ); + } + + // Section invariants + if let Ok(sections) = seg.sections(le, seg_data) { + let mut prev_end: u64 = 0; + for sec in sections { + let sect_raw = sec.sectname(); + let sect_name = String::from_utf8_lossy(crate::macho::trim_nul(sect_raw)); + + let sec_addr = sec.addr(le); + let sec_size = sec.size(le); + let sec_offset = sec.offset(le) as u64; + let sec_align = sec.align(le); + + // Section addr within segment + if sec_size > 0 + && (sec_addr < vm_addr || sec_addr + sec_size > vm_addr + vm_size) + { + crate::bail!( + "validate: section {segname_str},{sect_name} addr \ + {sec_addr:#x}+{sec_size:#x} outside segment \ + [{vm_addr:#x}..{:#x})", + vm_addr + vm_size + ); + } + + // Section file offset within segment + let sec_type = sec.flags(le) & 0xFF; + let is_zerofill = sec_type == 0x01 || sec_type == 0x0C; + if sec_size > 0 && !is_zerofill && sec_offset > 0 && file_size > 0 { + if sec_offset < file_off + || sec_offset + sec_size > file_off + file_size + { + crate::bail!( + "validate: section {segname_str},{sect_name} file range \ + [{sec_offset:#x}..{:#x}) outside segment \ + [{file_off:#x}..{:#x})", + sec_offset + sec_size, + file_off + file_size + ); + } + } + + // Section alignment + if sec_size > 0 && sec_align > 0 { + let alignment = 1u64 << sec_align; + if sec_addr % alignment != 0 { + crate::bail!( + "validate: section {segname_str},{sect_name} addr \ + {sec_addr:#x} not aligned to 2^{sec_align} ({alignment})" + ); + } + } + + // No overlap with previous section + if sec_size > 0 && sec_addr > 0 && sec_addr < prev_end { + crate::bail!( + "validate: section {segname_str},{sect_name} at {sec_addr:#x} \ + overlaps previous section ending at {prev_end:#x}" + ); + } + if sec_size > 0 { + prev_end = sec_addr + sec_size; + } + } + } + } + } + + Ok(()) +} diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 9fe7f9c95..0db944adb 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -374,6 +374,9 @@ fn run_test( c }; + // Enable output validation in Wild itself. + cmd.env("WILD_VALIDATE_OUTPUT", "1"); + let link_result = cmd.output().map_err(|e| format!("wild: {e}"))?; // Check for expected errors. @@ -397,8 +400,11 @@ fn run_test( return Err(format!("Link failed:\n{stderr}")); } - // Binary content checks. + // Verify Mach-O structural invariants. let binary = std::fs::read(&output).map_err(|e| format!("read output: {e}"))?; + verify_macho_invariants(&binary, &output)?; + + // Binary content checks. for needle in &config.contains { if !binary_contains(&binary, needle.as_bytes()) { return Err(format!("Output binary does not contain '{needle}'")); @@ -453,6 +459,139 @@ fn run_test( Ok(()) } +/// Verify structural invariants of a Mach-O binary. +/// +/// These invariants must hold for dyld to load the binary correctly: +/// - All segments must be page-aligned (16KB on arm64). +/// - Section addresses must be within their parent segment's [vmaddr, vmaddr+vmsize). +/// - Section file offsets must be within [segment.fileoff, segment.fileoff+segment.filesize). +/// - Sections within a segment must not overlap. +/// - LC_SYMTAB offsets must be within the file. +/// - Chained fixup page starts must reference offsets within a page (< page_size). +fn verify_macho_invariants( + binary: &[u8], + path: &std::path::Path, +) -> Result<(), String> { + use object::read::macho::{MachHeader as _, Segment as _, Section as _}; + let le = object::Endianness::Little; + let header = object::macho::MachHeader64::::parse(binary, 0) + .map_err(|e| format!("{}: failed to parse Mach-O header: {e}", path.display()))?; + let mut cmds = header + .load_commands(le, binary, 0) + .map_err(|e| format!("{}: bad load commands: {e}", path.display()))?; + + let file_len = binary.len() as u64; + let page_size: u64 = 0x4000; // 16KB on arm64 + + while let Ok(Some(cmd)) = cmds.next() { + if let Ok(Some((seg, seg_data))) = cmd.segment_64() { + let segname = std::str::from_utf8( + &seg.segname[..seg.segname.iter().position(|&b| b == 0).unwrap_or(16)], + ) + .unwrap_or(""); + + let vm_addr = seg.vmaddr.get(le); + let vm_size = seg.vmsize.get(le); + let file_off = seg.fileoff.get(le); + let file_size = seg.filesize.get(le); + + // Invariant: segment vmaddr must be page-aligned. + if vm_addr % page_size != 0 && !segname.is_empty() { + return Err(format!( + "{}: segment {segname} vmaddr {vm_addr:#x} is not page-aligned", + path.display() + )); + } + + // Invariant: segment file offset must be page-aligned (except __PAGEZERO). + if file_size > 0 && file_off % page_size != 0 { + return Err(format!( + "{}: segment {segname} fileoff {file_off:#x} is not page-aligned", + path.display() + )); + } + + // Invariant: segment file content must fit in the file. + if file_off + file_size > file_len { + return Err(format!( + "{}: segment {segname} extends beyond file \ + (fileoff {file_off:#x} + filesize {file_size:#x} > file len {file_len:#x})", + path.display() + )); + } + + // Check sections within this segment. + if let Ok(sections) = seg.sections(le, seg_data) { + let mut prev_end: u64 = 0; + for sec in sections { + let sect_name_raw = sec.sectname(); + let sect_name = std::str::from_utf8( + §_name_raw + [..sect_name_raw.iter().position(|&b| b == 0).unwrap_or(16)], + ) + .unwrap_or(""); + + let sec_addr = sec.addr(le); + let sec_size = sec.size(le); + let sec_offset = sec.offset(le) as u64; + let sec_align = sec.align(le); + + // Invariant: section address must be within the segment. + if sec_size > 0 && (sec_addr < vm_addr || sec_addr + sec_size > vm_addr + vm_size) { + return Err(format!( + "{}: section {segname},{sect_name} addr {sec_addr:#x}+{sec_size:#x} \ + outside segment [{vm_addr:#x}..{:#x})", + path.display(), + vm_addr + vm_size + )); + } + + // Invariant: section file offset must be within the segment. + let sec_type = sec.flags(le) & 0xFF; + let is_zerofill = sec_type == 0x01 || sec_type == 0x0C; + if sec_size > 0 && !is_zerofill && sec_offset > 0 { + if sec_offset < file_off || sec_offset + sec_size > file_off + file_size { + return Err(format!( + "{}: section {segname},{sect_name} file range \ + [{sec_offset:#x}..{:#x}) outside segment [{file_off:#x}..{:#x})", + path.display(), + sec_offset + sec_size, + file_off + file_size + )); + } + } + + // Invariant: section must respect its alignment. + if sec_size > 0 && sec_align > 0 { + let alignment = 1u64 << sec_align; + if sec_addr % alignment != 0 { + return Err(format!( + "{}: section {segname},{sect_name} addr {sec_addr:#x} \ + not aligned to 2^{sec_align} ({alignment})", + path.display() + )); + } + } + + // Invariant: sections must not overlap (within the same segment). + if sec_addr > 0 && sec_addr < prev_end { + return Err(format!( + "{}: section {segname},{sect_name} at {sec_addr:#x} \ + overlaps previous section ending at {prev_end:#x}", + path.display() + )); + } + if sec_size > 0 { + prev_end = sec_addr + sec_size; + } + } + } + } + } + + Ok(()) +} + fn compile_source( src: &Path, build_dir: &Path, diff --git a/wild/tests/sources/macho/const-data/const-data.c b/wild/tests/sources/macho/const-data/const-data.c new file mode 100644 index 000000000..f6912c820 --- /dev/null +++ b/wild/tests/sources/macho/const-data/const-data.c @@ -0,0 +1,9 @@ +// Tests that __const section data is correctly placed and accessible. +//#ExpectSym:table + +static const int table[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +int main() { + int sum = 0; + for (int i = 0; i < 10; i++) sum += table[i]; + return sum == 55 ? 42 : 1; +} diff --git a/wild/tests/sources/macho/cstring-data/cstring-data.c b/wild/tests/sources/macho/cstring-data/cstring-data.c new file mode 100644 index 000000000..45c96a0d3 --- /dev/null +++ b/wild/tests/sources/macho/cstring-data/cstring-data.c @@ -0,0 +1,8 @@ +// Tests that __cstring literals are accessible and correctly merged. +#include + +int main() { + const char* a = "hello"; + const char* b = "world"; + return (strlen(a) == 5 && strlen(b) == 5) ? 42 : 1; +} diff --git a/wild/tests/sources/macho/mixed-sections/mixed-sections.c b/wild/tests/sources/macho/mixed-sections/mixed-sections.c new file mode 100644 index 000000000..6418a2f6b --- /dev/null +++ b/wild/tests/sources/macho/mixed-sections/mixed-sections.c @@ -0,0 +1,26 @@ +// Tests correct handling of multiple section types together: +// __text, __const, __cstring, __data, __bss, __got. +// This exercises the section header generation for the DATA segment. +//#LinkerDriver:clang +//#Object:mixed-sections1.c + +#include + +extern int mutable_val; +extern const int readonly_table[]; +void bump(void); +const char* get_name(void); + +int main() { + // __data: mutable global + bump(); + if (mutable_val != 11) return 1; + + // __const: read-only table + if (readonly_table[0] + readonly_table[3] != 104) return 2; + + // __cstring: string literal from another TU + if (strcmp(get_name(), "hello") != 0) return 3; + + return 42; +} diff --git a/wild/tests/sources/macho/mixed-sections/mixed-sections1.c b/wild/tests/sources/macho/mixed-sections/mixed-sections1.c new file mode 100644 index 000000000..282ae70c1 --- /dev/null +++ b/wild/tests/sources/macho/mixed-sections/mixed-sections1.c @@ -0,0 +1,4 @@ +int mutable_val = 10; +const int readonly_table[] = {1, 2, 3, 103}; +void bump(void) { mutable_val++; } +const char* get_name(void) { return "hello"; } diff --git a/wild/tests/sources/macho/mutable-globals/mutable-globals.c b/wild/tests/sources/macho/mutable-globals/mutable-globals.c new file mode 100644 index 000000000..bc5e78858 --- /dev/null +++ b/wild/tests/sources/macho/mutable-globals/mutable-globals.c @@ -0,0 +1,12 @@ +// Tests that mutable global data (__data section) works correctly. +//#Object:mutable-globals1.c + +extern int counter; +void increment(void); + +int main() { + increment(); + increment(); + increment(); + return counter == 3 ? 42 : 1; +} diff --git a/wild/tests/sources/macho/mutable-globals/mutable-globals1.c b/wild/tests/sources/macho/mutable-globals/mutable-globals1.c new file mode 100644 index 000000000..a7d87a92f --- /dev/null +++ b/wild/tests/sources/macho/mutable-globals/mutable-globals1.c @@ -0,0 +1,2 @@ +int counter = 0; +void increment(void) { counter++; } diff --git a/wild/tests/sources/macho/rust-subprocess/rust-subprocess.rs b/wild/tests/sources/macho/rust-subprocess/rust-subprocess.rs new file mode 100644 index 000000000..d3a9b38d2 --- /dev/null +++ b/wild/tests/sources/macho/rust-subprocess/rust-subprocess.rs @@ -0,0 +1,28 @@ +//#LinkerDriver:clang + +// Tests that string formatting, env vars, and subprocess execution work. +// This exercises __const, __data, __cstring, and GOT entries together — +// similar to what proc-macro2's build script does. + +use std::env; +use std::process::Command; + +fn main() { + // String formatting exercises __const vtables and __cstring data. + let msg = format!("hello {} world", 42); + assert_eq!(msg, "hello 42 world"); + + // Env var access exercises libc GOT entries. + env::set_var("WILD_TEST_VAR", "test_value"); + let val = env::var("WILD_TEST_VAR").unwrap(); + assert_eq!(val, "test_value"); + + // Subprocess execution exercises many sections together. + let output = Command::new("echo") + .arg("hi") + .output() + .expect("failed to run echo"); + assert!(output.status.success()); + + std::process::exit(42); +} From 279f3db519e6c1bc3301293f4c0512ab6a274693 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 06:04:15 +0100 Subject: [PATCH 14/21] feat(macho): fix TLS, section overlap, and symbol resolution - Give __thread_vars its own output section (PREINIT_ARRAY) so all TLV descriptors are contiguous across objects - Fix TLS offset computation: fallback for TBSS symbols in extern resolution path, force 8-byte alignment for S_THREAD_LOCAL_VARIABLES - Filter __thread_vars key/offset fields from chained fixup chain and zero corrupted key fields - Disambiguate __TEXT,__const vs __DATA,__const via section_name() rename to __text_const, each mapped to a dedicated output section ID - Separate __literal4/8/16 from __cstring (different output section IDs) to prevent layout pipeline part overlap - Separate __DATA,__const from __data (CSTRING output section ID) - Fix write_exe_symtab n_sect: compute from section address ranges instead of hardcoding section 1 - Add validation invariants: TLV key=0, no duplicate TLV offsets, section file-offset overlap, section data write overlap, symbol n_value within n_sect range, chained fixup chain integrity - Dynamic section header generation for all output sections - New tests: rust-tls, rust-build-script-sim, rust-format-strings, rust-large-data, tls-alignment Signed-off-by: Giles Cope --- .claude/settings.local.json | 5 +- libwild/src/macho.rs | 57 +- libwild/src/macho_writer.rs | 1181 +++++++++++++---- .../rust-build-script-sim.rs | 64 + .../rust-format-strings.rs | 31 + .../macho/rust-large-data/rust-large-data.rs | 44 + wild/tests/sources/macho/rust-tls/rust-tls.rs | 7 - .../macho/tls-alignment/tls-alignment.c | 12 + .../macho/tls-alignment/tls-alignment1.c | 6 + 9 files changed, 1119 insertions(+), 288 deletions(-) create mode 100644 wild/tests/sources/macho/rust-build-script-sim/rust-build-script-sim.rs create mode 100644 wild/tests/sources/macho/rust-format-strings/rust-format-strings.rs create mode 100644 wild/tests/sources/macho/rust-large-data/rust-large-data.rs create mode 100644 wild/tests/sources/macho/tls-alignment/tls-alignment.c create mode 100644 wild/tests/sources/macho/tls-alignment/tls-alignment1.c diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 7ca8445d2..d5f3e8064 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -52,7 +52,10 @@ "Bash(clang -c /tmp/t_ext_main.c -o /tmp/t_ext_main.o)", "Bash(/tmp/t_ext_out)", "Bash(RUST_BACKTRACE=1 /tmp/rust_map)", - "Bash(RUST_BACKTRACE=full /tmp/rust_map)" + "Bash(RUST_BACKTRACE=full /tmp/rust_map)", + "Bash(/tmp/test_shared/shared-basic)", + "Bash(objdump --macho --private-headers /tmp/test_shared/libshared-basic-lib.dylib)", + "Bash(objdump --macho --private-headers /tmp/test_shared/shared-basic)" ] } } diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index 67d181499..c0f2fdd34 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -213,7 +213,15 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { s as *const macho::Section64, §ion_header.0 as *const macho::Section64, ) { - return Ok(trim_nul(s.sectname())); + let sectname = trim_nul(s.sectname()); + let segname = trim_nul(&s.segname); + // __const appears in both __TEXT (read-only, no pointers) and + // __DATA (has pointer relocations). Qualify with segment name + // so they map to different output sections. + if sectname == b"__const" && segname == b"__TEXT" { + return Ok(b"__text_const"); + } + return Ok(sectname); } } Err(error!("Section header not found in file's section table")) @@ -254,8 +262,17 @@ impl<'data> platform::ObjectFile<'data> for File<'data> { } fn section_alignment(&self, section: &SectionHeader) -> crate::error::Result { - // Mach-O stores alignment as a power of 2 - Ok(1u64 << section.0.align(LE)) + let raw_align = 1u64 << section.0.align(LE); + // __thread_vars descriptors contain pointers and need 8-byte alignment, + // but rustc/clang emit them with align=1. Force minimum 8-byte alignment + // to match ld64 behaviour. + let sec_type = section.0.flags(LE) & 0xFF; + if sec_type == 0x13 { + // S_THREAD_LOCAL_VARIABLES + Ok(raw_align.max(8)) + } else { + Ok(raw_align) + } } fn relocations( @@ -375,8 +392,11 @@ impl platform::SectionHeader for SectionHeader { } fn is_tls(&self) -> bool { + // Only __thread_data and __thread_bss are actual TLS data sections. + // __thread_vars is the descriptor table that lives in regular DATA — + // it must NOT be marked as TLS so it gets a normal section resolution. let sectname = trim_nul(self.0.sectname()); - sectname == b"__thread_vars" || sectname == b"__thread_data" || sectname == b"__thread_bss" + sectname == b"__thread_data" || sectname == b"__thread_bss" } fn is_merge_section(&self) -> bool { @@ -1509,7 +1529,9 @@ impl platform::Platform for MachO { // __TEXT segment (r-x): headers, code, read-only data, stubs builder.add_section(output_section_id::FILE_HEADER); - builder.add_section(output_section_id::RODATA); + builder.add_section(output_section_id::RODATA); // __cstring + builder.add_section(output_section_id::COMMENT); // __literal4/8/16 + builder.add_section(output_section_id::DATA_REL_RO); // __text_const builder.add_sections(&custom.ro); builder.add_section(output_section_id::TEXT); builder.add_sections(&custom.exec); @@ -1519,6 +1541,8 @@ impl platform::Platform for MachO { // __DATA segment (rw-): writable data, GOT, BSS builder.add_section(output_section_id::DATA); + builder.add_section(output_section_id::CSTRING); // __DATA,__const + builder.add_section(output_section_id::PREINIT_ARRAY); // __thread_vars builder.add_section(output_section_id::INIT_ARRAY); // __mod_init_func builder.add_section(output_section_id::FINI_ARRAY); // __mod_term_func builder.add_sections(&custom.data); @@ -1539,15 +1563,17 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { SectionRule::exact_section(b"__text", output_section_id::TEXT), SectionRule::exact_section(b"__stubs", output_section_id::TEXT), SectionRule::exact_section(b"__stub_helper", output_section_id::TEXT), - // Sections like __const, __cstring, __literal* can appear in both __TEXT and - // __DATA segments. The pipeline groups by name, so both variants merge into one - // output section. Placing them all in DATA ensures pointers get rebase fixups. - SectionRule::exact_section(b"__const", output_section_id::DATA), - // __cstring and __literal* are truly read-only (no pointers). Keep in RODATA. + // Each Mach-O section gets a dedicated output section ID where possible. + // Sharing output section IDs between sections with different names can + // cause data overlap when the layout pipeline assigns overlapping parts. + // __DATA,__const has pointer relocations — give it CSTRING (unused regular + // section on Mach-O) to keep it separate from __data (both align 8). + SectionRule::exact_section(b"__const", output_section_id::CSTRING), + SectionRule::exact_section(b"__text_const", output_section_id::DATA_REL_RO), SectionRule::exact_section(b"__cstring", output_section_id::RODATA), - SectionRule::exact_section(b"__literal4", output_section_id::RODATA), - SectionRule::exact_section(b"__literal8", output_section_id::RODATA), - SectionRule::exact_section(b"__literal16", output_section_id::RODATA), + SectionRule::exact_section(b"__literal4", output_section_id::COMMENT), + SectionRule::exact_section(b"__literal8", output_section_id::COMMENT), + SectionRule::exact_section(b"__literal16", output_section_id::COMMENT), SectionRule::exact_section(b"__data", output_section_id::DATA), SectionRule::exact_section(b"__la_symbol_ptr", output_section_id::DATA), SectionRule::exact_section(b"__nl_symbol_ptr", output_section_id::DATA), @@ -1556,7 +1582,10 @@ const MACHO_SECTION_RULES: &[crate::layout_rules::SectionRule<'static>] = { // This separates TLS bind fixups from GOT bind fixups in the chain. // __thread_vars must NOT share the GOT output section — GOT-only entries // (e.g. for __eh_frame personality pointers) would overlap with TLV descriptors. - SectionRule::exact_section(b"__thread_vars", output_section_id::DATA), + // __thread_vars uses PREINIT_ARRAY (unused on Mach-O) as its dedicated + // output section so all thread_vars from all objects are grouped contiguously. + // Using DATA would interleave them with __data from other objects. + SectionRule::exact_section(b"__thread_vars", output_section_id::PREINIT_ARRAY), SectionRule::exact_section(b"__thread_data", output_section_id::TDATA), SectionRule::exact_section(b"__thread_bss", output_section_id::TBSS), // Constructor/destructor function pointer arrays (Mach-O equivalent of diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 3c2d06fa3..d4e13be6d 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -116,6 +116,7 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> } let output_path = layout.symbol_db.args.output(); + std::fs::write(output_path.as_ref(), &buf) .map_err(|e| crate::error!("Failed to write: {e}"))?; @@ -137,6 +138,7 @@ pub(crate) fn write_direct>(layout: &Layout<'_, MachO> } } } + Ok(()) } @@ -279,6 +281,10 @@ fn write_macho>( let mut imports: Vec = Vec::new(); let has_extra_dylibs = !layout.symbol_db.args.extra_dylibs.is_empty(); + // Track section write ranges for overlap detection (validation only). + let validate = layout.symbol_db.args.common().validate_output; + let mut write_ranges: Vec<(usize, usize, String)> = Vec::new(); + // Copy section data and apply relocations for group in &layout.group_layouts { for file_layout in &group.files { @@ -293,11 +299,28 @@ fn write_macho>( &mut bind_fixups, &mut imports, has_extra_dylibs, + if validate { Some(&mut write_ranges) } else { None }, )?; } } } + // Validate: no two section data writes should overlap. + if validate && !write_ranges.is_empty() { + write_ranges.sort_by_key(|r| r.0); + for w in write_ranges.windows(2) { + let (off1, size1, ref name1) = w[0]; + let (off2, _size2, ref name2) = w[1]; + if off1 + size1 > off2 { + crate::bail!( + "validate: section data write overlap: \ + {name1} [{off1:#x}..{:#x}) and {name2} [{off2:#x}..)", + off1 + size1 + ); + } + } + } + // Write PLT stubs and collect bind fixups for imported symbols write_stubs_and_got::( out, @@ -319,10 +342,117 @@ fn write_macho>( has_extra_dylibs, )?; - // Build chained fixup data: merge rebase + bind, encode per-page chains + // Build chained fixup data: merge rebase + bind, encode per-page chains. + // + // Filter out fixups that fall on __thread_vars `key` or `offset` fields. + // TLV descriptors are 24-byte structs: (init_ptr, key, offset). + // Only `init` (at offset 0 of each descriptor) should have a fixup (bind to + // __tlv_bootstrap). The `key` (offset 8) and `offset` (offset 16) fields are + // plain values that dyld manages — they must NOT be in the fixup chain. + // Find __thread_vars key/offset field file offsets to exclude from + // the fixup chain. TLV descriptors are 24 bytes: only the init pointer + // (byte 0) should have a fixup. The key (byte 8) and offset (byte 16) + // are plain values that must not be in the chain. + // + // We find the thread_vars address range by scanning all bind+rebase + // fixups: every fixup at a position that's (n*24 + 8) or (n*24 + 16) + // relative to the first __tlv_bootstrap bind is a key/offset field. + // + // Simpler approach: collect ALL fixup file offsets that target TDATA + // or TBSS addresses (these are the TLV offset fields whose values + // were correctly computed by apply_relocations). They should NOT have + // rebase fixups because we wrote TLS-relative offsets, not absolute + // addresses. However, the non-extern relocation path may have created + // rebase fixups anyway. Remove them. + // Build set of file offsets for __thread_vars key/offset fields. + // These must NOT be in the fixup chain. We identify them by scanning + // the output for the bind fixups we already created for __tlv_bootstrap + // and init-function pointers — every such fixup marks the start of a + // 24-byte TLV descriptor. The key (+8) and offset (+16) fields after + // each descriptor start must be excluded. + let tvars_key_offset_positions: std::collections::HashSet = { + let mut positions = std::collections::HashSet::new(); + // Every fixup (bind or rebase) that's at a 24-byte-aligned position + // within the thread_vars output IS a descriptor start. + // But we don't know exactly where tvars is in the output. + // Use a different approach: find ALL fixups in the DATA segment, + // and for each one, check if the 8 bytes before it are also a fixup + // (which would make this a key field after an init fixup) or if + // 16 bytes before is a fixup (making this an offset field). + // + // Actually simplest: find tvars range from the bind fixups for + // __tlv_bootstrap. The first and last such bind define the range. + let mut tvars_start = usize::MAX; + let mut tvars_end = 0usize; + for f in &bind_fixups { + if let Some(imp) = imports.get(f.import_index as usize) { + if imp.name == b"__tlv_bootstrap" { + tvars_start = tvars_start.min(f.file_offset); + tvars_end = tvars_end.max(f.file_offset + 24); // descriptor size + } + } + } + // Also scan rebase fixups that target init functions (which are in + // __thread_data/__thread_bss). These are at descriptor +0 too. + // A rebase targeting TDATA/TBSS means it's a TLS offset value (written + // by apply_relocations). But init-function rebase fixups target TEXT. + // To catch all descriptors, extend the range to cover all rebase fixups + // between the first and last __tlv_bootstrap binds. + // Actually, the tvars section is contiguous. Extend by scanning: + // starting from the first __tlv_bootstrap bind, every 24 bytes is a + // descriptor until we run out. + if tvars_start != usize::MAX { + // Find the total tvars block: from the first bind, walk forward + // checking if there's a fixup or data at each 24-byte boundary. + // The block size = (number of descriptors) * 24. + // We know from bind_fixups how many __tlv_bootstrap entries there are, + // but some descriptors have rebase inits instead. Use the DATA output + // section's thread_vars content size. + // The simplest: compute from the input objects. + let le = object::Endianness::Little; + let mut total_tvars_size = 0usize; + for group in &layout.group_layouts { + for file_layout in &group.files { + if let FileLayout::Object(obj) = file_layout { + for sec_idx in 0..obj.object.sections.len() { + if let Some(s) = obj.object.sections.get(sec_idx) { + use object::read::macho::Section as _; + if s.flags(le) & 0xFF == 0x13 { + total_tvars_size += s.size(le) as usize; + } + } + } + } + } + } + tvars_end = tvars_start + total_tvars_size; + } + + if tvars_start != usize::MAX { + for off in (tvars_start..tvars_end).step_by(24) { + positions.insert(off + 8); // key field + positions.insert(off + 16); // offset field + } + } + positions + }; + rebase_fixups.sort_by_key(|f| f.file_offset); bind_fixups.sort_by_key(|f| f.file_offset); + // Zero out __thread_vars key fields. Key must always be 0 — dyld + // initializes it at runtime with a pthread key. Relocation application + // may have written garbage into key positions from non-extern relocations. + // Key is at offset +8 in each 24-byte descriptor. + // tvars_key_offset_positions contains both key (+8) and offset (+16) positions. + // Key positions: those that are 8 bytes before an offset position. + for &pos in &tvars_key_offset_positions { + // Check if pos+8 is also in the set (making this a key field) + if tvars_key_offset_positions.contains(&(pos + 8)) && pos + 8 <= out.len() { + out[pos..pos + 8].fill(0); + } + } + let data_seg_start = if mappings.len() > 1 { mappings[1].file_offset as usize } else { @@ -344,6 +474,9 @@ fn write_macho>( if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + if tvars_key_offset_positions.contains(&f.file_offset) { + continue; + } let target_offset = f.target.wrapping_sub(image_base); all_data_fixups.push((f.file_offset, target_offset & 0xF_FFFF_FFFF)); } @@ -351,6 +484,9 @@ fn write_macho>( if f.file_offset < data_seg_start || f.file_offset >= data_seg_end { continue; } + // Don't filter bind fixups for __thread_vars init pointers — + // those ARE legitimate (bind to __tlv_bootstrap). + // Only filter rebase fixups for key/offset fields. let encoded = (1u64 << 63) | (f.import_index as u64 & 0xFF_FFFF); all_data_fixups.push((f.file_offset, encoded)); } @@ -563,7 +699,7 @@ fn write_dylib_symtab( // nlist64: n_strx (4), n_type (1), n_sect (1), n_desc (2), n_value (8) out[pos..pos + 4].copy_from_slice(&str_offsets[i].to_le_bytes()); out[pos + 4] = 0x0F; // N_SECT | N_EXT - out[pos + 5] = 1; // n_sect: section 1 (__text) + out[pos + 5] = 1; // n_sect: section 1 (__text) — patched later out[pos + 6..pos + 8].copy_from_slice(&0u16.to_le_bytes()); // n_desc out[pos + 8..pos + 16].copy_from_slice(&value.to_le_bytes()); pos += 16; @@ -743,6 +879,30 @@ fn write_exe_symtab( strtab.push(0); } + // Build section ranges from the already-written headers for n_sect lookup. + let section_ranges: Vec<(u64, u64)> = { + let mut ranges = Vec::new(); + let mut hoff = 32usize; + let ncmds = u32::from_le_bytes(out[16..20].try_into().unwrap_or([0; 4])) as usize; + for _ in 0..ncmds { + if hoff + 8 > out.len() { break; } + let cmd = u32::from_le_bytes(out[hoff..hoff + 4].try_into().unwrap()); + let cmdsize = u32::from_le_bytes(out[hoff + 4..hoff + 8].try_into().unwrap()) as usize; + if cmd == LC_SEGMENT_64 && hoff + 72 <= out.len() { + let nsects = u32::from_le_bytes(out[hoff + 64..hoff + 68].try_into().unwrap()) as usize; + for j in 0..nsects { + let so = hoff + 72 + j * 80; + if so + 48 > out.len() { break; } + let addr = u64::from_le_bytes(out[so + 32..so + 40].try_into().unwrap()); + let size = u64::from_le_bytes(out[so + 40..so + 48].try_into().unwrap()); + ranges.push((addr, addr + size)); + } + } + hoff += cmdsize; + } + ranges + }; + // Write nlist64 entries (16 bytes each, must be 8-byte aligned) let symoff = (start + 7) & !7; let nsyms = entries.len(); @@ -751,10 +911,18 @@ fn write_exe_symtab( if pos + 16 > out.len() { break; } - // nlist64: n_strx (4), n_type (1), n_sect (1), n_desc (2), n_value (8) + let n_sect = if *n_type == 0x02 { + 0u8 // N_ABS + } else { + section_ranges + .iter() + .position(|&(s, e)| *value >= s && *value < e) + .map(|idx| (idx + 1) as u8) + .unwrap_or(0) + }; out[pos..pos + 4].copy_from_slice(&str_offsets[i].to_le_bytes()); out[pos + 4] = *n_type; - out[pos + 5] = 1; // n_sect: section 1 (__text) + out[pos + 5] = n_sect; out[pos + 6..pos + 8].copy_from_slice(&0u16.to_le_bytes()); out[pos + 8..pos + 16].copy_from_slice(&value.to_le_bytes()); pos += 16; @@ -1229,9 +1397,24 @@ fn write_object_sections( bind_fixups: &mut Vec, imports: &mut Vec, has_extra_dylibs: bool, + mut write_ranges: Option<&mut Vec<(usize, usize, String)>>, ) -> Result { use object::read::macho::Section as MachOSection; + // Verify that sections/section_resolutions/object.sections have same length. + if let Some(ref _ranges) = write_ranges { + let loaded = obj.sections.len(); + let resolutions = obj.section_resolutions.len(); + let input = obj.object.sections.len(); + if loaded != resolutions || loaded != input { + crate::bail!( + "validate: section count mismatch for {}: \ + loaded={loaded} resolutions={resolutions} input={input}", + obj.input + ); + } + } + for (sec_idx, _slot) in obj.sections.iter().enumerate() { let section_res = &obj.section_resolutions[sec_idx]; let Some(output_addr) = section_res.address() else { @@ -1246,6 +1429,24 @@ fn write_object_sections( None => continue, }; + // Log __const section resolutions for debugging + if let Some(ref _ranges) = write_ranges { + use object::read::macho::Section as _; + let sectname = crate::macho::trim_nul(input_section.sectname()); + let segname = crate::macho::trim_nul(&input_section.segname); + if sectname == b"__const" { + let input_addr = input_section.addr(le); + let input_size = input_section.size(le); + let _ = std::fs::OpenOptions::new().create(true).append(true) + .open("/tmp/wild_const_debug.log") + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "sec[{sec_idx}] {},{}: input={input_addr:#x}+{input_size:#x} → output={output_addr:#x} foff={file_offset:#x}", + String::from_utf8_lossy(segname), String::from_utf8_lossy(sectname)) + }); + } + } + let sec_type = input_section.flags(le) & 0xFF; if sec_type == 0x01 || sec_type == 0x0C || sec_type == 0x12 { continue; @@ -1283,6 +1484,39 @@ fn write_object_sections( } if file_offset + input_size <= out.len() { + if let Some(ref mut ranges) = write_ranges { + let sectname = crate::macho::trim_nul(input_section.sectname()); + let segname = crate::macho::trim_nul(&input_section.segname); + ranges.push(( + file_offset, + input_size, + format!( + "{},{}", + String::from_utf8_lossy(segname), + String::from_utf8_lossy(sectname) + ), + )); + + // Invariant: verify round-trip — after copy, reading the first + // 8 bytes from the output at the resolved address must match + // the first 8 bytes of the input section data. If they differ, + // another section's data was already at that position. + if input_size >= 8 { + let expected = &input_data[..8]; + let actual = &out[file_offset..file_offset + 8]; + // Only check if the position was previously zero (fresh) + if actual != [0u8; 8] && actual != expected { + crate::bail!( + "validate: section {},{} at foff={file_offset:#x} — \ + output already has data {:02x?} but input starts with {:02x?}", + String::from_utf8_lossy(segname), + String::from_utf8_lossy(sectname), + actual, + expected + ); + } + } + } out[file_offset..file_offset + input_size].copy_from_slice(input_data); } @@ -1408,12 +1642,63 @@ fn apply_relocations( let fallback = obj.object.symbols.symbol(sym_idx).ok().and_then(|sym| { let n_sect = sym.n_sect(); if n_sect == 0 { + // Symbol is undefined (no section). Check if it has a name + // that looks like a TLS init symbol. + let name = sym.name(le, obj.object.symbols.strings()).unwrap_or(b""); + if name.ends_with(b"$tlv$init") { + let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/wild_tls_debug.log") + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "TLS $tlv$init with n_sect=0: {}", String::from_utf8_lossy(name)) + }); + } return None; } let sec_idx = n_sect as usize - 1; - let sec_out = obj.section_resolutions.get(sec_idx)?.address()?; + // Try section_resolutions first. + let sec_res_addr = obj + .section_resolutions + .get(sec_idx) + .and_then(|r| r.address()); + if let Some(sec_out) = sec_res_addr { + let sec_in = + obj.object.sections.get(sec_idx).map(|s| s.addr.get(le))?; + let result = sec_out + sym.n_value(le).wrapping_sub(sec_in); + let name = sym.name(le, obj.object.symbols.strings()).unwrap_or(b""); + if name.ends_with(b"$tlv$init") { + let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/wild_tls_debug.log") + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "TLS resolved: sec_out={sec_out:#x} sec_in={sec_in:#x} n_value={:#x} result={result:#x}", sym.n_value(le)) + }); + } + return Some(result); + } + // Section resolution missing — fall back to TDATA/TBSS for TLS. + use object::read::macho::Section as _; + let sec_type = obj + .object + .sections + .get(sec_idx) + .map(|s| s.flags(le) & 0xFF)?; let sec_in = obj.object.sections.get(sec_idx).map(|s| s.addr.get(le))?; - Some(sec_out + sym.n_value(le).wrapping_sub(sec_in)) + let sym_offset = sym.n_value(le).wrapping_sub(sec_in); + let tdata = layout.section_layouts.get(output_section_id::TDATA); + let tbss = layout.section_layouts.get(output_section_id::TBSS); + match sec_type { + 0x11 if tdata.mem_size > 0 => { + tracing::warn!("TLS fallback: tdata + {sym_offset:#x} -> {:#x}", tdata.mem_offset + sym_offset); + Some(tdata.mem_offset + sym_offset) + } + 0x12 if tbss.mem_size > 0 => { + tracing::warn!("TLS fallback: tbss + {sym_offset:#x} -> {:#x}", tbss.mem_offset + sym_offset); + Some(tbss.mem_offset + sym_offset) + } + _ => { + tracing::warn!("TLS fallback MISS: sec_type={sec_type:#x}"); + None + } + } }); if let Some(addr) = fallback { let got = other.and_then(|r| r.format_specific.got_address); @@ -1438,15 +1723,54 @@ fn apply_relocations( continue; } let sec_idx = sec_ord - 1; - let Some(output_sec_addr) = obj + let output_sec_addr = obj .section_resolutions .get(sec_idx) - .and_then(|r| r.address()) - else { - continue; - }; - // Return section base; addend is added below along with extern path. - (output_sec_addr, None, None) + .and_then(|r| r.address()); + if let Some(addr) = output_sec_addr { + (addr, None, None) + } else { + // Section resolution missing. For TLS sections (__thread_data, + // __thread_bss), fall back to the TDATA/TBSS output section layout. + // Read the in-place value to get the symbol's offset within the + // input section, then compute the output address. + use object::read::macho::Section as _; + let input_sec = obj.object.sections.get(sec_idx); + let sec_type = input_sec.map(|s| s.flags(le) & 0xFF).unwrap_or(0); + let input_sec_base = input_sec.map(|s| s.addr.get(le)).unwrap_or(0); + let tdata = layout.section_layouts.get(output_section_id::TDATA); + let tbss = layout.section_layouts.get(output_section_id::TBSS); + match sec_type { + 0x11 if tdata.mem_size > 0 => { + // Read in-place addend: absolute input address at reloc position + let in_place = if patch_file_offset + 8 <= out.len() { + u64::from_le_bytes( + out[patch_file_offset..patch_file_offset + 8] + .try_into() + .unwrap_or([0; 8]), + ) + } else { + 0 + }; + let sym_offset = in_place.wrapping_sub(input_sec_base); + (tdata.mem_offset + sym_offset, None, None) + } + 0x12 if tbss.mem_size > 0 => { + let in_place = if patch_file_offset + 8 <= out.len() { + u64::from_le_bytes( + out[patch_file_offset..patch_file_offset + 8] + .try_into() + .unwrap_or([0; 8]), + ) + } else { + 0 + }; + let sym_offset = in_place.wrapping_sub(input_sec_base); + (tbss.mem_offset + sym_offset, None, None) + } + _ => continue, + } + } }; let target_addr = (target_addr as i64 + addend) as u64; @@ -1555,10 +1879,27 @@ fn apply_relocations( let in_tbss = tbss.mem_size > 0 && target_addr >= tbss.mem_offset && target_addr < tbss.mem_offset + tbss.mem_size; + if !in_tdata && !in_tbss && target_addr > 0 { + // Log non-TLS rebases that MIGHT be TLS + if reloc.r_extern { + use object::read::macho::Nlist as _; + if let Ok(sym) = obj.object.symbols.symbol( + object::SymbolIndex(reloc.r_symbolnum as usize), + ) { + let name = sym.name(le, obj.object.symbols.strings()).unwrap_or(b""); + if name.ends_with(b"$tlv$init") { + let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/wild_tls_debug.log") + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "MISSED TLS: target={target_addr:#x} tdata=[{:#x}..{:#x}) tbss=[{:#x}..{:#x})", + tdata.mem_offset, tdata.mem_offset+tdata.mem_size, + tbss.mem_offset, tbss.mem_offset+tbss.mem_size) + }); + } + } + } + } if in_tdata || in_tbss { - // TLS offset relative to the GLOBAL TLS template start. - // The template is the TDATA section content (init data for - // all TLS variables across all objects) followed by TBSS. let tls_init_start = tdata.mem_offset; let tls_init_size = tdata.mem_size; let tls_offset = if in_tbss { @@ -1567,6 +1908,11 @@ fn apply_relocations( } else { target_addr.saturating_sub(tls_init_start) }; + let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/wild_tls_debug.log") + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "TLS write: foff={patch_file_offset:#x} offset={tls_offset:#x} target={target_addr:#x}") + }); out[patch_file_offset..patch_file_offset + 8] .copy_from_slice(&tls_offset.to_le_bytes()); } else { @@ -2447,6 +2793,49 @@ fn build_unwind_info_section( out } +/// Mach-O section metadata for a given output section ID. +struct MachoSectionInfo { + segname: &'static [u8; 16], + sectname: [u8; 16], + flags: u32, +} + +/// Map an OutputSectionId to Mach-O section name and flags. +/// Returns None for sections that don't need their own section header +/// (e.g. FILE_HEADER, BSS handled specially, etc.). +fn macho_section_info(id: crate::output_section_id::OutputSectionId) -> Option { + use crate::output_section_id; + fn name16(s: &[u8]) -> [u8; 16] { + let mut buf = [0u8; 16]; + let len = s.len().min(16); + buf[..len].copy_from_slice(&s[..len]); + buf + } + static TEXT_SEG: &[u8; 16] = b"__TEXT\0\0\0\0\0\0\0\0\0\0"; + static DATA_SEG: &[u8; 16] = b"__DATA\0\0\0\0\0\0\0\0\0\0"; + + let (segname, sectname, flags) = match id { + output_section_id::TEXT => (TEXT_SEG, name16(b"__text"), 0x8000_0400u32), + output_section_id::PLT_GOT => (TEXT_SEG, name16(b"__stubs"), 0x8000_0408), + output_section_id::GCC_EXCEPT_TABLE => (TEXT_SEG, name16(b"__gcc_except_tab"), 0), + output_section_id::EH_FRAME => (TEXT_SEG, name16(b"__eh_frame"), 0x6800_000B), + output_section_id::RODATA => (TEXT_SEG, name16(b"__cstring"), 0), + output_section_id::COMMENT => (TEXT_SEG, name16(b"__literal"), 0), + output_section_id::DATA_REL_RO => (TEXT_SEG, name16(b"__const"), 0), + output_section_id::DATA => (DATA_SEG, name16(b"__data"), 0), + output_section_id::CSTRING => (DATA_SEG, name16(b"__const"), 0), + output_section_id::GOT => (DATA_SEG, name16(b"__got"), 0x06), + output_section_id::PREINIT_ARRAY => (DATA_SEG, name16(b"__thread_vars"), 0x13), + output_section_id::INIT_ARRAY => (DATA_SEG, name16(b"__mod_init_func"), 0x09), + output_section_id::FINI_ARRAY => (DATA_SEG, name16(b"__mod_term_func"), 0x0E), + output_section_id::TDATA => (DATA_SEG, name16(b"__thread_data"), 0x11), + output_section_id::TBSS => (DATA_SEG, name16(b"__thread_bss"), 0x12), + output_section_id::BSS => (DATA_SEG, name16(b"__bss"), 0x01), + _ => return None, + }; + Some(MachoSectionInfo { segname, sectname, flags }) +} + /// Write Mach-O headers. Returns the chained fixups file offset. fn write_headers( out: &mut [u8], @@ -2496,16 +2885,7 @@ fn write_headers( let tdata_layout = layout.section_layouts.get(output_section_id::TDATA); let tbss_layout = layout.section_layouts.get(output_section_id::TBSS); let has_tlv = tdata_layout.mem_size > 0 || tbss_layout.mem_size > 0; - let has_tvars = has_tlv; - let plt_layout = layout.section_layouts.get(output_section_id::PLT_GOT); - let has_stubs = plt_layout.mem_size > 0; - let got_layout = layout.section_layouts.get(output_section_id::GOT); - let has_got = got_layout.mem_size > 0; - let gcc_except_layout = layout - .section_layouts - .get(output_section_id::GCC_EXCEPT_TABLE); - let has_gcc_except = gcc_except_layout.mem_size > 0; - + let _has_tvars = has_tlv; // Scan for .rustc section (proc-macro metadata) before computing cmd sizes let mut rustc_addr = 0u64; let mut rustc_size = 0u64; @@ -2569,29 +2949,112 @@ fn write_headers( add_cmd(&mut ncmds, &mut cmdsize, 72); } // PAGEZERO (exe only) let rustc_in_text = has_rustc && rustc_addr < text_vm_start + text_filesize; - let eh_frame_layout = layout.section_layouts.get(output_section_id::EH_FRAME); - let has_eh_frame = eh_frame_layout.mem_size > 0; let has_unwind_info = unwind_info_size > 0; - let text_nsects = 1 - + if rustc_in_text { 1u32 } else { 0 } - + if has_stubs { 1 } else { 0 } - + if has_gcc_except { 1 } else { 0 } - + if has_eh_frame { 1 } else { 0 } - + if has_unwind_info { 1 } else { 0 }; - add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT - let init_array_layout = layout.section_layouts.get(output_section_id::INIT_ARRAY); - let has_init_array = init_array_layout.mem_size > 0; - if has_data { - let mut data_nsects = if has_tvars { 2u32 } else { 0 }; - if has_rustc { - data_nsects += 1; + + // Dynamically collect TEXT and DATA section headers from all output sections. + // This replaces the hardcoded section counting. + struct SectionHeader { + segname: [u8; 16], + sectname: [u8; 16], + addr: u64, + size: u64, + offset: u32, + align: u32, + flags: u32, + } + + let mut text_sections: Vec = Vec::new(); + let mut data_sections: Vec = Vec::new(); + + static TEXT_SEG_NAME: [u8; 16] = *b"__TEXT\0\0\0\0\0\0\0\0\0\0"; + static DATA_SEG_NAME: [u8; 16] = *b"__DATA\0\0\0\0\0\0\0\0\0\0"; + + // Enumerate all output sections that have content. + for (sec_id, sec_layout) in layout.section_layouts.iter() { + if sec_layout.mem_size == 0 { + continue; } - if has_init_array { - data_nsects += 1; + let file_off = vm_addr_to_file_offset(sec_layout.mem_offset, mappings) + .unwrap_or(0) as u32; + if let Some(info) = macho_section_info(sec_id) { + let hdr = SectionHeader { + segname: *info.segname, + sectname: info.sectname, + addr: sec_layout.mem_offset, + size: sec_layout.mem_size, + offset: file_off, + align: sec_layout.alignment.exponent as u32, + flags: info.flags, + }; + if *info.segname == TEXT_SEG_NAME { + text_sections.push(hdr); + } else { + data_sections.push(hdr); + } } - if has_got { - data_nsects += 1; + } + // Sort by address within each segment. + text_sections.sort_by_key(|s| s.addr); + data_sections.sort_by_key(|s| s.addr); + + // Add special sections: .rustc (if in TEXT), __unwind_info + if rustc_in_text { + let rustc_foff = vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(0) as u32; + text_sections.push(SectionHeader { + segname: TEXT_SEG_NAME, + sectname: *b".rustc\0\0\0\0\0\0\0\0\0\0", + addr: rustc_addr, + size: rustc_size, + offset: rustc_foff, + align: 0, + flags: 0, + }); + } + if has_unwind_info { + let ui_foff = vm_addr_to_file_offset(unwind_info_vm_addr, mappings).unwrap_or(0) as u32; + text_sections.push(SectionHeader { + segname: TEXT_SEG_NAME, + sectname: *b"__unwind_info\0\0\0", + addr: unwind_info_vm_addr, + size: unwind_info_size, + offset: ui_foff, + align: 2, + flags: 0, + }); + } + // Re-sort TEXT after adding special sections. + text_sections.sort_by_key(|s| s.addr); + + // Add .rustc in DATA if not in TEXT. + if has_rustc && !rustc_in_text { + let rc_addr = rustc_addr.max(data_vmaddr); + let rc_foff = vm_addr_to_file_offset(rustc_addr, mappings) + .unwrap_or(data_fileoff as usize) as u32; + data_sections.push(SectionHeader { + segname: DATA_SEG_NAME, + sectname: *b".rustc\0\0\0\0\0\0\0\0\0\0", + addr: rc_addr, + size: rustc_size, + offset: rc_foff, + align: 0, + flags: 0, + }); + data_sections.sort_by_key(|s| s.addr); + } + + // Fix up __thread_data: override type to S_THREAD_LOCAL_REGULAR and extend + // Fix __thread_data flags (set correct Mach-O section type). + for sec in &mut data_sections { + let name = crate::macho::trim_nul(&sec.sectname); + if name == b"__thread_data" { + sec.flags = 0x11; // S_THREAD_LOCAL_REGULAR } + } + + let text_nsects = text_sections.len() as u32; + add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * text_nsects); // TEXT + if has_data { + let data_nsects = data_sections.len() as u32; add_cmd(&mut ncmds, &mut cmdsize, 72 + 80 * data_nsects); } add_cmd(&mut ncmds, &mut cmdsize, 72); // LINKEDIT @@ -2649,109 +3112,29 @@ fn write_headers( w.u32(VM_PROT_READ | VM_PROT_EXECUTE); w.u32(text_nsects); w.u32(0); - w.name16(b"__text"); - w.name16(b"__TEXT"); - w.u64(text_layout.mem_offset); - w.u64(text_layout.mem_size); - w.u32(text_layout.file_offset as u32); - w.u32(2); - w.u32(0); - w.u32(0); - w.u32(0x80000400); - w.u32(0); - w.u32(0); - w.u32(0); - if rustc_in_text { - let rustc_foff = vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(0) as u32; - w.name16(b".rustc"); - w.name16(b"__TEXT"); - w.u64(rustc_addr); - w.u64(rustc_size); - w.u32(rustc_foff); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - } - if has_stubs { - let stubs_foff = - vm_addr_to_file_offset(plt_layout.mem_offset, mappings).unwrap_or(0) as u32; - w.name16(b"__stubs"); - w.name16(b"__TEXT"); - w.u64(plt_layout.mem_offset); - w.u64(plt_layout.mem_size); - w.u32(stubs_foff); - w.u32(2); // align 2^2 = 4 - w.u32(0); // reloff - w.u32(0); // nreloc - w.u32(0x80000408); // S_SYMBOL_STUBS | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS - w.u32(0); // reserved1 (indirect symbol table index, 0 for now) - w.u32(12); // reserved2 = stub size - w.u32(0); // reserved3 - } - if has_gcc_except { - let ge_foff = - vm_addr_to_file_offset(gcc_except_layout.mem_offset, mappings).unwrap_or(0) as u32; - w.name16(b"__gcc_except_tab"); - w.name16(b"__TEXT"); - w.u64(gcc_except_layout.mem_offset); - w.u64(gcc_except_layout.mem_size); - w.u32(ge_foff); - w.u32(2); // align 2^2 = 4 - w.u32(0); // reloff - w.u32(0); // nreloc - w.u32(0); // flags = S_REGULAR - w.u32(0); // reserved1 - w.u32(0); // reserved2 - w.u32(0); // reserved3 - } - if has_eh_frame { - let eh_foff = - vm_addr_to_file_offset(eh_frame_layout.mem_offset, mappings).unwrap_or(0) as u32; - w.name16(b"__eh_frame"); - w.name16(b"__TEXT"); - w.u64(eh_frame_layout.mem_offset); - w.u64(eh_frame_layout.mem_size); - w.u32(eh_foff); - w.u32(3); // align 2^3 = 8 - w.u32(0); - w.u32(0); - w.u32(0x6800_000B); // S_COALESCED | S_ATTR_NO_TOC | S_ATTR_STRIP_STATIC_SYMS | S_ATTR_LIVE_SUPPORT - w.u32(0); - w.u32(0); - w.u32(0); - } - if has_unwind_info { - let ui_foff = vm_addr_to_file_offset(unwind_info_vm_addr, mappings).unwrap_or(0) as u32; - w.name16(b"__unwind_info"); - w.name16(b"__TEXT"); - w.u64(unwind_info_vm_addr); - w.u64(unwind_info_size); - w.u32(ui_foff); - w.u32(2); // align 2^2 = 4 + // Write TEXT section headers. + for sec in &text_sections { + w.buf[w.pos..w.pos + 16].copy_from_slice(&sec.sectname); + w.pos += 16; + w.buf[w.pos..w.pos + 16].copy_from_slice(&sec.segname); + w.pos += 16; + w.u64(sec.addr); + w.u64(sec.size); + w.u32(sec.offset); + w.u32(sec.align); w.u32(0); // reloff w.u32(0); // nreloc - w.u32(0); // flags = S_REGULAR + w.u32(sec.flags); w.u32(0); // reserved1 - w.u32(0); // reserved2 + // reserved2: stub size for S_SYMBOL_STUBS + let reserved2 = if sec.flags & 0xFF == 0x08 { 12u32 } else { 0 }; + w.u32(reserved2); w.u32(0); // reserved3 } if has_data { - let mut nsects = if has_tvars { 2u32 } else { 0 }; - if has_rustc { - nsects += 1; - } - if has_init_array { - nsects += 1; - } - if has_got { - nsects += 1; - } - let data_cmd_size = 72 + 80 * nsects; + let data_nsects = data_sections.len() as u32; + let data_cmd_size = 72 + 80 * data_nsects; w.u32(LC_SEGMENT_64); w.u32(data_cmd_size); w.name16(b"__DATA"); @@ -2761,138 +3144,26 @@ fn write_headers( w.u64(data_filesize); w.u32(VM_PROT_READ | VM_PROT_WRITE); w.u32(VM_PROT_READ | VM_PROT_WRITE); - w.u32(nsects); + w.u32(data_nsects); w.u32(0); - if has_tvars { - // Section addresses must be within [data_vmaddr, data_vmaddr+data_vmsize). - // Clamp to segment range. - // Find actual __thread_vars address by scanning object sections - let le = object::Endianness::Little; - let mut tvars_addr = u64::MAX; - let mut tvars_size = 0u64; - let mut tdata_addr = u64::MAX; - let mut tdata_size = 0u64; - for group in &layout.group_layouts { - for file_layout in &group.files { - if let FileLayout::Object(obj) = file_layout { - for (sec_idx, _) in obj.sections.iter().enumerate() { - if let Some(s) = obj.object.sections.get(sec_idx) { - use object::read::macho::Section as _; - let sec_type = s.flags(le) & 0xFF; - if let Some(addr) = obj.section_resolutions[sec_idx].address() { - if sec_type == 0x13 { - // S_THREAD_LOCAL_VARIABLES - tvars_addr = tvars_addr.min(addr); - tvars_size += s.size(le); - } else if sec_type == 0x11 { - // S_THREAD_LOCAL_REGULAR - tdata_addr = tdata_addr.min(addr); - tdata_size += s.size(le); - } - } - } - } - } - } - } - tvars_size = (tvars_size / 24) * 24; - if tvars_addr == u64::MAX { - tvars_addr = 0; - } - // If no type-0x11 sections, use TDATA layout (may be empty but correctly positioned) - if tdata_addr == u64::MAX { - tdata_addr = tdata_layout.mem_offset; - } - let tvars_foff = vm_addr_to_file_offset(tvars_addr, mappings) - .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__thread_vars"); - w.name16(b"__DATA"); - w.u64(tvars_addr); - w.u64(tvars_size); - w.u32(tvars_foff); - w.u32(3); - w.u32(0); - w.u32(0); - w.u32(0x13); // S_THREAD_LOCAL_VARIABLES - w.u32(0); - w.u32(0); - w.u32(0); - - // __thread_data: init template. Size includes TBSS for dyld. - let tdata_init_addr = tdata_addr; - let tdata_init_size = ((tdata_size + 7) & !7) + tbss_layout.mem_size; - let tdata_init_foff = vm_addr_to_file_offset(tdata_init_addr, mappings) - .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__thread_data"); - w.name16(b"__DATA"); - w.u64(tdata_init_addr); - w.u64(tdata_init_size); - w.u32(tdata_init_foff); - w.u32(2); - w.u32(0); - w.u32(0); - w.u32(0x11); // S_THREAD_LOCAL_REGULAR - w.u32(0); - w.u32(0); - w.u32(0); - } - if has_rustc { - // Always emit .rustc in __DATA for rustc to find metadata. - let rc_addr = if rustc_in_text { - data_vmaddr - } else { - rustc_addr.max(data_vmaddr) - }; - let rc_foff = if rustc_in_text { - data_fileoff as u32 - } else { - vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(data_fileoff as usize) as u32 - }; - w.name16(b".rustc"); - w.name16(b"__DATA"); - w.u64(rc_addr); - w.u64(rustc_size); - w.u32(rc_foff); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - w.u32(0); - } - if has_got { - let got_foff = vm_addr_to_file_offset(got_layout.mem_offset, mappings) - .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__got"); - w.name16(b"__DATA"); - w.u64(got_layout.mem_offset); - w.u64(got_layout.mem_size); - w.u32(got_foff); - w.u32(3); // align 2^3 = 8 + + // Write DATA section headers. + for sec in &data_sections { + w.buf[w.pos..w.pos + 16].copy_from_slice(&sec.sectname); + w.pos += 16; + w.buf[w.pos..w.pos + 16].copy_from_slice(&sec.segname); + w.pos += 16; + w.u64(sec.addr); + w.u64(sec.size); + w.u32(sec.offset); + w.u32(sec.align); w.u32(0); // reloff w.u32(0); // nreloc - w.u32(0x06); // S_NON_LAZY_SYMBOL_POINTERS + w.u32(sec.flags); w.u32(0); // reserved1 w.u32(0); // reserved2 w.u32(0); // reserved3 } - if has_init_array { - let ia_foff = vm_addr_to_file_offset(init_array_layout.mem_offset, mappings) - .unwrap_or(data_fileoff as usize) as u32; - w.name16(b"__mod_init_func"); - w.name16(b"__DATA"); - w.u64(init_array_layout.mem_offset); - w.u64(init_array_layout.mem_size); - w.u32(ia_foff); - w.u32(3); // align 2^3 = 8 - w.u32(0); - w.u32(0); - w.u32(0x09); // S_MOD_INIT_FUNC_POINTERS - w.u32(0); - w.u32(0); - w.u32(0); - } } let (last_file_end, linkedit_vm) = if has_data { @@ -3674,6 +3945,384 @@ fn validate_macho_output(buf: &[u8]) -> Result { } } } + + // Check TLS invariants for __thread_vars descriptors. + if let Ok(Some((seg, seg_data))) = cmd.segment_64() { + if crate::macho::trim_nul(&seg.segname) == b"__DATA" { + if let Ok(sections) = seg.sections(le, seg_data) { + let mut tdata_size = 0u64; + let mut tbss_size = 0u64; + let mut tvars_foff = 0usize; + let mut tvars_count = 0usize; + for sec in sections { + let sec_type = sec.flags(le) & 0xFF; + let size = sec.size(le); + match sec_type { + 0x11 => tdata_size = size, + 0x12 => tbss_size = size, + 0x13 => { + tvars_foff = sec.offset(le) as usize; + tvars_count = size as usize / 24; + } + _ => {} + } + } + let tls_total = tdata_size + tbss_size; + + if tvars_count > 0 && tls_total > 0 { + let mut offsets = Vec::new(); + for i in 0..tvars_count { + let base = tvars_foff + i * 24; + if base + 24 > buf.len() { + break; + } + let key = + u64::from_le_bytes(buf[base + 8..base + 16].try_into().unwrap()); + let offset = + u64::from_le_bytes(buf[base + 16..base + 24].try_into().unwrap()); + + // Invariant: key must be 0 (dyld manages it at runtime) + if key != 0 { + crate::bail!( + "validate: TLV descriptor [{i}] key={key:#x} (must be 0)" + ); + } + + // Invariant: offset must not have fixup encoding + // (high bits in 51-63 must be 0) + if (offset >> 51) != 0 { + crate::bail!( + "validate: TLV descriptor [{i}] offset={offset:#x} \ + has fixup encoding (bits 51+ set)" + ); + } + + // Invariant: offset must be within TLS block + if offset >= tls_total { + crate::bail!( + "validate: TLV descriptor [{i}] offset={offset:#x} \ + exceeds TLS block size {tls_total:#x} \ + (thread_data={tdata_size:#x} + thread_bss={tbss_size:#x})" + ); + } + + offsets.push(offset); + } + + // Invariant: no two TLV descriptors should share the same offset + // (unless both are zero — which indicates a bug but may not crash) + offsets.sort(); + for w in offsets.windows(2) { + if w[0] == w[1] && tvars_count > 1 { + crate::bail!( + "validate: duplicate TLV offset {:#x} — \ + two thread-locals share the same TLS slot", + w[0] + ); + } + } + } + } + } + } + + // Check LC_SYMTAB + if let Ok(Some(symtab)) = cmd.symtab() { + let symoff = symtab.symoff.get(le) as u64; + let nsyms = symtab.nsyms.get(le) as u64; + let stroff = symtab.stroff.get(le) as u64; + let strsize = symtab.strsize.get(le) as u64; + let sym_end = symoff + nsyms * 16; + if sym_end > file_len { + crate::bail!( + "validate: LC_SYMTAB extends beyond file \ + (symoff {symoff:#x} + {nsyms}*16 = {sym_end:#x} > {file_len:#x})" + ); + } + if stroff + strsize > file_len { + crate::bail!( + "validate: LC_SYMTAB strtab extends beyond file \ + (stroff {stroff:#x} + {strsize:#x} > {file_len:#x})" + ); + } + } + } + + // Symbol-section consistency check: every defined symbol's n_value must + // fall within the address range of the section identified by its n_sect. + // This catches layout bugs where a symbol is resolved using the wrong + // section's output address. + { + let mut cmds_sym = header + .load_commands(le, buf, 0) + .map_err(|e| crate::error!("validate: {e}"))?; + // Collect all sections with their address ranges + let mut section_ranges: Vec<(u64, u64)> = Vec::new(); // (addr, addr+size) + while let Ok(Some(cmd)) = cmds_sym.next() { + if let Ok(Some((seg, seg_data))) = cmd.segment_64() { + if let Ok(sections) = seg.sections(le, seg_data) { + for sec in sections { + let addr = sec.addr(le); + let size = sec.size(le); + section_ranges.push((addr, addr + size)); + } + } + } + if let Ok(Some(symtab)) = cmd.symtab() { + let symoff = symtab.symoff.get(le) as usize; + let nsyms = symtab.nsyms.get(le) as usize; + let stroff = symtab.stroff.get(le) as usize; + for i in 0..nsyms { + let sym_off = symoff + i * 16; + if sym_off + 16 > buf.len() { + break; + } + let n_strx = u32::from_le_bytes(buf[sym_off..sym_off + 4].try_into().unwrap()); + let n_type = buf[sym_off + 4]; + let n_sect = buf[sym_off + 5]; + let n_value = + u64::from_le_bytes(buf[sym_off + 8..sym_off + 16].try_into().unwrap()); + + // Only check defined symbols in a section (N_SECT = 0x0e) + if (n_type & 0x0e) != 0x0e || n_sect == 0 { + continue; + } + let sec_idx = n_sect as usize - 1; + if sec_idx >= section_ranges.len() { + continue; + } + let (sec_start, sec_end) = section_ranges[sec_idx]; + if n_value < sec_start || n_value > sec_end { + // Get symbol name for the error message + let name = if (n_strx as usize) < buf.len() - stroff { + let name_start = stroff + n_strx as usize; + let name_end = buf[name_start..] + .iter() + .position(|&b| b == 0) + .map(|p| name_start + p) + .unwrap_or(name_start); + String::from_utf8_lossy(&buf[name_start..name_end]).to_string() + } else { + format!("") + }; + crate::bail!( + "validate: symbol '{name}' n_value={n_value:#x} is outside \ + section {sec_idx} range [{sec_start:#x}..{sec_end:#x})" + ); + } + } + } + } + } + + // Global section file-offset overlap check: no two sections should + // write to the same file bytes. This catches bugs where multiple input + // sections map to overlapping parts of the same output section. + { + let mut cmds2 = header + .load_commands(le, buf, 0) + .map_err(|e| crate::error!("validate: bad load commands: {e}"))?; + let mut all_sections: Vec<(u64, u64, String)> = Vec::new(); + while let Ok(Some(cmd)) = cmds2.next() { + if let Ok(Some((seg, seg_data))) = cmd.segment_64() { + let segname = String::from_utf8_lossy(crate::macho::trim_nul(&seg.segname)); + if let Ok(sections) = seg.sections(le, seg_data) { + for sec in sections { + let sectname = + String::from_utf8_lossy(crate::macho::trim_nul(sec.sectname())); + let sec_offset = sec.offset(le) as u64; + let sec_size = sec.size(le); + let sec_type = sec.flags(le) & 0xFF; + // Skip zerofill sections (no file data) + if sec_size > 0 && sec_offset > 0 && sec_type != 0x01 && sec_type != 0x0C + { + all_sections + .push((sec_offset, sec_size, format!("{segname},{sectname}"))); + } + } + } + } + } + all_sections.sort_by_key(|s| s.0); + for w in all_sections.windows(2) { + let (off1, size1, ref name1) = w[0]; + let (off2, _size2, ref name2) = w[1]; + if off1 + size1 > off2 { + crate::bail!( + "validate: section file ranges overlap: \ + {name1} [{off1:#x}..{:#x}) and {name2} [{off2:#x}..)", + off1 + size1 + ); + } + } + } + + // Validate chained fixup chains: walk every chain entry and verify + // rebase targets are within the image and strides stay within pages. + validate_chained_fixups(buf)?; + + Ok(()) +} + +/// Walk all chained fixup chains and validate each entry. +fn validate_chained_fixups(buf: &[u8]) -> Result { + use object::read::macho::MachHeader as _; + let le = object::Endianness::Little; + let header = match object::macho::MachHeader64::::parse(buf, 0) { + Ok(h) => h, + Err(_) => return Ok(()), + }; + let mut cmds = match header.load_commands(le, buf, 0) { + Ok(c) => c, + Err(_) => return Ok(()), + }; + + // Find LC_DYLD_CHAINED_FIXUPS and the DATA segment + let mut cf_off = 0u32; + let mut cf_size = 0u32; + let mut data_fileoff = 0u64; + let mut _data_vmaddr = 0u64; + let mut image_end = 0u64; // highest vmaddr + vmsize + + // Scan load commands manually for chained fixups offset. + { + let mut off = 32usize; // after Mach-O 64 header + let ncmds = u32::from_le_bytes(buf[16..20].try_into().unwrap_or([0; 4])) as usize; + for _ in 0..ncmds { + if off + 8 > buf.len() { + break; + } + let cmd_val = u32::from_le_bytes(buf[off..off + 4].try_into().unwrap()); + let cmdsize = u32::from_le_bytes(buf[off + 4..off + 8].try_into().unwrap()) as usize; + if cmd_val == 0x8000_0034 && off + 16 <= buf.len() { + cf_off = u32::from_le_bytes(buf[off + 8..off + 12].try_into().unwrap()); + cf_size = u32::from_le_bytes(buf[off + 12..off + 16].try_into().unwrap()); + } + off += cmdsize; + } + } + + while let Ok(Some(cmd)) = cmds.next() { + if let Ok(Some((seg, _))) = cmd.segment_64() { + let va = seg.vmaddr.get(le); + let vs = seg.vmsize.get(le); + image_end = image_end.max(va + vs); + let segname = crate::macho::trim_nul(&seg.segname); + if segname == b"__DATA" { + data_fileoff = seg.fileoff.get(le); + _data_vmaddr = va; + } + } + } + + if cf_off == 0 || cf_size == 0 { + return Ok(()); // no chained fixups + } + + let cf = match buf.get(cf_off as usize..(cf_off + cf_size) as usize) { + Some(d) => d, + None => return Ok(()), + }; + if cf.len() < 32 { + return Ok(()); + } + + let starts_offset = u32::from_le_bytes(cf[4..8].try_into().unwrap()) as usize; + let imports_count = u32::from_le_bytes(cf[16..20].try_into().unwrap()); + + if starts_offset + 4 > cf.len() { + return Ok(()); + } + let seg_count = u32::from_le_bytes(cf[starts_offset..starts_offset + 4].try_into().unwrap()); + + for s in 0..seg_count as usize { + let seg_off_pos = starts_offset + 4 + s * 4; + if seg_off_pos + 4 > cf.len() { + break; + } + let seg_off = + u32::from_le_bytes(cf[seg_off_pos..seg_off_pos + 4].try_into().unwrap()) as usize; + if seg_off == 0 { + continue; + } + let ss = starts_offset + seg_off; + if ss + 22 > cf.len() { + continue; + } + let page_size = u16::from_le_bytes(cf[ss + 4..ss + 6].try_into().unwrap()) as u64; + let page_count = u16::from_le_bytes(cf[ss + 20..ss + 22].try_into().unwrap()) as usize; + + if page_size == 0 { + continue; + } + + for p in 0..page_count { + let ps_pos = ss + 22 + p * 2; + if ps_pos + 2 > cf.len() { + break; + } + let ps = u16::from_le_bytes(cf[ps_pos..ps_pos + 2].try_into().unwrap()); + if ps == 0xFFFF { + continue; + } + if ps as u64 >= page_size { + crate::bail!( + "validate: chained fixup page start {ps:#x} >= page_size {page_size:#x} \ + (seg {s}, page {p})" + ); + } + + // Walk the chain + let page_file_off = data_fileoff as usize + p * page_size as usize; + let mut file_off = page_file_off + ps as usize; + let mut chain_count = 0u32; + loop { + if file_off + 8 > buf.len() { + crate::bail!( + "validate: fixup chain entry at file offset {file_off:#x} \ + beyond file end (seg {s}, page {p}, entry {chain_count})" + ); + } + let val = u64::from_le_bytes(buf[file_off..file_off + 8].try_into().unwrap()); + let bind = (val >> 63) & 1; + let next_stride = ((val >> 51) & 0xFFF) as usize; + + if bind != 0 { + let ordinal = (val & 0xFF_FFFF) as u32; + if ordinal >= imports_count { + crate::bail!( + "validate: bind ordinal {ordinal} >= imports_count {imports_count} \ + at file offset {file_off:#x} (seg {s}, page {p})" + ); + } + } else { + let target = val & 0xF_FFFF_FFFF; + if target > 0 && target > image_end { + crate::bail!( + "validate: rebase target {target:#x} beyond image end {image_end:#x} \ + at file offset {file_off:#x} (seg {s}, page {p})" + ); + } + } + + chain_count += 1; + if next_stride == 0 { + break; + } + + let next_off = file_off + next_stride * 4; + let next_in_page = next_off - page_file_off; + if next_in_page >= page_size as usize { + crate::bail!( + "validate: fixup chain crosses page boundary at file offset \ + {file_off:#x}, next at +{} bytes = offset {next_in_page:#x} in page \ + (page_size={page_size:#x}, seg {s}, page {p})", + next_stride * 4 + ); + } + file_off = next_off; + } + } } Ok(()) diff --git a/wild/tests/sources/macho/rust-build-script-sim/rust-build-script-sim.rs b/wild/tests/sources/macho/rust-build-script-sim/rust-build-script-sim.rs new file mode 100644 index 000000000..b470567d4 --- /dev/null +++ b/wild/tests/sources/macho/rust-build-script-sim/rust-build-script-sim.rs @@ -0,0 +1,64 @@ +//#LinkerDriver:clang + +// Simulates what proc-macro2's build script does: run a subprocess, +// capture output, parse strings, write to files. This exercises +// __const vtables, __data globals, __cstring literals, and GOT +// entries together under realistic conditions. + +use std::collections::HashMap; +use std::io::Write; +use std::process::Command; + +fn probe_rustc_version() -> Option { + let output = Command::new("rustc") + .arg("--version") + .output() + .ok()?; + let stdout = String::from_utf8(output.stdout).ok()?; + // Parse "rustc 1.XX.Y (...)" + let version = stdout.split(' ').nth(1)?; + let minor = version.split('.').nth(1)?; + minor.parse().ok() +} + +fn build_feature_map(version: u32) -> HashMap { + let mut features = HashMap::new(); + features.insert("proc_macro".to_string(), version >= 30); + features.insert("span_locations".to_string(), version >= 45); + features.insert("literal_c_string".to_string(), version >= 77); + features.insert("source_text".to_string(), version >= 80); + features.insert("is_available".to_string(), version >= 71); + features +} + +fn write_output(features: &HashMap) -> std::io::Result<()> { + let mut buf = Vec::new(); + for (name, enabled) in features { + if *enabled { + writeln!(buf, "cargo:rustc-cfg={name}")?; + } + } + // Just verify we can produce output, don't actually write to cargo + assert!(!buf.is_empty()); + Ok(()) +} + +fn main() { + let version = probe_rustc_version().unwrap_or(0); + assert!(version > 50, "rustc version too old: {version}"); + + let features = build_feature_map(version); + assert!(features.len() == 5); + assert!(*features.get("proc_macro").unwrap()); + + write_output(&features).expect("write failed"); + + // Exercise format strings and dynamic allocation + let msgs: Vec = (0..100) + .map(|i| format!("cargo:rustc-check-cfg=cfg(feature_{i})")) + .collect(); + assert_eq!(msgs.len(), 100); + assert!(msgs[42].contains("feature_42")); + + std::process::exit(42); +} diff --git a/wild/tests/sources/macho/rust-format-strings/rust-format-strings.rs b/wild/tests/sources/macho/rust-format-strings/rust-format-strings.rs new file mode 100644 index 000000000..243d02a48 --- /dev/null +++ b/wild/tests/sources/macho/rust-format-strings/rust-format-strings.rs @@ -0,0 +1,31 @@ +//#LinkerDriver:clang + +// Tests that Rust format strings and string constants are correctly linked +// when combined with thread-local storage. This exercises __const vtables, +// __cstring data, and __thread_vars alignment together. +// The proc-macro2 build script crashes because __thread_vars descriptors +// end up at a non-8-byte-aligned address. + +use std::process::Command; +use std::ffi::OsString; +use std::env; + +fn rustc_minor_version() -> Option { + let rustc: OsString = env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); + let output = Command::new(rustc).arg("--version").output().ok()?; + let version = std::str::from_utf8(&output.stdout).ok()?; + let mut pieces = version.split('.'); + if pieces.next() != Some("rustc 1") { + return None; + } + pieces.next()?.parse().ok() +} + +fn main() { + let version = rustc_minor_version().unwrap_or(0); + if version > 50 { + let msg = format!("rustc version: 1.{version}"); + assert!(msg.contains("rustc version:"), "format! corrupted: {msg:?}"); + } + std::process::exit(42); +} diff --git a/wild/tests/sources/macho/rust-large-data/rust-large-data.rs b/wild/tests/sources/macho/rust-large-data/rust-large-data.rs new file mode 100644 index 000000000..f05647ce1 --- /dev/null +++ b/wild/tests/sources/macho/rust-large-data/rust-large-data.rs @@ -0,0 +1,44 @@ +//#LinkerDriver:clang + +// Tests linking with large __data section (many vtables, string constants, +// and data pointers). This exercises chained fixup rebase entries across +// multiple pages of the DATA segment. + +use std::collections::HashMap; +use std::io::Write; + +fn build_map() -> HashMap> { + let mut map = HashMap::new(); + for i in 0..50 { + let key = format!("key_{i:04}"); + let val: Vec = (0..100).map(|j| ((i * 7 + j * 3) % 256) as u8).collect(); + map.insert(key, val); + } + map +} + +fn format_output(map: &HashMap>) -> Vec { + let mut buf = Vec::new(); + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort(); + for key in keys { + let val = &map[key]; + writeln!(buf, "{}: {} bytes, sum={}", key, val.len(), + val.iter().map(|&b| b as u64).sum::()).unwrap(); + } + buf +} + +fn main() { + let map = build_map(); + assert_eq!(map.len(), 50); + + let output = format_output(&map); + assert!(output.len() > 1000); + + // Verify specific entries + assert!(map.contains_key("key_0042")); + assert_eq!(map["key_0000"].len(), 100); + + std::process::exit(42); +} diff --git a/wild/tests/sources/macho/rust-tls/rust-tls.rs b/wild/tests/sources/macho/rust-tls/rust-tls.rs index c29694fef..8b6eece64 100644 --- a/wild/tests/sources/macho/rust-tls/rust-tls.rs +++ b/wild/tests/sources/macho/rust-tls/rust-tls.rs @@ -8,18 +8,11 @@ thread_local!(static FOO: Cell = Cell::new(1)); fn main() { assert_eq!(FOO.get(), 1); FOO.set(2); - - // each thread starts out with the initial value of 1 let t = thread::spawn(move || { assert_eq!(FOO.get(), 1); FOO.set(3); }); - - // wait for the thread to complete and bail out on panic t.join().unwrap(); - - // we retain our original value of 2 despite the child thread assert_eq!(FOO.get(), 2); - std::process::exit(42); } diff --git a/wild/tests/sources/macho/tls-alignment/tls-alignment.c b/wild/tests/sources/macho/tls-alignment/tls-alignment.c new file mode 100644 index 000000000..53a658697 --- /dev/null +++ b/wild/tests/sources/macho/tls-alignment/tls-alignment.c @@ -0,0 +1,12 @@ +// Tests that TLS variables are properly aligned when preceded by +// odd-sized data sections. The __thread_vars descriptors must be +// 8-byte aligned for dyld to process them correctly. +//#Object:tls-alignment1.c + +extern __thread int tls_val; +int get_tls(void); + +int main() { + tls_val = 10; + return get_tls() == 10 ? 42 : 1; +} diff --git a/wild/tests/sources/macho/tls-alignment/tls-alignment1.c b/wild/tests/sources/macho/tls-alignment/tls-alignment1.c new file mode 100644 index 000000000..0fe74332f --- /dev/null +++ b/wild/tests/sources/macho/tls-alignment/tls-alignment1.c @@ -0,0 +1,6 @@ +// Odd-sized data to misalign subsequent sections +const char padding[] = + "abc"; // 4 bytes including NUL — ensures __data has odd alignment + +__thread int tls_val = 0; +int get_tls(void) { return tls_val; } From a2f791775b12f3aacb8a2ae606c0b59556ed24c1 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 06:06:16 +0100 Subject: [PATCH 15/21] chore: fmt + gitignore Signed-off-by: Giles Cope --- .claude/settings.local.json | 61 --------------------------- .gitignore | 1 + libwild/src/eh_frame.rs | 1 - libwild/src/macho.rs | 4 +- libwild/src/macho_writer.rs | 61 +++++++++++++++++---------- wild/tests/macho_integration_tests.rs | 16 +++---- 6 files changed, 50 insertions(+), 94 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index d5f3e8064..000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(wc -l /Users/gilescope/git/gilescope/wild/libwild/src/*.rs)", - "WebSearch", - "WebFetch(domain:crates.io)", - "WebFetch(domain:lib.rs)", - "WebFetch(domain:docs.rs)", - "Bash(rustup install:*)", - "Bash(rustup override:*)", - "Bash(grep -A5 \"fn is_undefined\" ~/.cargo/registry/src/*/object-0.39.*/src/read/macho/symbol.rs)", - "Bash(grep -n \"S_REGULAR\\\\|S_ZEROFILL\\\\|S_CSTRING_LITERALS\\\\|S_NON_LAZY\\\\|S_LAZY\\\\|SECTION_TYPE\\\\|S_ATTR_PURE_INST\\\\|S_ATTR_SOME_INST\\\\|SECTION_ATTRIBUTES\" ~/.cargo/registry/src/*/object-0.39.*/src/macho.rs)", - "Bash(grep -n \"ARM64_RELOC\" ~/.cargo/registry/src/*/object-0.39.*/src/macho.rs)", - "Bash(clang -v -c /tmp/test_macho.c -o /tmp/test_macho.o)", - "Bash(clang -### /tmp/test_macho.o -o /tmp/test_macho)", - "Bash(./target/debug/wild:*)", - "Bash(otool -l /tmp/test_macho.o)", - "Read(//private/tmp/**)", - "Bash(otool -l /tmp/test_wild_macho)", - "Bash(/tmp/test_wild_macho)", - "Bash(codesign:*)", - "Bash(clang /tmp/test_macho.o -o /tmp/test_ref_macho -Wl,-no_compact_unwind)", - "Bash(otool -l /tmp/test_ref_macho)", - "Bash(clang -c /tmp/test_add.c -o /tmp/test_add.o)", - "Bash(clang -c /tmp/test_main2.c -o /tmp/test_main2.o)", - "Bash(/tmp/test_multi)", - "Bash(otool:*)", - "Bash(grep:*)", - "Bash(/tmp/test_multi2)", - "Bash(chmod +x /Users/gilescope/git/gilescope/wild/tests/macho_tests.sh)", - "Bash(bash /Users/gilescope/git/gilescope/wild/tests/macho_tests.sh)", - "Bash(clang -c /tmp/t4_data.c -o /tmp/t4_data.o)", - "Bash(clang -c /tmp/t4_main.c -o /tmp/t4_main.o)", - "Bash(nm:*)", - "Bash(clang -x c -c -o /tmp/dbg.o -)", - "Bash(/tmp/dbg_out)", - "Bash(clang -x c -c -o /tmp/ref.o -)", - "Bash(clang /tmp/ref.o -o /tmp/ref_out -Wl,-no_compact_unwind)", - "Bash(git stash:*)", - "Bash(clang -x c -c -o /tmp/old.o -)", - "Bash(/tmp/old_out)", - "Bash(clang -x c -c -o /tmp/new.o -)", - "Bash(DYLD_PRINT_APIS=1 /tmp/new_out)", - "Bash(/tmp/new_out)", - "Bash(clang -x c -c -o /tmp/fix.o -)", - "Bash(/tmp/fix_out)", - "Bash(clang -c /tmp/t_static.c -o /tmp/t_static.o)", - "Bash(/tmp/t_static_out)", - "Bash(clang -c /tmp/t_extern_data.c -o /tmp/t_extern_data.o)", - "Bash(clang -c /tmp/t_extern_main.c -o /tmp/t_extern_main.o)", - "Bash(clang -c /tmp/t_ext_data.c -o /tmp/t_ext_data.o)", - "Bash(clang -c /tmp/t_ext_main.c -o /tmp/t_ext_main.o)", - "Bash(/tmp/t_ext_out)", - "Bash(RUST_BACKTRACE=1 /tmp/rust_map)", - "Bash(RUST_BACKTRACE=full /tmp/rust_map)", - "Bash(/tmp/test_shared/shared-basic)", - "Bash(objdump --macho --private-headers /tmp/test_shared/libshared-basic-lib.dylib)", - "Bash(objdump --macho --private-headers /tmp/test_shared/shared-basic)" - ] - } -} diff --git a/.gitignore b/.gitignore index 1c31bc6ef..f0d03371c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ fakes-debug/sde-cet-checker-out.txt *.bench-results .DS_Store *.rcgu.o +.claude/settings.local.json diff --git a/libwild/src/eh_frame.rs b/libwild/src/eh_frame.rs index 18d181a13..a5e246c8c 100644 --- a/libwild/src/eh_frame.rs +++ b/libwild/src/eh_frame.rs @@ -58,4 +58,3 @@ pub(crate) struct CieAtOffset<'data> { pub(crate) offset: u32, pub(crate) cie: Cie<'data>, } - diff --git a/libwild/src/macho.rs b/libwild/src/macho.rs index c0f2fdd34..dae6758be 100644 --- a/libwild/src/macho.rs +++ b/libwild/src/macho.rs @@ -1529,8 +1529,8 @@ impl platform::Platform for MachO { // __TEXT segment (r-x): headers, code, read-only data, stubs builder.add_section(output_section_id::FILE_HEADER); - builder.add_section(output_section_id::RODATA); // __cstring - builder.add_section(output_section_id::COMMENT); // __literal4/8/16 + builder.add_section(output_section_id::RODATA); // __cstring + builder.add_section(output_section_id::COMMENT); // __literal4/8/16 builder.add_section(output_section_id::DATA_REL_RO); // __text_const builder.add_sections(&custom.ro); builder.add_section(output_section_id::TEXT); diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index d4e13be6d..ae4a6f154 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -299,7 +299,11 @@ fn write_macho>( &mut bind_fixups, &mut imports, has_extra_dylibs, - if validate { Some(&mut write_ranges) } else { None }, + if validate { + Some(&mut write_ranges) + } else { + None + }, )?; } } @@ -430,7 +434,7 @@ fn write_macho>( if tvars_start != usize::MAX { for off in (tvars_start..tvars_end).step_by(24) { - positions.insert(off + 8); // key field + positions.insert(off + 8); // key field positions.insert(off + 16); // offset field } } @@ -885,14 +889,19 @@ fn write_exe_symtab( let mut hoff = 32usize; let ncmds = u32::from_le_bytes(out[16..20].try_into().unwrap_or([0; 4])) as usize; for _ in 0..ncmds { - if hoff + 8 > out.len() { break; } + if hoff + 8 > out.len() { + break; + } let cmd = u32::from_le_bytes(out[hoff..hoff + 4].try_into().unwrap()); let cmdsize = u32::from_le_bytes(out[hoff + 4..hoff + 8].try_into().unwrap()) as usize; if cmd == LC_SEGMENT_64 && hoff + 72 <= out.len() { - let nsects = u32::from_le_bytes(out[hoff + 64..hoff + 68].try_into().unwrap()) as usize; + let nsects = + u32::from_le_bytes(out[hoff + 64..hoff + 68].try_into().unwrap()) as usize; for j in 0..nsects { let so = hoff + 72 + j * 80; - if so + 48 > out.len() { break; } + if so + 48 > out.len() { + break; + } let addr = u64::from_le_bytes(out[so + 32..so + 40].try_into().unwrap()); let size = u64::from_le_bytes(out[so + 40..so + 48].try_into().unwrap()); ranges.push((addr, addr + size)); @@ -1883,10 +1892,13 @@ fn apply_relocations( // Log non-TLS rebases that MIGHT be TLS if reloc.r_extern { use object::read::macho::Nlist as _; - if let Ok(sym) = obj.object.symbols.symbol( - object::SymbolIndex(reloc.r_symbolnum as usize), - ) { - let name = sym.name(le, obj.object.symbols.strings()).unwrap_or(b""); + if let Ok(sym) = obj + .object + .symbols + .symbol(object::SymbolIndex(reloc.r_symbolnum as usize)) + { + let name = + sym.name(le, obj.object.symbols.strings()).unwrap_or(b""); if name.ends_with(b"$tlv$init") { let _ = std::fs::OpenOptions::new().create(true).append(true).open("/tmp/wild_tls_debug.log") .and_then(|mut f| { @@ -2833,7 +2845,11 @@ fn macho_section_info(id: crate::output_section_id::OutputSectionId) -> Option (DATA_SEG, name16(b"__bss"), 0x01), _ => return None, }; - Some(MachoSectionInfo { segname, sectname, flags }) + Some(MachoSectionInfo { + segname, + sectname, + flags, + }) } /// Write Mach-O headers. Returns the chained fixups file offset. @@ -2974,8 +2990,7 @@ fn write_headers( if sec_layout.mem_size == 0 { continue; } - let file_off = vm_addr_to_file_offset(sec_layout.mem_offset, mappings) - .unwrap_or(0) as u32; + let file_off = vm_addr_to_file_offset(sec_layout.mem_offset, mappings).unwrap_or(0) as u32; if let Some(info) = macho_section_info(sec_id) { let hdr = SectionHeader { segname: *info.segname, @@ -3028,8 +3043,8 @@ fn write_headers( // Add .rustc in DATA if not in TEXT. if has_rustc && !rustc_in_text { let rc_addr = rustc_addr.max(data_vmaddr); - let rc_foff = vm_addr_to_file_offset(rustc_addr, mappings) - .unwrap_or(data_fileoff as usize) as u32; + let rc_foff = + vm_addr_to_file_offset(rustc_addr, mappings).unwrap_or(data_fileoff as usize) as u32; data_sections.push(SectionHeader { segname: DATA_SEG_NAME, sectname: *b".rustc\0\0\0\0\0\0\0\0\0\0", @@ -3838,7 +3853,9 @@ fn write_relocatable_object(layout: &Layout<'_, MachO>) -> Result { /// # Chained fixups invariants /// - Page start offsets are within a page (< page_size) fn validate_macho_output(buf: &[u8]) -> Result { - use object::read::macho::{MachHeader as _, Section as _, Segment as _}; + use object::read::macho::MachHeader as _; + use object::read::macho::Section as _; + use object::read::macho::Segment as _; let le = object::Endianness::Little; let header = object::macho::MachHeader64::::parse(buf, 0) .map_err(|e| crate::error!("validate: bad Mach-O header: {e}"))?; @@ -3908,9 +3925,7 @@ fn validate_macho_output(buf: &[u8]) -> Result { let sec_type = sec.flags(le) & 0xFF; let is_zerofill = sec_type == 0x01 || sec_type == 0x0C; if sec_size > 0 && !is_zerofill && sec_offset > 0 && file_size > 0 { - if sec_offset < file_off - || sec_offset + sec_size > file_off + file_size - { + if sec_offset < file_off || sec_offset + sec_size > file_off + file_size { crate::bail!( "validate: section {segname_str},{sect_name} file range \ [{sec_offset:#x}..{:#x}) outside segment \ @@ -4134,10 +4149,12 @@ fn validate_macho_output(buf: &[u8]) -> Result { let sec_size = sec.size(le); let sec_type = sec.flags(le) & 0xFF; // Skip zerofill sections (no file data) - if sec_size > 0 && sec_offset > 0 && sec_type != 0x01 && sec_type != 0x0C - { - all_sections - .push((sec_offset, sec_size, format!("{segname},{sectname}"))); + if sec_size > 0 && sec_offset > 0 && sec_type != 0x01 && sec_type != 0x0C { + all_sections.push(( + sec_offset, + sec_size, + format!("{segname},{sectname}"), + )); } } } diff --git a/wild/tests/macho_integration_tests.rs b/wild/tests/macho_integration_tests.rs index 0db944adb..badcb6d5a 100644 --- a/wild/tests/macho_integration_tests.rs +++ b/wild/tests/macho_integration_tests.rs @@ -468,11 +468,10 @@ fn run_test( /// - Sections within a segment must not overlap. /// - LC_SYMTAB offsets must be within the file. /// - Chained fixup page starts must reference offsets within a page (< page_size). -fn verify_macho_invariants( - binary: &[u8], - path: &std::path::Path, -) -> Result<(), String> { - use object::read::macho::{MachHeader as _, Segment as _, Section as _}; +fn verify_macho_invariants(binary: &[u8], path: &std::path::Path) -> Result<(), String> { + use object::read::macho::MachHeader as _; + use object::read::macho::Section as _; + use object::read::macho::Segment as _; let le = object::Endianness::Little; let header = object::macho::MachHeader64::::parse(binary, 0) .map_err(|e| format!("{}: failed to parse Mach-O header: {e}", path.display()))?; @@ -526,8 +525,7 @@ fn verify_macho_invariants( for sec in sections { let sect_name_raw = sec.sectname(); let sect_name = std::str::from_utf8( - §_name_raw - [..sect_name_raw.iter().position(|&b| b == 0).unwrap_or(16)], + §_name_raw[..sect_name_raw.iter().position(|&b| b == 0).unwrap_or(16)], ) .unwrap_or(""); @@ -537,7 +535,9 @@ fn verify_macho_invariants( let sec_align = sec.align(le); // Invariant: section address must be within the segment. - if sec_size > 0 && (sec_addr < vm_addr || sec_addr + sec_size > vm_addr + vm_size) { + if sec_size > 0 + && (sec_addr < vm_addr || sec_addr + sec_size > vm_addr + vm_size) + { return Err(format!( "{}: section {segname},{sect_name} addr {sec_addr:#x}+{sec_size:#x} \ outside segment [{vm_addr:#x}..{:#x})", From 761d58ba0287024d63c25cb67dd1a162446df901 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 07:39:34 +0100 Subject: [PATCH 16/21] fix(macho): correct PAGEOFF12 scaling for 128-bit SIMD loads The write_pageoff12 function extracted the access size shift from bits 31:30 of the instruction, which works for integer LDR/STR but is wrong for SIMD/FP loads. For ldr q0 (128-bit), bits 31:30 are 00 (interpreted as byte access = no shift) but the actual scale is 16 bytes (shift=4). This caused the page offset to be 16x too large, making ldr q0 read from the wrong address. This was the root cause of the println! crash: the BufWriter init constant (a 16-byte value loaded via ldr q0) was fetched from a wrong offset, producing garbage in the stdout mutex/RefCell state. Found by adapting LLVM lld's arm64-relocs.s test which exercises exactly this relocation pattern. Signed-off-by: Giles Cope --- libwild/src/macho_writer.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index ae4a6f154..6ad4126fb 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -2103,8 +2103,19 @@ fn write_adrp(out: &mut [u8], offset: usize, pc: u64, target: u64) { fn write_pageoff12(out: &mut [u8], offset: usize, target: u64) { let page_off = (target & 0xFFF) as u32; let insn = read_u32(out, offset); + // Determine the access size shift for scaled load/store instructions. + // For integer LDR/STR: bits 31:30 encode the size directly. + // For SIMD/FP LDR/STR (V bit = bit 26): size depends on both + // bits 31:30 and opc bits 23:22. let shift = if (insn & 0x3B00_0000) == 0x3900_0000 { - (insn >> 30) & 0x3 + let size = (insn >> 30) & 0x3; + let v = (insn >> 26) & 1; + let opc = (insn >> 22) & 0x3; + if v == 1 && opc == 3 && size == 0 { + 4 // 128-bit SIMD (Q register): scale by 16 = 2^4 + } else { + size + } } else { 0 }; From 51a01d521e40c27476156ca489a05fc023b8a812 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 07:58:49 +0100 Subject: [PATCH 17/21] fix(macho): increase LINKEDIT size estimate for large binaries The LINKEDIT segment size estimation was too small for binaries with many symbols, causing the output file to be truncated. This made codesign fail ("main executable failed strict validation"), leaving binaries unsigned, which macOS kills with SIGKILL on execution. Increased the estimate to account for chained fixups data and longer average symbol names. Signed-off-by: Giles Cope --- libwild/src/macho_writer.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index 6ad4126fb..b8282bab4 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -227,10 +227,13 @@ fn build_mappings_and_size( .iter() .filter(|r| r.is_some()) .count(); - // Each nlist64 = 16 bytes, average symbol name ~60 bytes + NUL - let symtab_estimate = n_syms * (16 + 64); - let linkedit_estimate = 8192 + n_exports * 256 + symtab_estimate; - let total = linkedit_offset as usize + linkedit_estimate.max(8192); + // Each nlist64 = 16 bytes, average symbol name ~80 bytes + NUL. + // Also account for chained fixups data (page starts, imports, symbols). + let symtab_estimate = n_syms * (16 + 80); + let n_fixups = n_syms; // rough upper bound: each symbol may need a fixup + let fixups_estimate = 8192 + n_fixups * 8; // page starts + import entries + let linkedit_estimate = fixups_estimate + n_exports * 256 + symtab_estimate; + let total = linkedit_offset as usize + linkedit_estimate.max(16384); (mappings, total) } From 7ee1572a36155b4dd8610bce30f77addcfa3d441 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 08:01:45 +0100 Subject: [PATCH 18/21] fix(macho): increase LINKEDIT buffer estimate for large Rust binaries Signed-off-by: Giles Cope --- libwild/src/macho_writer.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/libwild/src/macho_writer.rs b/libwild/src/macho_writer.rs index b8282bab4..ad5bb49c3 100644 --- a/libwild/src/macho_writer.rs +++ b/libwild/src/macho_writer.rs @@ -227,13 +227,15 @@ fn build_mappings_and_size( .iter() .filter(|r| r.is_some()) .count(); - // Each nlist64 = 16 bytes, average symbol name ~80 bytes + NUL. - // Also account for chained fixups data (page starts, imports, symbols). - let symtab_estimate = n_syms * (16 + 80); - let n_fixups = n_syms; // rough upper bound: each symbol may need a fixup - let fixups_estimate = 8192 + n_fixups * 8; // page starts + import entries + // Each nlist64 = 16 bytes, Rust mangled symbol names average ~200 bytes. + // Also account for chained fixups data (page starts, imports, symbol names). + // Overestimating is cheap (buffer is truncated to actual size); underestimating + // causes silent data loss and codesign failure. + let symtab_estimate = n_syms * (16 + 200); + let n_fixups = n_syms; + let fixups_estimate = 16384 + n_fixups * 12; let linkedit_estimate = fixups_estimate + n_exports * 256 + symtab_estimate; - let total = linkedit_offset as usize + linkedit_estimate.max(16384); + let total = linkedit_offset as usize + linkedit_estimate.max(65536); (mappings, total) } From 8ade4aba77f2b22d76ecb05531ecadcb7016a253 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 08:09:11 +0100 Subject: [PATCH 19/21] feat: add lld MachO test suite (Apache 2.0 + LLVM Exception) Import 76 ARM64-relevant assembly tests from LLVM lld's MachO test suite. These provide comprehensive coverage of relocations, stubs, thunks, TLS, compact unwind, dead stripping, and ObjC features. Test runner assembles each .s file with clang, links with Wild, and validates the output with codesign. 21 tests pass, 2 ignored. Signed-off-by: Giles Cope --- wild/Cargo.toml | 5 + wild/tests/lld-macho/LICENSE.TXT | 278 +++++ wild/tests/lld-macho/README.md | 50 + wild/tests/lld-macho/abs-symbols.s | 23 + wild/tests/lld-macho/adhoc-codesign-hash.s | 23 + wild/tests/lld-macho/adhoc-codesign.s | 112 ++ wild/tests/lld-macho/application-extension.s | 115 ++ wild/tests/lld-macho/archive.s | 66 ++ wild/tests/lld-macho/arm64-32-dtrace.s | 23 + .../tests/lld-macho/arm64-32-reloc-got-load.s | 49 + .../lld-macho/arm64-branch-addend-stubs.s | 80 ++ wild/tests/lld-macho/arm64-dtrace.s | 23 + wild/tests/lld-macho/arm64-objc-stubs-dead.s | 27 + wild/tests/lld-macho/arm64-objc-stubs-dyn.s | 76 ++ wild/tests/lld-macho/arm64-objc-stubs-fix.s | 34 + wild/tests/lld-macho/arm64-objc-stubs.s | 92 ++ wild/tests/lld-macho/arm64-reloc-got-load.s | 51 + .../lld-macho/arm64-reloc-pointer-to-got.s | 36 + wild/tests/lld-macho/arm64-reloc-tlv-load.s | 63 + wild/tests/lld-macho/arm64-relocs.s | 70 ++ wild/tests/lld-macho/arm64-stubs.s | 65 ++ .../lld-macho/arm64-thunk-for-alignment.s | 44 + .../lld-macho/arm64-thunk-icf-body-dedup.s | 80 ++ wild/tests/lld-macho/arm64-thunk-starvation.s | 57 + wild/tests/lld-macho/arm64-thunk-visibility.s | 83 ++ wild/tests/lld-macho/arm64-thunks.s | 419 +++++++ wild/tests/lld-macho/bind-opcodes.s | 186 +++ .../lld-macho/bp-section-orderer-stress.s | 108 ++ wild/tests/lld-macho/bp-section-orderer.s | 186 +++ wild/tests/lld-macho/bss.s | 125 ++ wild/tests/lld-macho/cgdata-generate-merge.s | 89 ++ wild/tests/lld-macho/cgdata-generate.s | 93 ++ wild/tests/lld-macho/compact-unwind.s | 184 +++ .../lld-macho/compression-order-sections.s | 112 ++ wild/tests/lld-macho/cstring-tailmerge-objc.s | 144 +++ wild/tests/lld-macho/cstring-tailmerge.s | 85 ++ wild/tests/lld-macho/dead-strip.s | 1014 ++++++++++++++++ wild/tests/lld-macho/dwarf-no-compile-unit.s | 15 + wild/tests/lld-macho/dyld-stub-binder.s | 66 ++ wild/tests/lld-macho/eh-frame-dead-strip.s | 46 + wild/tests/lld-macho/eh-frame.s | 172 +++ wild/tests/lld-macho/encryption-info.s | 35 + wild/tests/lld-macho/fat-arch.s | 45 + wild/tests/lld-macho/header.s | 28 + wild/tests/lld-macho/icf-arm64.s | 109 ++ .../lld-macho/icf-safe-missing-addrsig.s | 112 ++ wild/tests/lld-macho/ignore-incompat-arch.s | 72 ++ wild/tests/lld-macho/local-alias-to-weak.s | 149 +++ wild/tests/lld-macho/loh-adrp-add-ldr.s | 185 +++ wild/tests/lld-macho/loh-adrp-add.s | 90 ++ wild/tests/lld-macho/loh-adrp-adrp.s | 72 ++ wild/tests/lld-macho/loh-adrp-ldr-got-ldr.s | 263 +++++ wild/tests/lld-macho/loh-adrp-ldr-got.s | 35 + wild/tests/lld-macho/loh-adrp-ldr.s | 133 +++ wild/tests/lld-macho/loh-arm64-32.s | 64 ++ wild/tests/lld-macho/loh-parsing.s | 24 + wild/tests/lld-macho/no-pie.s | 17 + .../objc-category-merging-complete-test.s | 1023 +++++++++++++++++ ...jc-category-merging-erase-objc-name-test.s | 308 +++++ .../lld-macho/objc-category-merging-minimal.s | 387 +++++++ .../objc-category-merging-swift-class-ext.s | 442 +++++++ .../lld-macho/objc-category-merging-swift.s | 410 +++++++ wild/tests/lld-macho/objc-methname.s | 44 + .../objc-relative-method-lists-simple.s | 258 +++++ wild/tests/lld-macho/objc-selrefs.s | 81 ++ .../lld-macho/order-file-cstring-tailmerge.s | 56 + wild/tests/lld-macho/order-file-cstring.s | 230 ++++ .../tests/lld-macho/order-file-strip-hashes.s | 96 ++ wild/tests/lld-macho/order-file.s | 188 +++ wild/tests/lld-macho/pagezero.s | 37 + wild/tests/lld-macho/reexport-with-symlink.s | 75 ++ wild/tests/lld-macho/reexport-without-rpath.s | 121 ++ wild/tests/lld-macho/reloc-subtractor.s | 74 ++ wild/tests/lld-macho/section-order.s | 58 + wild/tests/lld-macho/segments.s | 73 ++ wild/tests/lld-macho/skip-platform-checks.s | 12 + wild/tests/lld-macho/tapi-link-by-arch.s | 19 + wild/tests/lld-macho/tapi-rpath.s | 89 ++ wild/tests/lld-macho/tlv.s | 132 +++ wild/tests/lld_macho_tests.rs | 141 +++ 80 files changed, 10456 insertions(+) create mode 100644 wild/tests/lld-macho/LICENSE.TXT create mode 100644 wild/tests/lld-macho/README.md create mode 100644 wild/tests/lld-macho/abs-symbols.s create mode 100644 wild/tests/lld-macho/adhoc-codesign-hash.s create mode 100644 wild/tests/lld-macho/adhoc-codesign.s create mode 100644 wild/tests/lld-macho/application-extension.s create mode 100644 wild/tests/lld-macho/archive.s create mode 100644 wild/tests/lld-macho/arm64-32-dtrace.s create mode 100644 wild/tests/lld-macho/arm64-32-reloc-got-load.s create mode 100644 wild/tests/lld-macho/arm64-branch-addend-stubs.s create mode 100644 wild/tests/lld-macho/arm64-dtrace.s create mode 100644 wild/tests/lld-macho/arm64-objc-stubs-dead.s create mode 100644 wild/tests/lld-macho/arm64-objc-stubs-dyn.s create mode 100644 wild/tests/lld-macho/arm64-objc-stubs-fix.s create mode 100644 wild/tests/lld-macho/arm64-objc-stubs.s create mode 100644 wild/tests/lld-macho/arm64-reloc-got-load.s create mode 100644 wild/tests/lld-macho/arm64-reloc-pointer-to-got.s create mode 100644 wild/tests/lld-macho/arm64-reloc-tlv-load.s create mode 100644 wild/tests/lld-macho/arm64-relocs.s create mode 100644 wild/tests/lld-macho/arm64-stubs.s create mode 100644 wild/tests/lld-macho/arm64-thunk-for-alignment.s create mode 100644 wild/tests/lld-macho/arm64-thunk-icf-body-dedup.s create mode 100644 wild/tests/lld-macho/arm64-thunk-starvation.s create mode 100644 wild/tests/lld-macho/arm64-thunk-visibility.s create mode 100644 wild/tests/lld-macho/arm64-thunks.s create mode 100644 wild/tests/lld-macho/bind-opcodes.s create mode 100644 wild/tests/lld-macho/bp-section-orderer-stress.s create mode 100644 wild/tests/lld-macho/bp-section-orderer.s create mode 100644 wild/tests/lld-macho/bss.s create mode 100644 wild/tests/lld-macho/cgdata-generate-merge.s create mode 100644 wild/tests/lld-macho/cgdata-generate.s create mode 100644 wild/tests/lld-macho/compact-unwind.s create mode 100644 wild/tests/lld-macho/compression-order-sections.s create mode 100644 wild/tests/lld-macho/cstring-tailmerge-objc.s create mode 100644 wild/tests/lld-macho/cstring-tailmerge.s create mode 100644 wild/tests/lld-macho/dead-strip.s create mode 100644 wild/tests/lld-macho/dwarf-no-compile-unit.s create mode 100644 wild/tests/lld-macho/dyld-stub-binder.s create mode 100644 wild/tests/lld-macho/eh-frame-dead-strip.s create mode 100644 wild/tests/lld-macho/eh-frame.s create mode 100644 wild/tests/lld-macho/encryption-info.s create mode 100644 wild/tests/lld-macho/fat-arch.s create mode 100644 wild/tests/lld-macho/header.s create mode 100644 wild/tests/lld-macho/icf-arm64.s create mode 100644 wild/tests/lld-macho/icf-safe-missing-addrsig.s create mode 100644 wild/tests/lld-macho/ignore-incompat-arch.s create mode 100644 wild/tests/lld-macho/local-alias-to-weak.s create mode 100644 wild/tests/lld-macho/loh-adrp-add-ldr.s create mode 100644 wild/tests/lld-macho/loh-adrp-add.s create mode 100644 wild/tests/lld-macho/loh-adrp-adrp.s create mode 100644 wild/tests/lld-macho/loh-adrp-ldr-got-ldr.s create mode 100644 wild/tests/lld-macho/loh-adrp-ldr-got.s create mode 100644 wild/tests/lld-macho/loh-adrp-ldr.s create mode 100644 wild/tests/lld-macho/loh-arm64-32.s create mode 100644 wild/tests/lld-macho/loh-parsing.s create mode 100644 wild/tests/lld-macho/no-pie.s create mode 100644 wild/tests/lld-macho/objc-category-merging-complete-test.s create mode 100644 wild/tests/lld-macho/objc-category-merging-erase-objc-name-test.s create mode 100644 wild/tests/lld-macho/objc-category-merging-minimal.s create mode 100644 wild/tests/lld-macho/objc-category-merging-swift-class-ext.s create mode 100644 wild/tests/lld-macho/objc-category-merging-swift.s create mode 100644 wild/tests/lld-macho/objc-methname.s create mode 100644 wild/tests/lld-macho/objc-relative-method-lists-simple.s create mode 100644 wild/tests/lld-macho/objc-selrefs.s create mode 100644 wild/tests/lld-macho/order-file-cstring-tailmerge.s create mode 100644 wild/tests/lld-macho/order-file-cstring.s create mode 100644 wild/tests/lld-macho/order-file-strip-hashes.s create mode 100644 wild/tests/lld-macho/order-file.s create mode 100644 wild/tests/lld-macho/pagezero.s create mode 100644 wild/tests/lld-macho/reexport-with-symlink.s create mode 100644 wild/tests/lld-macho/reexport-without-rpath.s create mode 100644 wild/tests/lld-macho/reloc-subtractor.s create mode 100644 wild/tests/lld-macho/section-order.s create mode 100644 wild/tests/lld-macho/segments.s create mode 100644 wild/tests/lld-macho/skip-platform-checks.s create mode 100644 wild/tests/lld-macho/tapi-link-by-arch.s create mode 100644 wild/tests/lld-macho/tapi-rpath.s create mode 100644 wild/tests/lld-macho/tlv.s create mode 100644 wild/tests/lld_macho_tests.rs diff --git a/wild/Cargo.toml b/wild/Cargo.toml index 343cf0303..7435f655d 100644 --- a/wild/Cargo.toml +++ b/wild/Cargo.toml @@ -22,6 +22,11 @@ name = "macho_integration_tests" path = "tests/macho_integration_tests.rs" harness = false +[[test]] +name = "lld_macho_tests" +path = "tests/lld_macho_tests.rs" +harness = false + [dependencies] libwild = { path = "../libwild", version = "0.8.0" } diff --git a/wild/tests/lld-macho/LICENSE.TXT b/wild/tests/lld-macho/LICENSE.TXT new file mode 100644 index 000000000..cba22f66a --- /dev/null +++ b/wild/tests/lld-macho/LICENSE.TXT @@ -0,0 +1,278 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== +University of Illinois/NCSA +Open Source License + +Copyright (c) 2011-2019 by the contributors listed in CREDITS.TXT +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. diff --git a/wild/tests/lld-macho/README.md b/wild/tests/lld-macho/README.md new file mode 100644 index 000000000..d4eeab7c0 --- /dev/null +++ b/wild/tests/lld-macho/README.md @@ -0,0 +1,50 @@ +# lld MachO Test Suite + +These tests are adapted from LLVM lld's MachO linker test suite. + +## Source + + + +## License + +Apache License v2.0 with LLVM Exceptions — see [LICENSE.TXT](LICENSE.TXT). + +## Format + +Tests use LLVM's LIT format: + +- `# RUN:` directives show how to assemble and link +- `# CHECK:` directives show expected output +- `# REQUIRES: aarch64` means the test needs ARM64 support +- `split-file %s %t` splits the file at `#---` markers + +## Usage with Wild + +To run a test manually: + +```sh +# Assemble (strip RUN/CHECK comments first) +grep -v '^#' test.s > clean.s +clang -c -target arm64-apple-macos clean.s -o test.o + +# Link with Wild +wild test.o -dylib -arch arm64 -lSystem -o test.dylib + +# Verify with objdump +objdump --macho -d test.dylib +``` + +## Cherry-picking new tests + +To add tests from upstream lld: + +```sh +# Sparse checkout the lld tests +git clone --depth 1 --filter=blob:none --sparse \ + https://github.com/llvm/llvm-project.git /tmp/llvm +cd /tmp/llvm && git sparse-checkout set lld/test/MachO + +# Copy desired tests +cp /tmp/llvm/lld/test/MachO/new-test.s wild/tests/lld-macho/ +``` diff --git a/wild/tests/lld-macho/abs-symbols.s b/wild/tests/lld-macho/abs-symbols.s new file mode 100644 index 000000000..5c106e5b9 --- /dev/null +++ b/wild/tests/lld-macho/abs-symbols.s @@ -0,0 +1,23 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: %lld -lSystem %t.o -o %t +# RUN: llvm-objdump --macho --syms --exports-trie %t | FileCheck %s + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK-DAG: 000000000000dead g *ABS* _foo +# CHECK-DAG: 000000000000beef g *ABS* _weakfoo +# CHECK-DAG: 000000000000cafe l *ABS* _localfoo + +# CHECK-LABEL: Exports trie: +# CHECK-DAG: 0x0000DEAD _foo [absolute] +# CHECK-DAG: 0x0000BEEF _weakfoo [absolute] + +.globl _foo, _weakfoo, _main +.weak_definition _weakfoo +_foo = 0xdead +_weakfoo = 0xbeef +_localfoo = 0xcafe + +.text +_main: + ret diff --git a/wild/tests/lld-macho/adhoc-codesign-hash.s b/wild/tests/lld-macho/adhoc-codesign-hash.s new file mode 100644 index 000000000..977ca43cf --- /dev/null +++ b/wild/tests/lld-macho/adhoc-codesign-hash.s @@ -0,0 +1,23 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; mkdir -p %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/empty-arm64-macos.o %s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-iossimulator -o %t/empty-arm64-iossimulator.o %s +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/empty-x86_64-macos.o %s + +# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-macos.dylib %t/empty-arm64-macos.o +# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/empty-arm64-iossimulator.dylib %t/empty-arm64-iossimulator.o +# RUN: %lld -arch x86_64 -dylib -adhoc_codesign -o %t/empty-x86_64-macos.dylib %t/empty-x86_64-macos.o + +# RUN: obj2yaml %t/empty-arm64-macos.dylib | FileCheck %s -D#DATA_OFFSET=16432 -D#DATA_SIZE=304 +# RUN: obj2yaml %t/empty-arm64-iossimulator.dylib | FileCheck %s -D#DATA_OFFSET=16432 -D#DATA_SIZE=304 +# RUN: obj2yaml %t/empty-x86_64-macos.dylib | FileCheck %s -D#DATA_OFFSET=4144 -D#DATA_SIZE=208 + +# CHECK: - cmd: LC_CODE_SIGNATURE +# CHECK-NEXT: cmdsize: 16 +# CHECK-NEXT: dataoff: [[#DATA_OFFSET]] +# CHECK-NEXT: datasize: [[#DATA_SIZE]] + +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-macos.dylib 16432 304 0 16432 +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-arm64-iossimulator.dylib 16432 304 0 16432 +# RUN: %python %p/Inputs/code-signature-check.py %t/empty-x86_64-macos.dylib 4144 208 0 4144 diff --git a/wild/tests/lld-macho/adhoc-codesign.s b/wild/tests/lld-macho/adhoc-codesign.s new file mode 100644 index 000000000..8e422ca2c --- /dev/null +++ b/wild/tests/lld-macho/adhoc-codesign.s @@ -0,0 +1,112 @@ +# REQUIRES: x86, aarch64 + +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/main-arm64-macos.o %t/main.s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-iossimulator -o %t/main-arm64-sim.o %t/main.s +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/main-x86_64-macos.o %t/main.s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/foo-arm64-macos.o %t/foo.s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-iossimulator -o %t/foo-arm64-sim.o %t/foo.s +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/foo-x86_64-macos.o %t/foo.s + +# Exhaustive test for: +# (x86_64-macos, arm64-macos, arm64-ios-simulator) x (default, -adhoc_codesign, -no_adhoc-codesign) x (execute, dylib, bundle) + +# RUN: %lld -lSystem -arch x86_64 -execute -o %t/out %t/main-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out | FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch x86_64 -dylib -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch x86_64 -bundle -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s + +# RUN: %lld -lSystem -arch x86_64 -execute -adhoc_codesign -o %t/out %t/main-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch x86_64 -dylib -adhoc_codesign -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch x86_64 -bundle -adhoc_codesign -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s + +# RUN: %lld -lSystem -arch x86_64 -execute -no_adhoc_codesign -o %t/out %t/main-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch x86_64 -dylib -no_adhoc_codesign -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch x86_64 -bundle -no_adhoc_codesign -o %t/out %t/foo-x86_64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s + + +# RUN: %lld -lSystem -arch arm64 -execute -o %t/out %t/main-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out | FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch arm64 -dylib -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch arm64 -bundle -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s + +# RUN: %lld -lSystem -arch arm64 -execute -adhoc_codesign -o %t/out %t/main-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch arm64 -dylib -adhoc_codesign -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %lld -arch arm64 -bundle -adhoc_codesign -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s + +# RUN: %lld -lSystem -arch arm64 -execute -no_adhoc_codesign -o %t/out %t/main-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch arm64 -dylib -no_adhoc_codesign -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %lld -arch arm64 -bundle -no_adhoc_codesign -o %t/out %t/foo-arm64-macos.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s + + +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -execute -o %t/out %t/main-arm64-sim.o -syslibroot %S/Inputs/iPhoneSimulator.sdk -lSystem +# RUN: llvm-objdump --macho --all-headers %t/out | FileCheck --check-prefix=ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -dylib -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -bundle -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s + +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -execute -adhoc_codesign -o %t/out %t/main-arm64-sim.o -syslibroot %S/Inputs/iPhoneSimulator.sdk -lSystem +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -dylib -adhoc_codesign -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -bundle -adhoc_codesign -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=ADHOC %s + +# RUN: %no-arg-lld -lSystem -arch arm64 -platform_version ios-simulator 14.0 15.0 -execute -no_adhoc_codesign -o %t/out %t/main-arm64-sim.o -syslibroot %S/Inputs/iPhoneSimulator.sdk +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -dylib -no_adhoc_codesign -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s +# RUN: %no-arg-lld -arch arm64 -platform_version ios-simulator 14.0 15.0 -bundle -no_adhoc_codesign -o %t/out %t/foo-arm64-sim.o +# RUN: llvm-objdump --macho --all-headers %t/out| FileCheck --check-prefix=NO-ADHOC %s + +# RUN: %lld -arch x86_64 -dylib -o %t/out_installname.dylib -install_name @rpath/MyInstallName %t/foo-x86_64-macos.o -adhoc_codesign +# RUN: %lld -arch x86_64 -dylib -o %t/out_no_installname.dylib %t/foo-x86_64-macos.o -adhoc_codesign + +## Smoke check to verify the dataoff and datasize value before using them with code-signature-check.py +# RUN: llvm-objdump --macho --all-headers %t/out_installname.dylib | FileCheck %s --check-prefix CS-ID-PRE -D#DATA_OFFSET=4176 -D#DATA_SIZE=192 +# RUN: llvm-objdump --macho --all-headers %t/out_no_installname.dylib | FileCheck %s --check-prefix CS-ID-PRE -D#DATA_OFFSET=4176 -D#DATA_SIZE=208 + +## Verify that the 'Identifier' (aka 'Code Directory ID') field are set to the install-name, if available. +# RUN: %python %p/Inputs/code-signature-check.py %t/out_installname.dylib 4176 192 0 4176 | FileCheck %s --check-prefix CS-ID-INSTALL +# RUN: %python %p/Inputs/code-signature-check.py %t/out_no_installname.dylib 4176 208 0 4176 | FileCheck %s --check-prefix CS-ID-NO-INSTALL + +# ADHOC: cmd LC_CODE_SIGNATURE +# ADHOC-NEXT: cmdsize 16 + +# NO-ADHOC-NOT: cmd LC_CODE_SIGNATURE + +# CS-ID-PRE: cmd LC_CODE_SIGNATURE +# CS-ID-PRE-NEXT: cmdsize 16 +# CS-ID-PRE-NEXT: dataoff [[#DATA_OFFSET]] +# CS-ID-PRE-NEXT: datasize [[#DATA_SIZE]] + +# CS-ID-INSTALL: Code Directory ID: MyInstallName +# CS-ID-NO-INSTALL: Code Directory ID: out_no_installname.dylib + +#--- foo.s +.globl _foo +_foo: + ret + +#--- main.s +.globl _main +_main: + ret diff --git a/wild/tests/lld-macho/application-extension.s b/wild/tests/lld-macho/application-extension.s new file mode 100644 index 000000000..ddd16f33f --- /dev/null +++ b/wild/tests/lld-macho/application-extension.s @@ -0,0 +1,115 @@ +# REQUIRES: aarch64 + +## --no-leading-lines is needed for .tbd files. +# RUN: rm -rf %t; split-file --no-leading-lines %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/foo.o %t/foo.s +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t/bar.o %t/bar.s + +## MH_APP_EXTENSION_SAFE is only set on dylibs, and only if requested. +# RUN: %lld -arch arm64 -dylib -o %t/foo.dylib %t/foo.o +# RUN: llvm-otool -hv %t/foo.dylib | FileCheck --check-prefix=NOAPPEXT %s +# RUN: %lld -arch arm64 -dylib -o %t/foo-appext.dylib %t/foo.o \ +# RUN: -application_extension +# RUN: llvm-otool -hv %t/foo-appext.dylib | FileCheck --check-prefix=APPEXT %s +# RUN: %lld -arch arm64 -dylib -o %t/foo-noappext.dylib %t/foo.o \ +# RUN: -application_extension -no_application_extension +# RUN: llvm-otool -hv %t/foo-noappext.dylib \ +# RUN: | FileCheck --check-prefix=NOAPPEXT %s +# RUN: %lld -arch arm64 -bundle -o %t/foo.so %t/foo.o \ +# RUN: -application_extension +# RUN: llvm-otool -hv %t/foo.so | FileCheck --check-prefix=NOAPPEXT %s + +# APPEXT: APP_EXTENSION_SAFE +# NOAPPEXT-NOT: APP_EXTENSION_SAFE + +## The warning is emitted for all target types. +# RUN: %lld -arch arm64 -dylib -o %t/bar.dylib %t/bar.o \ +# RUN: -application_extension %t/foo-appext.dylib +# RUN: %lld -arch arm64 -dylib -o %t/bar.dylib %t/bar.o \ +# RUN: -application_extension -L %t -ltbd-appext +# RUN: not %lld -arch arm64 -dylib -o %t/bar.dylib %t/bar.o \ +# RUN: -application_extension %t/foo-noappext.dylib \ +# RUN: 2>&1 | FileCheck --check-prefix=WARN %s +# RUN: not %lld -arch arm64 -dylib -o %t/bar.dylib %t/bar.o \ +# RUN: -application_extension -L %t -ltbd-noappext \ +# RUN: 2>&1 | FileCheck --check-prefix=WARN %s +# RUN: not %lld -arch arm64 -bundle -o %t/bar.so %t/bar.o \ +# RUN: -application_extension %t/foo-noappext.dylib \ +# RUN: 2>&1 | FileCheck --check-prefix=WARN %s +# RUN: not %lld -arch arm64 -bundle -o %t/bar.so %t/bar.o \ +# RUN: -application_extension -L %t -ltbd-noappext \ +# RUN: 2>&1 | FileCheck --check-prefix=WARN %s + +# WARN: using '-application_extension' with unsafe dylib: + +## Test we warn on dylibs loaded indirectly via reexports. +# RUN: not %lld -arch arm64 -dylib -o %t/bar.dylib %t/bar.o \ +# RUN: -application_extension -L %t -lbaz-noappext-reexport \ +# RUN: -u _baz 2>&1 | FileCheck --check-prefix=WARN %s + +#--- foo.s +.globl _foo +.p2align 2 +_foo: + ret + +#--- libtbd-appext.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +uuids: + - target: arm64-macos + value: 2E994C7F-3F03-3A07-879C-55690D22BEDA +install-name: '/usr/lib/libtbd-appext.dylib' +exports: + - targets: [ arm64-macos ] + symbols: [ _foo ] +... + +#--- libtbd-noappext.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +flags: [ not_app_extension_safe ] +uuids: + - target: arm64-macos + value: 2E994C7F-3F03-3A07-879C-55690D22BEDA +install-name: '/usr/lib/libtbd-noappext.dylib' +exports: + - targets: [ arm64-macos ] + symbols: [ _foo ] +... + +#--- bar.s +.globl _bar +.p2align 2 +_bar: + ret + +#--- libbaz-noappext-reexport.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +uuids: + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000001 +install-name: '/usr/lib/libbaz.dylib' +reexported-libraries: + - targets: [ arm64-macos ] + libraries: [ '/usr/lib/libbaz-noappext-reexported.dylib'] +--- !tapi-tbd +tbd-version: 4 +targets: [ arm64-macos ] +flags: [ not_app_extension_safe ] +uuids: + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000003 +install-name: '/usr/lib/libbaz-noappext-reexported.dylib' +parent-umbrella: + - targets: [ arm64-macos ] + umbrella: baz +exports: + - targets: [ arm64-macos ] + symbols: [ _baz ] +... diff --git a/wild/tests/lld-macho/archive.s b/wild/tests/lld-macho/archive.s new file mode 100644 index 000000000..c324be0ac --- /dev/null +++ b/wild/tests/lld-macho/archive.s @@ -0,0 +1,66 @@ +# REQUIRES: x86 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/2.s -o %t/2.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/3.s -o %t/3.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/4.s -o %t/4.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o + +# RUN: llvm-ar rcs %t/test.a %t/2.o %t/3.o %t/4.o +# RUN: %lld %t/main.o %t/test.a -o %t/test.out + +## TODO: Run llvm-nm -p to validate symbol order +# RUN: llvm-nm %t/test.out | FileCheck %s +# CHECK: T _bar +# CHECK: T _boo +# CHECK: T _main + +## Linking with the archive first in the command line shouldn't change anything +# RUN: %lld %t/test.a %t/main.o -o %t/test.out +# RUN: llvm-nm %t/test.out | FileCheck %s --check-prefix ARCHIVE-FIRST +# ARCHIVE-FIRST: T _bar +# ARCHIVE-FIRST: T _boo +# ARCHIVE-FIRST: T _main + +# RUN: llvm-nm %t/test.out | FileCheck %s --check-prefix VISIBLE +# VISIBLE-NOT: T _undefined +# VISIBLE-NOT: T _unused + +# RUN: %lld %t/test.a %t/main.o -o %t/all-load -noall_load -all_load +# RUN: llvm-nm %t/all-load | FileCheck %s --check-prefix ALL-LOAD +# ALL-LOAD: T _bar +# ALL-LOAD: T _boo +# ALL-LOAD: T _main +# ALL-LOAD: T _unused + +# RUN: %lld %t/test.a %t/main.o -o %t/no-all-load -all_load -noall_load +# RUN: llvm-nm %t/no-all-load | FileCheck %s --check-prefix NO-ALL-LOAD +# RUN: %lld %t/test.a %t/main.o -o %t/no-all-load-only -noall_load +# RUN: llvm-nm %t/no-all-load-only | FileCheck %s --check-prefix NO-ALL-LOAD +# NO-ALL-LOAD-NOT: T _unused + +## Multiple archives defining the same symbols aren't an issue, due to lazy +## loading +# RUN: cp %t/test.a %t/test2.a +# RUN: %lld %t/test.a %t/test2.a %t/main.o -o /dev/null + +#--- 2.s +.globl _boo +_boo: + ret + +#--- 3.s +.globl _bar +_bar: + ret + +#--- 4.s +.globl _undefined, _unused +_unused: + ret + +#--- main.s +.globl _main +_main: + callq _boo + callq _bar + ret diff --git a/wild/tests/lld-macho/arm64-32-dtrace.s b/wild/tests/lld-macho/arm64-32-dtrace.s new file mode 100644 index 000000000..26c91bd28 --- /dev/null +++ b/wild/tests/lld-macho/arm64-32-dtrace.s @@ -0,0 +1,23 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/arm64-32-dtrace.s -o %t/arm64-32-dtrace.o +# RUN: %lld-watchos -arch arm64_32 -o %t/arm64-32-dtrace %t/arm64-32-dtrace.o + +## If references of dtrace symbols are handled by lld, their relocation should be replaced with the following instructions +# RUN: llvm-objdump --macho -D %t/arm64-32-dtrace | FileCheck %s --check-prefix=CHECK + +# CHECK: 00 00 80 d2 mov x0, #0 + +# CHECK: 1f 20 03 d5 nop + +#--- arm64-32-dtrace.s + .globl _main +_main: + bl ___dtrace_isenabled$Foo$added$v1 + .reference ___dtrace_typedefs$Foo$v2 + bl ___dtrace_probe$Foo$added$v1$696e74 + .reference ___dtrace_stability$Foo$v1$1_1_0_1_1_0_1_1_0_1_1_0_1_1_0 + ret + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-32-reloc-got-load.s b/wild/tests/lld-macho/arm64-32-reloc-got-load.s new file mode 100644 index 000000000..85431b11e --- /dev/null +++ b/wild/tests/lld-macho/arm64-32-reloc-got-load.s @@ -0,0 +1,49 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %t/main.s -o %t/main.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %t/foobar.s -o %t/foobar.o + +# RUN: %lld-watchos -lSystem -arch arm64_32 -o %t/static %t/main.o %t/foobar.o +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --syms %t/static | FileCheck %s --check-prefix=STATIC + +# RUN: %lld-watchos -lSystem -arch arm64_32 -dylib -o %t/libfoo.dylib %t/foobar.o +# RUN: %lld-watchos -lSystem -arch arm64_32 -o %t/main %t/main.o %t/libfoo.dylib +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --section-headers %t/main | FileCheck %s --check-prefix=DYLIB + +# STATIC-LABEL: _main: +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#%x,PAGE:]] +# STATIC-NEXT: add x8, x8, #[[#%u,FOO_OFF:]] +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#PAGE]] +# STATIC-NEXT: add x8, x8, #[[#%u,BAR_OFF:]] +# STATIC-NEXT: ret + +# STATIC-LABEL: SYMBOL TABLE: +# STATIC-DAG: {{0*}}[[#%x,PAGE+FOO_OFF]] g F __TEXT,__text _foo +# STATIC-DAG: {{0*}}[[#%x,PAGE+BAR_OFF]] g F __TEXT,__text _bar + +# DYLIB-LABEL: _main: +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#%x,GOT:]] +# DYLIB-NEXT: ldr w8, [x8, #4] +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#GOT]] +# DYLIB-NEXT: ldr w8, [x8] +# DYLIB-NEXT: ret +# DYLIB-EMPTY: +# DYLIB-NEXT: Sections: +# DYLIB-NEXT: Idx Name Size VMA Type +# DYLIB: [[#]] __got 00000008 [[#%.8x,GOT]] DATA + +#--- main.s +.globl _main, _foo, _bar +.p2align 2 +_main: + adrp x8, _foo@GOTPAGE + ldr w8, [x8, _foo@GOTPAGEOFF] + adrp x8, _bar@GOTPAGE + ldr w8, [x8, _bar@GOTPAGEOFF] + ret + +#--- foobar.s +.globl _foo, _bar +_foo: +_bar: diff --git a/wild/tests/lld-macho/arm64-branch-addend-stubs.s b/wild/tests/lld-macho/arm64-branch-addend-stubs.s new file mode 100644 index 000000000..f1301f580 --- /dev/null +++ b/wild/tests/lld-macho/arm64-branch-addend-stubs.s @@ -0,0 +1,80 @@ +# REQUIRES: aarch64 + +## Test that branch relocations with non-zero addends correctly target the +## actual function address, not the stub address. When a symbol is accessed +## via both a regular call (goes through stub) and a branch with addend +## (targeting an interior point), the addend must be applied to the real +## function VA, not the stub VA. +## +## This test uses -flat_namespace on a dylib, which makes locally-defined +## symbols interposable and thus accessible via stubs. This creates the +## scenario where a function is both defined locally AND in stubs. + +# RUN: rm -rf %t; mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t/test.o +# RUN: %lld -arch arm64 -dylib -lSystem -flat_namespace %t/test.o -o %t/test.dylib + +# RUN: llvm-objdump --no-print-imm-hex --macho -d %t/test.dylib | FileCheck %s + +## With -flat_namespace, _target_func is interposable so regular calls go +## through stubs. But the branch with addend must go to the actual function +## address + addend, not stub + addend. +## +## Note: This means `bl _target_func` and `bl _target_func+16` could target +## different functions if interposition occurs at runtime. This is intentional: +## branching to an interior point implies reliance on the original function's +## layout, which an interposed replacement wouldn't preserve. There's no +## meaningful way to "interpose" an interior offset, so we target the original. + +## _target_func layout: +## offset 0: nop +## offset 4: nop +## offset 8: nop +## offset 12: nop +## offset 16: mov w0, #42 <- this is what _target_func+16 should reach +## offset 20: ret + +## Verify _target_func layout and capture the address of the mov instruction +## (which is at _target_func + 16) +# CHECK-LABEL: _target_func: +# CHECK: nop +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: [[#%x,INTERIOR:]]:{{.*}}mov w0, #42 +# CHECK-NEXT: ret + +## Verify the caller structure: +## - First bl goes to stub (marked with "symbol stub for:") +## - Second bl goes to [[INTERIOR]] (the _target_func+16 address captured above) +## +## The key assertion: the second bl MUST target _target_func+16 (INTERIOR), +## NOT stub+16. If the bug exists, it would target stub+16 which would be +## garbage (pointing past the stub section). +# CHECK-LABEL: _caller: +# CHECK: bl {{.*}} symbol stub for: _target_func +# CHECK-NEXT: bl 0x[[#INTERIOR]] +# CHECK-NEXT: ret + +.text +.globl _target_func, _caller +.p2align 2 + +_target_func: + ## 4 nops = 16 bytes to offset 0x10 + nop + nop + nop + nop + ## This is at _target_func + 16 + mov w0, #42 + ret + +_caller: + ## Regular call to _target_func - goes through stub due to -flat_namespace + bl _target_func + ## Branch with addend - must go to actual function + 16, not stub + 16 + bl _target_func + 16 + ret + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-dtrace.s b/wild/tests/lld-macho/arm64-dtrace.s new file mode 100644 index 000000000..36854195e --- /dev/null +++ b/wild/tests/lld-macho/arm64-dtrace.s @@ -0,0 +1,23 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/arm64-dtrace.s -o %t/arm64-dtrace.o +# RUN: %lld -arch arm64 -o %t/arm64-dtrace %t/arm64-dtrace.o + +## If references of dtrace symbols are handled by lld, their relocation should be replaced with the following instructions +# RUN: llvm-objdump --macho -D %t/arm64-dtrace | FileCheck %s --check-prefix=CHECK + +# CHECK: 00 00 80 d2 mov x0, #0 + +# CHECK: 1f 20 03 d5 nop + +#--- arm64-dtrace.s + .globl _main +_main: + bl ___dtrace_isenabled$Foo$added$v1 + .reference ___dtrace_typedefs$Foo$v2 + bl ___dtrace_probe$Foo$added$v1$696e74 + .reference ___dtrace_stability$Foo$v1$1_1_0_1_1_0_1_1_0_1_1_0_1_1_0 + ret + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-objc-stubs-dead.s b/wild/tests/lld-macho/arm64-objc-stubs-dead.s new file mode 100644 index 000000000..5dcb171c1 --- /dev/null +++ b/wild/tests/lld-macho/arm64-objc-stubs-dead.s @@ -0,0 +1,27 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o + +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o +# RUN: llvm-nm %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -dead_strip -o %t.out %t.o +# RUN: llvm-nm %t.out | FileCheck %s --check-prefix=DEAD + +# CHECK: _foo +# CHECK: _objc_msgSend$length + +# DEAD-NOT: _foo +# DEAD-NOT: _objc_msgSend$length + +.section __TEXT,__text + +.globl _foo +_foo: + bl _objc_msgSend$length + ret + +.globl _main +_main: + ret + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-objc-stubs-dyn.s b/wild/tests/lld-macho/arm64-objc-stubs-dyn.s new file mode 100644 index 000000000..9358fc5b3 --- /dev/null +++ b/wild/tests/lld-macho/arm64-objc-stubs-dyn.s @@ -0,0 +1,76 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -dead_strip +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_fast +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_small +# RUN: llvm-otool -vs __TEXT __stubs %t.out | FileCheck %s --check-prefix=STUB +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=SMALL + +# Unlike arm64-objc-stubs.s, in this test, _objc_msgSend is not defined, +# which usually binds with libobjc.dylib. +# 1. -objc_stubs_fast: No change as it uses GOT. +# 2. -objc_stubs_small: Create a (shared) stub to invoke _objc_msgSend, to minimize the size. + +# CHECK: Contents of (__TEXT,__objc_stubs) section + +# CHECK-NEXT: _objc_msgSend$foo: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x10] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-NEXT: _objc_msgSend$length: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x18] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-EMPTY: + +# STUB: Contents of (__TEXT,__stubs) section +# STUB-NEXT: adrp x16, 8 ; 0x100008000 +# STUB-NEXT: ldr x16, [x16] +# STUB-NEXT: br x16 + +# SMALL: Contents of (__TEXT,__objc_stubs) section +# SMALL-NEXT: _objc_msgSend$foo: +# SMALL-NEXT: adrp x1, 8 ; 0x100008000 +# SMALL-NEXT: ldr x1, [x1, #0x18] +# SMALL-NEXT: b +# SMALL-NEXT: _objc_msgSend$length: +# SMALL-NEXT: adrp x1, 8 ; 0x100008000 +# SMALL-NEXT: ldr x1, [x1, #0x20] +# SMALL-NEXT: b + +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +.text + +.globl _main +_main: + bl _objc_msgSend$length + bl _objc_msgSend$foo + bl _objc_msgSend$foo + ret diff --git a/wild/tests/lld-macho/arm64-objc-stubs-fix.s b/wild/tests/lld-macho/arm64-objc-stubs-fix.s new file mode 100644 index 000000000..0dbec361f --- /dev/null +++ b/wild/tests/lld-macho/arm64-objc-stubs-fix.s @@ -0,0 +1,34 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -lSystem -fixup_chains -o %t.out %t.o +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=CHECK --check-prefix=FIRST + +# Prepend a dummy entry to check if the address for _objc_msgSend is valid. +# RUN: %lld -arch arm64 -lSystem -fixup_chains -e _dummy -U _dummy -o %t.out %t.o +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=CHECK --check-prefix=SECOND + +# CHECK: Contents of (__TEXT,__objc_stubs) section + +# CHECK-NEXT: _objc_msgSend$foo: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# FIRST-NEXT: ldr x16, [x16] +# SECOND-NEXT:ldr x16, [x16, #0x8] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-EMPTY: + +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$foo + ret diff --git a/wild/tests/lld-macho/arm64-objc-stubs.s b/wild/tests/lld-macho/arm64-objc-stubs.s new file mode 100644 index 000000000..da25b1292 --- /dev/null +++ b/wild/tests/lld-macho/arm64-objc-stubs.s @@ -0,0 +1,92 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -U _external_func +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -dead_strip -U _external_func +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_fast -U _external_func +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=FASTALIGN +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small -U _external_func +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=SMALL +# RUN: llvm-otool -l %t.out | FileCheck %s --check-prefix=SMALLALIGN +# RUN: llvm-objdump --section-headers %t.out | FileCheck %s --check-prefix=SECTIONS + +# CHECK: Contents of (__TEXT,__objc_stubs) section + +# CHECK-NEXT: _objc_msgSend$foo: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x18] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-NEXT: _objc_msgSend$length: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x20] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-EMPTY: + +# FASTALIGN: sectname __objc_stubs +# FASTALIGN-NEXT: segname __TEXT +# FASTALIGN-NEXT: addr +# FASTALIGN-NEXT: size +# FASTALIGN-NEXT: offset +# FASTALIGN-NEXT: align 2^5 (32) + +# SMALL: _objc_msgSend$foo: +# SMALL-NEXT: adrp x1, 8 ; 0x100008000 +# SMALL-NEXT: ldr x1, [x1, #0x18] +# SMALL-NEXT: b + +# SMALL-NEXT: _objc_msgSend$length: +# SMALL-NEXT: adrp x1, 8 ; 0x100008000 +# SMALL-NEXT: ldr x1, [x1, #0x20] +# SMALL-NEXT: b + +# SMALLALIGN: sectname __objc_stubs +# SMALLALIGN-NEXT: segname __TEXT +# SMALLALIGN-NEXT: addr +# SMALLALIGN-NEXT: size +# SMALLALIGN-NEXT: offset +# SMALLALIGN-NEXT: align 2^2 (4) + +## Check correct section ordering +# SECTIONS: Sections: +# SECTIONS: __text +# SECTIONS: __stubs +# SECTIONS: __objc_stubs + +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$length + bl _objc_msgSend$foo + bl _objc_msgSend$foo + bl _external_func + ret diff --git a/wild/tests/lld-macho/arm64-reloc-got-load.s b/wild/tests/lld-macho/arm64-reloc-got-load.s new file mode 100644 index 000000000..0186d41dd --- /dev/null +++ b/wild/tests/lld-macho/arm64-reloc-got-load.s @@ -0,0 +1,51 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foobar.s -o %t/foobar.o + +# RUN: %lld -lSystem -arch arm64 -o %t/static %t/main.o %t/foobar.o +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --syms %t/static | FileCheck %s --check-prefix=STATIC + +# RUN: %lld -lSystem -arch arm64 -dylib -o %t/libfoo.dylib %t/foobar.o +# RUN: %lld -lSystem -arch arm64 -o %t/main %t/main.o %t/libfoo.dylib +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --section-headers %t/main | FileCheck %s --check-prefix=DYLIB + +# STATIC-LABEL: _main: +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#%x,PAGE:]] +# STATIC-NEXT: add x8, x8, #[[#%u,FOO_OFF:]] +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#PAGE]] +# STATIC-NEXT: add x8, x8, #[[#%u,BAR_OFF:]] +# STATIC-NEXT: ret + +# STATIC-LABEL: SYMBOL TABLE: +# STATIC-DAG: {{0*}}[[#%x,PAGE+FOO_OFF]] g F __TEXT,__text _foo +# STATIC-DAG: {{0*}}[[#%x,PAGE+BAR_OFF]] g F __TEXT,__text _bar + +# DYLIB-LABEL: _main: +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#%x,GOT:]] +# DYLIB-NEXT: ldr x8, [x8, #8] ; literal pool symbol address: _foo +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#GOT]] +# DYLIB-NEXT: ldr x8, [x8] ; literal pool symbol address: _bar +# DYLIB-NEXT: ret +# DYLIB-EMPTY: +# DYLIB-NEXT: Sections: +# DYLIB-NEXT: Idx Name Size VMA Type +# DYLIB: [[#]] __got 00000010 {{0*}}[[#GOT]] DATA + +#--- main.s +.globl _main, _foo, _bar +.p2align 2 +_main: + adrp x8, _foo@GOTPAGE + ldr x8, [x8, _foo@GOTPAGEOFF] + adrp x8, _bar@GOTPAGE + ldr x8, [x8, _bar@GOTPAGEOFF] + ret + +#--- foobar.s +.globl _foo, _bar +_foo: + .space 0 +_bar: + .space 0 diff --git a/wild/tests/lld-macho/arm64-reloc-pointer-to-got.s b/wild/tests/lld-macho/arm64-reloc-pointer-to-got.s new file mode 100644 index 000000000..2551d7125 --- /dev/null +++ b/wild/tests/lld-macho/arm64-reloc-pointer-to-got.s @@ -0,0 +1,36 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -lSystem -arch arm64 -o %t %t.o +# RUN: llvm-objdump --macho -d --full-contents --section-headers %t | FileCheck %s + +## FIXME: Even though we have reserved a GOT slot for _foo due to +## POINTER_TO_GOT, we should still be able to relax this GOT_LOAD reference to +## it. +# CHECK: _main: +# CHECK-NEXT: adrp x8, [[#]] ; 0x100004000 +# CHECK-NEXT: ldr x8, [x8] +# CHECK-NEXT: ret + +# CHECK: Idx Name Size VMA Type +# CHECK: [[#]] __got 00000008 0000000100004000 DATA +# CHECK: [[#]] __data 00000004 0000000100008000 DATA + +## The relocated data should contain the difference between the addresses of +## __data and __got in little-endian form. +# CHECK: Contents of section __DATA,__data: +# CHECK-NEXT: 100008000 00c0ffff + +.globl _main, _foo +.p2align 2 +_main: + adrp x8, _foo@GOTPAGE + ldr x8, [x8, _foo@GOTPAGEOFF] + ret + +.p2align 2 +_foo: + ret + +.data +.long _foo@GOT - . diff --git a/wild/tests/lld-macho/arm64-reloc-tlv-load.s b/wild/tests/lld-macho/arm64-reloc-tlv-load.s new file mode 100644 index 000000000..c525f2db1 --- /dev/null +++ b/wild/tests/lld-macho/arm64-reloc-tlv-load.s @@ -0,0 +1,63 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foobar.s -o %t/foobar.o + +# RUN: %lld -lSystem -arch arm64 -o %t/static %t/main.o %t/foobar.o +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --syms %t/static | FileCheck %s --check-prefix=STATIC + +# RUN: %lld -lSystem -arch arm64 -dylib -o %t/libfoo.dylib %t/foobar.o +# RUN: %lld -lSystem -arch arm64 -o %t/main %t/main.o %t/libfoo.dylib +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --section-headers %t/main | FileCheck %s --check-prefix=DYLIB + +# STATIC-LABEL: _main: +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#%x,PAGE:]] +# STATIC-NEXT: add x8, x8, #[[#%u,FOO_OFF:]] +# STATIC-NEXT: adrp x8, [[#]] ; 0x[[#PAGE]] +# STATIC-NEXT: add x8, x8, #[[#%u,BAR_OFF:]] +# STATIC-NEXT: ret + +# STATIC-LABEL: SYMBOL TABLE: +# STATIC-DAG: {{0*}}[[#%x,PAGE+FOO_OFF]] g O __DATA,__thread_vars _foo +# STATIC-DAG: {{0*}}[[#%x,PAGE+BAR_OFF]] g O __DATA,__thread_vars _bar + +# DYLIB-LABEL: _main: +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#%x,TLV:]] +# DYLIB-NEXT: ldr x8, [x8, #8] ; literal pool symbol address: _foo +# DYLIB-NEXT: adrp x8, [[#]] ; 0x[[#TLV]] +# DYLIB-NEXT: ldr x8, [x8] ; literal pool symbol address: _bar +# DYLIB-NEXT: ret +# DYLIB-EMPTY: +# DYLIB-NEXT: Sections: +# DYLIB-NEXT: Idx Name Size VMA Type +# DYLIB: [[#]] __thread_ptrs 00000010 {{0*}}[[#TLV]] DATA + +#--- main.s +.globl _main, _foo, _bar +.p2align 2 +_main: + adrp x8, _foo@TLVPPAGE + ldr x8, [x8, _foo@TLVPPAGEOFF] + adrp x8, _bar@TLVPPAGE + ldr x8, [x8, _bar@TLVPPAGEOFF] + ret + +#--- foobar.s +.globl _foo, _bar + +.section __DATA,__thread_data,thread_local_regular +_foo$tlv$init: + .long 123 +_bar$tlv$init: + .long 123 + +.section __DATA,__thread_vars,thread_local_variables +_foo: + .quad __tlv_bootstrap + .quad 0 + .quad _foo$tlv$init +_bar: + .quad __tlv_bootstrap + .quad 0 + .quad _bar$tlv$init diff --git a/wild/tests/lld-macho/arm64-relocs.s b/wild/tests/lld-macho/arm64-relocs.s new file mode 100644 index 000000000..4bd0f6b8a --- /dev/null +++ b/wild/tests/lld-macho/arm64-relocs.s @@ -0,0 +1,70 @@ +# REQUIRES: aarch64 +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -dylib -arch arm64 -lSystem -o %t %t.o +# RUN: llvm-objdump --syms %t > %t.objdump +# RUN: llvm-objdump --no-print-imm-hex --macho -d --section=__const %t >> %t.objdump +# RUN: FileCheck %s < %t.objdump + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK-DAG: [[#%x,PTR_1:]] l O __DATA_CONST,__const _ptr_1 +# CHECK-DAG: [[#%x,PTR_2:]] l O __DATA_CONST,__const _ptr_2 +# CHECK-DAG: [[#%x,BAR:]] g F __TEXT,__text _bar +# CHECK-DAG: [[#%x,BAZ:]] g O __DATA,__data _baz + +# CHECK-LABEL: _foo: +## BRANCH26 relocations are 4-byte aligned, so 123 is truncated to 120 +# CHECK-NEXT: bl 0x[[#BAR+120]] +## PAGE21 relocations are aligned to 4096 bytes +# CHECK-NEXT: adrp x2, [[#]] ; 0x[[#BAZ+4096-128]] +# CHECK-NEXT: ldr x2, [x2, #128] +# CHECK-NEXT: adrp x3, 8 ; 0x8000 +# CHECK-NEXT: ldr q0, [x3, #144] +# CHECK-NEXT: ret + +# CHECK-LABEL: Contents of (__DATA_CONST,__const) section +# CHECK: [[#PTR_1]] {{0*}}[[#BAZ]] 00000000 00000000 00000000 +# CHECK: [[#PTR_2]] {{0*}}[[#BAZ+123]] 00000000 00000000 00000000 + +.text +.globl _foo, _bar, _baz, _quux +.p2align 2 +_foo: + ## Generates ARM64_RELOC_BRANCH26 and ARM64_RELOC_ADDEND + bl _bar + 123 + ## Generates ARM64_RELOC_PAGE21 and ADDEND + adrp x2, _baz@PAGE + 4097 + ## Generates ARM64_RELOC_PAGEOFF12 + ldr x2, [x2, _baz@PAGEOFF] + + ## Generates ARM64_RELOC_PAGE21 + adrp x3, _quux@PAGE + ## Generates ARM64_RELOC_PAGEOFF12 with internal slide 4 + ldr q0, [x3, _quux@PAGEOFF] + ret + +.p2align 2 +_bar: + ret + +.data +.space 128 +_baz: +.space 1 + +.p2align 4 +_quux: +.quad 0 +.quad 80 + +.section __DATA_CONST,__const +## These generate ARM64_RELOC_UNSIGNED symbol relocations. llvm-mc seems to +## generate UNSIGNED section relocations only for compact unwind sections, so +## those relocations are being tested in compact-unwind.s. +_ptr_1: + .quad _baz + .space 8 +_ptr_2: + .quad _baz + 123 + .space 8 + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-stubs.s b/wild/tests/lld-macho/arm64-stubs.s new file mode 100644 index 000000000..55e0f0613 --- /dev/null +++ b/wild/tests/lld-macho/arm64-stubs.s @@ -0,0 +1,65 @@ +# REQUIRES: aarch64 + +## Test arm64 stubs +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/bar.s -o %t/bar.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/test.s -o %t/test.o +# RUN: %lld -arch arm64 -dylib -install_name @executable_path/libfoo.dylib %t/foo.o -o %t/libfoo.dylib +# RUN: %lld -arch arm64 -dylib -install_name @executable_path/libbar.dylib %t/bar.o -o %t/libbar.dylib +# RUN: %lld -arch arm64 -lSystem %t/libfoo.dylib %t/libbar.dylib %t/test.o -o %t/test -no_fixup_chains +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --section="__TEXT,__stubs" --section="__TEXT,__stub_helper" %t/test | FileCheck %s -DREG=x16 + +## Test arm64_32 stubs +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/foo.s -o %t/foo32.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/bar.s -o %t/bar32.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %t/test.s -o %t/test32.o +# RUN: %lld-watchos -dylib -install_name @executable_path/libfoo.dylib %t/foo32.o -o %t/libfoo32.dylib +# RUN: %lld-watchos -dylib -install_name @executable_path/libbar.dylib %t/bar32.o -o %t/libbar32.dylib +# RUN: %lld-watchos -lSystem %t/libfoo32.dylib %t/libbar32.dylib %t/test32.o -o %t/test32 -no_fixup_chains +# RUN: llvm-objdump --no-print-imm-hex --macho -d --no-show-raw-insn --section="__TEXT,__stubs" --section="__TEXT,__stub_helper" %t/test32 | FileCheck %s -DREG=w16 + +# CHECK: _main: +# CHECK-NEXT: bl 0x[[#%x,FOO:]] ; symbol stub for: _foo +# CHECK-NEXT: bl 0x[[#%x,BAR:]] ; symbol stub for: _bar +# CHECK-NEXT: ret + +# CHECK-LABEL: Contents of (__TEXT,__stubs) section +# CHECK-NEXT: [[#BAR]]: adrp x16 +# CHECK-NEXT: ldr [[REG]], [x16{{.*}}] ; literal pool symbol address: _bar +# CHECK-NEXT: br x16 +# CHECK-NEXT: [[#FOO]]: adrp x16 +# CHECK-NEXT: ldr [[REG]], [x16{{.*}}] ; literal pool symbol address: _foo +# CHECK-NEXT: br x16 + +# CHECK-LABEL: Contents of (__TEXT,__stub_helper) section +# CHECK-NEXT: [[#%x,HELPER_HEADER:]]: adrp x17 +# CHECK-NEXT: add x17, x17 +# CHECK-NEXT: stp x16, x17, [sp, #-16]! +# CHECK-NEXT: adrp x16 +# CHECK-NEXT: ldr [[REG]], [x16] ; literal pool symbol address: dyld_stub_binder +# CHECK-NEXT: br x16 +# CHECK-NEXT: ldr w16, 0x[[#%x,BAR_BIND_OFF_ADDR:]] +# CHECK-NEXT: b 0x[[#HELPER_HEADER]] +# CHECK-NEXT: [[#BAR_BIND_OFF_ADDR]]: udf #0 +# CHECK-NEXT: ldr w16, 0x[[#%x,FOO_BIND_OFF_ADDR:]] +# CHECK-NEXT: b 0x[[#HELPER_HEADER]] +# CHECK-NEXT: [[#FOO_BIND_OFF_ADDR]]: udf #11 + +#--- foo.s +.globl _foo +_foo: + +#--- bar.s +.globl _bar +_bar: + +#--- test.s +.text +.globl _main + +.p2align 2 +_main: + bl _foo + bl _bar + ret diff --git a/wild/tests/lld-macho/arm64-thunk-for-alignment.s b/wild/tests/lld-macho/arm64-thunk-for-alignment.s new file mode 100644 index 000000000..f497b81f7 --- /dev/null +++ b/wild/tests/lld-macho/arm64-thunk-for-alignment.s @@ -0,0 +1,44 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/bar.s -o %t/bar.o +# RUN: %lld -dylib -arch arm64 -lSystem -o %t/out %t/foo.o %t/bar.o + +# RUN: llvm-objdump --macho --syms %t/out | FileCheck %s +# CHECK: _bar.thunk.0 + +## Regression test for PR59259. Previously, we neglected to check section +## alignments when deciding when to create thunks. + +## If we ignore alignment, the total size of _spacer1 + _spacer2 below is just +## under the limit at which we attempt to insert thunks between the spacers. +## However, with alignment accounted for, their total size ends up being +## 0x8000000, which is just above the max forward branch range, making thunk +## insertion necessary. Thus, not accounting for alignment led to an error. + +#--- foo.s + +_foo: + b _bar + +## Size of a `b` instruction. +.equ callSize, 4 +## Refer to `slop` in TextOutputSection::finalize(). +.equ slopSize, 12 * 256 + +_spacer1: + .space 0x4000000 - slopSize - 2 * callSize - 1 + +.subsections_via_symbols + +#--- bar.s +.globl _bar + +.p2align 14 +_spacer2: + .space 0x4000000 + +_bar: + ret + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/arm64-thunk-icf-body-dedup.s b/wild/tests/lld-macho/arm64-thunk-icf-body-dedup.s new file mode 100644 index 000000000..06d1065c2 --- /dev/null +++ b/wild/tests/lld-macho/arm64-thunk-icf-body-dedup.s @@ -0,0 +1,80 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; mkdir %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t/input.o + +## Verify that ICF body-folded symbols share a single branch-extension thunk +## rather than each getting its own. +# RUN: %lld -arch arm64 -lSystem -o %t/icf-all %t/input.o --icf=all -map %t/icf-all.map +# RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %t/icf-all | FileCheck %s --check-prefix=BODY +# RUN: FileCheck %s --input-file %t/icf-all.map --check-prefix=BODY-MAP + +## Both calls in _main branch to the same thunk address. +# BODY-LABEL: <_main>: +# BODY: bl 0x[[#%x, THUNK:]] <_target_a.thunk.0> +# BODY-NEXT: bl 0x[[#%x, THUNK]] <_target_a.thunk.0> + +## Only one thunk should be created for the folded functions. +# BODY-MAP: .thunk.0 +# BODY-MAP-NOT: .thunk.0 + +## Verify that safe_thunks ICF still creates separate branch-extension thunks +## when needed. +# RUN: %lld -arch arm64 -lSystem -o %t/icf-safe %t/input.o --icf=safe_thunks +# RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %t/icf-safe | FileCheck %s --check-prefix=SAFE + +## Each call gets its own branch-extension thunk. +# SAFE-LABEL: <_main>: +# SAFE: bl 0x[[#%x, THUNK_A:]] <_target_a.thunk.0> +# SAFE-NEXT: bl 0x[[#%x, THUNK_B:]] <_target_b.thunk.0> + +.subsections_via_symbols + +.addrsig +.addrsig_sym _target_a +.addrsig_sym _target_b + +.text + +## A unique function placed before _target_a so that the assembler's automatic +## ltmp0 symbol lands here to make the test more readable. +.globl _unique_func +.p2align 2 +_unique_func: + mov w0, #1 + ret + +.globl _target_a +.p2align 2 +_target_a: + mov w0, #42 + ret + +.globl _target_b +.p2align 2 +_target_b: + mov w0, #42 + ret + +.globl _spacer +.p2align 2 +_spacer: + .space 0x8000000 + ret + +.globl _main +.p2align 2 +_main: + bl _target_a + bl _target_b + ret + +## With safe_thunks, _target_b's ICF thunk is a synthetic section appended +## after all regular inputs. This spacer pushes it out of branch range from +## _main so it also needs a branch-extension thunk. +.globl _spacer2 +.p2align 2 +_spacer2: + .space 0x8000000 + mov w0, #0 + ret diff --git a/wild/tests/lld-macho/arm64-thunk-starvation.s b/wild/tests/lld-macho/arm64-thunk-starvation.s new file mode 100644 index 000000000..9e2b54f84 --- /dev/null +++ b/wild/tests/lld-macho/arm64-thunk-starvation.s @@ -0,0 +1,57 @@ +# REQUIRES: aarch64 +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o + +## Regression test for PR51578. + +.subsections_via_symbols + +.globl _f1, _f2, _f3, _f4, _f5, _f6 +.p2align 2 +_f1: b _fn1 +_f2: b _fn2 +_f3: b _fn3 +_f4: b _fn4 +_f5: b _fn5 +_f6: b _fn6 +## 6 * 4 = 24 bytes for branches +## Currently leaves 12 bytes for one thunk, so 36 bytes. +## Uses < instead of <=, so 40 bytes. + +.global _spacer1, _spacer2 +## 0x8000000 is 128 MiB, one more than the forward branch limit, +## distributed over two functions since our thunk insertion algorithm +## can't deal with a single function that's 128 MiB. +## We leave just enough room so that the old thunking algorithm finalized +## both spacers when processing _f1 (24 bytes for the 4 bytes code for each +## of the 6 _f functions, 12 bytes for one thunk, 4 bytes because the forward +## branch range is 128 Mib - 4 bytes, and another 4 bytes because the algorithm +## uses `<` instead of `<=`, for a total of 44 bytes slop.) Of the slop, 20 +## bytes are actually room for thunks. +## _fn1-_fn6 aren't finalized because then there wouldn't be room for a thunk. +## But when a thunk is inserted to jump from _f1 to _fn1, that needs 12 bytes +## but _f2 is only 4 bytes later, so after _f1 there are only +## 20-(12-4) = 12 bytes left, after _f2 only 12-(12-4) 4 bytes, and after +## _f3 there's no more room for thunks and we can't make progress. +## The fix is to leave room for many more thunks. +## The same construction as this test case can defeat that too with enough +## consecutive jumps, but in practice there aren't hundreds of consecutive +## jump instructions. + +_spacer1: +.space 0x4000000 +_spacer2: +.space 0x4000000 - 44 + +.globl _fn1, _fn2, _fn3, _fn4, _fn5, _fn6 +.p2align 2 +_fn1: ret +_fn2: ret +_fn3: ret +_fn4: ret +_fn5: ret +_fn6: ret + +.globl _main +_main: + ret diff --git a/wild/tests/lld-macho/arm64-thunk-visibility.s b/wild/tests/lld-macho/arm64-thunk-visibility.s new file mode 100644 index 000000000..5fa195f8b --- /dev/null +++ b/wild/tests/lld-macho/arm64-thunk-visibility.s @@ -0,0 +1,83 @@ +# REQUIRES: aarch64 + +# foo.s and bar.s both contain TU-local symbols (think static function) +# with the same name, and both need a thunk. This tests that ld64.lld doesn't +# create a duplicate symbol for the two functions. + +# Test this both when the TU-local symbol is the branch source or target, +# and for both forward and backwards jumps. + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/bar.s -o %t/bar.o +# RUN: %lld -arch arm64 -lSystem -o %t.out %t/foo.o %t/bar.o + +#--- foo.s + +.subsections_via_symbols + +# Note: No .globl, since these are TU-local symbols. +.p2align 2 +_early_jumping_local_fn: b _some_late_external +.p2align 2 +_early_landing_local_fn: ret + +.globl _some_early_external +.p2align 2 +_some_early_external: b _late_landing_local_fn + +## 0x8000000 is 128 MiB, one more than the forward branch limit. +## Distribute that over two functions since our thunk insertion algorithm +## can't deal with a single function that's 128 MiB. +.global _spacer1, _spacer2 +_spacer1: +.space 0x4000000 +_spacer2: +.space 0x4000000 + +# Note: No .globl, since these are TU-local symbols. +.p2align 2 +_late_jumping_local_fn: b _some_early_external +.p2align 2 +_late_landing_local_fn: ret + +.globl _some_late_external +.p2align 2 +_some_late_external: b _early_landing_local_fn + +#--- bar.s + +.subsections_via_symbols + +# Note: No .globl, since these are TU-local symbols. +.p2align 2 +_early_jumping_local_fn: b _some_other_late_external +.p2align 2 +_early_landing_local_fn: ret + +.globl _some_other_early_external +.p2align 2 +_some_other_early_external: b _late_landing_local_fn + +## 0x8000000 is 128 MiB, one more than the forward branch limit. +## Distribute that over two functions since our thunk insertion algorithm +## can't deal with a single function that's 128 MiB. +.global _other_spacer1, _other_spacer1 +_spacer1: +.space 0x4000000 +_spacer2: +.space 0x4000000 + +# Note: No .globl, since these are TU-local symbols. +.p2align 2 +_late_jumping_local_fn: b _some_other_early_external +.p2align 2 +_late_landing_local_fn: ret + +.globl _some_other_late_external +.p2align 2 +_some_other_late_external: b _early_landing_local_fn + +.globl _main +_main: + ret diff --git a/wild/tests/lld-macho/arm64-thunks.s b/wild/tests/lld-macho/arm64-thunks.s new file mode 100644 index 000000000..cd3842895 --- /dev/null +++ b/wild/tests/lld-macho/arm64-thunks.s @@ -0,0 +1,419 @@ +# REQUIRES: aarch64 + +## Check for the following: +## (1) address match between thunk definitions and call destinations +## (2) address match between thunk page+offset computations and function +## definitions +## (3) a second thunk is created when the first one goes out of range +## (4) early calls to a dylib stub use a thunk, and later calls the stub +## directly +## (5) Thunks are created for all sections in the text segment with branches. +## (6) Thunks are in the linker map file. +## Notes: +## 0x4000000 = 64 Mi = half the magnitude of the forward-branch range + +# RUN: rm -rf %t; mkdir %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t/input.o +## Use --icf=safe_thunks to test that branch extension algo is compatible +## with safe_thunks ICF. +# RUN: %lld -arch arm64 -dead_strip -lSystem -U _extern_sym -map %t/thunk.map -o %t/thunk %t/input.o --icf=safe_thunks +# RUN: llvm-objdump --no-print-imm-hex -d --no-show-raw-insn %t/thunk | FileCheck %s +# RUN: llvm-objdump --macho --section-headers %t/thunk > %t/headers.txt +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/thunk >> %t/headers.txt +# RUN: llvm-otool -vs __TEXT __objc_stubs %t/thunk >> %t/headers.txt +# RUN: FileCheck %s --check-prefix=OBJC < %t/headers.txt + +# RUN: FileCheck %s --input-file %t/thunk.map --check-prefix=MAP + +# OBJC: Sections: +# OBJC: __text +# OBJC-NEXT: __lcxx_override +# OBJC-NEXT: __stubs +# OBJC-NEXT: __stub_helper +# OBJC-NEXT: __objc_stubs + +# OBJC: Contents of (__DATA,__objc_selrefs) section +# OBJC-NEXT: {{[0-9a-f]*}} __TEXT:__objc_methname:foo +# OBJC-NEXT: {{[0-9a-f]*}} __TEXT:__objc_methname:bar + +# OBJC: Contents of (__TEXT,__objc_stubs) section +# OBJC: _objc_msgSend$bar: +# OBJC: _objc_msgSend$foo: + +# MAP: 0x{{[[:xdigit:]]+}} {{.*}} _fold_func_low_addr +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _a +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _b +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _c +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _d.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _e.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _f.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _g.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _h.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} ___nan.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _d +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _e +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _f +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _g +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _a.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _b.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _h +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _main +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _fold_func_high_addr +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _c.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _d.thunk.1 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _e.thunk.1 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _f.thunk.1 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _fold_func_low_addr.thunk.0 +# MAP-NEXT: 0x{{[[:xdigit:]]+}} {{.*}} _z + + +# CHECK: Disassembly of section __TEXT,__text: + +# CHECK: [[#%.13x, A_PAGE:]][[#%.3x, A_OFFSET:]] <_a>: +# CHECK: bl 0x[[#%x, A:]] <_a> +# CHECK: bl 0x[[#%x, B:]] <_b> +# CHECK: bl 0x[[#%x, C:]] <_c> +# CHECK: bl 0x[[#%x, D_THUNK_0:]] <_d.thunk.0> +# CHECK: bl 0x[[#%x, E_THUNK_0:]] <_e.thunk.0> +# CHECK: bl 0x[[#%x, F_THUNK_0:]] <_f.thunk.0> +# CHECK: bl 0x[[#%x, G_THUNK_0:]] <_g.thunk.0> +# CHECK: bl 0x[[#%x, H_THUNK_0:]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0:]] <___nan.thunk.0> + +# CHECK: [[#%.13x, B_PAGE:]][[#%.3x, B_OFFSET:]] <_b>: +# CHECK: bl 0x[[#%x, A]] <_a> +# CHECK: bl 0x[[#%x, B]] <_b> +# CHECK: bl 0x[[#%x, C]] <_c> +# CHECK: bl 0x[[#%x, D_THUNK_0]] <_d.thunk.0> +# CHECK: bl 0x[[#%x, E_THUNK_0]] <_e.thunk.0> +# CHECK: bl 0x[[#%x, F_THUNK_0]] <_f.thunk.0> +# CHECK: bl 0x[[#%x, G_THUNK_0]] <_g.thunk.0> +# CHECK: bl 0x[[#%x, H_THUNK_0]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0]] <___nan.thunk.0> + +# CHECK: [[#%.13x, C_PAGE:]][[#%.3x, C_OFFSET:]] <_c>: +# CHECK: bl 0x[[#%x, A]] <_a> +# CHECK: bl 0x[[#%x, B]] <_b> +# CHECK: bl 0x[[#%x, C]] <_c> +# CHECK: bl 0x[[#%x, D:]] <_d> +# CHECK: bl 0x[[#%x, E:]] <_e> +# CHECK: bl 0x[[#%x, F_THUNK_0]] <_f.thunk.0> +# CHECK: bl 0x[[#%x, G_THUNK_0]] <_g.thunk.0> +# CHECK: bl 0x[[#%x, H_THUNK_0]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0]] <___nan.thunk.0> + +# CHECK: [[#%x, D_THUNK_0]] <_d.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, D_PAGE:]] +# CHECK: add x16, x16, #[[#D_OFFSET:]] + +# CHECK: [[#%x, E_THUNK_0]] <_e.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, E_PAGE:]] +# CHECK: add x16, x16, #[[#E_OFFSET:]] + +# CHECK: [[#%x, F_THUNK_0]] <_f.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, F_PAGE:]] +# CHECK: add x16, x16, #[[#F_OFFSET:]] + +# CHECK: [[#%x, G_THUNK_0]] <_g.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, G_PAGE:]] +# CHECK: add x16, x16, #[[#G_OFFSET:]] + +# CHECK: [[#%x, H_THUNK_0]] <_h.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, H_PAGE:]] +# CHECK: add x16, x16, #[[#H_OFFSET:]] + +# CHECK: [[#%x, NAN_THUNK_0]] <___nan.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, NAN_PAGE:]] +# CHECK: add x16, x16, #[[#NAN_OFFSET:]] + +# CHECK: [[#%x, D_PAGE + D_OFFSET]] <_d>: +# CHECK: bl 0x[[#%x, A]] <_a> +# CHECK: bl 0x[[#%x, B]] <_b> +# CHECK: bl 0x[[#%x, C]] <_c> +# CHECK: bl 0x[[#%x, D]] <_d> +# CHECK: bl 0x[[#%x, E]] <_e> +# CHECK: bl 0x[[#%x, F_THUNK_0]] <_f.thunk.0> +# CHECK: bl 0x[[#%x, G_THUNK_0]] <_g.thunk.0> +# CHECK: bl 0x[[#%x, H_THUNK_0]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0]] <___nan.thunk.0> + +# CHECK: [[#%x, E_PAGE + E_OFFSET]] <_e>: +# CHECK: bl 0x[[#%x, A_THUNK_0:]] <_a.thunk.0> +# CHECK: bl 0x[[#%x, B_THUNK_0:]] <_b.thunk.0> +# CHECK: bl 0x[[#%x, C]] <_c> +# CHECK: bl 0x[[#%x, D]] <_d> +# CHECK: bl 0x[[#%x, E]] <_e> +# CHECK: bl 0x[[#%x, F:]] <_f> +# CHECK: bl 0x[[#%x, G:]] <_g> +# CHECK: bl 0x[[#%x, H_THUNK_0]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0]] <___nan.thunk.0> + +# CHECK: [[#%x, F_PAGE + F_OFFSET]] <_f>: +# CHECK: bl 0x[[#%x, A_THUNK_0]] <_a.thunk.0> +# CHECK: bl 0x[[#%x, B_THUNK_0]] <_b.thunk.0> +# CHECK: bl 0x[[#%x, C]] <_c> +# CHECK: bl 0x[[#%x, D]] <_d> +# CHECK: bl 0x[[#%x, E]] <_e> +# CHECK: bl 0x[[#%x, F]] <_f> +# CHECK: bl 0x[[#%x, G]] <_g> +# CHECK: bl 0x[[#%x, H_THUNK_0]] <_h.thunk.0> +# CHECK: bl 0x[[#%x, NAN_THUNK_0]] <___nan.thunk.0> + +# CHECK: [[#%x, G_PAGE + G_OFFSET]] <_g>: +# CHECK: bl 0x[[#%x, A_THUNK_0]] <_a.thunk.0> +# CHECK: bl 0x[[#%x, B_THUNK_0]] <_b.thunk.0> +# CHECK: bl 0x[[#%x, C_THUNK_0:]] <_c.thunk.0> +# CHECK: bl 0x[[#%x, D_THUNK_1:]] <_d.thunk.1> +# CHECK: bl 0x[[#%x, E]] <_e> +# CHECK: bl 0x[[#%x, F]] <_f> +# CHECK: bl 0x[[#%x, G]] <_g> +# CHECK: bl 0x[[#%x, H:]] <_h> +# CHECK: bl 0x[[#%x, STUBS:]] + +# CHECK: [[#%x, A_THUNK_0]] <_a.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, A_PAGE]]000 +# CHECK: add x16, x16, #[[#%d, A_OFFSET]] + +# CHECK: [[#%x, B_THUNK_0]] <_b.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, B_PAGE]]000 +# CHECK: add x16, x16, #[[#%d, B_OFFSET]] + +# CHECK: [[#%x, H_PAGE + H_OFFSET]] <_h>: +# CHECK: bl 0x[[#%x, A_THUNK_0]] <_a.thunk.0> +# CHECK: bl 0x[[#%x, B_THUNK_0]] <_b.thunk.0> +# CHECK: bl 0x[[#%x, C_THUNK_0]] <_c.thunk.0> +# CHECK: bl 0x[[#%x, D_THUNK_1]] <_d.thunk.1> +# CHECK: bl 0x[[#%x, E]] <_e> +# CHECK: bl 0x[[#%x, F]] <_f> +# CHECK: bl 0x[[#%x, G]] <_g> +# CHECK: bl 0x[[#%x, H]] <_h> +# CHECK: bl 0x[[#%x, STUBS]] + +# CHECK: <_main>: +# CHECK: bl 0x[[#%x, A_THUNK_0]] <_a.thunk.0> +# CHECK: bl 0x[[#%x, B_THUNK_0]] <_b.thunk.0> +# CHECK: bl 0x[[#%x, C_THUNK_0]] <_c.thunk.0> +# CHECK: bl 0x[[#%x, D_THUNK_1]] <_d.thunk.1> +# CHECK: bl 0x[[#%x, E_THUNK_1:]] <_e.thunk.1> +# CHECK: bl 0x[[#%x, F_THUNK_1:]] <_f.thunk.1> +# CHECK: bl 0x[[#%x, G]] <_g> +# CHECK: bl 0x[[#%x, H]] <_h> +# CHECK: bl 0x[[#%x, STUBS]] + +# CHECK: [[#%x, C_THUNK_0]] <_c.thunk.0>: +# CHECK: adrp x16, 0x[[#%x, C_PAGE]]000 +# CHECK: add x16, x16, #[[#%d, C_OFFSET]] + +# CHECK: [[#%x, D_THUNK_1]] <_d.thunk.1>: +# CHECK: adrp x16, 0x[[#%x, D_PAGE]] +# CHECK: add x16, x16, #[[#D_OFFSET]] + +# CHECK: [[#%x, E_THUNK_1]] <_e.thunk.1>: +# CHECK: adrp x16, 0x[[#%x, E_PAGE]] +# CHECK: add x16, x16, #[[#E_OFFSET]] + +# CHECK: [[#%x, F_THUNK_1]] <_f.thunk.1>: +# CHECK: adrp x16, 0x[[#%x, F_PAGE]] +# CHECK: add x16, x16, #[[#F_OFFSET]] + +# CHECK: Disassembly of section __TEXT,__lcxx_override: +# CHECK: <_z>: +# CHECK: bl 0x[[#%x, A_THUNK_0]] <_a.thunk.0> + +# CHECK: Disassembly of section __TEXT,__stubs: + +# CHECK: [[#%x, NAN_PAGE + NAN_OFFSET]] <__stubs>: + +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.subsections_via_symbols + +.addrsig +.addrsig_sym _fold_func_low_addr +.addrsig_sym _fold_func_high_addr + +.text + +.globl _fold_func_low_addr +.p2align 2 +_fold_func_low_addr: + add x0, x0, x0 + add x1, x0, x1 + add x2, x0, x2 + ret + +.globl _a +.p2align 2 +_a: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + ret + +.globl _b +.p2align 2 +_b: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + .space 0x4000000-0x3c + ret + +.globl _c +.p2align 2 +_c: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + ret + +.globl _d +.p2align 2 +_d: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + .space 0x4000000-0x38 + ret + +.globl _e +.p2align 2 +_e: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + ret + +.globl _f +.p2align 2 +_f: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + .space 0x4000000-0x34 + ret + +.globl _g +.p2align 2 +_g: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + ret + +.globl _h +.p2align 2 +_h: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl ___nan + .space 0x4000000-0x30 + ret + +.globl _main +.p2align 2 +_main: + bl _a + bl _b + bl _c + bl _d + bl _e + bl _f + bl _g + bl _h + bl _fold_func_low_addr + bl _fold_func_high_addr + bl ___nan + bl _objc_msgSend$foo + bl _objc_msgSend$bar + ret + +.globl _fold_func_high_addr +.p2align 2 +_fold_func_high_addr: + add x0, x0, x0 + add x1, x0, x1 + add x2, x0, x2 + ret + + +.section __TEXT,__cstring + # The .space below has to be composed of non-zero characters. Otherwise, the + # linker will create a symbol for every '0' in the section, leading to + # dramatic memory usage and a huge linker map file + .space 0x4000000, 'A' + .byte 0 + + +.section __TEXT,__lcxx_override,regular,pure_instructions + +.globl _z +.no_dead_strip _z +.p2align 2 +_z: + bl _a + ## Ensure calling into stubs works + bl _extern_sym + ret diff --git a/wild/tests/lld-macho/bind-opcodes.s b/wild/tests/lld-macho/bind-opcodes.s new file mode 100644 index 000000000..cf294f2f7 --- /dev/null +++ b/wild/tests/lld-macho/bind-opcodes.s @@ -0,0 +1,186 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin --defsym PTR64=0 %t/test.s -o %t/test.o +# RUN: %lld -O2 -dylib %t/foo.o -o %t/libfoo.dylib +# RUN: %lld -O2 -lSystem %t/test.o %t/libfoo.dylib -o %t/test-x86_64 + +## Test (64-bit): +## 1/ We emit exactly one BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM per symbol. +## 2/ Combine BIND_OPCODE_DO_BIND and BIND_OPCODE_ADD_ADDR_ULEB pairs. +## 3/ Compact BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +## 4/ Use BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED if possible. +# RUN: obj2yaml %t/test-x86_64 | FileCheck %s + +# CHECK: BindOpcodes: +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: _foo +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK-NEXT: Imm: 1 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_DYLIB_ORDINAL_IMM +# CHECK-NEXT: Imm: 2 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB +# CHECK-NEXT: Imm: 2 +# CHECK-NEXT: ULEBExtraData: [ 0x0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x2, 0x8 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: SLEBExtraData: [ 1 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x1008 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: SLEBExtraData: [ 0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: _bar +# CHECK-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK-NEXT: Imm: 1 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0xFFFFFFFFFFFFEFD0 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED +# CHECK-NEXT: Imm: 1 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: ULEBExtraData: [ 0x1008 ] +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' +# CHECK-NEXT: Opcode: BIND_OPCODE_DONE +# CHECK-NEXT: Imm: 0 +# CHECK-NEXT: Symbol: '' + +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin --defsym PTR32=0 %t/test.s -o %t/test.o +# RUN: %lld-watchos -O2 -dylib %t/foo.o -o %t/libfoo.dylib +# RUN: %lld-watchos -O2 -dylib %t/test.o %t/libfoo.dylib -o %t/libtest-arm64_32.dylib + +## Test (32-bit): +## 1/ We emit exactly one BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM per symbol. +## 2/ Combine BIND_OPCODE_DO_BIND and BIND_OPCODE_ADD_ADDR_ULEB pairs. +## 3/ Compact BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +## 4/ Use BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED if possible. +# RUN: obj2yaml %t/libtest-arm64_32.dylib | FileCheck %s --check-prefix=CHECK32 + +# CHECK32: BindOpcodes: +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: Symbol: _foo +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK32-NEXT: Imm: 1 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_DYLIB_ORDINAL_IMM +# CHECK32-NEXT: Imm: 2 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB +# CHECK32-NEXT: Imm: 1 +# CHECK32-NEXT: ULEBExtraData: [ 0x0 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: ULEBExtraData: [ 0x2, 0x4 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: SLEBExtraData: [ 1 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: ULEBExtraData: [ 0x1004 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_ADDEND_SLEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: SLEBExtraData: [ 0 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: Symbol: _bar +# CHECK32-NEXT: Opcode: BIND_OPCODE_SET_TYPE_IMM +# CHECK32-NEXT: Imm: 1 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_ADD_ADDR_ULEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: ULEBExtraData: [ 0xFFFFFFFFFFFFEFE8 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED +# CHECK32-NEXT: Imm: 1 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: ULEBExtraData: [ 0x1004 ] +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DO_BIND +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: Symbol: '' +# CHECK32-NEXT: Opcode: BIND_OPCODE_DONE +# CHECK32-NEXT: Imm: 0 +# CHECK32-NEXT: Symbol: '' + +# RUN: llvm-objdump --macho --bind %t/test-x86_64 | FileCheck %s -D#PTR=8 --check-prefix=BIND +# RUN: llvm-objdump --macho --bind %t/libtest-arm64_32.dylib | FileCheck %s -D#PTR=4 --check-prefix=BIND +# BIND: Bind table: +# BIND-NEXT: segment section address type addend dylib symbol +# BIND-NEXT: __DATA __data 0x[[#%X,DATA:]] pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + mul(PTR, 2)]] pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + mul(PTR, 4)]] pointer 1 libfoo _foo +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + 4096 + mul(PTR, 6)]] pointer 0 libfoo _foo +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + PTR]] pointer 0 libfoo _bar +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + mul(PTR, 3)]] pointer 0 libfoo _bar +# BIND-NEXT: __DATA __data 0x[[#%.8X,DATA + 4096 + mul(PTR, 5)]] pointer 0 libfoo _bar +# BIND-EMPTY: + +#--- foo.s +.globl _foo, _bar +_foo: + .space 4 +_bar: + .space 4 + +#--- test.s +.ifdef PTR64 +.macro ptr val + .quad \val +.endm +.endif + +.ifdef PTR32 +.macro ptr val + .int \val +.endm +.endif + +.data +ptr _foo +ptr _bar +ptr _foo +ptr _bar +ptr _foo+1 +.zero 0x1000 +ptr _bar +ptr _foo + +.globl _main +.text +_main: diff --git a/wild/tests/lld-macho/bp-section-orderer-stress.s b/wild/tests/lld-macho/bp-section-orderer-stress.s new file mode 100644 index 000000000..0bfd99eb3 --- /dev/null +++ b/wild/tests/lld-macho/bp-section-orderer-stress.s @@ -0,0 +1,108 @@ +# REQUIRES: aarch64 + +# Generate a large test case and check that the output is deterministic. + +# RUN: %python %s %t.s %t.proftext + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t.s -o %t.o +# RUN: llvm-profdata merge %t.proftext -o %t.profdata + +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main --icf=all -o - %t.o --irpgo-profile-sort=%t.profdata --compression-sort-startup-functions --compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order1.txt +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main --icf=all -o - %t.o --irpgo-profile-sort=%t.profdata --compression-sort-startup-functions --compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order2.txt +# RUN: diff %t.order1.txt %t.order2.txt + +# RUN: %lld -arch arm64 -lSystem -e _main --icf=all -o - %t.o --irpgo-profile=%t.profdata --bp-startup-sort=function --bp-compression-sort-startup-functions --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order1.txt +# RUN: %lld -arch arm64 -lSystem -e _main --icf=all -o - %t.o --irpgo-profile=%t.profdata --bp-startup-sort=function --bp-compression-sort-startup-functions --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - > %t.order2.txt +# RUN: diff %t.order1.txt %t.order2.txt +import random +import sys + +assembly_filepath = sys.argv[1] +proftext_filepath = sys.argv[2] + +random.seed(1234) +num_functions = 1000 +num_data = 100 +num_traces = 10 + +function_names = [f"f{n}" for n in range(num_functions)] +data_names = [f"d{n}" for n in range(num_data)] +profiled_functions = function_names[: int(num_functions / 2)] + +function_contents = [ + f""" +{name}: + add w0, w0, #{i % 4096} + add w1, w1, #{i % 10} + add w2, w0, #{i % 20} + adrp x3, {name}@PAGE + ret +""" + for i, name in enumerate(function_names) +] + +data_contents = [ + f""" +{name}: + .ascii "s{i % 2}-{i % 3}-{i % 5}" + .xword {name} +""" + for i, name in enumerate(data_names) +] + +trace_contents = [ + f""" +# Weight +1 +{", ".join(random.sample(profiled_functions, len(profiled_functions)))} +""" + for i in range(num_traces) +] + +profile_contents = [ + f""" +{name} +# Func Hash: +{i} +# Num Counters: +1 +# Counter Values: +1 +""" + for i, name in enumerate(profiled_functions) +] + +with open(assembly_filepath, "w") as f: + f.write( + f""" +.text +.globl _main + +_main: + ret + +{"".join(function_contents)} + +.data +{"".join(data_contents)} + +.subsections_via_symbols +""" + ) + +with open(proftext_filepath, "w") as f: + f.write( + f""" +:ir +:temporal_prof_traces + +# Num Traces +{num_traces} +# Trace Stream Size: +{num_traces} + +{"".join(trace_contents)} + +{"".join(profile_contents)} +""" + ) diff --git a/wild/tests/lld-macho/bp-section-orderer.s b/wild/tests/lld-macho/bp-section-orderer.s new file mode 100644 index 000000000..d7de90d6c --- /dev/null +++ b/wild/tests/lld-macho/bp-section-orderer.s @@ -0,0 +1,186 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o +# RUN: llvm-profdata merge %t/a.proftext -o %t/a.profdata + +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --irpgo-profile-sort=%t/a.profdata --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=STARTUP +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --irpgo-profile-sort=%t/a.profdata --verbose-bp-section-orderer --icf=all --compression-sort=none 2>&1 | FileCheck %s --check-prefix=STARTUP-ICF + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --irpgo-profile %t/a.profdata --bp-startup-sort=function --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=STARTUP +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --irpgo-profile=%t/a.profdata --bp-startup-sort=function --verbose-bp-section-orderer --icf=all --bp-compression-sort=none 2>&1 | FileCheck %s --check-prefix=STARTUP-ICF +# STARTUP: Ordered 5 sections ([[#]] bytes) using balanced partitioning +# STARTUP-ICF: Ordered 4 sections ([[#]] bytes) using balanced partitioning + +# Check that orderfiles take precedence over BP +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main -o - %t/a.o -order_file %t/a.orderfile --irpgo-profile-sort=%t/a.profdata | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE +# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/a.o -order_file %t/a.orderfile --compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE + +# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/a.o -order_file %t/a.orderfile --irpgo-profile=%t/a.profdata --bp-startup-sort=function | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE +# RUN: %lld -arch arm64 -lSystem -e _main -o - %t/a.o -order_file %t/a.orderfile --bp-compression-sort=both | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=ORDERFILE + +# Functions +# ORDERFILE: A +# ORDERFILE: F +# ORDERFILE: E +# ORDERFILE: D +# ORDERFILE-DAG: _main +# ORDERFILE-DAG: _B +# ORDERFILE-DAG: l_C +# ORDERFILE-DAG: merged1.Tgm +# ORDERFILE-DAG: merged2.Tgm + +# Data +# ORDERFILE: s3 +# ORDERFILE: r3 +# ORDERFILE: r2 +# ORDERFILE-DAG: s1 +# ORDERFILE-DAG: s2 +# ORDERFILE-DAG: r1 +# ORDERFILE-DAG: r4 + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --compression-sort=function 2>&1 | FileCheck %s --check-prefix=COMPRESSION-FUNC +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --compression-sort=data 2>&1 | FileCheck %s --check-prefix=COMPRESSION-DATA +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --compression-sort=both 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --compression-sort=both --irpgo-profile-sort=%t/a.profdata 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --bp-compression-sort=function 2>&1 | FileCheck %s --check-prefix=COMPRESSION-FUNC +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --bp-compression-sort=function --icf=all 2>&1 | FileCheck %s --check-prefix=COMPRESSION-ICF-FUNC +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --bp-compression-sort=data 2>&1 | FileCheck %s --check-prefix=COMPRESSION-DATA +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --bp-compression-sort=both 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o --verbose-bp-section-orderer --bp-compression-sort=both --irpgo-profile=%t/a.profdata --bp-startup-sort=function 2>&1 | FileCheck %s --check-prefix=COMPRESSION-BOTH + +# COMPRESSION-FUNC: Ordered 9 sections ([[#]] bytes) using balanced partitioning +# COMPRESSION-ICF-FUNC: Ordered 7 sections ([[#]] bytes) using balanced partitioning +# COMPRESSION-DATA: Ordered 7 sections ([[#]] bytes) using balanced partitioning +# COMPRESSION-BOTH: Ordered 16 sections ([[#]] bytes) using balanced partitioning + +#--- a.s +.text +.globl _main, A, _B, l_C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + +_main: + ret +A: + ret +_B: + add w0, w0, #1 + bl A + ret +l_C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222: + add w0, w0, #2 + bl A + ret +D: + add w0, w0, #2 + bl _B + ret +E: + add w0, w0, #2 + bl l_C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + ret +F: + add w0, w0, #3 + bl l_C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + ret +merged1.Tgm: + add w0, w0, #101 + ret +merged2.Tgm: + add w0, w0, #101 + ret + +.data +s1: + .ascii "hello world" +s2: + .ascii "i am a string" +s3: + .ascii "this is s3" +r1: + .quad s1 +r2: + .quad r1 +r3: + .quad r2 +r4: + .quad s2 + +# cstrings are ignored by runBalancedPartitioning() +.cstring +cstr: + .asciz "this is cstr" + +.bss +bss0: + .zero 10 + +.subsections_via_symbols + +#--- a.proftext +:ir +:temporal_prof_traces +# Num Traces +1 +# Trace Stream Size: +1 +# Weight +1 +A, B, C.__uniq.555555555555555555555555555555555555555.llvm.6666666666666666666, merged1, merged2 + +A +# Func Hash: +1111 +# Num Counters: +1 +# Counter Values: +1 + +B +# Func Hash: +2222 +# Num Counters: +1 +# Counter Values: +1 + +C.__uniq.555555555555555555555555555555555555555.llvm.6666666666666666666 +# Func Hash: +3333 +# Num Counters: +1 +# Counter Values: +1 + +D +# Func Hash: +4444 +# Num Counters: +1 +# Counter Values: +1 + +merged1 +# Func Hash: +5555 +# Num Counters: +1 +# Counter Values: +1 + +merged2 +# Func Hash: +6666 +# Num Counters: +1 +# Counter Values: +1 + +#--- a.orderfile +A +F +E +D +s3 +r3 +r2 diff --git a/wild/tests/lld-macho/bss.s b/wild/tests/lld-macho/bss.s new file mode 100644 index 000000000..d773e6762 --- /dev/null +++ b/wild/tests/lld-macho/bss.s @@ -0,0 +1,125 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: %lld -o %t %t.o +# RUN: llvm-readobj --section-headers --macho-segment %t | FileCheck %s + +## Check that __bss takes up zero file size, is at file offset zero, and appears +## at the end of its segment. Also check that __tbss is placed immediately +## before it. +## Zerofill sections in other segments (i.e. not __DATA) should also be placed +## at the end. + +# CHECK: Index: 1 +# CHECK-NEXT: Name: __data +# CHECK-NEXT: Segment: __DATA +# CHECK-NEXT: Address: +# CHECK-NEXT: Size: 0x8 +# CHECK-NEXT: Offset: 4096 +# CHECK-NEXT: Alignment: 0 +# CHECK-NEXT: RelocationOffset: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: Type: Regular (0x0) +# CHECK-NEXT: Attributes [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Reserved1: 0x0 +# CHECK-NEXT: Reserved2: 0x0 +# CHECK-NEXT: Reserved3: 0x0 + +# CHECK: Index: 2 +# CHECK-NEXT: Name: __thread_bss +# CHECK-NEXT: Segment: __DATA +# CHECK-NEXT: Address: +# CHECK-NEXT: Size: 0x4 +# CHECK-NEXT: Offset: 0 +# CHECK-NEXT: Alignment: 0 +# CHECK-NEXT: RelocationOffset: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: Type: ThreadLocalZerofill (0x12) +# CHECK-NEXT: Attributes [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Reserved1: 0x0 +# CHECK-NEXT: Reserved2: 0x0 +# CHECK-NEXT: Reserved3: 0x0 + +# CHECK: Index: 3 +# CHECK-NEXT: Name: __bss +# CHECK-NEXT: Segment: __DATA +# CHECK-NEXT: Address: +# CHECK-NEXT: Size: 0x10000 +# CHECK-NEXT: Offset: 0 +# CHECK-NEXT: Alignment: 0 +# CHECK-NEXT: RelocationOffset: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: Type: ZeroFill (0x1) +# CHECK-NEXT: Attributes [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Reserved1: 0x0 +# CHECK-NEXT: Reserved2: 0x0 +# CHECK-NEXT: Reserved3: 0x0 + +# CHECK: Index: 4 +# CHECK-NEXT: Name: foo +# CHECK-NEXT: Segment: FOO +# CHECK-NEXT: Address: +# CHECK-NEXT: Size: 0x8 +# CHECK-NEXT: Offset: 8192 +# CHECK-NEXT: Alignment: 0 +# CHECK-NEXT: RelocationOffset: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: Type: Regular (0x0) +# CHECK-NEXT: Attributes [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Reserved1: 0x0 +# CHECK-NEXT: Reserved2: 0x0 +# CHECK-NEXT: Reserved3: 0x0 + +# CHECK: Index: 5 +# CHECK-NEXT: Name: bss +# CHECK-NEXT: Segment: FOO +# CHECK-NEXT: Address: +# CHECK-NEXT: Size: 0x8 +# CHECK-NEXT: Offset: 0 +# CHECK-NEXT: Alignment: 0 +# CHECK-NEXT: RelocationOffset: 0x0 +# CHECK-NEXT: RelocationCount: 0 +# CHECK-NEXT: Type: ZeroFill (0x1) +# CHECK-NEXT: Attributes [ (0x0) +# CHECK-NEXT: ] +# CHECK-NEXT: Reserved1: 0x0 +# CHECK-NEXT: Reserved2: 0x0 +# CHECK-NEXT: Reserved3: 0x0 + +# CHECK: Name: __DATA +# CHECK-NEXT: Size: +# CHECK-NEXT: vmaddr: +# CHECK-NEXT: vmsize: 0x11000 +# CHECK-NEXT: fileoff: +# CHECK-NEXT: filesize: 4096 + +# CHECK: Name: FOO +# CHECK-NEXT: Size: +# CHECK-NEXT: vmaddr: +# CHECK-NEXT: vmsize: 0x9000 +# CHECK-NEXT: fileoff: +# CHECK-NEXT: filesize: 4096 + +.globl _main + +.text +_main: + movq $0, %rax + retq + +.bss +.zero 0x8000 + +.tbss _foo, 4 +.zero 0x8000 + +.data +.quad 0x1234 + +.zerofill FOO,bss,_zero_foo,0x8000 + +.section FOO,foo +.quad 123 diff --git a/wild/tests/lld-macho/cgdata-generate-merge.s b/wild/tests/lld-macho/cgdata-generate-merge.s new file mode 100644 index 000000000..4b6d4a5d8 --- /dev/null +++ b/wild/tests/lld-macho/cgdata-generate-merge.s @@ -0,0 +1,89 @@ +# UNSUPPORTED: system-windows +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t + +# Synthesize raw cgdata without the header (32 byte) from the indexed cgdata. +# RUN: llvm-cgdata --convert --format binary %t/raw-1.cgtext -o %t/raw-1.cgdata +# RUN: echo -n "s//" > %t/raw-1-sed.txt +# RUN: od -t x1 -j 32 -An %t/raw-1.cgdata | tr -d '\n\r\t' | sed 's/[ ][ ]*/ /g; s/^[ ]*//; s/[ ]*$//; s/[ ]/,0x/g; s/^/0x/' >> %t/raw-1-sed.txt +# RUN: echo "/g" >> %t/raw-1-sed.txt +# RUN: sed -f %t/raw-1-sed.txt %t/merge-template.s > %t/merge-1.s +# RUN: llvm-cgdata --convert --format binary %t/raw-2.cgtext -o %t/raw-2.cgdata +# RUN: echo -n "s//" > %t/raw-2-sed.txt +# RUN: od -t x1 -j 32 -An %t/raw-2.cgdata | tr -d '\n\r\t' | sed 's/[ ][ ]*/ /g; s/^[ ]*//; s/[ ]*$//; s/[ ]/,0x/g; s/^/0x/' >> %t/raw-2-sed.txt +# RUN: echo "/g" >> %t/raw-2-sed.txt +# RUN: sed -f %t/raw-2-sed.txt %t/merge-template.s > %t/merge-2.s + +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/merge-1.s -o %t/merge-1.o +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/merge-2.s -o %t/merge-2.o +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/main.s -o %t/main.o + +# This checks if the codegen data from the linker is identical to the merged codegen data +# from each object file, which is obtained using the llvm-cgdata tool. +# RUN: %no-arg-lld -dylib -arch arm64 -platform_version ios 14.0 15.0 -o %t/out \ +# RUN: %t/merge-1.o %t/merge-2.o %t/main.o --codegen-data-generate-path=%t/out-cgdata +# RUN: llvm-cgdata --merge %t/merge-1.o %t/merge-2.o %t/main.o -o %t/merge-cgdata +# RUN: diff %t/out-cgdata %t/merge-cgdata + +# Merge order doesn't matter in the yaml format. `main.o` is dropped due to missing __llvm_merge. +# RUN: llvm-cgdata --merge %t/merge-2.o %t/merge-1.o -o %t/merge-cgdata-shuffle +# RUN: llvm-cgdata --convert %t/out-cgdata -o %t/out-cgdata.yaml +# RUN: llvm-cgdata --convert %t/merge-cgdata-shuffle -o %t/merge-cgdata-shuffle.yaml +# RUN: diff %t/out-cgdata.yaml %t/merge-cgdata-shuffle.yaml + +# We can also generate the merged codegen data from the executable that is not dead-stripped. +# RUN: llvm-objdump -h %t/out| FileCheck %s +# CHECK: __llvm_merge +# RUN: llvm-cgdata --merge %t/out -o %t/merge-cgdata-exe +# RUN: diff %t/merge-cgdata-exe %t/merge-cgdata + +# Dead-strip will remove __llvm_merge sections from the final executable. +# But the codeden data is still correctly produced from the linker. +# RUN: %no-arg-lld -dylib -arch arm64 -platform_version ios 14.0 15.0 -o %t/out-strip \ +# RUN: %t/merge-1.o %t/merge-2.o %t/main.o -dead_strip --codegen-data-generate-path=%t/out-cgdata-strip +# RUN: llvm-cgdata --merge %t/merge-1.o %t/merge-2.o %t/main.o -o %t/merge-cgdata-strip +# RUN: diff %t/out-cgdata-strip %t/merge-cgdata-strip +# RUN: diff %t/out-cgdata-strip %t/merge-cgdata + +# Ensure no __llvm_merge section remains in the executable. +# RUN: llvm-objdump -h %t/out-strip | FileCheck %s --check-prefix=STRIP +# STRIP-NOT: __llvm_merge + +#--- raw-1.cgtext +:stable_function_map +--- +- Hash: 123 + FunctionName: f1 + ModuleName: 'foo.bc' + InstCount: 7 + IndexOperandHashes: + - InstIndex: 3 + OpndIndex: 0 + OpndHash: 456 +... + +#--- raw-2.cgtext +:stable_function_map +--- +- Hash: 123 + FunctionName: f2 + ModuleName: 'goo.bc' + InstCount: 7 + IndexOperandHashes: + - InstIndex: 3 + OpndIndex: 0 + OpndHash: 789 +... + +#--- merge-template.s +.section __DATA,__llvm_merge +_data: +.byte + +#--- main.s +.globl _main + +.text +_main: + ret diff --git a/wild/tests/lld-macho/cgdata-generate.s b/wild/tests/lld-macho/cgdata-generate.s new file mode 100644 index 000000000..63efc81cd --- /dev/null +++ b/wild/tests/lld-macho/cgdata-generate.s @@ -0,0 +1,93 @@ +# UNSUPPORTED: system-windows +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t + +# Synthesize raw cgdata without the header (32 byte) from the indexed cgdata. +# RUN: llvm-cgdata --convert --format binary %t/raw-1.cgtext -o %t/raw-1.cgdata +# RUN: echo -n "s//" > %t/raw-1-sed.txt +# RUN: od -t x1 -j 32 -An %t/raw-1.cgdata | tr -d '\n\r\t' | sed 's/[ ][ ]*/ /g; s/^[ ]*//; s/[ ]*$//; s/[ ]/,0x/g; s/^/0x/' >> %t/raw-1-sed.txt +# RUN: echo "/g" >> %t/raw-1-sed.txt +# RUN: sed -f %t/raw-1-sed.txt %t/merge-template.s > %t/merge-1.s +# RUN: llvm-cgdata --convert --format binary %t/raw-2.cgtext -o %t/raw-2.cgdata +# RUN: echo -n "s//" > %t/raw-2-sed.txt +# RUN: od -t x1 -j 32 -An %t/raw-2.cgdata | tr -d '\n\r\t' | sed 's/[ ][ ]*/ /g; s/^[ ]*//; s/[ ]*$//; s/[ ]/,0x/g; s/^/0x/' >> %t/raw-2-sed.txt +# RUN: echo "/g" >> %t/raw-2-sed.txt +# RUN: sed -f %t/raw-2-sed.txt %t/merge-template.s > %t/merge-2.s + +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/merge-1.s -o %t/merge-1.o +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/merge-2.s -o %t/merge-2.o +# RUN: llvm-mc -filetype obj -triple arm64-apple-darwin %t/main.s -o %t/main.o + +# This checks if the codegen data from the linker is identical to the merged codegen data +# from each object file, which is obtained using the llvm-cgdata tool. +# RUN: %no-arg-lld -dylib -arch arm64 -platform_version ios 14.0 15.0 -o %t/out \ +# RUN: %t/merge-1.o %t/merge-2.o %t/main.o --codegen-data-generate-path=%t/out-cgdata +# RUN: llvm-cgdata --merge %t/merge-1.o %t/merge-2.o %t/main.o -o %t/merge-cgdata +# RUN: diff %t/out-cgdata %t/merge-cgdata + +# Merge order doesn't matter. `main.o` is dropped due to missing __llvm_outline. +# RUN: llvm-cgdata --merge %t/merge-2.o %t/merge-1.o -o %t/merge-cgdata-shuffle +# RUN: diff %t/out-cgdata %t/merge-cgdata-shuffle + +# We can also generate the merged codegen data from the executable that is not dead-stripped. +# RUN: llvm-objdump -h %t/out| FileCheck %s +# CHECK: __llvm_outline +# RUN: llvm-cgdata --merge %t/out -o %t/merge-cgdata-exe +# RUN: diff %t/merge-cgdata-exe %t/merge-cgdata + +# Dead-strip will remove __llvm_outline sections from the final executable. +# But the codeden data is still correctly produced from the linker. +# RUN: %no-arg-lld -dylib -arch arm64 -platform_version ios 14.0 15.0 -o %t/out-strip \ +# RUN: %t/merge-1.o %t/merge-2.o %t/main.o -dead_strip --codegen-data-generate-path=%t/out-cgdata-strip +# RUN: llvm-cgdata --merge %t/merge-1.o %t/merge-2.o %t/main.o -o %t/merge-cgdata-strip +# RUN: diff %t/out-cgdata-strip %t/merge-cgdata-strip +# RUN: diff %t/out-cgdata-strip %t/merge-cgdata + +# Ensure no __llvm_outline section remains in the executable. +# RUN: llvm-objdump -h %t/out-strip | FileCheck %s --check-prefix=STRIP +# STRIP-NOT: __llvm_outline + +#--- raw-1.cgtext +:outlined_hash_tree +0: + Hash: 0x0 + Terminals: 0 + SuccessorIds: [ 1 ] +1: + Hash: 0x1 + Terminals: 0 + SuccessorIds: [ 2 ] +2: + Hash: 0x2 + Terminals: 4 + SuccessorIds: [ ] +... + +#--- raw-2.cgtext +:outlined_hash_tree +0: + Hash: 0x0 + Terminals: 0 + SuccessorIds: [ 1 ] +1: + Hash: 0x1 + Terminals: 0 + SuccessorIds: [ 2 ] +2: + Hash: 0x3 + Terminals: 5 + SuccessorIds: [ ] +... + +#--- merge-template.s +.section __DATA,__llvm_outline +_data: +.byte + +#--- main.s +.globl _main + +.text +_main: + ret diff --git a/wild/tests/lld-macho/compact-unwind.s b/wild/tests/lld-macho/compact-unwind.s new file mode 100644 index 000000000..6516f7162 --- /dev/null +++ b/wild/tests/lld-macho/compact-unwind.s @@ -0,0 +1,184 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 -emit-compact-unwind-non-canonical=true %t/my-personality.s -o %t/x86_64-my-personality.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 -emit-compact-unwind-non-canonical=true %t/main.s -o %t/x86_64-main.o +# RUN: %lld -arch x86_64 -lSystem -lc++ %t/x86_64-my-personality.o %t/x86_64-main.o -o %t/x86_64-personality-first +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x100000000 -DSEG=__TEXT +# RUN: %lld -dead_strip -arch x86_64 -lSystem -lc++ %t/x86_64-main.o %t/x86_64-my-personality.o -o %t/x86_64-personality-second +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x100000000 -DSEG=__TEXT + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 -emit-compact-unwind-non-canonical=true %t/my-personality.s -o %t/arm64-my-personality.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 -emit-compact-unwind-non-canonical=true %t/main.s -o %t/arm64-main.o +# RUN: %lld -arch arm64 -lSystem -lc++ %t/arm64-my-personality.o %t/arm64-main.o -o %t/arm64-personality-first +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x100000000 -DSEG=__TEXT +# RUN: %lld -dead_strip -arch arm64 -lSystem -lc++ %t/arm64-main.o %t/arm64-my-personality.o -o %t/arm64-personality-second +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x100000000 -DSEG=__TEXT + +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos -emit-compact-unwind-non-canonical=true %t/my-personality.s -o %t/arm64-32-my-personality.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos -emit-compact-unwind-non-canonical=true %t/main.s -o %t/arm64-32-main.o +# RUN: %lld-watchos -lSystem -lc++ %t/arm64-32-my-personality.o %t/arm64-32-main.o -o %t/arm64-32-personality-first +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-32-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x4000 -DSEG=__TEXT +# RUN: %lld-watchos -dead_strip -lSystem -lc++ %t/arm64-32-main.o %t/arm64-32-my-personality.o -o %t/arm64-32-personality-second +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/arm64-32-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x4000 -DSEG=__TEXT + +# RUN: %lld -arch x86_64 -rename_section __TEXT __gcc_except_tab __RODATA __gcc_except_tab -lSystem -lc++ %t/x86_64-my-personality.o %t/x86_64-main.o -o %t/x86_64-personality-first +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-first | FileCheck %s --check-prefixes=FIRST,CHECK -D#%x,BASE=0x100000000 -DSEG=__RODATA +# RUN: %lld -dead_strip -arch x86_64 -rename_section __TEXT __gcc_except_tab __RODATA __gcc_except_tab -lSystem -lc++ %t/x86_64-main.o %t/x86_64-my-personality.o -o %t/x86_64-personality-second +# RUN: llvm-objdump --macho --unwind-info --syms --indirect-symbols --rebase %t/x86_64-personality-second | FileCheck %s --check-prefixes=SECOND,CHECK -D#%x,BASE=0x100000000 -DSEG=__RODATA + +# FIRST: Indirect symbols for (__DATA_CONST,__got) +# FIRST-NEXT: address index name +# FIRST-DAG: 0x[[#%x,GXX_PERSONALITY:]] [[#]] ___gxx_personality_v0 +# FIRST-DAG: 0x[[#%x,MY_PERSONALITY:]] LOCAL + +# SECOND: Indirect symbols for (__DATA_CONST,__got) +# SECOND-NEXT: address index name +# SECOND-DAG: 0x[[#%x,GXX_PERSONALITY:]] [[#]] ___gxx_personality_v0 +# SECOND-DAG: 0x[[#%x,MY_PERSONALITY:]] LOCAL + +# CHECK: SYMBOL TABLE: +# CHECK-DAG: [[#%x,MAIN:]] g F __TEXT,__text _main +# CHECK-DAG: [[#%x,QUUX:]] g F __TEXT,__text _quux +# CHECK-DAG: [[#%x,FOO:]] l F __TEXT,__text _foo +# CHECK-DAG: [[#%x,BAZ:]] l F __TEXT,__text _baz +# CHECK-DAG: [[#%x,EXCEPTION0:]] g O [[SEG]],__gcc_except_tab _exception0 +# CHECK-DAG: [[#%x,EXCEPTION1:]] g O [[SEG]],__gcc_except_tab _exception1 + +# CHECK: Contents of __unwind_info section: +# CHECK: Personality functions: (count = 2) +# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#MY_PERSONALITY-BASE]] +# CHECK-DAG: personality[{{[0-9]+}}]: 0x{{0*}}[[#GXX_PERSONALITY-BASE]] +# CHECK: Top level indices: (count = 2) +# CHECK-DAG: [0]: function offset={{.*}}, 2nd level page offset=0x[[#%x,PAGEOFF:]], +# CHECK-DAG: [1]: function offset={{.*}}, 2nd level page offset=0x00000000, +# CHECK: LSDA descriptors: +# CHECK-DAG: function offset=0x[[#%.8x,FOO-BASE]], LSDA offset=0x[[#%.8x,EXCEPTION0-BASE]] +# CHECK-DAG: function offset=0x[[#%.8x,MAIN-BASE]], LSDA offset=0x[[#%.8x,EXCEPTION1-BASE]] +# CHECK: Second level indices: +# CHECK-NEXT: Second level index[0]: offset in section=0x[[#%.8x,PAGEOFF]] +# CHECK-DAG: function offset=0x[[#%.8x,MAIN-BASE]], encoding +# CHECK-DAG: function offset=0x[[#%.8x,FOO-BASE]], encoding +# CHECK-DAG: function offset=0x[[#%.8x,BAZ-BASE]], encoding +# CHECK-DAG: function offset=0x[[#%.8x,QUUX-BASE]], encoding{{.*}}=0x00000000 + +## Check that we do not add rebase opcodes to the compact unwind section. +# CHECK: Rebase table: +# CHECK-NEXT: segment section address type +# CHECK-NEXT: __DATA_CONST __got 0x{{[0-9A-F]*}} pointer +# CHECK-NOT: __TEXT + +## Check that we don't create an __unwind_info section if no unwind info +## remains after dead-stripping. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 \ +# RUN: %t/empty-after-dead-strip.s -o %t/x86_64-empty-after-dead-strip.o +# RUN: %lld -dylib -dead_strip -arch x86_64 -lSystem \ +# RUN: %t/x86_64-empty-after-dead-strip.o -o %t/x86_64-empty-after-strip.dylib +# RUN: llvm-objdump --macho --unwind-info %t/x86_64-empty-after-strip.dylib | \ +# RUN: FileCheck %s --check-prefixes=NOUNWIND --allow-empty +# NOUNWIND-NOT: Contents of __unwind_info section: + +#--- my-personality.s +.globl _my_personality, _exception0 +.text +.p2align 2 +.no_dead_strip _foo +_foo: + .cfi_startproc +## This will generate a section relocation. + .cfi_personality 155, _my_personality + .cfi_lsda 16, _exception0 + .cfi_def_cfa_offset 16 + ret + .cfi_endproc + +.p2align 2 +.no_dead_strip _bar +_bar: + .cfi_startproc +## Check that we dedup references to the same statically-linked personality. + .cfi_personality 155, _my_personality + .cfi_lsda 16, _exception0 + .cfi_def_cfa_offset 16 + ret + .cfi_endproc + +.data +.p2align 2 +## We put this personality in `__data` to test if we correctly handle +## personality symbols whose output addresses occur after that of the +## `__unwind_info` section. +_my_personality: + ret + +.section __TEXT,__gcc_except_tab +_exception0: + .space 1 + +.subsections_via_symbols + +#--- main.s +.globl _main, _quux, _my_personality, _exception1 + +.text +.p2align 2 +_main: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, _exception1 + .cfi_def_cfa_offset 16 + ret + .cfi_endproc + +## _quux has no unwind information. +## (In real life, it'd be part of a separate TU that was built with +## -fno-exceptions, while the previous and next TU might be Objective-C++ +## which has unwind info for Objective-C). +.p2align 2 +.no_dead_strip _quux +_quux: + ret + +.globl _abs +.no_dead_strip _abs +_abs = 4 + +.p2align 2 +.no_dead_strip _baz +_baz: + .cfi_startproc +## This will generate a symbol relocation. Check that we reuse the personality +## referenced by the section relocation in my_personality.s. + .cfi_personality 155, _my_personality + .cfi_lsda 16, _exception1 + .cfi_def_cfa_offset 16 + ret + .cfi_endproc + +.globl _stripped +_stripped: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, _exception1 + .cfi_def_cfa_offset 16 + ret + .cfi_endproc + + +.section __TEXT,__gcc_except_tab +_exception1: + .space 1 + +.subsections_via_symbols + +#--- empty-after-dead-strip.s +.text + +## Local symbol with unwind info. +## The symbol is removed by -dead_strip. +_foo : + .cfi_startproc + .cfi_def_cfa_offset 16 + retq + .cfi_endproc + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/compression-order-sections.s b/wild/tests/lld-macho/compression-order-sections.s new file mode 100644 index 000000000..40ceaf7cc --- /dev/null +++ b/wild/tests/lld-macho/compression-order-sections.s @@ -0,0 +1,112 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/b.s -o %t/b.o + +## Wildcard glob: all sections go to a single group +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=WILDCARD + +# WILDCARD: Sections for compression: 7 +# WILDCARD: Compression groups: 1 +# WILDCARD: *: 7 sections + +## Two globs: sections are grouped by the winning glob +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA*" \ +# RUN: --bp-compression-sort-section="__TEXT*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=TWO-GLOBS + +## Deprecated --bp-compression-sort=both still works +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=both \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-BOTH + +## Deprecated function/data modes still use the legacy buckets. +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=function \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-FUNCTION +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort=data \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=LEGACY-DATA + +# TWO-GLOBS: Sections for compression: 7 +# TWO-GLOBS: Compression groups: 2 +# TWO-GLOBS: __DATA*: 6 sections +# TWO-GLOBS: __TEXT*: 1 sections + +# LEGACY-BOTH: Sections for compression: 7 +# LEGACY-BOTH: Compression groups: 2 +# LEGACY-BOTH: legacy:function: 1 sections +# LEGACY-BOTH: legacy:data: 6 sections + +# LEGACY-FUNCTION: Sections for compression: 1 +# LEGACY-FUNCTION: Compression groups: 1 +# LEGACY-FUNCTION: legacy:function: 1 sections + +# LEGACY-DATA: Sections for compression: 6 +# LEGACY-DATA: Compression groups: 1 +# LEGACY-DATA: legacy:data: 6 sections + +## Single glob matching only TEXT +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__TEXT*" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=TEXT + +# TEXT: Sections for compression: 1 +# TEXT: Compression groups: 1 +# TEXT: __TEXT*: 1 sections + +## Exact section name glob +# RUN: %lld -arch arm64 -e _main -o %t/a.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA__custom" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=DATA + +# DATA: Sections for compression: 2 +# DATA: Compression groups: 1 +# DATA: __DATA__custom: 2 sections + +## Match priority: explicit match_priority wins +# RUN: %lld -arch arm64 -e _main -o %t/match.out %t/a.o %t/b.o \ +# RUN: --bp-compression-sort-section="__DATA*" \ +# RUN: --bp-compression-sort-section="__DATA__custom=0=1" \ +# RUN: --verbose-bp-section-orderer 2>&1 | FileCheck %s --check-prefix=MATCH + +# MATCH: Compression groups: 2 +# MATCH: __DATA*: 4 sections +# MATCH: __DATA__custom: 2 sections + +#--- a.s + .text + .globl _main +_main: + ret + + .data +data_01: + .ascii "data_01" +data_02: + .ascii "data_02" +data_03: + .ascii "data_03" + + .section __DATA,__custom +custom_06: + .ascii "custom_06" +custom_07: + .ascii "custom_07" + + .bss +bss0: + .zero 10 + +.subsections_via_symbols + +#--- b.s + .data +data_11: + .ascii "data_11" + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/cstring-tailmerge-objc.s b/wild/tests/lld-macho/cstring-tailmerge-objc.s new file mode 100644 index 000000000..46b2bbf9d --- /dev/null +++ b/wild/tests/lld-macho/cstring-tailmerge-objc.s @@ -0,0 +1,144 @@ +; REQUIRES: aarch64 +; RUN: rm -rf %t && split-file %s %t + +; Test that ObjC method names are tail merged and +; ObjCSelRefsHelper::makeSelRef() still works correctly + +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o +; RUN: %lld -dylib -arch arm64 --tail-merge-strings %t/a.o -o %t/a +; RUN: llvm-objdump --macho --section="__TEXT,__objc_methname" %t/a | FileCheck %s --implicit-check-not=error + +; RUN: %lld -dylib -arch arm64 --no-tail-merge-strings %t/a.o -o %t/nomerge +; RUN: llvm-objdump --macho --section="__TEXT,__objc_methname" %t/nomerge | FileCheck %s --check-prefixes=CHECK,NOMERGE --implicit-check-not=error + +; CHECK: withBar:error: +; NOMERGE: error: + +;--- a.mm +__attribute__((objc_root_class)) +@interface Foo +- (void)withBar:(int)bar error:(int)error; +- (void)error:(int)error; +@end + +@implementation Foo +- (void)withBar:(int)bar error:(int)error {} +- (void)error:(int)error {} +@end + +void *_objc_empty_cache; +void *_objc_empty_vtable; +;--- gen +clang -Oz -target arm64-apple-darwin a.mm -S -o - +;--- a.s + .build_version macos, 11, 0 + .section __TEXT,__text,regular,pure_instructions + .p2align 2 ; -- Begin function -[Foo withBar:error:] +"-[Foo withBar:error:]": ; @"\01-[Foo withBar:error:]" + .cfi_startproc +; %bb.0: + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[Foo error:] +"-[Foo error:]": ; @"\01-[Foo error:]" + .cfi_startproc +; %bb.0: + ret + .cfi_endproc + ; -- End function + .globl __objc_empty_vtable ; @_objc_empty_vtable +.zerofill __DATA,__common,__objc_empty_vtable,8,3 + .section __DATA,__objc_data + .globl _OBJC_CLASS_$_Foo ; @"OBJC_CLASS_$_Foo" + .p2align 3, 0x0 +_OBJC_CLASS_$_Foo: + .quad _OBJC_METACLASS_$_Foo + .quad 0 + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_CLASS_RO_$_Foo + + .globl _OBJC_METACLASS_$_Foo ; @"OBJC_METACLASS_$_Foo" + .p2align 3, 0x0 +_OBJC_METACLASS_$_Foo: + .quad _OBJC_METACLASS_$_Foo + .quad _OBJC_CLASS_$_Foo + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_METACLASS_RO_$_Foo + + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: ; @OBJC_CLASS_NAME_ + .asciz "Foo" + + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_METACLASS_RO_$_Foo" +__OBJC_METACLASS_RO_$_Foo: + .long 3 ; 0x3 + .long 40 ; 0x28 + .long 40 ; 0x28 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: ; @OBJC_METH_VAR_NAME_ + .asciz "withBar:error:" + + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: ; @OBJC_METH_VAR_TYPE_ + .asciz "v24@0:8i16i20" + + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.1: ; @OBJC_METH_VAR_NAME_.1 + .asciz "error:" + + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.2: ; @OBJC_METH_VAR_TYPE_.2 + .asciz "v20@0:8i16" + + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_INSTANCE_METHODS_Foo" +__OBJC_$_INSTANCE_METHODS_Foo: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[Foo withBar:error:]" + .quad l_OBJC_METH_VAR_NAME_.1 + .quad l_OBJC_METH_VAR_TYPE_.2 + .quad "-[Foo error:]" + + .p2align 3, 0x0 ; @"_OBJC_CLASS_RO_$_Foo" +__OBJC_CLASS_RO_$_Foo: + .long 2 ; 0x2 + .long 0 ; 0x0 + .long 0 ; 0x0 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_Foo + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .globl __objc_empty_cache ; @_objc_empty_cache +.zerofill __DATA,__common,__objc_empty_cache,8,3 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CLASS_$" +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_Foo + + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 64 + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/cstring-tailmerge.s b/wild/tests/lld-macho/cstring-tailmerge.s new file mode 100644 index 000000000..740f971eb --- /dev/null +++ b/wild/tests/lld-macho/cstring-tailmerge.s @@ -0,0 +1,85 @@ +; REQUIRES: aarch64 +; RUN: rm -rf %t && split-file %s %t + +; RUN: sed "s//0/g" %t/align.s.template > %t/align-1.s +; RUN: sed "s//1/g" %t/align.s.template > %t/align-2.s +; RUN: sed "s//2/g" %t/align.s.template > %t/align-4.s + +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/first.s -o %t/first.o +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/align-1.s -o %t/align-1.o +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/align-2.s -o %t/align-2.o +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/align-4.s -o %t/align-4.o + +; RUN: %lld -dylib -arch arm64 --tail-merge-strings %t/first.o %t/align-1.o -o %t/align-1 +; RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-1 | FileCheck %s --check-prefixes=CHECK,ALIGN1 + +; RUN: %lld -dylib -arch arm64 --tail-merge-strings %t/first.o %t/align-2.o -o %t/align-2 +; RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-2 | FileCheck %s --check-prefixes=CHECK,ALIGN2 + +; RUN: %lld -dylib -arch arm64 --tail-merge-strings %t/first.o %t/align-4.o -o %t/align-4 +; RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-4 | FileCheck %s --check-prefixes=CHECK,ALIGN4 + +; CHECK: Contents of (__TEXT,__cstring) section +; CHECK: [[#%.16x,START:]] get awkward offset{{$}} + +; ALIGN1: [[#%.16x,START+19]] myotherlongstr{{$}} +; ALIGN1: [[#%.16x,START+19+15]] otherstr{{$}} + +; ALIGN2: [[#%.16x,START+20]] myotherlongstr{{$}} +; ALIGN2: [[#%.16x,START+20+16]] longstr{{$}} +; ALIGN2: [[#%.16x,START+20+16+8]] otherstr{{$}} +; ALIGN2: [[#%.16x,START+20+16+8+10]] str{{$}} + +; ALIGN4: [[#%.16x,START+20]] myotherlongstr{{$}} +; ALIGN4: [[#%.16x,START+20+16]] otherlongstr{{$}} +; ALIGN4: [[#%.16x,START+20+16+16]] longstr{{$}} +; ALIGN4: [[#%.16x,START+20+16+16+8]] otherstr{{$}} +; ALIGN4: [[#%.16x,START+20+16+16+8+12]] str{{$}} + +; CHECK: SYMBOL TABLE: + +; ALIGN1: [[#%.16x,START+19]] l O __TEXT,__cstring _myotherlongstr +; ALIGN1: [[#%.16x,START+21]] l O __TEXT,__cstring _otherlongstr +; ALIGN1: [[#%.16x,START+26]] l O __TEXT,__cstring _longstr +; ALIGN1: [[#%.16x,START+34]] l O __TEXT,__cstring _otherstr +; ALIGN1: [[#%.16x,START+39]] l O __TEXT,__cstring _str + +; ALIGN2: [[#%.16x,START+20]] l O __TEXT,__cstring _myotherlongstr +; ALIGN2: [[#%.16x,START+20+2]] l O __TEXT,__cstring _otherlongstr +; ALIGN2: [[#%.16x,START+20+16]] l O __TEXT,__cstring _longstr +; ALIGN2: [[#%.16x,START+20+16+8]] l O __TEXT,__cstring _otherstr +; ALIGN2: [[#%.16x,START+20+16+8+10]] l O __TEXT,__cstring _str + +; ALIGN4: [[#%.16x,START+20]] l O __TEXT,__cstring _myotherlongstr +; ALIGN4: [[#%.16x,START+20+16]] l O __TEXT,__cstring _otherlongstr +; ALIGN4: [[#%.16x,START+20+16+16]] l O __TEXT,__cstring _longstr +; ALIGN4: [[#%.16x,START+20+16+16+8]] l O __TEXT,__cstring _otherstr +; ALIGN4: [[#%.16x,START+20+16+16+8+12]] l O __TEXT,__cstring _str + +;--- first.s +.cstring +.p2align 2 +.asciz "get awkward offset" ; length = 19 + +;--- align.s.template +.cstring + +.p2align + _myotherlongstr: +.asciz "myotherlongstr" ; length = 15 + +.p2align + _otherlongstr: +.asciz "otherlongstr" ; length = 13, tail offset = 2 + +.p2align + _longstr: +.asciz "longstr" ; length = 8, tail offset = 7 + +.p2align + _otherstr: +.asciz "otherstr" ; length = 9 + +.p2align + _str: +.asciz "str" ; length = 4, tail offset = 5 diff --git a/wild/tests/lld-macho/dead-strip.s b/wild/tests/lld-macho/dead-strip.s new file mode 100644 index 000000000..d107dad53 --- /dev/null +++ b/wild/tests/lld-macho/dead-strip.s @@ -0,0 +1,1014 @@ +# REQUIRES: x86, llvm-64-bits + +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/basics.s -o %t/basics.o + +## Check that .private_extern symbols are marked as local in the symbol table +## and aren't in the export trie. +# RUN: %lld -lSystem -dead_strip -map %t/map -u _ref_private_extern_u \ +# RUN: %t/basics.o -o %t/basics +# RUN: llvm-objdump --syms --section-headers %t/basics | \ +# RUN: FileCheck --check-prefix=EXEC --implicit-check-not _unref %s +# RUN: llvm-objdump --macho --section=__DATA,__ref_section \ +# RUN: --exports-trie --indirect-symbols %t/basics | \ +# RUN: FileCheck --check-prefix=EXECDATA --implicit-check-not _unref %s +# RUN: llvm-otool -l %t/basics | grep -q 'segname __PAGEZERO' +# EXEC-LABEL: Sections: +# EXEC-LABEL: Name +# EXEC-NEXT: __text +# EXEC-NEXT: __got +# EXEC-NEXT: __ref_section +# EXEC-NEXT: __common +# EXEC-LABEL: SYMBOL TABLE: +# EXEC-DAG: l {{.*}} _ref_data +# EXEC-DAG: l {{.*}} _ref_local +# EXEC-DAG: l {{.*}} _ref_from_no_dead_strip_globl +# EXEC-DAG: l {{.*}} _no_dead_strip_local +# EXEC-DAG: l {{.*}} _ref_from_no_dead_strip_local +# EXEC-DAG: l {{.*}} _ref_private_extern_u +# EXEC-DAG: l {{.*}} _main +# EXEC-DAG: l {{.*}} _ref_private_extern +# EXEC-DAG: g {{.*}} _no_dead_strip_globl +# EXEC-DAG: g {{.*}} _ref_com +# EXEC-DAG: g {{.*}} __mh_execute_header +# EXECDATA-LABEL: Indirect symbols +# EXECDATA-NEXT: name +# EXECDATA-NEXT: LOCAL +# EXECDATA-LABEL: Contents of (__DATA,__ref_section) section +# EXECDATA-NEXT: 04 00 00 00 00 00 00 00 05 00 00 00 00 00 00 00 +# EXECDATA-LABEL: Exports trie: +# EXECDATA-DAG: _ref_com +# EXECDATA-DAG: _no_dead_strip_globl +# EXECDATA-DAG: __mh_execute_header + +## Check that dead stripped symbols get listed properly. +# RUN: FileCheck --check-prefix=MAP %s < %t/map + +# MAP: _main +# MAP-LABEL: Dead Stripped Symbols +# MAP-DAG: <> 0x00000001 [ 2] _unref_com +# MAP-DAG: <> 0x00000008 [ 2] _unref_data +# MAP-DAG: <> 0x00000006 [ 2] _unref_extern +# MAP-DAG: <> 0x00000001 [ 2] _unref_local +# MAP-DAG: <> 0x00000007 [ 2] _unref_private_extern +# MAP-DAG: <> 0x00000008 [ 2] l_unref_data + +## Run dead stripping on code without any dead symbols. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/no-dead-symbols.s -o %t/no-dead-symbols.o +# RUN: %lld -lSystem -dead_strip -map %t/no-dead-symbols-map \ +# RUN: %t/no-dead-symbols.o -o %t/no-dead-symbols +## Mark the end of the file with a string. +# RUN: FileCheck --check-prefix=NODEADSYMBOLS %s < %t/no-dead-symbols-map + +# NODEADSYMBOLS-LABEL: # Symbols: +# NODEADSYMBOLS-NEXT: # Address Size File Name +# NODEADSYMBOLS-NEXT: _main +# NODEADSYMBOLS-LABEL: # Dead Stripped Symbols: +# NODEADSYMBOLS-NEXT: # Size File Name +# NODEADSYMBOLS-EMPTY: + +# RUN: %lld -dylib -dead_strip -u _ref_private_extern_u %t/basics.o -o %t/basics.dylib +# RUN: llvm-objdump --syms %t/basics.dylib | \ +# RUN: FileCheck --check-prefix=DYLIB --implicit-check-not _unref %s +# RUN: %lld -bundle -dead_strip -u _ref_private_extern_u %t/basics.o -o %t/basics.dylib +# RUN: llvm-objdump --syms %t/basics.dylib | \ +# RUN: FileCheck --check-prefix=DYLIB --implicit-check-not _unref %s +# DYLIB-LABEL: SYMBOL TABLE: +# DYLIB-DAG: l {{.*}} _ref_data +# DYLIB-DAG: l {{.*}} _ref_local +# DYLIB-DAG: l {{.*}} _ref_from_no_dead_strip_globl +# DYLIB-DAG: l {{.*}} _no_dead_strip_local +# DYLIB-DAG: l {{.*}} _ref_from_no_dead_strip_local +# DYLIB-DAG: l {{.*}} _ref_private_extern_u +# DYLIB-DAG: l {{.*}} _ref_private_extern +# DYLIB-DAG: g {{.*}} _ref_com +# DYLIB-DAG: g {{.*}} _unref_com +# DYLIB-DAG: g {{.*}} _unref_extern +# DYLIB-DAG: g {{.*}} _no_dead_strip_globl + +## Extern symbols aren't stripped from executables with -export_dynamic +# RUN: %lld -lSystem -dead_strip -export_dynamic -u _ref_private_extern_u \ +# RUN: %t/basics.o -o %t/basics-export-dyn +# RUN: llvm-objdump --syms --section-headers %t/basics-export-dyn | \ +# RUN: FileCheck --check-prefix=EXECDYN %s +# EXECDYN-LABEL: Sections: +# EXECDYN-LABEL: Name +# EXECDYN-NEXT: __text +# EXECDYN-NEXT: __got +# EXECDYN-NEXT: __ref_section +# EXECDYN-NEXT: __common +# EXECDYN-LABEL: SYMBOL TABLE: +# EXECDYN-DAG: l {{.*}} _ref_data +# EXECDYN-DAG: l {{.*}} _ref_local +# EXECDYN-DAG: l {{.*}} _ref_from_no_dead_strip_globl +# EXECDYN-DAG: l {{.*}} _no_dead_strip_local +# EXECDYN-DAG: l {{.*}} _ref_from_no_dead_strip_local +# EXECDYN-DAG: l {{.*}} _ref_private_extern_u +# EXECDYN-DAG: l {{.*}} _main +# EXECDYN-DAG: l {{.*}} _ref_private_extern +# EXECDYN-DAG: g {{.*}} _ref_com +# EXECDYN-DAG: g {{.*}} _unref_com +# EXECDYN-DAG: g {{.*}} _unref_extern +# EXECDYN-DAG: g {{.*}} _no_dead_strip_globl +# EXECDYN-DAG: g {{.*}} __mh_execute_header + +## Absolute symbol handling. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/abs.s -o %t/abs.o +# RUN: %lld -lSystem -dead_strip %t/abs.o -o %t/abs +# RUN: llvm-objdump --macho --syms --exports-trie %t/abs | \ +# RUN: FileCheck --check-prefix=ABS %s +#ABS-LABEL: SYMBOL TABLE: +#ABS-NEXT: g {{.*}} _main +#ABS-NEXT: g *ABS* _abs1 +#ABS-NEXT: g {{.*}} __mh_execute_header +#ABS-LABEL: Exports trie: +#ABS-NEXT: __mh_execute_header +#ABS-NEXT: _main +#ABS-NEXT: _abs1 [absolute] + +## Check that symbols from -exported_symbol(s_list) are preserved. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/exported-symbol.s -o %t/exported-symbol.o +# RUN: %lld -lSystem -dead_strip -exported_symbol _my_exported_symbol \ +# RUN: %t/exported-symbol.o -o %t/exported-symbol +# RUN: llvm-objdump --syms %t/exported-symbol | \ +# RUN: FileCheck --check-prefix=EXPORTEDSYMBOL --implicit-check-not _unref %s +# EXPORTEDSYMBOL-LABEL: SYMBOL TABLE: +# EXPORTEDSYMBOL-NEXT: l {{.*}} _main +# EXPORTEDSYMBOL-NEXT: l {{.*}} __mh_execute_header +# EXPORTEDSYMBOL-NEXT: g {{.*}} _my_exported_symbol + +## Check that mod_init_funcs and mod_term_funcs are not stripped. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/mod-funcs.s -o %t/mod-funcs.o +# RUN: %lld -lSystem -dead_strip %t/mod-funcs.o -o %t/mod-funcs +# RUN: llvm-objdump --syms %t/mod-funcs | \ +# RUN: FileCheck --check-prefix=MODFUNCS --implicit-check-not _unref %s +# MODFUNCS-LABEL: SYMBOL TABLE: +# MODFUNCS-NEXT: l {{.*}} _ref_from_init +# MODFUNCS-NEXT: l {{.*}} _ref_init +# MODFUNCS-NEXT: l {{.*}} _ref_from_term +# MODFUNCS-NEXT: l {{.*}} _ref_term +# MODFUNCS-NEXT: g {{.*}} _main +# MODFUNCS-NEXT: g {{.*}} __mh_execute_header + +## Check that DylibSymbols in dead subsections are stripped: They should +## not be in the import table and should have no import stubs. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/dylib.s -o %t/dylib.o +# RUN: %lld -dylib -dead_strip %t/dylib.o -o %t/dylib.dylib +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/strip-dylib-ref.s -o %t/strip-dylib-ref.o +# RUN: %lld -lSystem -dead_strip %t/strip-dylib-ref.o %t/dylib.dylib \ +# RUN: -o %t/strip-dylib-ref -U _ref_undef_fun -U _unref_undef_fun +# RUN: llvm-objdump --syms --bind --lazy-bind --weak-bind %t/strip-dylib-ref | \ +# RUN: FileCheck --check-prefix=STRIPDYLIB --implicit-check-not _unref %s +# STRIPDYLIB: SYMBOL TABLE: +# STRIPDYLIB-NEXT: l {{.*}} __dyld_private +# STRIPDYLIB-NEXT: g {{.*}} _main +# STRIPDYLIB-NEXT: g {{.*}} __mh_execute_header +# STRIPDYLIB-NEXT: *UND* dyld_stub_binder +# STRIPDYLIB-NEXT: *UND* _ref_dylib_fun +# STRIPDYLIB-NEXT: *UND* _ref_undef_fun +# STRIPDYLIB: Bind table: +# STRIPDYLIB: Lazy bind table: +# STRIPDYLIB: __DATA __la_symbol_ptr {{.*}} flat-namespace _ref_undef_fun +# STRIPDYLIB: __DATA __la_symbol_ptr {{.*}} dylib _ref_dylib_fun +# STRIPDYLIB: Weak bind table: +## Stubs smoke check: There should be two stubs entries, not four, but we +## don't verify that they belong to _ref_undef_fun and _ref_dylib_fun. +# RUN: llvm-objdump -d --section=__stubs --section=__stub_helper \ +# RUN: %t/strip-dylib-ref |FileCheck --check-prefix=STUBS %s +# STUBS-LABEL: <__stubs>: +# STUBS-NEXT: jmpq +# STUBS-NEXT: jmpq +# STUBS-NOT: jmpq +# STUBS-LABEL: <__stub_helper>: +# STUBS: pushq $0 +# STUBS: jmp +# STUBS: jmp +# STUBS-NOT: jmp +## An undefined symbol referenced from a dead-stripped function shouldn't +## produce a diagnostic: +# RUN: %lld -lSystem -dead_strip %t/strip-dylib-ref.o %t/dylib.dylib \ +# RUN: -o %t/strip-dylib-ref -U _ref_undef_fun + +## Check that referenced undefs are kept with -undefined dynamic_lookup. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/ref-undef.s -o %t/ref-undef.o +# RUN: %lld -lSystem -dead_strip %t/ref-undef.o \ +# RUN: -o %t/ref-undef -undefined dynamic_lookup +# RUN: llvm-objdump --syms --lazy-bind %t/ref-undef | \ +# RUN: FileCheck --check-prefix=STRIPDYNLOOKUP %s +# STRIPDYNLOOKUP: SYMBOL TABLE: +# STRIPDYNLOOKUP: *UND* _ref_undef_fun +# STRIPDYNLOOKUP: Lazy bind table: +# STRIPDYNLOOKUP: __DATA __la_symbol_ptr {{.*}} flat-namespace _ref_undef_fun + +## S_ATTR_LIVE_SUPPORT tests. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/live-support.s -o %t/live-support.o +# RUN: %lld -lSystem -dead_strip %t/live-support.o %t/dylib.dylib \ +# RUN: -U _ref_undef_fun -U _unref_undef_fun -o %t/live-support +# RUN: llvm-objdump --syms %t/live-support | \ +# RUN: FileCheck --check-prefix=LIVESUPP --implicit-check-not _unref %s +# LIVESUPP-LABEL: SYMBOL TABLE: +# LIVESUPP-NEXT: l {{.*}} _ref_ls_fun_fw +# LIVESUPP-NEXT: l {{.*}} _ref_ls_fun_bw +# LIVESUPP-NEXT: l {{.*}} _ref_ls_dylib_fun +# LIVESUPP-NEXT: l {{.*}} _ref_ls_undef_fun +# LIVESUPP-NEXT: l {{.*}} __dyld_private +# LIVESUPP-NEXT: g {{.*}} _main +# LIVESUPP-NEXT: g {{.*}} _bar +# LIVESUPP-NEXT: g {{.*}} _foo +# LIVESUPP-NEXT: g {{.*}} __mh_execute_header +# LIVESUPP-NEXT: *UND* dyld_stub_binder +# LIVESUPP-NEXT: *UND* _ref_dylib_fun +# LIVESUPP-NEXT: *UND* _ref_undef_fun + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/live-support-iterations.s -o %t/live-support-iterations.o +# RUN: %lld -lSystem -dead_strip %t/live-support-iterations.o \ +# RUN: -o %t/live-support-iterations +# RUN: llvm-objdump --syms %t/live-support-iterations | \ +# RUN: FileCheck --check-prefix=LIVESUPP2 --implicit-check-not _unref %s +# LIVESUPP2-LABEL: SYMBOL TABLE: +# LIVESUPP2-NEXT: l {{.*}} _bar +# LIVESUPP2-NEXT: l {{.*}} _foo_refd +# LIVESUPP2-NEXT: l {{.*}} _bar_refd +# LIVESUPP2-NEXT: l {{.*}} _baz +# LIVESUPP2-NEXT: l {{.*}} _baz_refd +# LIVESUPP2-NEXT: l {{.*}} _foo +# LIVESUPP2-NEXT: g {{.*}} _main +# LIVESUPP2-NEXT: g {{.*}} __mh_execute_header + +## Dead stripping should not remove the __TEXT,__unwind_info +## and __TEXT,__gcc_except_tab functions, but it should still +## remove the unreferenced function __Z5unref. +## The reference to ___gxx_personality_v0 should also not be +## stripped. +## (Need to use darwin19.0.0 to make -mc emit __LD,__compact_unwind.) +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin19.0.0 \ +# RUN: %t/unwind.s -o %t/unwind.o +# RUN: %lld -lc++ -lSystem -dead_strip %t/unwind.o -o %t/unwind +# RUN: llvm-objdump --syms %t/unwind | \ +# RUN: FileCheck --check-prefix=UNWIND --implicit-check-not unref %s +# RUN: llvm-otool -l %t/unwind | FileCheck --check-prefix=UNWINDSECT %s +# UNWINDSECT-DAG: sectname __unwind_info +# UNWINDSECT-DAG: sectname __gcc_except_tab +# UNWIND-LABEL: SYMBOL TABLE: +# UNWIND-NEXT: l O __TEXT,__gcc_except_tab GCC_except_table1 +# UNWIND-NEXT: l O __DATA,__data __dyld_private +# UNWIND-NEXT: g F __TEXT,__text _main +# UNWIND-NEXT: g F __TEXT,__text __mh_execute_header +# UNWIND-NEXT: *UND* dyld_stub_binder +# UNWIND-NEXT: *UND* __ZTIi +# UNWIND-NEXT: *UND* ___cxa_allocate_exception +# UNWIND-NEXT: *UND* ___cxa_begin_catch +# UNWIND-NEXT: *UND* ___cxa_end_catch +# UNWIND-NEXT: *UND* ___cxa_throw +# UNWIND-NEXT: *UND* ___gxx_personality_v0 +# UNWIND-NOT: GCC_except_table0 + +## If a dead stripped function has a strong ref to a dylib symbol but +## a live function only a weak ref, the dylib is still not a WEAK_DYLIB. +## This matches ld64. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-ref.s -o %t/weak-ref.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/strong-dead-ref.s -o %t/strong-dead-ref.o +# RUN: %lld -lSystem -dead_strip %t/weak-ref.o %t/strong-dead-ref.o \ +# RUN: %t/dylib.dylib -o %t/weak-ref +# RUN: llvm-otool -l %t/weak-ref | FileCheck -DDIR=%t --check-prefix=WEAK %s +# WEAK: cmd LC_LOAD_DYLIB +# WEAK-NEXT: cmdsize +# WEAK-NEXT: name /usr/lib/libSystem.dylib +# WEAK: cmd LC_LOAD_DYLIB +# WEAK-NEXT: cmdsize +# WEAK-NEXT: name [[DIR]]/dylib.dylib + +## A strong symbol that would override a weak import does not emit the +## "this overrides a weak import" opcode if it is dead-stripped. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/weak-dylib.s -o %t/weak-dylib.o +# RUN: %lld -dylib -dead_strip %t/weak-dylib.o -o %t/weak-dylib.dylib +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/dead-weak-override.s -o %t/dead-weak-override.o +# RUN: %lld -dead_strip %t/dead-weak-override.o %t/weak-dylib.dylib \ +# RUN: -o %t/dead-weak-override +# RUN: llvm-objdump --macho --weak-bind --private-header \ +# RUN: %t/dead-weak-override | FileCheck --check-prefix=DEADWEAK %s +# DEADWEAK-NOT: WEAK_DEFINES +# DEADWEAK: Weak bind table: +# DEADWEAK: segment section address type addend symbol +# DEADWEAK-NOT: strong _weak_in_dylib + +## Stripped symbols should not be in the debug info stabs entries. +# RUN: llvm-mc -g -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/debug.s -o %t/debug.o +# RUN: %lld -lSystem -dead_strip %t/debug.o -o %t/debug +# RUN: dsymutil -s %t/debug | FileCheck --check-prefix=EXECSTABS %s +# EXECSTABS-NOT: N_FUN {{.*}} '_unref' +# EXECSTABS: N_FUN {{.*}} '_main' +# EXECSTABS-NOT: N_FUN {{.*}} '_unref' + +# RUN: llvm-mc -g -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/literals.s -o %t/literals.o +# RUN: %lld -dylib -dead_strip %t/literals.o -o %t/literals +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" --section="__DATA,str_ptrs" \ +# RUN: --section="__TEXT,__literals" %t/literals | FileCheck %s --check-prefix=LIT +# LIT: Contents of (__TEXT,__cstring) section +# LIT-NEXT: foobar +# LIT-NEXT: Contents of (__DATA,str_ptrs) section +# LIT-NEXT: __TEXT:__cstring:bar +# LIT-NEXT: __TEXT:__cstring:bar +# LIT-NEXT: Contents of (__TEXT,__literals) section +# LIT-NEXT: ef be ad de {{$}} + +## Ensure that addrsig metadata does not keep unreferenced functions alive. +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/addrsig.s -o %t/addrsig.o +# RUN: %lld -lSystem -dead_strip --icf=safe %t/addrsig.o -o %t/addrsig +# RUN: llvm-objdump --syms %t/addrsig | \ +# RUN: FileCheck --check-prefix=ADDSIG --implicit-check-not _addrsig %s +# ADDSIG-LABEL: SYMBOL TABLE: +# ADDSIG-NEXT: g F __TEXT,__text _main +# ADDSIG-NEXT: g F __TEXT,__text __mh_execute_header +# ADDSIG-NEXT: *UND* dyld_stub_binder + +## Duplicate symbols that will be dead stripped later should not fail when using +## the --dead-stripped-duplicates flag +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/duplicate1.s -o %t/duplicate1.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos \ +# RUN: %t/duplicate2.s -o %t/duplicate2.o +# RUN: %lld -lSystem -dead_strip --dead-strip-duplicates -map %t/stripped-duplicate-map \ +# RUN: %t/duplicate1.o %t/duplicate2.o -o %t/duplicate +# RUN: llvm-objdump --syms %t/duplicate | FileCheck %s --check-prefix=DUP +# DUP-LABEL: SYMBOL TABLE: +# DUP-NEXT: g F __TEXT,__text _main +# DUP-NEXT: g F __TEXT,__text __mh_execute_header +# DUP-NEXT: *UND* dyld_stub_binder + +## Check that the duplicate dead stripped symbols get listed properly. +# RUN: FileCheck --check-prefix=DUPMAP %s < %t/stripped-duplicate-map +# DUPMAP: _main +# DUPMAP-LABEL: Dead Stripped Symbols +# DUPMAP: <> 0x00000001 [ 3] _foo + +#--- duplicate1.s +.text +.globl _main, _foo +_foo: + retq + +_main: + retq + +.subsections_via_symbols + +#--- duplicate2.s +.text +.globl _foo +_foo: + retq + +.subsections_via_symbols + +#--- basics.s +.comm _ref_com, 1 +.comm _unref_com, 1 + +.section __DATA,__unref_section +_unref_data: + .quad 4 + +l_unref_data: + .quad 5 + +## Referenced by no_dead_strip == S_ATTR_NO_DEAD_STRIP +.section __DATA,__ref_section,regular,no_dead_strip + +## Referenced because in no_dead_strip section. +_ref_data: + .quad 4 + +## This is a local symbol so it's not in the symbol table, but +## it is still in the section data. +l_ref_data: + .quad 5 + +.text + +# Exported symbols should not be stripped from dylibs +# or bundles, but they should be stripped from executables. +.globl _unref_extern +_unref_extern: + callq _ref_local + retq + +# Unreferenced local symbols should be stripped. +_unref_local: + retq + +# Same for unreferenced private externs. +.globl _unref_private_extern +.private_extern _unref_private_extern +_unref_private_extern: + # This shouldn't create an indirect symbol since it's + # a reference from a dead function. + movb _unref_com@GOTPCREL(%rip), %al + retq + +# Referenced local symbols should not be stripped. +_ref_local: + callq _ref_private_extern + retq + +# Same for referenced private externs. +# This one is referenced by a relocation. +.globl _ref_private_extern +.private_extern _ref_private_extern +_ref_private_extern: + retq + +# This one is referenced by a -u flag. +.globl _ref_private_extern_u +.private_extern _ref_private_extern_u +_ref_private_extern_u: + retq + +# Entry point should not be stripped for executables, even if hidden. +# For shared libraries this is stripped since it's just a regular hidden +# symbol there. +.globl _main +.private_extern _main +_main: + movb _ref_com@GOTPCREL(%rip), %al + callq _ref_local + retq + +# Things marked no_dead_strip should not be stripped either. +# (clang emits this e.g. for `__attribute__((used))` globals.) +# Both for .globl symbols... +.globl _no_dead_strip_globl +.no_dead_strip _no_dead_strip_globl +_no_dead_strip_globl: + callq _ref_from_no_dead_strip_globl + retq +_ref_from_no_dead_strip_globl: + retq + +# ...and for locals. +.no_dead_strip _no_dead_strip_local +_no_dead_strip_local: + callq _ref_from_no_dead_strip_local + retq +_ref_from_no_dead_strip_local: + retq + +.subsections_via_symbols + +#--- exported-symbol.s +.text + +.globl _unref_symbol +_unref_symbol: + retq + +.globl _my_exported_symbol +_my_exported_symbol: + retq + +.globl _main +_main: + retq + +.subsections_via_symbols + +#--- abs.s +.globl _abs1, _abs2, _abs3 + +.no_dead_strip _abs1 +_abs1 = 1 +_abs2 = 2 +_abs3 = 3 + +.section __DATA,__foo,regular,no_dead_strip +# Absolute symbols are not in a section, so the no_dead_strip +# on the section above has no effect. +.globl _abs4 +_abs4 = 4 + +.text +.globl _main +_main: + # This is relaxed away, so there's no relocation here and + # _abs3 isn't in the exported symbol table. + mov _abs3, %rax + retq + +.subsections_via_symbols + +#--- mod-funcs.s +## Roughly based on `clang -O2 -S` output for `struct A { A(); ~A(); }; A a;` +## for mod_init_funcs. mod_term_funcs then similar to that. +.section __TEXT,__StaticInit,regular,pure_instructions + +__unref: + retq + +_ref_from_init: + retq + +_ref_init: + callq _ref_from_init + retq + +_ref_from_term: + retq + +_ref_term: + callq _ref_from_term + retq + +.globl _main +_main: + retq + +.section __DATA,__mod_init_func,mod_init_funcs +.quad _ref_init + +.section __DATA,__mod_term_func,mod_term_funcs +.quad _ref_term + +.subsections_via_symbols + +#--- dylib.s +.text + +.globl _ref_dylib_fun +_ref_dylib_fun: + retq + +.globl _unref_dylib_fun +_unref_dylib_fun: + retq + +.subsections_via_symbols + +#--- strip-dylib-ref.s +.text + +_unref: + callq _ref_dylib_fun + callq _unref_dylib_fun + callq _ref_undef_fun + callq _unref_undef_fun + retq + +.globl _main +_main: + callq _ref_dylib_fun + callq _ref_undef_fun + retq + +.subsections_via_symbols + +#--- live-support.s +## In practice, live_support is used for instruction profiling +## data and asan. (Also for __eh_frame, but that needs special handling +## in the linker anyways.) +## This test isn't based on anything happening in real code though. +.section __TEXT,__ref_ls_fw,regular,live_support +_ref_ls_fun_fw: + # This is called by _main and is kept alive by normal + # forward liveness propagation, The live_support attribute + # does nothing in this case. + retq + +.section __TEXT,__unref_ls_fw,regular,live_support +_unref_ls_fun_fw: + retq + +.section __TEXT,__ref_ls_bw,regular,live_support +_ref_ls_fun_bw: + # This _calls_ something that's alive but isn't referenced itself. This is + # kept alive only due to this being in a live_support section. + callq _foo + + # _bar on the other hand is kept alive since it's called from here. + callq _bar + retq + +## Kept alive by a live symbol form a dynamic library. +_ref_ls_dylib_fun: + callq _ref_dylib_fun + retq + +## Kept alive by a live undefined symbol. +_ref_ls_undef_fun: + callq _ref_undef_fun + retq + +## All symbols in this live_support section reference dead symbols +## and are hence dead themselves. +.section __TEXT,__unref_ls_bw,regular,live_support +_unref_ls_fun_bw: + callq _unref + retq + +_unref_ls_dylib_fun_bw: + callq _unref_dylib_fun + retq + +_unref_ls_undef_fun_bw: + callq _unref_undef_fun + retq + +.text +.globl _unref +_unref: + retq + +.globl _bar +_bar: + retq + +.globl _foo +_foo: + callq _ref_ls_fun_fw + retq + +.globl _main +_main: + callq _ref_ls_fun_fw + callq _foo + callq _ref_dylib_fun + callq _ref_undef_fun + retq + +.subsections_via_symbols + +#--- live-support-iterations.s +.section __TEXT,_ls,regular,live_support + +## This is a live_support subsection that only becomes +## live after _foo below is processed. This means the algorithm of +## 1. mark things reachable from gc roots live +## 2. go through live sections and mark the ones live pointing to +## live symbols or sections +## needs more than one iteration, since _bar won't be live when step 2 +## runs for the first time. +## (ld64 gets this wrong -- it has different output based on if _bar is +## before _foo or after it.) +_bar: + callq _foo_refd + callq _bar_refd + retq + +## Same here. This is maybe more interesting since it references a live_support +## symbol instead of a "normal" symbol. +_baz: + callq _foo_refd + callq _baz_refd + retq + +_foo: + callq _main + callq _foo_refd + retq + +## Test no_dead_strip on a symbol in a live_support section. +## ld64 ignores this, but that doesn't look intentional. So lld honors it. +.no_dead_strip +_quux: + retq + + +.text +.globl _main +_main: + movq $0, %rax + retq + +_foo_refd: + retq + +_bar_refd: + retq + +_baz_refd: + retq + +.subsections_via_symbols + +#--- unwind.s +## This is the output of `clang -O2 -S throw.cc` where throw.cc +## looks like this: +## int unref() { +## try { +## throw 0; +## } catch (int i) { +## return i + 1; +## } +## } +## int main() { +## try { +## throw 0; +## } catch (int i) { +## return i; +## } +## } +.section __TEXT,__text,regular,pure_instructions +.globl __Z5unrefv ## -- Begin function _Z5unrefv +.p2align 4, 0x90 +__Z5unrefv: ## @_Z5unrefv +Lfunc_begin0: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, Lexception0 +## %bb.0: + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + subq $16, %rsp + movl $4, %edi + callq ___cxa_allocate_exception + movl $0, (%rax) +Ltmp0: + movq __ZTIi@GOTPCREL(%rip), %rsi + movq %rax, %rdi + xorl %edx, %edx + callq ___cxa_throw +Ltmp1: +## %bb.1: + ud2 +LBB0_2: +Ltmp2: + leaq -4(%rbp), %rcx + movq %rax, %rdi + movl %edx, %esi + movq %rcx, %rdx + callq __Z5unrefv.cold.1 + movl -4(%rbp), %eax + addq $16, %rsp + popq %rbp + retq +Lfunc_end0: + .cfi_endproc + .section __TEXT,__gcc_except_tab + .p2align 2 +GCC_except_table0: +Lexception0: + .byte 255 ## @LPStart Encoding = omit + .byte 155 ## @TType Encoding = indirect pcrel sdata4 + .uleb128 Lttbase0-Lttbaseref0 +Lttbaseref0: + .byte 1 ## Call site Encoding = uleb128 + .uleb128 Lcst_end0-Lcst_begin0 +Lcst_begin0: + .uleb128 Lfunc_begin0-Lfunc_begin0 ## >> Call Site 1 << + .uleb128 Ltmp0-Lfunc_begin0 ## Call between Lfunc_begin0 and Ltmp0 + .byte 0 ## has no landing pad + .byte 0 ## On action: cleanup + .uleb128 Ltmp0-Lfunc_begin0 ## >> Call Site 2 << + .uleb128 Ltmp1-Ltmp0 ## Call between Ltmp0 and Ltmp1 + .uleb128 Ltmp2-Lfunc_begin0 ## jumps to Ltmp2 + .byte 1 ## On action: 1 + .uleb128 Ltmp1-Lfunc_begin0 ## >> Call Site 3 << + .uleb128 Lfunc_end0-Ltmp1 ## Call between Ltmp1 and Lfunc_end0 + .byte 0 ## has no landing pad + .byte 0 ## On action: cleanup +Lcst_end0: + .byte 1 ## >> Action Record 1 << + ## Catch TypeInfo 1 + .byte 0 ## No further actions + .p2align 2 + ## >> Catch TypeInfos << + .long __ZTIi@GOTPCREL+4 ## TypeInfo 1 +Lttbase0: + .p2align 2 + ## -- End function + .section __TEXT,__text,regular,pure_instructions + .globl _main ## -- Begin function main + .p2align 4, 0x90 +_main: ## @main +Lfunc_begin1: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, Lexception1 +## %bb.0: + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + pushq %rbx + pushq %rax + .cfi_offset %rbx, -24 + movl $4, %edi + callq ___cxa_allocate_exception + movl $0, (%rax) +Ltmp3: + movq __ZTIi@GOTPCREL(%rip), %rsi + movq %rax, %rdi + xorl %edx, %edx + callq ___cxa_throw +Ltmp4: +## %bb.1: + ud2 +LBB1_2: +Ltmp5: + movq %rax, %rdi + callq ___cxa_begin_catch + movl (%rax), %ebx + callq ___cxa_end_catch + movl %ebx, %eax + addq $8, %rsp + popq %rbx + popq %rbp + retq +Lfunc_end1: + .cfi_endproc + .section __TEXT,__gcc_except_tab + .p2align 2 +GCC_except_table1: +Lexception1: + .byte 255 ## @LPStart Encoding = omit + .byte 155 ## @TType Encoding = indirect pcrel sdata4 + .uleb128 Lttbase1-Lttbaseref1 +Lttbaseref1: + .byte 1 ## Call site Encoding = uleb128 + .uleb128 Lcst_end1-Lcst_begin1 +Lcst_begin1: + .uleb128 Lfunc_begin1-Lfunc_begin1 ## >> Call Site 1 << + .uleb128 Ltmp3-Lfunc_begin1 ## Call between Lfunc_begin1 and Ltmp3 + .byte 0 ## has no landing pad + .byte 0 ## On action: cleanup + .uleb128 Ltmp3-Lfunc_begin1 ## >> Call Site 2 << + .uleb128 Ltmp4-Ltmp3 ## Call between Ltmp3 and Ltmp4 + .uleb128 Ltmp5-Lfunc_begin1 ## jumps to Ltmp5 + .byte 1 ## On action: 1 + .uleb128 Ltmp4-Lfunc_begin1 ## >> Call Site 3 << + .uleb128 Lfunc_end1-Ltmp4 ## Call between Ltmp4 and Lfunc_end1 + .byte 0 ## has no landing pad + .byte 0 ## On action: cleanup +Lcst_end1: + .byte 1 ## >> Action Record 1 << + ## Catch TypeInfo 1 + .byte 0 ## No further actions + .p2align 2 + ## >> Catch TypeInfos << + .long __ZTIi@GOTPCREL+4 ## TypeInfo 1 +Lttbase1: + .p2align 2 + ## -- End function + .section __TEXT,__text,regular,pure_instructions + .p2align 4, 0x90 ## -- Begin function _Z5unrefv.cold.1 +__Z5unrefv.cold.1: ## @_Z5unrefv.cold.1 + .cfi_startproc +## %bb.0: + pushq %rbp + .cfi_def_cfa_offset 16 + .cfi_offset %rbp, -16 + movq %rsp, %rbp + .cfi_def_cfa_register %rbp + pushq %rbx + pushq %rax + .cfi_offset %rbx, -24 + movq %rdx, %rbx + callq ___cxa_begin_catch + movl (%rax), %eax + incl %eax + movl %eax, (%rbx) + addq $8, %rsp + popq %rbx + popq %rbp + jmp ___cxa_end_catch ## TAILCALL + .cfi_endproc + ## -- End function +.subsections_via_symbols + +#--- weak-ref.s +.text +.weak_reference _ref_dylib_fun +.globl _main +_main: + callq _ref_dylib_fun + retq + +.subsections_via_symbols + +#--- strong-dead-ref.s +.text +.globl _unref_dylib_fun +_unref: + callq _unref_dylib_fun + retq + +.subsections_via_symbols + +#--- weak-dylib.s +.text +.globl _weak_in_dylib +.weak_definition _weak_in_dylib +_weak_in_dylib: + retq + +.subsections_via_symbols + +#--- dead-weak-override.s + +## Overrides the _weak_in_dylib symbol in weak-dylib, but is dead stripped. +.text + +#.no_dead_strip _weak_in_dylib +.globl _weak_in_dylib +_weak_in_dylib: + retq + +.globl _main +_main: + retq + +.subsections_via_symbols + +#--- debug.s +.text +.globl _unref +_unref: + retq + +.globl _main +_main: + retq + +.subsections_via_symbols + +#--- no-dead-symbols.s +.text +.globl _main +_main: + retq + +#--- literals.s +.cstring +_unref_foo: + .ascii "foo" +_bar: +Lbar: + .asciz "bar" +_unref_baz: + .asciz "baz" + +.literal4 +.p2align 2 +L._foo4: + .long 0xdeadbeef +L._bar4: + .long 0xdeadbeef +L._unref: + .long 0xfeedface + +.section __DATA,str_ptrs,literal_pointers +.globl _data +_data: + .quad _bar + .quad Lbar + +## The output binary has these integer literals put into a section that isn't +## marked with a S_*BYTE_LITERALS flag, so we don't mark word_ptrs with the +## S_LITERAL_POINTERS flag in order not to confuse llvm-objdump. +.section __DATA,word_ptrs +.globl _more_data +_more_data: + .quad L._foo4 + .quad L._bar4 + +.subsections_via_symbols + +#--- ref-undef.s +.globl _main +_main: + callq _ref_undef_fun +.subsections_via_symbols + +#--- addrsig.s +.globl _main, _addrsig +_main: + retq + +_addrsig: + retq + +.subsections_via_symbols + +.addrsig +.addrsig_sym _addrsig diff --git a/wild/tests/lld-macho/dwarf-no-compile-unit.s b/wild/tests/lld-macho/dwarf-no-compile-unit.s new file mode 100644 index 000000000..ced2467ca --- /dev/null +++ b/wild/tests/lld-macho/dwarf-no-compile-unit.s @@ -0,0 +1,15 @@ +# REQUIRES: aarch64 + +## Check that LLD does not crash if it encounters DWARF sections +## without __debug_info compile unit DIEs being present. + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 %t.o -o /dev/null + +.text +.globl _main +_main: + ret + +.section __DWARF,__debug_abbrev,regular,debug + .byte 0 diff --git a/wild/tests/lld-macho/dyld-stub-binder.s b/wild/tests/lld-macho/dyld-stub-binder.s new file mode 100644 index 000000000..170fe8abd --- /dev/null +++ b/wild/tests/lld-macho/dyld-stub-binder.s @@ -0,0 +1,66 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/bar.s -o %t/bar.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/test.s -o %t/test.o + +## Dylibs that don't do lazy dynamic calls don't need dyld_stub_binder. +# RUN: %no-lsystem-lld -arch arm64 -dylib %t/foo.o -o %t/libfoo.dylib +# RUN: llvm-nm -m %t/libfoo.dylib | FileCheck --check-prefix=NOSTUB %s + +## Binaries that don't do lazy dynamic calls but are linked against +## libSystem.dylib get a reference to dyld_stub_binder even if it's +## not needed. +# RUN: %lld -arch arm64 -lSystem -dylib %t/foo.o -o %t/libfoo.dylib +# RUN: llvm-nm -m %t/libfoo.dylib | FileCheck --check-prefix=STUB %s + +## Dylibs that do lazy dynamic calls do need dyld_stub_binder. +# RUN: not %no-lsystem-lld -arch arm64 -dylib %t/bar.o %t/libfoo.dylib \ +# RUN: -o %t/libbar.dylib -no_fixup_chains 2>&1 | \ +# RUN: FileCheck --check-prefix=MISSINGSTUB %s +# RUN: %lld -arch arm64 -lSystem -dylib %t/bar.o %t/libfoo.dylib \ +# RUN: -o %t/libbar.dylib -no_fixup_chains +# RUN: llvm-nm -m %t/libbar.dylib | FileCheck --check-prefix=STUB %s + +## As do executables. +# RUN: not %no-lsystem-lld -arch arm64 %t/libfoo.dylib %t/libbar.dylib %t/test.o \ +# RUN: -o %t/test -no_fixup_chains 2>&1 | FileCheck --check-prefix=MISSINGSTUB %s +# RUN: %lld -arch arm64 -lSystem %t/libfoo.dylib %t/libbar.dylib %t/test.o \ +# RUN: -o %t/test -no_fixup_chains +# RUN: llvm-nm -m %t/test | FileCheck --check-prefix=STUB %s + +## Test dynamic lookup of dyld_stub_binder. +# RUN: %no-lsystem-lld -arch arm64 %t/libfoo.dylib %t/libbar.dylib %t/test.o \ +# RUN: -o %t/test -undefined dynamic_lookup -no_fixup_chains +# RUN: llvm-nm -m %t/test | FileCheck --check-prefix=DYNSTUB %s +# RUN: %no-lsystem-lld -arch arm64 %t/libfoo.dylib %t/libbar.dylib %t/test.o \ +# RUN: -o %t/test -U dyld_stub_binder -no_fixup_chains +# RUN: llvm-nm -m %t/test | FileCheck --check-prefix=DYNSTUB %s + +# MISSINGSTUB: error: undefined symbol: dyld_stub_binder +# MISSINGSTUB-NEXT: >>> referenced by lazy binding (normally in libSystem.dylib) + +# NOSTUB-NOT: dyld_stub_binder +# STUB: (undefined) external dyld_stub_binder (from libSystem) +# DYNSTUB: (undefined) external dyld_stub_binder (dynamically looked up) + +#--- foo.s +.globl _foo +_foo: + +#--- bar.s +.text +.globl _bar +_bar: + bl _foo + ret + +#--- test.s +.text +.globl _main + +.p2align 2 +_main: + bl _foo + bl _bar + ret diff --git a/wild/tests/lld-macho/eh-frame-dead-strip.s b/wild/tests/lld-macho/eh-frame-dead-strip.s new file mode 100644 index 000000000..c9eb8c167 --- /dev/null +++ b/wild/tests/lld-macho/eh-frame-dead-strip.s @@ -0,0 +1,46 @@ +# REQUIRES: x86, aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 %t/strong.s -o %t/strong_x86_64.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos11.0 %t/weak.s -o %t/weak_x86_64.o +# RUN: %lld -dylib -dead_strip %t/strong_x86_64.o %t/weak_x86_64.o -o %t/libstrongweak_x86_64.dylib +# RUN: llvm-dwarfdump --eh-frame %t/libstrongweak_x86_64.dylib | FileCheck --check-prefixes CHECK,X86_64 %s +# RUN: %lld -dylib -dead_strip %t/weak_x86_64.o %t/strong_x86_64.o -o %t/libweakstrong_x86_64.dylib +# RUN: llvm-dwarfdump --eh-frame %t/libweakstrong_x86_64.dylib | FileCheck --check-prefixes CHECK,X86_64 %s + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos11.0 %t/strong.s -o %t/strong_arm64.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos11.0 %t/weak.s -o %t/weak_arm64.o +# RUN: %lld -arch arm64 -dylib -dead_strip %t/strong_arm64.o %t/weak_arm64.o -o %t/libstrongweak_arm64.dylib +# RUN: llvm-dwarfdump --eh-frame %t/libstrongweak_arm64.dylib | FileCheck --check-prefixes CHECK,ARM64 %s +# RUN: %lld -arch arm64 -dylib -dead_strip %t/weak_arm64.o %t/strong_arm64.o -o %t/libweakstrong_arm64.dylib +# RUN: llvm-dwarfdump --eh-frame %t/libweakstrong_arm64.dylib | FileCheck --check-prefixes CHECK,ARM64 %s + +## Verify that unneeded FDEs (and their CIEs) are dead-stripped even if they +## point to a live symbol (e.g. because we had multiple weak definitions). + +# CHECK: .eh_frame contents: +# X86_64: 00000000 00000014 00000000 CIE +# X86_64: 00000018 0000001c 0000001c FDE cie=00000000 +# ARM64: 00000000 00000010 00000000 CIE +# ARM64: 00000014 00000018 00000018 FDE cie=00000000 +# CHECK-NOT: CIE +# CHECK-NOT: FDE + +#--- strong.s +.globl _fun +_fun: + .cfi_startproc + ## cfi_escape cannot be encoded in compact unwind + .cfi_escape 0 + ret + .cfi_endproc + +#--- weak.s +.globl _fun +.weak_definition _fun +_fun: + .cfi_startproc + ## cfi_escape cannot be encoded in compact unwind + .cfi_escape 0 + ret + .cfi_endproc diff --git a/wild/tests/lld-macho/eh-frame.s b/wild/tests/lld-macho/eh-frame.s new file mode 100644 index 000000000..64fd364c8 --- /dev/null +++ b/wild/tests/lld-macho/eh-frame.s @@ -0,0 +1,172 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; mkdir %t + +# RUN: llvm-mc -emit-compact-unwind-non-canonical=true -filetype=obj -triple=x86_64-apple-macos10.15 %s -o %t/eh-frame-x86_64.o +# RUN: %lld -lSystem -lc++ %t/eh-frame-x86_64.o -o %t/eh-frame-x86_64 +# RUN: llvm-objdump --macho --syms --indirect-symbols --unwind-info \ +# RUN: --dwarf=frames %t/eh-frame-x86_64 | FileCheck %s -D#BASE=0x100000000 -D#DWARF_ENC=4 +# RUN: llvm-nm -m %t/eh-frame-x86_64 | FileCheck %s --check-prefix NO-EH-SYMS +# RUN: llvm-readobj --section-headers %t/eh-frame-x86_64 | FileCheck %s --check-prefix=ALIGN -D#ALIGN=3 + +## Test that we correctly handle the output of `ld -r`, which emits EH frames +## using subtractor relocations instead of implicitly encoding the offsets. +## In order to keep this test cross-platform, we check in ld64's output rather +## than invoking ld64 directly. NOTE: whenever this test is updated, the +## checked-in copy of `ld -r`'s output should be updated too! +# COM: ld -r %t/eh-frame-x86_64.o -o %S/Inputs/eh-frame-x86_64-r.o +# RUN: %lld -lSystem -lc++ %S/Inputs/eh-frame-x86_64-r.o -o %t/eh-frame-x86_64-r +# RUN: llvm-objdump --macho --syms --indirect-symbols --unwind-info \ +# RUN: --dwarf=frames %t/eh-frame-x86_64-r | FileCheck %s -D#BASE=0x100000000 -D#DWARF_ENC=4 +# RUN: llvm-nm -m %t/eh-frame-x86_64-r | FileCheck %s --check-prefix NO-EH-SYMS +# RUN: llvm-readobj --section-headers %t/eh-frame-x86_64-r | FileCheck %s --check-prefix=ALIGN -D#ALIGN=3 + +# RUN: llvm-mc -filetype=obj -emit-compact-unwind-non-canonical=true -triple=arm64-apple-macos11.0 %s -o %t/eh-frame-arm64.o +# RUN: %lld -arch arm64 -lSystem -lc++ %t/eh-frame-arm64.o -o %t/eh-frame-arm64 +# RUN: llvm-objdump --macho --syms --indirect-symbols --unwind-info \ +# RUN: --dwarf=frames %t/eh-frame-arm64 | FileCheck %s -D#BASE=0x100000000 -D#DWARF_ENC=3 +# RUN: llvm-nm -m %t/eh-frame-arm64 | FileCheck %s --check-prefix NO-EH-SYMS + +# COM: ld -r %t/eh-frame-arm64.o -o %S/Inputs/eh-frame-arm64-r.o +# RUN: %lld -arch arm64 -lSystem -lc++ %S/Inputs/eh-frame-arm64-r.o -o %t/eh-frame-arm64-r +# RUN: llvm-objdump --macho --syms --indirect-symbols --unwind-info \ +# RUN: --dwarf=frames %t/eh-frame-arm64-r | FileCheck %s -D#BASE=0x100000000 -D#DWARF_ENC=3 +# RUN: llvm-nm -m %t/eh-frame-arm64-r | FileCheck %s --check-prefix NO-EH-SYMS + +# ALIGN: Name: __eh_frame +# ALIGN-NEXT: Segment: __TEXT +# ALIGN-NEXT: Address: +# ALIGN-NEXT: Size: +# ALIGN-NEXT: Offset: +# ALIGN-NEXT: Alignment: [[#ALIGN]] + +# NO-EH-SYMS-NOT: __eh_frame + +# CHECK: Indirect symbols for (__DATA_CONST,__got) 2 entries +# CHECK: address index name +# CHECK: 0x[[#%x,GXX_PERSONALITY_GOT:]] {{.*}} ___gxx_personality_v0 +# CHECK: 0x[[#%x,MY_PERSONALITY_GOT:]] +# CHECK: SYMBOL TABLE: +# CHECK-DAG: [[#%x,F:]] l F __TEXT,__text _f +# CHECK-DAG: [[#%x,NO_UNWIND:]] l F __TEXT,__text _no_unwind +# CHECK-DAG: [[#%x,G:]] l F __TEXT,__text _g +# CHECK-DAG: [[#%x,H:]] l F __TEXT,__text _h +# CHECK-DAG: [[#%x,EXCEPT0:]] l O __TEXT,__gcc_except_tab GCC_except_table0 +# CHECK-DAG: [[#%x,EXCEPT1:]] l O __TEXT,__gcc_except_tab GCC_except_table1 +# CHECK-DAG: [[#%x,EXCEPT2:]] l O __TEXT,custom_except custom_except_table2 +# CHECK-DAG: [[#%x,MY_PERSONALITY:]] g F __TEXT,__text _my_personality +# CHECK: Contents of __unwind_info section: +# CHECK: Version: 0x1 +# CHECK: Number of personality functions in array: 0x1 +# CHECK: Number of indices in array: 0x2 +# CHECK: Personality functions: (count = 1) +# CHECK: personality[1]: 0x[[#%.8x,GXX_PERSONALITY_GOT - BASE]] +# CHECK: LSDA descriptors: +# CHECK: [0]: function offset=0x[[#%.8x,F - BASE]], LSDA offset=0x[[#%.8x,EXCEPT0 - BASE]] +# CHECK: [1]: function offset=0x[[#%.8x,G - BASE]], LSDA offset=0x[[#%.8x,EXCEPT1 - BASE]] +# CHECK: [2]: function offset=0x[[#%.8x,H - BASE]], LSDA offset=0x[[#%.8x,EXCEPT2 - BASE]] +# CHECK: Second level indices: +# CHECK: Second level index[0]: +# CHECK [0]: function offset=0x[[#%.8x,F - BASE]], encoding[{{.*}}]=0x52{{.*}} +# CHECK [1]: function offset=0x[[#%.8x,NO_UNWIND - BASE]], encoding[{{.*}}]=0x00000000 +# CHECK: [2]: function offset=0x[[#%.8x,G - BASE]], encoding[{{.*}}]=0x0[[#%x,DWARF_ENC]][[#%.6x, G_DWARF_OFF:]] +# CHECK: [3]: function offset=0x[[#%.8x,H - BASE]], encoding[{{.*}}]=0x0[[#%x,DWARF_ENC]][[#%.6x, H_DWARF_OFF:]] +# CHECK: [4]: function offset=0x[[#%.8x,MY_PERSONALITY - BASE]], encoding[{{.*}}]=0x00000000 + +# CHECK: .debug_frame contents: +# CHECK: .eh_frame contents: + +# CHECK: [[#%.8x,CIE1_OFF:]] {{.*}} CIE +# CHECK: Format: DWARF32 +# CHECK: Version: 1 +# CHECK: Augmentation: "zPLR" +# CHECK: Code alignment factor: 1 +# CHECK: Data alignment factor: -8 +# CHECK: Return address column: +# CHECK: Personality Address: [[#%.16x,GXX_PERSONALITY_GOT]] +# CHECK: Augmentation data: 9B {{(([[:xdigit:]]{2} ){4})}}10 10 + +# CHECK: [[#%.8x,G_DWARF_OFF]] {{.*}} [[#%.8x,G_DWARF_OFF + 4 - CIE1_OFF]] FDE cie=[[#CIE1_OFF]] pc=[[#%x,G]] +# CHECK: Format: DWARF32 +# CHECK: LSDA Address: [[#%.16x,EXCEPT1]] +# CHECK: DW_CFA_def_cfa_offset: +8 +# CHECK: 0x[[#%x,G]]: + +# CHECK: [[#%.8x,CIE2_OFF:]] {{.*}} CIE +# CHECK: Format: DWARF32 +# CHECK: Version: 1 +# CHECK: Augmentation: "zPLR" +# CHECK: Code alignment factor: 1 +# CHECK: Data alignment factor: -8 +# CHECK: Return address column: +# CHECK: Personality Address: [[#%.16x,MY_PERSONALITY_GOT]] +# CHECK: Augmentation data: 9B {{(([[:xdigit:]]{2} ){4})}}10 10 + +# CHECK: [[#%.8x,H_DWARF_OFF]] {{.*}} [[#%.8x,H_DWARF_OFF + 4 - CIE2_OFF]] FDE cie=[[#CIE2_OFF]] pc=[[#%x,H]] +# CHECK: Format: DWARF32 +# CHECK: LSDA Address: [[#%.16x,EXCEPT2]] +# CHECK: DW_CFA_def_cfa_offset: +8 +# CHECK: 0x[[#%x,H]]: + +.globl _my_personality, _main + +.text +## _f's unwind info can be encoded with compact unwind, so we shouldn't see an +## FDE entry for it in the output file. +.p2align 2 +_f: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, Lexception0 + .cfi_def_cfa_offset 8 + ret + .cfi_endproc + +.p2align 2 +_no_unwind: + ret + +.p2align 2 +_g: + .cfi_startproc + .cfi_personality 155, ___gxx_personality_v0 + .cfi_lsda 16, Lexception1 + .cfi_def_cfa_offset 8 + ## cfi_escape cannot be encoded in compact unwind, so we must keep _g's FDE + .cfi_escape 0x2e, 0x10 + ret + .cfi_endproc + +.p2align 2 +_h: + .cfi_startproc + .cfi_personality 155, _my_personality + .cfi_lsda 16, Lexception2 + .cfi_def_cfa_offset 8 + ## cfi_escape cannot be encoded in compact unwind, so we must keep _h's FDE + .cfi_escape 0x2e, 0x10 + ret + .cfi_endproc + +.p2align 2 +_my_personality: + ret + +.p2align 2 +_main: + ret + +.section __TEXT,__gcc_except_tab +GCC_except_table0: +Lexception0: + .byte 255 + +GCC_except_table1: +Lexception1: + .byte 255 + +.section __TEXT,custom_except +custom_except_table2: +Lexception2: + .byte 255 + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/encryption-info.s b/wild/tests/lld-macho/encryption-info.s new file mode 100644 index 000000000..fc97d0f88 --- /dev/null +++ b/wild/tests/lld-macho/encryption-info.s @@ -0,0 +1,35 @@ +# REQUIRES: aarch64, x86 +# RUN: rm -rf %t; mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/test.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t/watchos-test.o + +# RUN: %lld -lSystem -o %t/test %t/test.o +# RUN: llvm-objdump --macho --all-headers %t/test | FileCheck %s --check-prefix=NO-ENCRYPTION -DSUFFIX=_64 + +# RUN: %lld -lSystem -encryptable -o %t/test %t/test.o +# RUN: llvm-objdump --macho --all-headers %t/test | FileCheck %s --check-prefix=ENCRYPTION -DSUFFIX=_64 -D#PAGE_SIZE=4096 + +# RUN: %lld-watchos -lSystem -o %t/watchos-test %t/watchos-test.o +# RUN: llvm-objdump --macho --all-headers %t/watchos-test | FileCheck %s --check-prefix=ENCRYPTION -DSUFFIX= -D#PAGE_SIZE=16384 + +# RUN: %lld-watchos -lSystem -no_encryption -o %t/watchos-test %t/watchos-test.o +# RUN: llvm-objdump --macho --all-headers %t/watchos-test | FileCheck %s --check-prefix=NO-ENCRYPTION -DSUFFIX= + +# ENCRYPTION: segname __TEXT +# ENCRYPTION-NEXT: vmaddr +# ENCRYPTION-NEXT: vmsize +# ENCRYPTION-NEXT: fileoff 0 +# ENCRYPTION-NEXT: filesize [[#TEXT_SIZE:]] + +# ENCRYPTION: cmd LC_ENCRYPTION_INFO[[SUFFIX]]{{$}} +# ENCRYPTION-NEXT: cmdsize +# ENCRYPTION-NEXT: cryptoff [[#PAGE_SIZE]] +# ENCRYPTION-NEXT: cryptsize [[#TEXT_SIZE - PAGE_SIZE]] +# ENCRYPTION-NEXT: cryptid 0 + +# NO-ENCRYPTION-NOT: LC_ENCRYPTION_INFO[[SUFFIX]]{{$}} + +.globl _main +.p2align 2 +_main: + ret diff --git a/wild/tests/lld-macho/fat-arch.s b/wild/tests/lld-macho/fat-arch.s new file mode 100644 index 000000000..59b82cd90 --- /dev/null +++ b/wild/tests/lld-macho/fat-arch.s @@ -0,0 +1,45 @@ +# REQUIRES: x86,aarch64 +## FIXME: The tests doesn't run on windows right now because of llvm-mc (can't produce triple=arm64-apple-macos11.0) +# UNSUPPORTED: system-windows + +# RUN: llvm-mc -filetype=obj -triple=i386-apple-darwin %s -o %t.i386.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.x86_64.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos11.0 %s -o %t.arm64.o + +# RUN: llvm-lipo %t.i386.o %t.x86_64.o -create -o %t.fat.o +# RUN: %lld -o /dev/null %t.fat.o +# RUN: llvm-lipo %t.i386.o -create -o %t.noarch.o +# RUN: not %no-fatal-warnings-lld -o /dev/null %t.noarch.o 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t.noarch.o +# CHECK: warning: [[FILE]]: ignoring file because it is universal (i386) but does not contain the x86_64 architecture + +# RUN: not %lld -arch arm64 -o /dev/null %t.fat.o 2>&1 | \ +# RUN: FileCheck --check-prefix=CHECK-FAT %s -DFILE=%t.fat.o +# CHECK-FAT: error: [[FILE]]: ignoring file because it is universal (i386,x86_64) but does not contain the arm64 architecture + +## Validates that we read the cpu-subtype correctly from a fat exec. +# RUN: %lld -o %t.x86_64.out %t.x86_64.o +# RUN: %lld -arch arm64 -o %t.arm64.out %t.arm64.o +# RUN: llvm-lipo %t.x86_64.out %t.arm64.out -create -o %t.fat.exec.out +# RUN: %lld -arch x86_64 %t.x86_64.o -bundle_loader %t.fat.exec.out -bundle -o %t.fat.bundle + +# RUN: llvm-otool -h %t.fat.bundle > %t.bundle_header.txt +# RUN: llvm-otool -f %t.fat.exec.out >> %t.bundle_header.txt +# RUN: cat %t.bundle_header.txt | FileCheck %s --check-prefix=CPU-SUB + +# CPU-SUB: magic cputype cpusubtype caps filetype ncmds sizeofcmds flags +# CPU-SUB-NEXT: 0xfeedfacf 16777223 3 0x{{.+}} {{.+}} {{.+}} {{.+}} {{.+}} + +# CPU-SUB: Fat headers +# CPU-SUB: nfat_arch 2 +# CPU-SUB: architecture 0 +# CPU-SUB-NEXT: cputype 16777223 +# CPU-SUB-NEXT: cpusubtype 3 +# CPU-SUB: architecture 1 +# CPU-SUB-NEXT: cputype 16777228 +# CPU-SUB-NEXT: cpusubtype 0 + +.text +.global _main +_main: + ret diff --git a/wild/tests/lld-macho/header.s b/wild/tests/lld-macho/header.s new file mode 100644 index 000000000..e7ddf9456 --- /dev/null +++ b/wild/tests/lld-macho/header.s @@ -0,0 +1,28 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t && mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/x86-64-test.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t/arm64-test.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t/arm64-32-test.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t/arm64-32-test.o + +# RUN: %lld -lSystem -arch x86_64 -o %t/x86-64-executable %t/x86-64-test.o +# RUN: %lld -lSystem -arch arm64 -o %t/arm64-executable %t/arm64-test.o +# RUN: %lld-watchos -lSystem -o %t/arm64-32-executable %t/arm64-32-test.o + +# RUN: %lld -arch x86_64 -dylib -o %t/x86-64-dylib %t/x86-64-test.o + +## NOTE: recent versions of ld64 don't emit LIB64 for x86-64-executable, maybe we should follow suit +# RUN: llvm-objdump --macho --private-header %t/x86-64-executable | FileCheck %s --check-prefix=EXEC -DCPU=X86_64 -DSUBTYPE=ALL -DCAPS=LIB64 +# RUN: llvm-objdump --macho --private-header %t/arm64-executable | FileCheck %s --check-prefix=EXEC -DCPU=ARM64 -DSUBTYPE=ALL -DCAPS=0x00 +# RUN: llvm-objdump --macho --private-header %t/arm64-32-executable | FileCheck %s --check-prefix=EXEC -DCPU=ARM64_32 -DSUBTYPE=V8 -DCAPS=0x00 + +# RUN: llvm-objdump --macho --private-header %t/x86-64-dylib | FileCheck %s --check-prefix=DYLIB -DCPU=X86_64 -DSUBTYPE=ALL -DCAPS=0x00 + +# EXEC: magic cputype cpusubtype caps filetype {{.*}} flags +# EXEC-NEXT: MH_MAGIC{{(_64)?}} [[CPU]] [[SUBTYPE]] [[CAPS]] EXECUTE {{.*}} NOUNDEFS DYLDLINK TWOLEVEL PIE{{$}} + +# DYLIB: magic cputype cpusubtype caps filetype {{.*}} flags +# DYLIB-NEXT: MH_MAGIC_64{{(_64)?}} [[CPU]] [[SUBTYPE]] [[CAPS]] DYLIB {{.*}} NOUNDEFS DYLDLINK TWOLEVEL NO_REEXPORTED_DYLIBS{{$}} + +.globl _main +_main: diff --git a/wild/tests/lld-macho/icf-arm64.s b/wild/tests/lld-macho/icf-arm64.s new file mode 100644 index 000000000..7d74af8ce --- /dev/null +++ b/wild/tests/lld-macho/icf-arm64.s @@ -0,0 +1,109 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 %t/main.s -o %t/main.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin19.0.0 %t/f2.s -o %t/f2.o +# RUN: %lld -arch arm64 -lSystem --icf=all -o %t/main %t/main.o %t/f2.o +# RUN: llvm-objdump -d --syms --print-imm-hex %t/main | FileCheck %s + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK: [[#%x,F1_REF:]] g F __TEXT,__text _f1 +# CHECK: [[#%x,F1_REF:]] g F __TEXT,__text _f2 + +# CHECK-LABEL: Disassembly of section __TEXT,__text: +# CHECK: <_main>: +# CHECK: bl 0x[[#%x,F1_REF:]] +# CHECK: bl 0x[[#%x,F1_REF:]] + +#--- main.s + +.subsections_via_symbols + +.literal16 +.p2align 3 +L_align16: +.quad 0xffffffffffffffff +.short 0xaaaa +.short 0xaaaa +.space 4, 0xaa + +.literal8 +.p2align 3 +L_align8: +.quad 0xeeeeeeeeeeeeeeee + +.literal4 +.p2align 2 +L_align4: +.short 0xbbbb +.short 0xbbbb + + +.text +.p2align 2 + +.globl _main, _f1, _f2 + +## Test that loading from __literalN sections at non-literal boundaries +## doesn't confuse ICF. This function should be folded with the identical +## _f2 in f2 (which uses literals of the same value in a different isec). +_f1: + adrp x9, L_align16@PAGE + 4 + add x9, x9, L_align16@PAGEOFF + 4 + ldr x10, [x9] + + adrp x9, L_align8@PAGE + 4 + add x9, x9, L_align8@PAGEOFF + 4 + ldr w11, [x9] + + adrp x9, L_align4@PAGE + 2 + add x9, x9, L_align4@PAGEOFF + 2 + ldrh w12, [x9] + + ret + +_main: + bl _f1 + bl _f2 + +#--- f2.s + +.subsections_via_symbols + +.literal16 +.p2align 3 +L_align16: +.quad 0xffffffffffffffff +.short 0xaaaa +.short 0xaaaa +.space 4, 170 + +.literal8 +.p2align 3 +L_align8: +.quad 0xeeeeeeeeeeeeeeee + +.literal4 +.p2align 2 +L_align4: +.short 0xbbbb +.short 0xbbbb + +.text +.p2align 2 + +.globl _f2 +_f2: + adrp x9, L_align16@PAGE + 4 + add x9, x9, L_align16@PAGEOFF + 4 + ldr x10, [x9] + + adrp x9, L_align8@PAGE + 4 + add x9, x9, L_align8@PAGEOFF + 4 + ldr w11, [x9] + + adrp x9, L_align4@PAGE + 2 + add x9, x9, L_align4@PAGEOFF + 2 + ldrh w12, [x9] + + ret diff --git a/wild/tests/lld-macho/icf-safe-missing-addrsig.s b/wild/tests/lld-macho/icf-safe-missing-addrsig.s new file mode 100644 index 000000000..d289fee47 --- /dev/null +++ b/wild/tests/lld-macho/icf-safe-missing-addrsig.s @@ -0,0 +1,112 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/with-addrsig.s -o %t/with-addrsig.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/without-addrsig.s -o %t/without-addrsig.o +# RUN: %lld -arch arm64 -lSystem --icf=safe -dylib -map %t/with-addrsig-safe.map -o %t/with-addrsig.dylib %t/with-addrsig.o +# RUN: %lld -arch arm64 -lSystem --icf=safe -dylib -map %t/without-addrsig-safe.map -o %t/without-addrsig.dylib %t/without-addrsig.o +# RUN: %lld -arch arm64 -lSystem --icf=safe_thunks -dylib -map %t/with-addrsig-safe-thunks.map -o %t/with-addrsig-thunks.dylib %t/with-addrsig.o +# RUN: %lld -arch arm64 -lSystem --icf=safe_thunks -dylib -map %t/without-addrsig-safe-thunks.map -o %t/without-addrsig-thunks.dylib %t/without-addrsig.o +# RUN: FileCheck %s --check-prefix=ADDRSIG-SAFE < %t/with-addrsig-safe.map +# RUN: FileCheck %s --check-prefix=NO-ADDRSIG-SAFE < %t/without-addrsig-safe.map +# RUN: FileCheck %s --check-prefix=ADDRSIG-SAFE-THUNKS < %t/with-addrsig-safe-thunks.map +# RUN: FileCheck %s --check-prefix=NO-ADDRSIG-SAFE-THUNKS < %t/without-addrsig-safe-thunks.map + +## Input has addrsig section: _g1 and _g2 are address-significant, so _g2 is +## thunk-folded in safe_thunks ICF and remains untouched in safe ICF. +## _f2 is always body-folded into _f1 regardless of ICF level. + +# ADDRSIG-SAFE: 0x00000008 [ 2] _f1 +# ADDRSIG-SAFE-NEXT: 0x00000000 [ 2] _f2 +# ADDRSIG-SAFE: 0x00000008 [ 2] _g1 +# ADDRSIG-SAFE-NEXT: 0x00000008 [ 2] _g2 + +# ADDRSIG-SAFE-THUNKS: 0x00000008 [ 2] _f1 +# ADDRSIG-SAFE-THUNKS-NEXT: 0x00000000 [ 2] _f2 +# ADDRSIG-SAFE-THUNKS: 0x00000008 [ 2] _g1 +# ADDRSIG-SAFE-THUNKS: 0x00000004 [ 2] _g2 + +## Input does not have addrsig section: everything is address-significant, so +## no folding happened in safe ICF, and _f2, _g2 are thunk-folded into _f1, _g1 +## respectively. + +# NO-ADDRSIG-SAFE: 0x00000008 [ 2] _f1 +# NO-ADDRSIG-SAFE-NEXT: 0x00000008 [ 2] _f2 +# NO-ADDRSIG-SAFE-NEXT: 0x00000008 [ 2] _g1 +# NO-ADDRSIG-SAFE-NEXT: 0x00000008 [ 2] _g2 + +# NO-ADDRSIG-SAFE-THUNKS: 0x00000008 [ 2] _f1 +# NO-ADDRSIG-SAFE-THUNKS-NEXT: 0x00000008 [ 2] _g1 +# NO-ADDRSIG-SAFE-THUNKS: 0x00000004 [ 2] _g2 +# NO-ADDRSIG-SAFE-THUNKS-NEXT: 0x00000004 [ 2] _f2 + +#--- with-addrsig.s +.subsections_via_symbols +.text +.p2align 2 + +.globl _f1 +_f1: + mov w0, #0 + ret + +.globl _f2 +_f2: + mov w0, #0 + ret + +.globl _g1 +_g1: + mov w0, #1 + ret + +.globl _g2 +_g2: + mov w0, #1 + ret + +.globl _call_all +_call_all: + bl _f1 + bl _f2 + bl _g1 + bl _g2 + ret + +.addrsig +.addrsig_sym _call_all +.addrsig_sym _g1 +.addrsig_sym _g2 + +#--- without-addrsig.s +.subsections_via_symbols +.text +.p2align 2 + +.globl _f1 +_f1: + mov w0, #0 + ret + +.globl _f2 +_f2: + mov w0, #0 + ret + +.globl _g1 +_g1: + mov w0, #1 + ret + +.globl _g2 +_g2: + mov w0, #1 + ret + +.globl _call_all +_call_all: + bl _f1 + bl _f2 + bl _g1 + bl _g2 + ret diff --git a/wild/tests/lld-macho/ignore-incompat-arch.s b/wild/tests/lld-macho/ignore-incompat-arch.s new file mode 100644 index 000000000..676b5e56f --- /dev/null +++ b/wild/tests/lld-macho/ignore-incompat-arch.s @@ -0,0 +1,72 @@ +# REQUIRES: x86, aarch64 +## Test that LLD correctly ignored archives with incompatible architecture without crashing. + +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos %t/callee.s -o %t/callee_arm64.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %t/callee.s -o %t/callee_x86_64.o + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos %t/caller.s -o %t/caller_arm64.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %t/caller.s -o %t/caller_x86_64.o + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos %t/main.s -o %t/main_arm64.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %t/main.s -o %t/main_x86_64.o + +# RUN: llvm-ar rc %t/libcallee_arm64.a %t/callee_arm64.o +# RUN: llvm-ar r %t/libcallee_x86.a %t/callee_x86_64.o + +# RUN: llvm-ar r %t/libcaller_arm64.a %t/caller_arm64.o +# RUN: llvm-ar r %t/libcaller_x86.a %t/caller_x86_64.o + +## Symbol from the arm64 archive should be ignored even tho it appears before the x86 archive. +# RUN: %no-fatal-warnings-lld -map %t/x86_a.map -arch x86_64 %t/main_x86_64.o %t/libcallee_arm64.a %t/libcallee_x86.a %t/libcaller_x86.a -o %t/x86_a.out 2>&1 \ +# RUN: | FileCheck -check-prefix=X86-WARNING %s + +# RUN: %no-fatal-warnings-lld -map %t/x86_b.map -arch x86_64 %t/main_x86_64.o %t/libcallee_x86.a %t/libcallee_arm64.a %t/libcaller_x86.a -o %t/x86_b.out 2>&1 \ +# RUN: | FileCheck -check-prefix=X86-WARNING %s + +# RUN: %no-fatal-warnings-lld -map %t/arm64_a.map -arch arm64 %t/main_arm64.o %t/libcallee_x86.a %t/libcallee_arm64.a %t/libcaller_arm64.a -o %t/arm64_a.out 2>&1 \ +# RUN: | FileCheck -check-prefix=ARM64-WARNING %s + +# RUN: %no-fatal-warnings-lld -map %t/arm64_b.map -arch arm64 %t/main_arm64.o %t/libcallee_arm64.a %t/libcallee_x86.a %t/libcaller_arm64.a -o %t/arm64_b.out 2>&1 \ +# RUN: | FileCheck -check-prefix=ARM64-WARNING %s + +## Verify that the output doesn't take any symbol from the in-compat archive +# RUN: FileCheck --check-prefix=SYM-X86 %s --input-file=%t/x86_a.map +# RUN: FileCheck --check-prefix=SYM-X86 %s --input-file=%t/x86_b.map + +# RUN: FileCheck --check-prefix=SYM-ARM64 %s --input-file=%t/arm64_a.map +# RUN: FileCheck --check-prefix=SYM-ARM64 %s --input-file=%t/arm64_b.map + + +# X86-WARNING: libcallee_arm64.a has architecture arm64 which is incompatible with target architecture x86_64 + +# ARM64-WARNING: libcallee_x86.a has architecture x86_64 which is incompatible with target architecture arm64 + +# SYM-X86-NOT: libcallee_arm64.a +# SYM-X86: {{.+}}main_x86_64.o +# SYM-X86: {{.+}}libcallee_x86.a(callee_x86_64.o) +# SYM-X86: {{.+}}libcaller_x86.a(caller_x86_64.o) + +# SYM-ARM64-NOT: libcallee_x86.a +# SYM-ARM64: {{.+}}main_arm64.o +# SYM-ARM64: {{.+}}libcallee_arm64.a(callee_arm64.o) +# SYM-ARM64: {{.+}}libcaller_arm64.a(caller_arm64.o) + + +#--- callee.s +.globl _callee +_callee: + ret + +#--- caller.s +.globl _caller +_caller: + .quad _callee + ret + +#--- main.s +.globl _main +_main: + .quad _caller + ret diff --git a/wild/tests/lld-macho/local-alias-to-weak.s b/wild/tests/lld-macho/local-alias-to-weak.s new file mode 100644 index 000000000..feb4c0a2e --- /dev/null +++ b/wild/tests/lld-macho/local-alias-to-weak.s @@ -0,0 +1,149 @@ +# REQUIRES: x86 +## This test checks that when we coalesce weak definitions, their local symbol +## aliases defs don't cause the coalesced data to be retained. This was +## motivated by MC's aarch64 backend which automatically creates `ltmp` +## symbols at the start of each .text section. These symbols are frequently +## aliases of other symbols created by clang or other inputs to MC. I've chosen +## to explicitly create them here since we can then reference those symbols for +## a more complete test. +## +## Not retaining the data matters for more than just size -- we have a use case +## that depends on proper data coalescing to emit a valid file format. We also +## need this behavior to properly deduplicate the __objc_protolist section; +## failure to do this can result in dyld crashing on iOS 13. +## +## Finally, ld64 does all this regardless of whether .subsections_via_symbols is +## specified. We don't. But again, given how rare the lack of that directive is +## (I've only seen it from hand-written assembly inputs), I don't think we need +## to worry about it. + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/weak-then-local.s -o %t/weak-then-local.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/local-then-weak.s -o %t/local-then-weak.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/no-subsections.s -o %t/no-subsections.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/no-dead-strip.s -o %t/no-dead-strip.o + +# RUN: %lld -lSystem -dylib %t/weak-then-local.o %t/local-then-weak.o -o %t/test1 +# RUN: llvm-objdump --macho --syms --section="__DATA,__data" --weak-bind %t/test1 | FileCheck %s +# RUN: %lld -lSystem -dylib %t/local-then-weak.o %t/weak-then-local.o -o %t/test2 +# RUN: llvm-objdump --macho --syms --section="__DATA,__data" --weak-bind %t/test2 | FileCheck %s + +## Check that we only have one copy of 0x123 in the data, not two. +# CHECK: Contents of (__DATA,__data) section +# CHECK-NEXT: 0000000000001000 23 01 00 00 00 00 00 00 00 10 00 00 00 00 00 00 {{$}} +# CHECK-NEXT: 0000000000001010 00 10 00 00 00 00 00 00 {{$}} +# CHECK-EMPTY: +# CHECK-NEXT: SYMBOL TABLE: +# CHECK-NEXT: 0000000000001000 l O __DATA,__data _alias +# CHECK-NEXT: 0000000000001008 l O __DATA,__data _ref +# CHECK-NEXT: 0000000000001000 l O __DATA,__data _alias +# CHECK-NEXT: 0000000000001010 l O __DATA,__data _ref +# CHECK-NEXT: 0000000000001000 w O __DATA,__data _weak +# CHECK-NEXT: 0000000000000000 *UND* dyld_stub_binder +# CHECK-EMPTY: +## Even though the references were to the non-weak `_alias` symbols, ld64 still +## emits weak binds as if they were the `_weak` symbol itself. We do not. I +## don't know of any programs that rely on this behavior, so I'm just +## documenting it here. +# CHECK-NEXT: Weak bind table: +# CHECK-NEXT: segment section address type addend symbol +# CHECK-EMPTY: + +# RUN: %lld -lSystem -dylib %t/local-then-weak.o %t/no-subsections.o -o %t/sub-nosub +# RUN: llvm-objdump --macho --syms --section="__DATA,__data" %t/sub-nosub | FileCheck %s --check-prefix SUB-NOSUB + +## This test case demonstrates a shortcoming of LLD: If .subsections_via_symbols +## isn't enabled, we don't elide the contents of coalesced weak symbols if they +## are part of a section that has other non-coalesced symbols. In contrast, LD64 +## does elide the contents. +# SUB-NOSUB: Contents of (__DATA,__data) section +# SUB-NOSUB-NEXT: 0000000000001000 23 01 00 00 00 00 00 00 00 10 00 00 00 00 00 00 +# SUB-NOSUB-NEXT: 0000000000001010 00 00 00 00 00 00 00 00 23 01 00 00 00 00 00 00 +# SUB-NOSUB-EMPTY: +# SUB-NOSUB-NEXT: SYMBOL TABLE: +# SUB-NOSUB-NEXT: 0000000000001000 l O __DATA,__data _alias +# SUB-NOSUB-NEXT: 0000000000001008 l O __DATA,__data _ref +# SUB-NOSUB-NEXT: 0000000000001010 l O __DATA,__data _zeros +# SUB-NOSUB-NEXT: 0000000000001000 l O __DATA,__data _alias +# SUB-NOSUB-NEXT: 0000000000001000 w O __DATA,__data _weak +# SUB-NOSUB-NEXT: 0000000000000000 *UND* dyld_stub_binder + +# RUN: %lld -lSystem -dylib %t/no-subsections.o %t/local-then-weak.o -o %t/nosub-sub +# RUN: llvm-objdump --macho --syms --section="__DATA,__data" %t/nosub-sub | FileCheck %s --check-prefix NOSUB-SUB + +# NOSUB-SUB: Contents of (__DATA,__data) section +# NOSUB-SUB-NEXT: 0000000000001000 00 00 00 00 00 00 00 00 23 01 00 00 00 00 00 00 +# NOSUB-SUB-NEXT: 0000000000001010 08 10 00 00 00 00 00 00 {{$}} +# NOSUB-SUB-EMPTY: +# NOSUB-SUB-NEXT: SYMBOL TABLE: +# NOSUB-SUB-NEXT: 0000000000001000 l O __DATA,__data _zeros +# NOSUB-SUB-NEXT: 0000000000001008 l O __DATA,__data _alias +# NOSUB-SUB-NEXT: 0000000000001008 l O __DATA,__data _alias +# NOSUB-SUB-NEXT: 0000000000001010 l O __DATA,__data _ref +# NOSUB-SUB-NEXT: 0000000000001008 w O __DATA,__data _weak +# NOSUB-SUB-NEXT: 0000000000000000 *UND* dyld_stub_binder + +## Verify that we don't drop any flags that the aliases have (such as +## .no_dead_strip). This is a regression test. We previously had subsections +## that were mistakenly stripped. + +# RUN: %lld -lSystem -dead_strip %t/no-dead-strip.o -o %t/no-dead-strip +# RUN: llvm-objdump --macho --section-headers %t/no-dead-strip | FileCheck %s \ +# RUN: --check-prefix=NO-DEAD-STRIP +# NO-DEAD-STRIP: __data 00000010 + +#--- weak-then-local.s +.globl _weak +.weak_definition _weak +.data +_weak: +_alias: + .quad 0x123 + +_ref: + .quad _alias + +.subsections_via_symbols + +#--- local-then-weak.s +.globl _weak +.weak_definition _weak +.data +_alias: +_weak: + .quad 0x123 + +_ref: + .quad _alias + +.subsections_via_symbols + +#--- no-subsections.s +.globl _weak +.weak_definition _weak +.data +_zeros: +.space 8 + +_weak: +_alias: + .quad 0x123 + +#--- no-dead-strip.s +.globl _main + +_main: + ret + +.data +.no_dead_strip l_foo, l_bar + +_foo: +l_foo: + .quad 0x123 + +l_bar: +_bar: + .quad 0x123 + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/loh-adrp-add-ldr.s b/wild/tests/lld-macho/loh-adrp-add-ldr.s new file mode 100644 index 000000000..efab90531 --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-add-ldr.s @@ -0,0 +1,185 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 %t.o -o %t +# RUN: llvm-objdump --no-print-imm-hex -d --macho %t | FileCheck %s + +## This is mostly a copy of loh-adrp-ldr-got-ldr.s's `local.s` test, except that Adrp+Ldr+Ldr +## triples have been changed to Adrp+Add+Ldr. The performed optimization is the same. +.text +.align 2 +.globl _main +_main: + +### Transformation to a literal LDR +## Basic case +L1: adrp x0, _close@PAGE +L2: add x1, x0, _close@PAGEOFF +L3: ldr x2, [x1] +# CHECK-LABEL: _main: +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2 + +## Load with offset +L4: adrp x0, _close@PAGE +L5: add x1, x0, _close@PAGEOFF +L6: ldr x2, [x1, #8] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2 + +## 32 bit load +L7: adrp x0, _close@PAGE +L8: add x1, x0, _close@PAGEOFF +L9: ldr w1, [x1] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr w1, _close + +## Floating point +L10: adrp x0, _close@PAGE +L11: add x1, x0, _close@PAGEOFF +L12: ldr s1, [x1] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr s1, _close + +L13: adrp x0, _close@PAGE +L14: add x1, x0, _close@PAGEOFF +L15: ldr d1, [x1, #8] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr d1, _close8 + +L16: adrp x0, _close@PAGE +L17: add x1, x0, _close@PAGEOFF +L18: ldr q0, [x1] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr q0, _close + + +### Transformation to ADR+LDR +## 1 byte floating point load +L19: adrp x0, _close@PAGE +L20: add x1, x0, _close@PAGEOFF +L21: ldr b2, [x1] +# CHECK-NEXT: adr x1 +# CHECK-NEXT: nop +# CHECK-NEXT: ldr b2, [x1] + +## 1 byte GPR load, zero extend +L22: adrp x0, _close@PAGE +L23: add x1, x0, _close@PAGEOFF +L24: ldrb w2, [x1] +# CHECK-NEXT: adr x1 +# CHECK-NEXT: nop +# CHECK-NEXT: ldrb w2, [x1] + +## 1 byte GPR load, sign extend +L25: adrp x0, _close@PAGE +L26: add x1, x0, _close@PAGEOFF +L27: ldrsb x2, [x1] +# CHECK-NEXT: adr x1 +# CHECK-NEXT: nop +# CHECK-NEXT: ldrsb x2, [x1] + +## Unaligned +L28: adrp x0, _unaligned@PAGE +L29: add x1, x0, _unaligned@PAGEOFF +L30: ldr x2, [x1] +# CHECK-NEXT: adr x1 +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2, [x1] + + +### Transformation to ADRP + immediate LDR +## Basic test: target is far +L31: adrp x0, _far@PAGE +L32: add x1, x0, _far@PAGEOFF +L33: ldr x2, [x1] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2 + +## With offset +L34: adrp x0, _far@PAGE +L35: add x1, x0, _far@PAGEOFF +L36: ldr x2, [x1, #8] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2 + +### No changes +## Far and unaligned +L37: adrp x0, _far_unaligned@PAGE +L38: add x1, x0, _far_unaligned@PAGEOFF +L39: ldr x2, [x1] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: add x1, x0 +# CHECK-NEXT: ldr x2, [x1] + +## Far with large offset (_far_offset@PAGE + #255 > 4095) +L40: adrp x0, _far_offset@PAGE +L41: add x1, x0, _far_offset@PAGEOFF +L42: ldrb w2, [x1, #255] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: add x1, x0 +# CHECK-NEXT: ldrb w2, [x1, #255] + +### Invalid inputs; the instructions should be left untouched. +## Registers don't match +L43: adrp x0, _far@PAGE +L44: add x1, x0, _far@PAGEOFF +L45: ldr x2, [x2] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: add x1, x0 +# CHECK-NEXT: ldr x2, [x2] + +## Targets don't match +L46: adrp x0, _close@PAGE +L47: add x1, x0, _close8@PAGEOFF +L48: ldr x2, [x1] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: add x1, x0 +# CHECK-NEXT: ldr x2, [x1] + +.data +.align 4 + .quad 0 +_close: + .quad 0 +_close8: + .quad 0 + .byte 0 +_unaligned: + .quad 0 + +.space 1048576 +.align 12 + .quad 0 +_far: + .quad 0 + .byte 0 +_far_unaligned: + .quad 0 +.space 4000 +_far_offset: + .byte 0 + +.loh AdrpAddLdr L1, L2, L3 +.loh AdrpAddLdr L4, L5, L6 +.loh AdrpAddLdr L7, L8, L9 +.loh AdrpAddLdr L10, L11, L12 +.loh AdrpAddLdr L13, L14, L15 +.loh AdrpAddLdr L16, L17, L18 +.loh AdrpAddLdr L19, L20, L21 +.loh AdrpAddLdr L22, L23, L24 +.loh AdrpAddLdr L25, L26, L27 +.loh AdrpAddLdr L28, L29, L30 +.loh AdrpAddLdr L31, L32, L33 +.loh AdrpAddLdr L34, L35, L36 +.loh AdrpAddLdr L37, L38, L39 +.loh AdrpAddLdr L40, L41, L42 +.loh AdrpAddLdr L43, L44, L45 diff --git a/wild/tests/lld-macho/loh-adrp-add.s b/wild/tests/lld-macho/loh-adrp-add.s new file mode 100644 index 000000000..6026be8d4 --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-add.s @@ -0,0 +1,90 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 %t.o -o %t +# RUN: llvm-objdump -d --macho %t | FileCheck %s + +# CHECK-LABEL: _main: +## Out of range, before +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: add x0, x0 +## In range, before +# CHECK-NEXT: adr x1 +# CHECK-NEXT: nop +## Registers don't match (invalid input) +# CHECK-NEXT: adrp x2 +# CHECK-NEXT: add x0 +## Not an adrp instruction (invalid input) +# CHECK-NEXT: nop +# CHECK-NEXT: add x4 +## In range, after +# CHECK-NEXT: adr x5 +# CHECK-NEXT: nop +## In range, add's destination register is not the same as its source +# CHECK-NEXT: adr x7 +# CHECK-NEXT: nop +## Valid, non-adjacent instructions - start +# CHECK-NEXT: adr x8 +## Out of range, after +# CHECK-NEXT: adrp x9 +# CHECK-NEXT: add x9, x9 +## Valid, non-adjacent instructions - end +# CHECK-NEXT: nop + +.text +.align 2 +_before_far: + .space 1048576 + +_before_near: + nop + +.globl _main +_main: +L1: + adrp x0, _before_far@PAGE +L2: + add x0, x0, _before_far@PAGEOFF +L3: + adrp x1, _before_near@PAGE +L4: + add x1, x1, _before_near@PAGEOFF +L5: + adrp x2, _before_near@PAGE +L6: + add x0, x0, _before_near@PAGEOFF +L9: + nop +L10: + add x4, x4, _after_near@PAGEOFF +L11: + adrp x5, _after_near@PAGE +L12: + add x5, x5, _after_near@PAGEOFF +L13: + adrp x6, _after_near@PAGE +L14: + add x7, x6, _after_near@PAGEOFF +L15: + adrp x8, _after_near@PAGE +L16: + adrp x9, _after_far@PAGE +L17: + add x9, x9, _after_far@PAGEOFF +L18: + add x8, x8, _after_near@PAGEOFF + +_after_near: + .space 1048576 + +_after_far: + nop + +.loh AdrpAdd L1, L2 +.loh AdrpAdd L3, L4 +.loh AdrpAdd L5, L6 +.loh AdrpAdd L9, L10 +.loh AdrpAdd L11, L12 +.loh AdrpAdd L13, L14 +.loh AdrpAdd L15, L18 +.loh AdrpAdd L16, L17 diff --git a/wild/tests/lld-macho/loh-adrp-adrp.s b/wild/tests/lld-macho/loh-adrp-adrp.s new file mode 100644 index 000000000..55d6a614f --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-adrp.s @@ -0,0 +1,72 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 %t.o -o %t +# RUN: llvm-objdump -d --macho %t | FileCheck %s + +# CHECK-LABEL: _main: +## Valid +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: nop +## Mismatched registers +# CHECK-NEXT: adrp x1 +# CHECK-NEXT: adrp x2 +## Not on the same page +# CHECK-NEXT: adrp x3 +# CHECK-NEXT: adrp x3 +## Not an adrp instruction (invalid) +# CHECK-NEXT: nop +# CHECK-NEXT: adrp x4 +## Other relaxations take precedence over AdrpAdrp +# CHECK-NEXT: adr x6 +# CHECK-NEXT: nop +# CHECK-NEXT: adr x6 +# CHECK-NEXT: nop + +.text +.align 2 + +.globl _main +_main: +L1: + adrp x0, _foo@PAGE +L2: + adrp x0, _bar@PAGE +L3: + adrp x1, _foo@PAGE +L4: + adrp x2, _bar@PAGE +L5: + adrp x3, _foo@PAGE +L6: + adrp x3, _baz@PAGE +L7: + nop +L8: + adrp x4, _baz@PAGE +L9: + adrp x5, _foo@PAGE +L10: + add x6, x5, _foo@PAGEOFF +L11: + adrp x5, _bar@PAGE +L12: + add x6, x5, _bar@PAGEOFF + +.data +.align 12 +_foo: + .byte 0 +_bar: + .byte 0 +.space 4094 +_baz: + .byte 0 + +.loh AdrpAdrp L1, L2 +.loh AdrpAdrp L3, L4 +.loh AdrpAdrp L5, L6 +.loh AdrpAdrp L7, L8 +.loh AdrpAdrp L9, L11 +.loh AdrpAdd L9, L10 +.loh AdrpAdd L11, L12 diff --git a/wild/tests/lld-macho/loh-adrp-ldr-got-ldr.s b/wild/tests/lld-macho/loh-adrp-ldr-got-ldr.s new file mode 100644 index 000000000..1905666f8 --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-ldr-got-ldr.s @@ -0,0 +1,263 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/lib.s -o %t/lib.o +# RUN: %lld -arch arm64 -dylib -o %t/lib.dylib %t/lib.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/external.s -o %t/near-got.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/external.s -defsym=PADDING=1 -o %t/far-got.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/local.s -o %t/local.o +# RUN: %lld -arch arm64 %t/near-got.o %t/lib.dylib -o %t/NearGot +# RUN: %lld -arch arm64 %t/far-got.o %t/lib.dylib -o %t/FarGot +# RUN: %lld -arch arm64 %t/local.o -o %t/Local +# RUN: llvm-objdump --no-print-imm-hex -d --macho %t/NearGot | FileCheck %s -check-prefix=NEAR-GOT +# RUN: llvm-objdump --no-print-imm-hex -d --macho %t/FarGot | FileCheck %s -check-prefix=FAR-GOT +# RUN: llvm-objdump --no-print-imm-hex -d --macho %t/Local | FileCheck %s -check-prefix=LOCAL + +#--- external.s +.text +.align 2 +.globl _main +_main: + +## Basic test +L1: adrp x0, _external@GOTPAGE +L2: ldr x1, [x0, _external@GOTPAGEOFF] +L3: ldr x2, [x1] +# NEAR-GOT-LABEL: _main: +# NEAR-GOT-NEXT: nop +# NEAR-GOT-NEXT: ldr x1, #{{.*}} ; literal pool symbol address: _external +# NEAR-GOT-NEXT: ldr x2, [x1] +# FAR-GOT-LABEL: _main: +# FAR-GOT-NEXT: adrp x0 +# FAR-GOT-NEXT: ldr x1 +# FAR-GOT-NEXT: ldr x2, [x1] + +## The second load has an offset +L4: adrp x0, _external@GOTPAGE +L5: ldr x1, [x0, _external@GOTPAGEOFF] +L6: ldr q2, [x1, #16] +# NEAR-GOT-NEXT: nop +# NEAR-GOT-NEXT: ldr x1, #{{.*}} ; literal pool symbol address: _external +# NEAR-GOT-NEXT: ldr q2, [x1, #16] +# FAR-GOT-NEXT: adrp x0 +# FAR-GOT-NEXT: ldr x1 +# FAR-GOT-NEXT: ldr q2, [x1, #16] + +### Tests for invalid inputs +.ifndef PADDING +## Registers don't match +L7: adrp x0, _external@GOTPAGE +L8: ldr x1, [x1, _external@GOTPAGEOFF] +L9: ldr x2, [x1] +# NEAR-GOT-NEXT: adrp x0 +# NEAR-GOT-NEXT: ldr x1 +# NEAR-GOT-NEXT: ldr x2, [x1] + +## Registers don't match +L10: adrp x0, _external@GOTPAGE +L11: ldr x1, [x0, _external@GOTPAGEOFF] +L12: ldr x2, [x0] +# NEAR-GOT-NEXT: adrp x0 +# NEAR-GOT-NEXT: ldr x1 +# NEAR-GOT-NEXT: ldr x2, [x0] + +## Not an LDR (immediate) +L13: adrp x0, _external@GOTPAGE +L14: ldr x1, 0 +L15: ldr x2, [x1] +# NEAR-GOT-NEXT: adrp x0 +# NEAR-GOT-NEXT: ldr x1 +# NEAR-GOT-NEXT: ldr x2, [x1] + +.loh AdrpLdrGotLdr L7, L8, L9 +.loh AdrpLdrGotLdr L10, L11, L12 +.loh AdrpLdrGotLdr L13, L14, L15 +.endif + +.loh AdrpLdrGotLdr L1, L2, L3 +.loh AdrpLdrGotLdr L4, L5, L6 + +.ifdef PADDING +.space 1048576 +.endif +.data + + +#--- lib.s +.data +.align 4 +.globl _external +_external: + .zero 32 + +#--- local.s +.text +.align 2 +.globl _main +_main: + +### Transformation to a literal LDR +## Basic case +L1: adrp x0, _close@GOTPAGE +L2: ldr x1, [x0, _close@GOTPAGEOFF] +L3: ldr x2, [x1] +# LOCAL-LABEL: _main: +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr x2 + +## Load with offset +L4: adrp x0, _close@GOTPAGE +L5: ldr x1, [x0, _close@GOTPAGEOFF] +L6: ldr x2, [x1, #8] +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr x2 + +## 32 bit load +L7: adrp x0, _close@GOTPAGE +L8: ldr x1, [x0, _close@GOTPAGEOFF] +L9: ldr w1, [x1] +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr w1, _close + +## Floating point +L10: adrp x0, _close@GOTPAGE +L11: ldr x1, [x0, _close@GOTPAGEOFF] +L12: ldr s1, [x1] +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr s1, _close + +L13: adrp x0, _close@GOTPAGE +L14: ldr x1, [x0, _close@GOTPAGEOFF] +L15: ldr d1, [x1, #8] +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr d1, _close8 + +L16: adrp x0, _close@GOTPAGE +L17: ldr x1, [x0, _close@GOTPAGEOFF] +L18: ldr q0, [x1] +# LOCAL-NEXT: nop +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr q0, _close + + +### Transformation to ADR+LDR +## 1 byte floating point load +L19: adrp x0, _close@GOTPAGE +L20: ldr x1, [x0, _close@GOTPAGEOFF] +L21: ldr b2, [x1] +# LOCAL-NEXT: adr x1 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr b2, [x1] + +## 1 byte GPR load, zero extend +L22: adrp x0, _close@GOTPAGE +L23: ldr x1, [x0, _close@GOTPAGEOFF] +L24: ldrb w2, [x1] +# LOCAL-NEXT: adr x1 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldrb w2, [x1] + +## 1 byte GPR load, sign extend +L25: adrp x0, _close@GOTPAGE +L26: ldr x1, [x0, _close@GOTPAGEOFF] +L27: ldrsb x2, [x1] +# LOCAL-NEXT: adr x1 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldrsb x2, [x1] + +## Unaligned +L28: adrp x0, _unaligned@GOTPAGE +L29: ldr x1, [x0, _unaligned@GOTPAGEOFF] +L30: ldr x2, [x1] +# LOCAL-NEXT: adr x1 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr x2, [x1] + + +### Transformation to ADRP + immediate LDR +## Basic test: target is far +L31: adrp x0, _far@GOTPAGE +L32: ldr x1, [x0, _far@GOTPAGEOFF] +L33: ldr x2, [x1] +# LOCAL-NEXT: adrp x0 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr x2 + +## With offset +L34: adrp x0, _far@GOTPAGE +L35: ldr x1, [x0, _far@GOTPAGEOFF] +L36: ldr x2, [x1, #8] +# LOCAL-NEXT: adrp x0 +# LOCAL-NEXT: nop +# LOCAL-NEXT: ldr x2 + +### No changes other than GOT relaxation +## Far and unaligned +L37: adrp x0, _far_unaligned@GOTPAGE +L38: ldr x1, [x0, _far_unaligned@GOTPAGEOFF] +L39: ldr x2, [x1] +# LOCAL-NEXT: adrp x0 +# LOCAL-NEXT: add x1, x0 +# LOCAL-NEXT: ldr x2, [x1] + +## Far with large offset (_far_offset@GOTPAGEOFF + #255 > 4095) +L40: adrp x0, _far_offset@GOTPAGE +L41: ldr x1, [x0, _far_offset@GOTPAGEOFF] +L42: ldrb w2, [x1, #255] +# LOCAL-NEXT: adrp x0 +# LOCAL-NEXT: add x1, x0 +# LOCAL-NEXT: ldrb w2, [x1, #255] + +### Tests for invalid inputs, only GOT relaxation should happen +## Registers don't match +L43: adrp x0, _far@GOTPAGE +L44: ldr x1, [x0, _far@GOTPAGEOFF] +L45: ldr x2, [x2] +# LOCAL-NEXT: adrp x0 +# LOCAL-NEXT: add x1, x0 +# LOCAL-NEXT: ldr x2, [x2] + +.data +.align 4 + .quad 0 +_close: + .quad 0 +_close8: + .quad 0 + .byte 0 +_unaligned: + .quad 0 + +.space 1048576 +.align 12 + .quad 0 +_far: + .quad 0 + .byte 0 +_far_unaligned: + .quad 0 +.space 4000 +_far_offset: + .byte 0 + + +.loh AdrpLdrGotLdr L1, L2, L3 +.loh AdrpLdrGotLdr L4, L5, L6 +.loh AdrpLdrGotLdr L7, L8, L9 +.loh AdrpLdrGotLdr L10, L11, L12 +.loh AdrpLdrGotLdr L13, L14, L15 +.loh AdrpLdrGotLdr L16, L17, L18 +.loh AdrpLdrGotLdr L19, L20, L21 +.loh AdrpLdrGotLdr L22, L23, L24 +.loh AdrpLdrGotLdr L25, L26, L27 +.loh AdrpLdrGotLdr L28, L29, L30 +.loh AdrpLdrGotLdr L31, L32, L33 +.loh AdrpLdrGotLdr L34, L35, L36 +.loh AdrpLdrGotLdr L37, L38, L39 +.loh AdrpLdrGotLdr L40, L41, L42 +.loh AdrpLdrGotLdr L43, L44, L45 diff --git a/wild/tests/lld-macho/loh-adrp-ldr-got.s b/wild/tests/lld-macho/loh-adrp-ldr-got.s new file mode 100644 index 000000000..5363a1167 --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-ldr-got.s @@ -0,0 +1,35 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/obj.s -o %t/obj.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/dylib.s -o %t/dylib.o +# RUN: %lld -arch arm64 -dylib -o %t/libdylib.dylib %t/dylib.o +# RUN: %lld -arch arm64 %t/obj.o %t/libdylib.dylib -o %t/AdrpLdrGot +# RUN: llvm-objdump -d --macho %t/AdrpLdrGot | FileCheck %s + +#--- obj.s +.text +.globl _main +# CHECK-LABEL: _main: +_main: +## The referenced symbol is local +L1: adrp x0, _local@GOTPAGE +L2: ldr x0, [x0, _local@GOTPAGEOFF] +# CHECK-NEXT: adr x0 +# CHECK-NEXT: nop + +## The referenced symbol is in a dylib +L3: adrp x1, _external@GOTPAGE +L4: ldr x1, [x1, _external@GOTPAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x1 + +_local: + nop + +.loh AdrpLdrGot L1, L2 +.loh AdrpLdrGot L3, L4 + +#--- dylib.s +.globl _external +_external: diff --git a/wild/tests/lld-macho/loh-adrp-ldr.s b/wild/tests/lld-macho/loh-adrp-ldr.s new file mode 100644 index 000000000..956632ae9 --- /dev/null +++ b/wild/tests/lld-macho/loh-adrp-ldr.s @@ -0,0 +1,133 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 %t.o -o %t +# RUN: llvm-objdump --no-print-imm-hex -d --macho %t | FileCheck %s + +.text +.align 2 +_before_far: + .space 1048576 + +.align 2 +_before_near: + .quad 0 + +.globl _main +# CHECK-LABEL: _main: +_main: +## Out of range, before +L1: adrp x0, _before_far@PAGE +L2: ldr x0, [x0, _before_far@PAGEOFF] +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: ldr x0 + +## In range, before +L3: adrp x1, _before_near@PAGE +L4: ldr x1, [x1, _before_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x1, #-20 + +## Registers don't match (invalid input) +L5: adrp x2, _before_near@PAGE +L6: ldr x3, [x3, _before_near@PAGEOFF] +# CHECK-NEXT: adrp x2 +# CHECK-NEXT: ldr x3 + +## Not an adrp instruction +L9: udf 0 +L10: ldr x5, [x5, _after_near@PAGEOFF] +# CHECK-NEXT: udf +# CHECK-NEXT: ldr x5 + +## Not an ldr with an immediate offset +L11: adrp x6, _after_near@PAGE +L12: ldr x6, 0 +# CHECK-NEXT: adrp x6 +# CHECK-NEXT: ldr x6, #0 + +## Byte load, unsupported +L15: adrp x8, _after_near@PAGE +L16: ldr b8, [x8, _after_near@PAGEOFF] +# CHECK-NEXT: adrp x8 +# CHECK-NEXT: ldr b8 + +## Halfword load, unsupported +L17: adrp x9, _after_near@PAGE +L18: ldr h9, [x9, _after_near@PAGEOFF] +# CHECK-NEXT: adrp x9 +# CHECK-NEXT: ldr h9 + +## Word load +L19: adrp x10, _after_near@PAGE +L20: ldr w10, [x10, _after_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr w10, _after_near + +## With addend +L21: adrp x11, _after_near@PAGE + 8 +L22: ldr x11, [x11, _after_near@PAGEOFF + 8] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x11 + +## Signed 32-bit read from 16-bit value, unsupported +L23: adrp x12, _after_near@PAGE +L24: ldrsb w12, [x12, _after_near@PAGEOFF] +# CHECK-NEXT: adrp x12 +# CHECK-NEXT: ldrsb w12 + +## 64-bit load from signed 32-bit value +L25: adrp x13, _after_near@PAGE +L26: ldrsw x13, [x13, _after_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldrsw x13, _after_near + +## Single precision FP read +L27: adrp x14, _after_near@PAGE +L28: ldr s0, [x14, _after_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr s0, _after_near + +## Double precision FP read +L29: adrp x15, _after_near@PAGE +L30: ldr d0, [x15, _after_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr d0, _after_near + +## Quad precision FP read +L31: adrp x16, _after_near@PAGE +L32: ldr q0, [x16, _after_near@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr q0, _after_near + +## Out of range, after +L33: adrp x17, _after_far@PAGE +L34: ldr x17, [x17, _after_far@PAGEOFF] +# CHECK-NEXT: adrp x17 +# CHECK-NEXT: ldr x17 + +.data +.align 4 +_after_near: + .quad 0 + .quad 0 +.space 1048576 + +_after_far: + .quad 0 + +.loh AdrpLdr L1, L2 +.loh AdrpLdr L3, L4 +.loh AdrpLdr L5, L6 +.loh AdrpLdr L9, L10 +.loh AdrpLdr L11, L12 +.loh AdrpLdr L15, L16 +.loh AdrpLdr L17, L18 +.loh AdrpLdr L19, L20 +.loh AdrpLdr L21, L22 +.loh AdrpLdr L23, L24 +.loh AdrpLdr L25, L26 +.loh AdrpLdr L27, L28 +.loh AdrpLdr L29, L30 +.loh AdrpLdr L31, L32 +.loh AdrpLdr L33, L34 diff --git a/wild/tests/lld-macho/loh-arm64-32.s b/wild/tests/lld-macho/loh-arm64-32.s new file mode 100644 index 000000000..906d0e1ce --- /dev/null +++ b/wild/tests/lld-macho/loh-arm64-32.s @@ -0,0 +1,64 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t.o +# RUN: %lld-watchos -U _external %t.o -o %t +# RUN: llvm-objdump -d --macho %t | FileCheck %s + +.text +.align 2 +.globl _foo +_foo: + ret +.globl _bar +_bar: + ret + +.globl _main +_main: +# CHECK-LABEL: _main: + +L1: adrp x0, _foo@PAGE +L2: add x0, x0, _foo@PAGEOFF +# CHECK-NEXT: adr x0 +# CHECK-NEXT: nop + +L3: adrp x0, _ptr@PAGE +L4: add x1, x0, _ptr@PAGEOFF +L5: ldr x2, [x1] +# CHECK-NEXT: nop +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x2 + +L6: adrp x0, _foo@PAGE +L7: adrp x0, _bar@PAGE +# CHECK-NEXT: adrp x0 +# CHECK-NEXT: nop + +L8: adrp x0, _ptr@PAGE +L9: ldr x0, [x0, _ptr@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr x0 + +L10: adrp x0, _ptr@PAGE +L11: ldr w0, [x0, _ptr@PAGEOFF] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr w0, _ptr + +L12: adrp x0, _external@PAGE +L13: ldr w1, [x0, _external@PAGEOFF] +L14: ldr x2, [x1] +# CHECK-NEXT: nop +# CHECK-NEXT: ldr w1, 0x{{.*}} +# CHECK-NEXT: ldr x2, [x1] + +.data +.align 4 +_ptr: + .quad 0 + +.loh AdrpAdd L1, L2 +.loh AdrpAddLdr L3, L4, L5 +.loh AdrpAdrp L6, L7 +.loh AdrpLdr L8, L9 +.loh AdrpLdrGot L10, L11 +.loh AdrpLdrGotLdr L12, L13, L14 diff --git a/wild/tests/lld-macho/loh-parsing.s b/wild/tests/lld-macho/loh-parsing.s new file mode 100644 index 000000000..aad1af359 --- /dev/null +++ b/wild/tests/lld-macho/loh-parsing.s @@ -0,0 +1,24 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -dylib %t.o -o /dev/null + +## Check that we parse the LOH & match it to its referent sections correctly, +## even when there are other subsections that don't get parsed as regular +## sections. (We would previously segfault.) +## __debug_info is one such section that gets special-case handling. + +.text +_foo: + +.section __DWARF,__debug_info,regular,debug + +## __StaticInit occurs after __debug_info in the input object file, so the +## LOH-matching code will have to "walk" past __debug_info while searching for +## __StaticInit. Thus this verifies that we can skip past __debug_info +## correctly. +.section __TEXT,__StaticInit +L1: adrp x1, _foo@PAGE +L2: ldr x1, [x1, _foo@PAGEOFF] + +.loh AdrpLdr L1, L2 diff --git a/wild/tests/lld-macho/no-pie.s b/wild/tests/lld-macho/no-pie.s new file mode 100644 index 000000000..c51e2b3f9 --- /dev/null +++ b/wild/tests/lld-macho/no-pie.s @@ -0,0 +1,17 @@ +# REQUIRES: aarch64, x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.x86_64.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.arm64.o +# RUN: llvm-mc -filetype=obj -triple=arm64e-apple-darwin %s -o %t.arm64e.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t.arm64_32.o + +# RUN: %lld -arch x86_64 -lSystem -no_pie -o %t %t.x86_64.o +# RUN: not %lld -arch arm64 -lSystem -no_pie -o %t %t.arm64.o 2>&1 | FileCheck %s +# RUN: not %lld -arch arm64e -lSystem -no_pie -o %t %t.arm64e.o 2>&1 | FileCheck %s +# RUN: not %lld-watchos -arch arm64_32 -lSystem -no_pie -o %t %t.arm64_32.o 2>&1 | FileCheck %s + +# CHECK: error: -no_pie ignored for arm64 + +.globl _main +_main: + ret diff --git a/wild/tests/lld-macho/objc-category-merging-complete-test.s b/wild/tests/lld-macho/objc-category-merging-complete-test.s new file mode 100644 index 000000000..3befd683e --- /dev/null +++ b/wild/tests/lld-macho/objc-category-merging-complete-test.s @@ -0,0 +1,1023 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t && cd %t + +############ Test merging multiple categories into a single category ############ +## Create a dylib to link against(a64_file1.dylib) and merge categories in the main binary (file2_merge_a64.exe) +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o a64_file1.o a64_file1.s +# RUN: %lld -arch arm64 a64_file1.o -o a64_file1.dylib -dylib + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o a64_file2.o a64_file2.s +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -o a64_file2_no_merge.exe a64_file1.dylib a64_file2.o +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -o a64_file2_no_merge_v2.exe a64_file1.dylib a64_file2.o -no_objc_category_merging +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -o a64_file2_no_merge_v3.exe a64_file1.dylib a64_file2.o -objc_category_merging -no_objc_category_merging +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -o a64_file2_merge.exe -objc_category_merging a64_file1.dylib a64_file2.o + +# RUN: llvm-objdump --objc-meta-data --macho a64_file2_no_merge.exe | FileCheck %s --check-prefixes=NO_MERGE_CATS +# RUN: llvm-objdump --objc-meta-data --macho a64_file2_no_merge_v2.exe | FileCheck %s --check-prefixes=NO_MERGE_CATS +# RUN: llvm-objdump --objc-meta-data --macho a64_file2_no_merge_v3.exe | FileCheck %s --check-prefixes=NO_MERGE_CATS +# RUN: llvm-objdump --objc-meta-data --macho a64_file2_merge.exe | FileCheck %s --check-prefixes=MERGE_CATS + +############ Test merging multiple categories into the base class ############ +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -o a64_file2_merge_into_class.exe -objc_category_merging a64_file1.o a64_file2.o +# RUN: llvm-objdump --objc-meta-data --macho a64_file2_merge_into_class.exe | FileCheck %s --check-prefixes=MERGE_CATS_CLS + + +MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass(Category02|Category03) +MERGE_CATS-NEXT: name {{.*}} Category02|Category03 +MERGE_CATS: instanceMethods +MERGE_CATS-NEXT: entsize 24 +MERGE_CATS-NEXT: count 4 +MERGE_CATS-NEXT: name {{.*}} class02InstanceMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp -[MyBaseClass(Category02) class02InstanceMethod] +MERGE_CATS-NEXT: name {{.*}} myProtocol02Method +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp -[MyBaseClass(Category02) myProtocol02Method] +MERGE_CATS-NEXT: name {{.*}} class03InstanceMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp -[MyBaseClass(Category03) class03InstanceMethod] +MERGE_CATS-NEXT: name {{.*}} myProtocol03Method +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp -[MyBaseClass(Category03) myProtocol03Method] +MERGE_CATS-NEXT: classMethods {{.*}} +MERGE_CATS-NEXT: entsize 24 +MERGE_CATS-NEXT: count 4 +MERGE_CATS-NEXT: name {{.*}} class02ClassMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp +[MyBaseClass(Category02) class02ClassMethod] +MERGE_CATS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS-NEXT: imp +[MyBaseClass(Category02) MyProtocol02Prop] +MERGE_CATS-NEXT: name {{.*}} class03ClassMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp +[MyBaseClass(Category03) class03ClassMethod] +MERGE_CATS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS-NEXT: imp +[MyBaseClass(Category03) MyProtocol03Prop] +MERGE_CATS-NEXT: protocols +MERGE_CATS-NEXT: count 2 +MERGE_CATS-NEXT: list[0] {{.*}} (struct protocol_t *) +MERGE_CATS-NEXT: isa 0x0 +MERGE_CATS-NEXT: name {{.*}} MyProtocol02 +MERGE_CATS-NEXT: protocols 0x0 +MERGE_CATS-NEXT: instanceMethods +MERGE_CATS-NEXT: entsize 24 +MERGE_CATS-NEXT: count 2 +MERGE_CATS-NEXT: name {{.*}} myProtocol02Method +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp 0x0 +MERGE_CATS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS-NEXT: imp 0x0 +MERGE_CATS-NEXT: classMethods +MERGE_CATS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS-NEXT: optionalClassMethods 0x0 +MERGE_CATS-NEXT: instanceProperties {{.*}} +MERGE_CATS-NEXT: list[1] {{.*}} +MERGE_CATS-NEXT: isa 0x0 +MERGE_CATS-NEXT: name {{.*}} MyProtocol03 +MERGE_CATS-NEXT: protocols 0x0 +MERGE_CATS-NEXT: instanceMethods +MERGE_CATS-NEXT: entsize 24 +MERGE_CATS-NEXT: count 2 +MERGE_CATS-NEXT: name {{.*}} myProtocol03Method +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp 0x0 +MERGE_CATS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS-NEXT: imp 0x0 +MERGE_CATS-NEXT: classMethods 0x0 +MERGE_CATS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS-NEXT: optionalClassMethods 0x0 +MERGE_CATS-NEXT: instanceProperties {{.*}} +MERGE_CATS-NEXT: instanceProperties +MERGE_CATS-NEXT: entsize 16 +MERGE_CATS-NEXT: count 2 +MERGE_CATS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS-NEXT: attributes {{.*}} Ti,R,D +MERGE_CATS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS-NEXT: attributes {{.*}} Ti,R,D +MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category04 + + +NO_MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass(Category02|Category03) +NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 +NO_MERGE_CATS: instanceMethods +NO_MERGE_CATS-NEXT: 24 +NO_MERGE_CATS-NEXT: 2 +NO_MERGE_CATS: classMethods +NO_MERGE_CATS-NEXT: 24 +NO_MERGE_CATS-NEXT: 2 + + +MERGE_CATS_CLS: _OBJC_CLASS_$_MyBaseClass +MERGE_CATS_CLS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass +MERGE_CATS_CLS-NEXT: superclass 0x0 +MERGE_CATS_CLS-NEXT: cache {{.*}} __objc_empty_cache +MERGE_CATS_CLS-NEXT: vtable 0x0 +MERGE_CATS_CLS-NEXT: data {{.*}} (struct class_ro_t *) +MERGE_CATS_CLS-NEXT: flags 0x2 RO_ROOT +MERGE_CATS_CLS-NEXT: instanceStart 0 +MERGE_CATS_CLS-NEXT: instanceSize 4 +MERGE_CATS_CLS-NEXT: reserved 0x0 +MERGE_CATS_CLS-NEXT: ivarLayout 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyBaseClass +MERGE_CATS_CLS-NEXT: baseMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 8 +MERGE_CATS_CLS-NEXT: name {{.*}} class02InstanceMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category02) class02InstanceMethod] +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category02) myProtocol02Method] +MERGE_CATS_CLS-NEXT: name {{.*}} class03InstanceMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category03) class03InstanceMethod] +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category03) myProtocol03Method] +MERGE_CATS_CLS-NEXT: name {{.*}} baseInstanceMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass baseInstanceMethod] +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass myProtocol01Method] +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass MyProtocol01Prop] +MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop: +MERGE_CATS_CLS-NEXT: types {{.*}} v20@0:8i16 +MERGE_CATS_CLS-NEXT: imp -[MyBaseClass setMyProtocol01Prop:] +MERGE_CATS_CLS-NEXT: baseProtocols {{.*}} +MERGE_CATS_CLS-NEXT: count 3 +MERGE_CATS_CLS-NEXT: list[0] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 2 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: list[1] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 2 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: list[2] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 3 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop: +MERGE_CATS_CLS-NEXT: types {{.*}} v20@0:8i16 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: ivars {{.*}} +MERGE_CATS_CLS-NEXT: entsize 32 +MERGE_CATS_CLS-NEXT: count 1 +MERGE_CATS_CLS-NEXT: offset {{.*}} 0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop +MERGE_CATS_CLS-NEXT: type {{.*}} i +MERGE_CATS_CLS-NEXT: alignment 2 +MERGE_CATS_CLS-NEXT: size 4 +MERGE_CATS_CLS-NEXT: weakIvarLayout 0x0 +MERGE_CATS_CLS-NEXT: baseProperties {{.*}} +MERGE_CATS_CLS-NEXT: entsize 16 +MERGE_CATS_CLS-NEXT: count 3 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,R,D +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,R,D +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop +MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,N,VMyProtocol01Prop +MERGE_CATS_CLS-NEXT: Meta Class +MERGE_CATS_CLS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass +MERGE_CATS_CLS-NEXT: superclass {{.*}} _OBJC_CLASS_$_MyBaseClass +MERGE_CATS_CLS-NEXT: cache {{.*}} __objc_empty_cache +MERGE_CATS_CLS-NEXT: vtable 0x0 +MERGE_CATS_CLS-NEXT: data {{.*}} (struct class_ro_t *) +MERGE_CATS_CLS-NEXT: flags 0x3 RO_META RO_ROOT +MERGE_CATS_CLS-NEXT: instanceStart 40 +MERGE_CATS_CLS-NEXT: instanceSize 40 +MERGE_CATS_CLS-NEXT: reserved 0x0 +MERGE_CATS_CLS-NEXT: ivarLayout 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyBaseClass +MERGE_CATS_CLS-NEXT: baseMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 5 +MERGE_CATS_CLS-NEXT: name {{.*}} class02ClassMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category02) class02ClassMethod] +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category02) MyProtocol02Prop] +MERGE_CATS_CLS-NEXT: name {{.*}} class03ClassMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category03) class03ClassMethod] +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category03) MyProtocol03Prop] +MERGE_CATS_CLS-NEXT: name {{.*}} baseClassMethod +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp +[MyBaseClass baseClassMethod] +MERGE_CATS_CLS-NEXT: baseProtocols {{.*}} +MERGE_CATS_CLS-NEXT: count 3 +MERGE_CATS_CLS-NEXT: list[0] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 2 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: list[1] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 2 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: list[2] {{.*}} (struct protocol_t *) +MERGE_CATS_CLS-NEXT: isa 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01 +MERGE_CATS_CLS-NEXT: protocols 0x0 +MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +MERGE_CATS_CLS-NEXT: entsize 24 +MERGE_CATS_CLS-NEXT: count 3 +MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method +MERGE_CATS_CLS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop +MERGE_CATS_CLS-NEXT: types {{.*}} i16@0:8 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop: +MERGE_CATS_CLS-NEXT: types {{.*}} v20@0:8i16 +MERGE_CATS_CLS-NEXT: imp 0x0 +MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *) +MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0 +MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0 +MERGE_CATS_CLS-NEXT: instanceProperties {{.*}} +MERGE_CATS_CLS-NEXT: ivars 0x0 +MERGE_CATS_CLS-NEXT: weakIvarLayout 0x0 +MERGE_CATS_CLS-NEXT: baseProperties 0x0 +MERGE_CATS_CLS: __OBJC_$_CATEGORY_MyBaseClass_$_Category04 + + +#--- a64_file1.s + +## @protocol MyProtocol01 +## - (void)myProtocol01Method; +## @property (nonatomic) int MyProtocol01Prop; +## @end +## +## __attribute__((objc_root_class)) +## @interface MyBaseClass +## - (void)baseInstanceMethod; +## - (void)myProtocol01Method; +## + (void)baseClassMethod; +## @end +## +## @implementation MyBaseClass +## @synthesize MyProtocol01Prop; +## - (void)baseInstanceMethod {} +## - (void)myProtocol01Method {} +## + (void)baseClassMethod {} +## @end +## +## void *_objc_empty_cache; + + .section __TEXT,__text,regular,pure_instructions + .p2align 2 ; -- Begin function -[MyBaseClass baseInstanceMethod] +"-[MyBaseClass baseInstanceMethod]": ; @"\01-[MyBaseClass baseInstanceMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass myProtocol01Method] +"-[MyBaseClass myProtocol01Method]": ; @"\01-[MyBaseClass myProtocol01Method]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function +[MyBaseClass baseClassMethod] +"+[MyBaseClass baseClassMethod]": ; @"\01+[MyBaseClass baseClassMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass MyProtocol01Prop] +"-[MyBaseClass MyProtocol01Prop]": ; @"\01-[MyBaseClass MyProtocol01Prop]" + .cfi_startproc +; %bb.0: ; %entry +Lloh0: + adrp x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@PAGE +Lloh1: + ldrsw x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@PAGEOFF] + ldr w0, [x0, x8] + ret + .loh AdrpLdr Lloh0, Lloh1 + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass setMyProtocol01Prop:] +"-[MyBaseClass setMyProtocol01Prop:]": ; @"\01-[MyBaseClass setMyProtocol01Prop:]" + .cfi_startproc +; %bb.0: ; %entry +Lloh2: + adrp x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@PAGE +Lloh3: + ldrsw x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@PAGEOFF] + str w2, [x0, x8] + ret + .loh AdrpLdr Lloh2, Lloh3 + .cfi_endproc + ; -- End function + .private_extern _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop ; @"OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop" + .section __DATA,__objc_ivar + .globl _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop + .p2align 2, 0x0 +_OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop: + .long 0 ; 0x0 + .section __DATA,__objc_data + .globl _OBJC_CLASS_$_MyBaseClass ; @"OBJC_CLASS_$_MyBaseClass" + .p2align 3, 0x0 +_OBJC_CLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad 0 + .quad __objc_empty_cache + .quad 0 + .quad __OBJC_CLASS_RO_$_MyBaseClass + .globl _OBJC_METACLASS_$_MyBaseClass ; @"OBJC_METACLASS_$_MyBaseClass" + .p2align 3, 0x0 +_OBJC_METACLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad _OBJC_CLASS_$_MyBaseClass + .quad __objc_empty_cache + .quad 0 + .quad __OBJC_METACLASS_RO_$_MyBaseClass + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: ; @OBJC_CLASS_NAME_ + .asciz "MyBaseClass" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: ; @OBJC_METH_VAR_NAME_ + .asciz "baseClassMethod" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: ; @OBJC_METH_VAR_TYPE_ + .asciz "v16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CLASS_METHODS_MyBaseClass" +__OBJC_$_CLASS_METHODS_MyBaseClass: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyBaseClass baseClassMethod]" + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.1: ; @OBJC_CLASS_NAME_.1 + .asciz "MyProtocol01" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.2: ; @OBJC_METH_VAR_NAME_.2 + .asciz "myProtocol01Method" +l_OBJC_METH_VAR_NAME_.3: ; @OBJC_METH_VAR_NAME_.3 + .asciz "MyProtocol01Prop" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.4: ; @OBJC_METH_VAR_TYPE_.4 + .asciz "i16@0:8" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.5: ; @OBJC_METH_VAR_NAME_.5 + .asciz "setMyProtocol01Prop:" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.6: ; @OBJC_METH_VAR_TYPE_.6 + .asciz "v20@0:8i16" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01" +__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01: + .long 24 ; 0x18 + .long 3 ; 0x3 + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad 0 + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad 0 + .quad l_OBJC_METH_VAR_NAME_.5 + .quad l_OBJC_METH_VAR_TYPE_.6 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_PROP_NAME_ATTR_: ; @OBJC_PROP_NAME_ATTR_ + .asciz "MyProtocol01Prop" +l_OBJC_PROP_NAME_ATTR_.7: ; @OBJC_PROP_NAME_ATTR_.7 + .asciz "Ti,N" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyProtocol01" +__OBJC_$_PROP_LIST_MyProtocol01: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_ + .quad l_OBJC_PROP_NAME_ATTR_.7 + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01" +__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01: + .quad l_OBJC_METH_VAR_TYPE_ + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad l_OBJC_METH_VAR_TYPE_.6 + .private_extern __OBJC_PROTOCOL_$_MyProtocol01 ; @"_OBJC_PROTOCOL_$_MyProtocol01" + .section __DATA,__data + .globl __OBJC_PROTOCOL_$_MyProtocol01 + .weak_definition __OBJC_PROTOCOL_$_MyProtocol01 + .p2align 3, 0x0 +__OBJC_PROTOCOL_$_MyProtocol01: + .quad 0 + .quad l_OBJC_CLASS_NAME_.1 + .quad 0 + .quad __OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01 + .quad 0 + .quad 0 + .quad 0 + .quad __OBJC_$_PROP_LIST_MyProtocol01 + .long 96 ; 0x60 + .long 0 ; 0x0 + .quad __OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01 + .quad 0 + .quad 0 + .private_extern __OBJC_LABEL_PROTOCOL_$_MyProtocol01 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol01" + .section __DATA,__objc_protolist,coalesced,no_dead_strip + .globl __OBJC_LABEL_PROTOCOL_$_MyProtocol01 + .weak_definition __OBJC_LABEL_PROTOCOL_$_MyProtocol01 + .p2align 3, 0x0 +__OBJC_LABEL_PROTOCOL_$_MyProtocol01: + .quad __OBJC_PROTOCOL_$_MyProtocol01 + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_CLASS_PROTOCOLS_$_MyBaseClass" +__OBJC_CLASS_PROTOCOLS_$_MyBaseClass: + .quad 1 ; 0x1 + .quad __OBJC_PROTOCOL_$_MyProtocol01 + .quad 0 + .p2align 3, 0x0 ; @"_OBJC_METACLASS_RO_$_MyBaseClass" +__OBJC_METACLASS_RO_$_MyBaseClass: + .long 3 ; 0x3 + .long 40 ; 0x28 + .long 40 ; 0x28 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_CLASS_METHODS_MyBaseClass + .quad __OBJC_CLASS_PROTOCOLS_$_MyBaseClass + .quad 0 + .quad 0 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.8: ; @OBJC_METH_VAR_NAME_.8 + .asciz "baseInstanceMethod" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_INSTANCE_METHODS_MyBaseClass" +__OBJC_$_INSTANCE_METHODS_MyBaseClass: + .long 24 ; 0x18 + .long 4 ; 0x4 + .quad l_OBJC_METH_VAR_NAME_.8 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass baseInstanceMethod]" + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass myProtocol01Method]" + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad "-[MyBaseClass MyProtocol01Prop]" + .quad l_OBJC_METH_VAR_NAME_.5 + .quad l_OBJC_METH_VAR_TYPE_.6 + .quad "-[MyBaseClass setMyProtocol01Prop:]" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.9: ; @OBJC_METH_VAR_TYPE_.9 + .asciz "i" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_INSTANCE_VARIABLES_MyBaseClass" +__OBJC_$_INSTANCE_VARIABLES_MyBaseClass: + .long 32 ; 0x20 + .long 1 ; 0x1 + .quad _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_.9 + .long 2 ; 0x2 + .long 4 ; 0x4 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_PROP_NAME_ATTR_.10: ; @OBJC_PROP_NAME_ATTR_.10 + .asciz "Ti,N,VMyProtocol01Prop" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyBaseClass" +__OBJC_$_PROP_LIST_MyBaseClass: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_ + .quad l_OBJC_PROP_NAME_ATTR_.10 + .p2align 3, 0x0 ; @"_OBJC_CLASS_RO_$_MyBaseClass" +__OBJC_CLASS_RO_$_MyBaseClass: + .long 2 ; 0x2 + .long 0 ; 0x0 + .long 4 ; 0x4 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_MyBaseClass + .quad __OBJC_CLASS_PROTOCOLS_$_MyBaseClass + .quad __OBJC_$_INSTANCE_VARIABLES_MyBaseClass + .quad 0 + .quad __OBJC_$_PROP_LIST_MyBaseClass + .globl __objc_empty_cache ; @_objc_empty_cache +.zerofill __DATA,__common,__objc_empty_cache,8,3 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CLASS_$" +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_MyBaseClass + .no_dead_strip __OBJC_LABEL_PROTOCOL_$_MyProtocol01 + .no_dead_strip __OBJC_PROTOCOL_$_MyProtocol01 + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 96 +.subsections_via_symbols + + +#--- a64_file2.s + +## @protocol MyProtocol01 +## - (void)myProtocol01Method; +## @end +## +## @protocol MyProtocol02 +## - (void)myProtocol02Method; +## @property(readonly) int MyProtocol02Prop; +## @end +## +## @protocol MyProtocol03 +## - (void)myProtocol03Method; +## @property(readonly) int MyProtocol03Prop; +## @end +## +## +## __attribute__((objc_root_class)) +## @interface MyBaseClass +## - (void)baseInstanceMethod; +## - (void)myProtocol01Method; +## + (void)baseClassMethod; +## @end +## +## +## +## @interface MyBaseClass(Category02) +## - (void)class02InstanceMethod; +## - (void)myProtocol02Method; +## + (void)class02ClassMethod; +## + (int)MyProtocol02Prop; +## @end +## +## @implementation MyBaseClass(Category02) +## - (void)class02InstanceMethod {} +## - (void)myProtocol02Method {} +## + (void)class02ClassMethod {} +## + (int)MyProtocol02Prop { return 0;} +## @dynamic MyProtocol02Prop; +## @end +## +## @interface MyBaseClass(Category03) +## - (void)class03InstanceMethod; +## - (void)myProtocol03Method; +## + (void)class03ClassMethod; +## + (int)MyProtocol03Prop; +## @end +## +## @implementation MyBaseClass(Category03) +## - (void)class03InstanceMethod {} +## - (void)myProtocol03Method {} +## + (void)class03ClassMethod {} +## + (int)MyProtocol03Prop { return 0;} +## @dynamic MyProtocol03Prop; +## @end +## +## // This category shouldn't be merged +## @interface MyBaseClass(Category04) +## + (void)load; +## @end +## +## @implementation MyBaseClass(Category04) +## + (void)load {} +## @end +## +## int main() { +## return 0; +## } + + + .section __TEXT,__text,regular,pure_instructions + .p2align 2 ; -- Begin function -[MyBaseClass(Category02) class02InstanceMethod] +"-[MyBaseClass(Category02) class02InstanceMethod]": ; @"\01-[MyBaseClass(Category02) class02InstanceMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass(Category02) myProtocol02Method] +"-[MyBaseClass(Category02) myProtocol02Method]": ; @"\01-[MyBaseClass(Category02) myProtocol02Method]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function +[MyBaseClass(Category02) class02ClassMethod] +"+[MyBaseClass(Category02) class02ClassMethod]": ; @"\01+[MyBaseClass(Category02) class02ClassMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function +[MyBaseClass(Category02) MyProtocol02Prop] +"+[MyBaseClass(Category02) MyProtocol02Prop]": ; @"\01+[MyBaseClass(Category02) MyProtocol02Prop]" + .cfi_startproc +; %bb.0: ; %entry + b _OUTLINED_FUNCTION_0 + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass(Category03) class03InstanceMethod] +"-[MyBaseClass(Category03) class03InstanceMethod]": ; @"\01-[MyBaseClass(Category03) class03InstanceMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass(Category03) myProtocol03Method] +"-[MyBaseClass(Category03) myProtocol03Method]": ; @"\01-[MyBaseClass(Category03) myProtocol03Method]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function +[MyBaseClass(Category03) class03ClassMethod] +"+[MyBaseClass(Category03) class03ClassMethod]": ; @"\01+[MyBaseClass(Category03) class03ClassMethod]" + .cfi_startproc +; %bb.0: ; %entry + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function +[MyBaseClass(Category03) MyProtocol03Prop] +"+[MyBaseClass(Category03) MyProtocol03Prop]": ; @"\01+[MyBaseClass(Category03) MyProtocol03Prop]" + .cfi_startproc +; %bb.0: ; %entry + b _OUTLINED_FUNCTION_0 + .cfi_endproc + ; -- End function + .p2align 2 +"+[MyBaseClass(Category04) load]": + .cfi_startproc +; %bb.0: + ret + .cfi_endproc + .globl _main ; -- Begin function main + .p2align 2 +_main: ; @main + .cfi_startproc +; %bb.0: ; %entry + b _OUTLINED_FUNCTION_0 + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function OUTLINED_FUNCTION_0 +_OUTLINED_FUNCTION_0: ; @OUTLINED_FUNCTION_0 Tail Call + .cfi_startproc +; %bb.0: + mov w0, #0 + ret + .cfi_endproc + ; -- End function + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: ; @OBJC_CLASS_NAME_ + .asciz "Category02" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: ; @OBJC_METH_VAR_NAME_ + .asciz "class02InstanceMethod" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: ; @OBJC_METH_VAR_TYPE_ + .asciz "v16@0:8" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.1: ; @OBJC_METH_VAR_NAME_.1 + .asciz "myProtocol02Method" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02" +__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category02) class02InstanceMethod]" + .quad l_OBJC_METH_VAR_NAME_.1 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category02) myProtocol02Method]" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.2: ; @OBJC_METH_VAR_NAME_.2 + .asciz "class02ClassMethod" +l_OBJC_METH_VAR_NAME_.3: ; @OBJC_METH_VAR_NAME_.3 + .asciz "MyProtocol02Prop" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.4: ; @OBJC_METH_VAR_TYPE_.4 + .asciz "i16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02" +__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyBaseClass(Category02) class02ClassMethod]" + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad "+[MyBaseClass(Category02) MyProtocol02Prop]" + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.5: ; @OBJC_CLASS_NAME_.5 + .asciz "MyProtocol02" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02" +__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_.1 + .quad l_OBJC_METH_VAR_TYPE_ + .quad 0 + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_PROP_NAME_ATTR_: ; @OBJC_PROP_NAME_ATTR_ + .asciz "MyProtocol02Prop" +l_OBJC_PROP_NAME_ATTR_.6: ; @OBJC_PROP_NAME_ATTR_.6 + .asciz "Ti,R" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyProtocol02" +__OBJC_$_PROP_LIST_MyProtocol02: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_ + .quad l_OBJC_PROP_NAME_ATTR_.6 + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02" +__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02: + .quad l_OBJC_METH_VAR_TYPE_ + .quad l_OBJC_METH_VAR_TYPE_.4 + .private_extern __OBJC_PROTOCOL_$_MyProtocol02 ; @"_OBJC_PROTOCOL_$_MyProtocol02" + .section __DATA,__data + .globl __OBJC_PROTOCOL_$_MyProtocol02 + .weak_definition __OBJC_PROTOCOL_$_MyProtocol02 + .p2align 3, 0x0 +__OBJC_PROTOCOL_$_MyProtocol02: + .quad 0 + .quad l_OBJC_CLASS_NAME_.5 + .quad 0 + .quad __OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02 + .quad 0 + .quad 0 + .quad 0 + .quad __OBJC_$_PROP_LIST_MyProtocol02 + .long 96 ; 0x60 + .long 0 ; 0x0 + .quad __OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02 + .quad 0 + .quad 0 + .private_extern __OBJC_LABEL_PROTOCOL_$_MyProtocol02 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol02" + .section __DATA,__objc_protolist,coalesced,no_dead_strip + .globl __OBJC_LABEL_PROTOCOL_$_MyProtocol02 + .weak_definition __OBJC_LABEL_PROTOCOL_$_MyProtocol02 + .p2align 3, 0x0 +__OBJC_LABEL_PROTOCOL_$_MyProtocol02: + .quad __OBJC_PROTOCOL_$_MyProtocol02 + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02" +__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02: + .quad 1 ; 0x1 + .quad __OBJC_PROTOCOL_$_MyProtocol02 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_PROP_NAME_ATTR_.7: ; @OBJC_PROP_NAME_ATTR_.7 + .asciz "Ti,R,D" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyBaseClass_$_Category02" +__OBJC_$_PROP_LIST_MyBaseClass_$_Category02: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_ + .quad l_OBJC_PROP_NAME_ATTR_.7 + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category02" +__OBJC_$_CATEGORY_MyBaseClass_$_Category02: + .quad l_OBJC_CLASS_NAME_ + .quad _OBJC_CLASS_$_MyBaseClass + .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02 + .quad __OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02 + .quad __OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02 + .quad __OBJC_$_PROP_LIST_MyBaseClass_$_Category02 + .quad 0 + .long 64 ; 0x40 + .space 4 + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.8: ; @OBJC_CLASS_NAME_.8 + .asciz "Category03" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.9: ; @OBJC_METH_VAR_NAME_.9 + .asciz "class03InstanceMethod" +l_OBJC_METH_VAR_NAME_.10: ; @OBJC_METH_VAR_NAME_.10 + .asciz "myProtocol03Method" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03" +__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_.9 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category03) class03InstanceMethod]" + .quad l_OBJC_METH_VAR_NAME_.10 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category03) myProtocol03Method]" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.11: ; @OBJC_METH_VAR_NAME_.11 + .asciz "class03ClassMethod" +l_OBJC_METH_VAR_NAME_.12: ; @OBJC_METH_VAR_NAME_.12 + .asciz "MyProtocol03Prop" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03" +__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_.11 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyBaseClass(Category03) class03ClassMethod]" + .quad l_OBJC_METH_VAR_NAME_.12 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad "+[MyBaseClass(Category03) MyProtocol03Prop]" + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.13: ; @OBJC_CLASS_NAME_.13 + .asciz "MyProtocol03" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03" +__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03: + .long 24 ; 0x18 + .long 2 ; 0x2 + .quad l_OBJC_METH_VAR_NAME_.10 + .quad l_OBJC_METH_VAR_TYPE_ + .quad 0 + .quad l_OBJC_METH_VAR_NAME_.12 + .quad l_OBJC_METH_VAR_TYPE_.4 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_PROP_NAME_ATTR_.14: ; @OBJC_PROP_NAME_ATTR_.14 + .asciz "MyProtocol03Prop" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyProtocol03" +__OBJC_$_PROP_LIST_MyProtocol03: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_.14 + .quad l_OBJC_PROP_NAME_ATTR_.6 + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03" +__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03: + .quad l_OBJC_METH_VAR_TYPE_ + .quad l_OBJC_METH_VAR_TYPE_.4 + .private_extern __OBJC_PROTOCOL_$_MyProtocol03 ; @"_OBJC_PROTOCOL_$_MyProtocol03" + .section __DATA,__data + .globl __OBJC_PROTOCOL_$_MyProtocol03 + .weak_definition __OBJC_PROTOCOL_$_MyProtocol03 + .p2align 3, 0x0 +__OBJC_PROTOCOL_$_MyProtocol03: + .quad 0 + .quad l_OBJC_CLASS_NAME_.13 + .quad 0 + .quad __OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03 + .quad 0 + .quad 0 + .quad 0 + .quad __OBJC_$_PROP_LIST_MyProtocol03 + .long 96 ; 0x60 + .long 0 ; 0x0 + .quad __OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03 + .quad 0 + .quad 0 + .private_extern __OBJC_LABEL_PROTOCOL_$_MyProtocol03 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol03" + .section __DATA,__objc_protolist,coalesced,no_dead_strip + .globl __OBJC_LABEL_PROTOCOL_$_MyProtocol03 + .weak_definition __OBJC_LABEL_PROTOCOL_$_MyProtocol03 + .p2align 3, 0x0 +__OBJC_LABEL_PROTOCOL_$_MyProtocol03: + .quad __OBJC_PROTOCOL_$_MyProtocol03 + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03" +__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03: + .quad 1 ; 0x1 + .quad __OBJC_PROTOCOL_$_MyProtocol03 + .quad 0 + .p2align 3, 0x0 ; @"_OBJC_$_PROP_LIST_MyBaseClass_$_Category03" +__OBJC_$_PROP_LIST_MyBaseClass_$_Category03: + .long 16 ; 0x10 + .long 1 ; 0x1 + .quad l_OBJC_PROP_NAME_ATTR_.14 + .quad l_OBJC_PROP_NAME_ATTR_.7 + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category03" +__OBJC_$_CATEGORY_MyBaseClass_$_Category03: + .quad l_OBJC_CLASS_NAME_.8 + .quad _OBJC_CLASS_$_MyBaseClass + .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03 + .quad __OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03 + .quad __OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03 + .quad __OBJC_$_PROP_LIST_MyBaseClass_$_Category03 + .quad 0 + .long 64 ; 0x40 + .space 4 + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.15: + .asciz "Category04" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.16: + .asciz "load" + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category04: + .long 24 + .long 1 + .quad l_OBJC_METH_VAR_NAME_.16 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyBaseClass(Category04) load]" + .p2align 3, 0x0 +__OBJC_$_CATEGORY_MyBaseClass_$_Category04: + .quad l_OBJC_CLASS_NAME_.15 + .quad _OBJC_CLASS_$_MyBaseClass + .quad 0 + .quad __OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category04 + .quad 0 + .quad 0 + .quad 0 + .long 64 + .space 4 + .section __DATA,__objc_catlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CATEGORY_$" +l_OBJC_LABEL_CATEGORY_$: + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category03 + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category04 + .section __DATA,__objc_nlcatlist,regular,no_dead_strip + .p2align 3, 0x0 +l_OBJC_LABEL_NONLAZY_CATEGORY_$: + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category04 + + .no_dead_strip __OBJC_LABEL_PROTOCOL_$_MyProtocol02 + .no_dead_strip __OBJC_LABEL_PROTOCOL_$_MyProtocol03 + .no_dead_strip __OBJC_PROTOCOL_$_MyProtocol02 + .no_dead_strip __OBJC_PROTOCOL_$_MyProtocol03 + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 96 +.subsections_via_symbols diff --git a/wild/tests/lld-macho/objc-category-merging-erase-objc-name-test.s b/wild/tests/lld-macho/objc-category-merging-erase-objc-name-test.s new file mode 100644 index 000000000..bc0b27d19 --- /dev/null +++ b/wild/tests/lld-macho/objc-category-merging-erase-objc-name-test.s @@ -0,0 +1,308 @@ +; REQUIRES: aarch64 + +; Here we test that if we defined a protocol MyTestProtocol and also a category MyTestProtocol +; then when merging the category into the base class (and deleting the category), we don't +; delete the 'MyTestProtocol' name + +; RUN: mkdir -p %t.dir + +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o %t.dir/erase-objc-name.o %s +; RUN: %lld -no_objc_relative_method_lists -arch arm64 -dylib -o %t.dir/erase-objc-name.dylib %t.dir/erase-objc-name.o -objc_category_merging +; RUN: llvm-objdump --objc-meta-data --macho %t.dir/erase-objc-name.dylib | FileCheck %s --check-prefixes=MERGE_CATS + +; === Check merge categories enabled === +; Check that the original categories are not there +; MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01 +; MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + +; Check that we get the expected output - most importantly that the protocol is named `MyTestProtocol` +; MERGE_CATS: Contents of (__DATA_CONST,__objc_classlist) section +; MERGE_CATS-NEXT: _OBJC_CLASS_$_MyBaseClass +; MERGE_CATS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass +; MERGE_CATS-NEXT: superclass {{.*}} +; MERGE_CATS-NEXT: cache {{.*}} +; MERGE_CATS-NEXT: vtable {{.*}} +; MERGE_CATS-NEXT: data {{.*}} (struct class_ro_t *) +; MERGE_CATS-NEXT: flags {{.*}} RO_ROOT +; MERGE_CATS-NEXT: instanceStart 0 +; MERGE_CATS-NEXT: instanceSize 0 +; MERGE_CATS-NEXT: reserved {{.*}} +; MERGE_CATS-NEXT: ivarLayout {{.*}} +; MERGE_CATS-NEXT: name {{.*}} MyBaseClass +; MERGE_CATS-NEXT: baseMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: entsize 24 +; MERGE_CATS-NEXT: count 2 +; MERGE_CATS-NEXT: name {{.*}} getValue +; MERGE_CATS-NEXT: types {{.*}} i16@0:8 +; MERGE_CATS-NEXT: imp -[MyBaseClass(MyTestProtocol) getValue] +; MERGE_CATS-NEXT: name {{.*}} baseInstanceMethod +; MERGE_CATS-NEXT: types {{.*}} v16@0:8 +; MERGE_CATS-NEXT: imp -[MyBaseClass baseInstanceMethod] +; MERGE_CATS-NEXT: baseProtocols {{.*}} +; MERGE_CATS-NEXT: count 1 +; MERGE_CATS-NEXT: list[0] {{.*}} (struct protocol_t *) +; MERGE_CATS-NEXT: isa {{.*}} +; MERGE_CATS-NEXT: name {{.*}} MyTestProtocol +; MERGE_CATS-NEXT: protocols {{.*}} +; MERGE_CATS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: entsize 24 +; MERGE_CATS-NEXT: count 1 +; MERGE_CATS-NEXT: name {{.*}} getValue +; MERGE_CATS-NEXT: types {{.*}} i16@0:8 +; MERGE_CATS-NEXT: imp {{.*}} +; MERGE_CATS-NEXT: classMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: optionalInstanceMethods {{.*}} +; MERGE_CATS-NEXT: optionalClassMethods {{.*}} +; MERGE_CATS-NEXT: instanceProperties {{.*}} +; MERGE_CATS-NEXT: ivars {{.*}} +; MERGE_CATS-NEXT: weakIvarLayout {{.*}} +; MERGE_CATS-NEXT: baseProperties {{.*}} +; MERGE_CATS-NEXT: Meta Class +; MERGE_CATS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass +; MERGE_CATS-NEXT: superclass {{.*}} _OBJC_CLASS_$_MyBaseClass +; MERGE_CATS-NEXT: cache {{.*}} +; MERGE_CATS-NEXT: vtable {{.*}} +; MERGE_CATS-NEXT: data {{.*}} (struct class_ro_t *) +; MERGE_CATS-NEXT: flags {{.*}} RO_META RO_ROOT +; MERGE_CATS-NEXT: instanceStart 40 +; MERGE_CATS-NEXT: instanceSize 40 +; MERGE_CATS-NEXT: reserved {{.*}} +; MERGE_CATS-NEXT: ivarLayout {{.*}} +; MERGE_CATS-NEXT: name {{.*}} MyBaseClass +; MERGE_CATS-NEXT: baseMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: baseProtocols {{.*}} +; MERGE_CATS-NEXT: count 1 +; MERGE_CATS-NEXT: list[0] {{.*}} (struct protocol_t *) +; MERGE_CATS-NEXT: isa {{.*}} +; MERGE_CATS-NEXT: name {{.*}} MyTestProtocol +; MERGE_CATS-NEXT: protocols {{.*}} +; MERGE_CATS-NEXT: instanceMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: entsize 24 +; MERGE_CATS-NEXT: count 1 +; MERGE_CATS-NEXT: name {{.*}} getValue +; MERGE_CATS-NEXT: types {{.*}} i16@0:8 +; MERGE_CATS-NEXT: imp {{.*}} +; MERGE_CATS-NEXT: classMethods {{.*}} (struct method_list_t *) +; MERGE_CATS-NEXT: optionalInstanceMethods {{.*}} +; MERGE_CATS-NEXT: optionalClassMethods {{.*}} +; MERGE_CATS-NEXT: instanceProperties {{.*}} +; MERGE_CATS-NEXT: ivars {{.*}} +; MERGE_CATS-NEXT: weakIvarLayout {{.*}} +; MERGE_CATS-NEXT: baseProperties {{.*}} +; MERGE_CATS-NEXT: Contents of (__DATA_CONST,__objc_protolist) section +; MERGE_CATS-NEXT: {{.*}} {{.*}} __OBJC_PROTOCOL_$_MyTestProtocol +; MERGE_CATS-NEXT: Contents of (__DATA_CONST,__objc_imageinfo) section +; MERGE_CATS-NEXT: version 0 +; MERGE_CATS-NEXT: flags {{.*}} OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES + + +; ================== repro.sh ==================== +; # Write the Objective-C code to a file +; cat << EOF > MyClass.m +; @protocol MyTestProtocol +; - (int)getValue; +; @end +; +; __attribute__((objc_root_class)) +; @interface MyBaseClass +; - (void)baseInstanceMethod; +; @end +; +; @implementation MyBaseClass +; - (void)baseInstanceMethod {} +; @end +; +; @interface MyBaseClass (MyTestProtocol) +; @end +; +; @implementation MyBaseClass (MyTestProtocol) +; +; - (int)getValue { +; return 0x30; +; } +; +; @end +; EOF +; +; # Compile the Objective-C file to assembly +; xcrun clang -S -arch arm64 MyClass.m -o MyClass.s +; ============================================== + + + .section __TEXT,__text,regular,pure_instructions + .p2align 2 ; -- Begin function -[MyBaseClass baseInstanceMethod] +"-[MyBaseClass baseInstanceMethod]": ; @"\01-[MyBaseClass baseInstanceMethod]" + .cfi_startproc +; %bb.0: + sub sp, sp, #16 + .cfi_def_cfa_offset 16 + str x0, [sp, #8] + str x1, [sp] + add sp, sp, #16 + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass(MyTestProtocol) getValue] +"-[MyBaseClass(MyTestProtocol) getValue]": ; @"\01-[MyBaseClass(MyTestProtocol) getValue]" + .cfi_startproc +; %bb.0: + sub sp, sp, #16 + .cfi_def_cfa_offset 16 + str x0, [sp, #8] + str x1, [sp] + mov w0, #48 ; =0x30 + add sp, sp, #16 + ret + .cfi_endproc + ; -- End function + .section __DATA,__objc_data + .globl _OBJC_CLASS_$_MyBaseClass ; @"OBJC_CLASS_$_MyBaseClass" + .p2align 3, 0x0 +_OBJC_CLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad 0 + .quad __objc_empty_cache + .quad 0 + .quad __OBJC_CLASS_RO_$_MyBaseClass + .globl _OBJC_METACLASS_$_MyBaseClass ; @"OBJC_METACLASS_$_MyBaseClass" + .p2align 3, 0x0 +_OBJC_METACLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad _OBJC_CLASS_$_MyBaseClass + .quad __objc_empty_cache + .quad 0 + .quad __OBJC_METACLASS_RO_$_MyBaseClass + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: ; @OBJC_CLASS_NAME_ + .asciz "MyBaseClass" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_METACLASS_RO_$_MyBaseClass" +__OBJC_METACLASS_RO_$_MyBaseClass: + .long 131 ; 0x83 + .long 40 ; 0x28 + .long 40 ; 0x28 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: ; @OBJC_METH_VAR_NAME_ + .asciz "baseInstanceMethod" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: ; @OBJC_METH_VAR_TYPE_ + .asciz "v16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_INSTANCE_METHODS_MyBaseClass" +__OBJC_$_INSTANCE_METHODS_MyBaseClass: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass baseInstanceMethod]" + .p2align 3, 0x0 ; @"_OBJC_CLASS_RO_$_MyBaseClass" +__OBJC_CLASS_RO_$_MyBaseClass: + .long 130 ; 0x82 + .long 0 ; 0x0 + .long 0 ; 0x0 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_MyBaseClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_.1: ; @OBJC_CLASS_NAME_.1 + .asciz "MyTestProtocol" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.2: ; @OBJC_METH_VAR_NAME_.2 + .asciz "getValue" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_.3: ; @OBJC_METH_VAR_TYPE_.3 + .asciz "i16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_MyTestProtocol" +__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_MyTestProtocol: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_.3 + .quad "-[MyBaseClass(MyTestProtocol) getValue]" + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyTestProtocol" +__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyTestProtocol: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_.3 + .quad 0 + .p2align 3, 0x0 ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyTestProtocol" +__OBJC_$_PROTOCOL_METHOD_TYPES_MyTestProtocol: + .quad l_OBJC_METH_VAR_TYPE_.3 + .private_extern __OBJC_PROTOCOL_$_MyTestProtocol ; @"_OBJC_PROTOCOL_$_MyTestProtocol" + .section __DATA,__data + .globl __OBJC_PROTOCOL_$_MyTestProtocol + .weak_definition __OBJC_PROTOCOL_$_MyTestProtocol + .p2align 3, 0x0 +__OBJC_PROTOCOL_$_MyTestProtocol: + .quad 0 + .quad l_OBJC_CLASS_NAME_.1 + .quad 0 + .quad __OBJC_$_PROTOCOL_INSTANCE_METHODS_MyTestProtocol + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .long 96 ; 0x60 + .long 0 ; 0x0 + .quad __OBJC_$_PROTOCOL_METHOD_TYPES_MyTestProtocol + .quad 0 + .quad 0 + .private_extern __OBJC_LABEL_PROTOCOL_$_MyTestProtocol ; @"_OBJC_LABEL_PROTOCOL_$_MyTestProtocol" + .section __DATA,__objc_protolist,coalesced,no_dead_strip + .globl __OBJC_LABEL_PROTOCOL_$_MyTestProtocol + .weak_definition __OBJC_LABEL_PROTOCOL_$_MyTestProtocol + .p2align 3, 0x0 +__OBJC_LABEL_PROTOCOL_$_MyTestProtocol: + .quad __OBJC_PROTOCOL_$_MyTestProtocol + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_MyTestProtocol" +__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_MyTestProtocol: + .quad 1 ; 0x1 + .quad __OBJC_PROTOCOL_$_MyTestProtocol + .quad 0 + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_MyBaseClass_$_MyTestProtocol" +__OBJC_$_CATEGORY_MyBaseClass_$_MyTestProtocol: + .quad l_OBJC_CLASS_NAME_.1 + .quad _OBJC_CLASS_$_MyBaseClass + .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_MyTestProtocol + .quad 0 + .quad __OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_MyTestProtocol + .quad 0 + .quad 0 + .long 64 ; 0x40 + .space 4 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CLASS_$" +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_MyBaseClass + .section __DATA,__objc_catlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CATEGORY_$" +l_OBJC_LABEL_CATEGORY_$: + .quad __OBJC_$_CATEGORY_MyBaseClass_$_MyTestProtocol + .no_dead_strip __OBJC_PROTOCOL_$_MyTestProtocol + .no_dead_strip __OBJC_LABEL_PROTOCOL_$_MyTestProtocol + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 64 + +__objc_empty_cache: +_$sBOWV: + .quad 0 + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/objc-category-merging-minimal.s b/wild/tests/lld-macho/objc-category-merging-minimal.s new file mode 100644 index 000000000..d4d5933aa --- /dev/null +++ b/wild/tests/lld-macho/objc-category-merging-minimal.s @@ -0,0 +1,387 @@ +# REQUIRES: aarch64 +# UNSUPPORTED: system-windows +# due to awk usage + +# RUN: rm -rf %t; split-file %s %t && cd %t + +############ Test merging multiple categories into a single category ############ +## Create a dylib with a fake base class to link against in when merging between categories +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o a64_fakedylib.o a64_fakedylib.s +# RUN: %lld -arch arm64 a64_fakedylib.o -o a64_fakedylib.dylib -dylib + +## Create our main testing dylib - linking against the fake dylib above +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o merge_cat_minimal.o merge_cat_minimal.s +# RUN: %lld -arch arm64 -dylib -o merge_cat_minimal_no_merge.dylib a64_fakedylib.dylib merge_cat_minimal.o +# RUN: %lld -arch arm64 -dylib -o merge_cat_minimal_merge.dylib -objc_category_merging a64_fakedylib.dylib merge_cat_minimal.o + +## Now verify that the flag caused category merging to happen appropriatelly +# RUN: llvm-objdump --objc-meta-data --macho merge_cat_minimal_no_merge.dylib | FileCheck %s --check-prefixes=NO_MERGE_CATS +# RUN: llvm-objdump --objc-meta-data --macho merge_cat_minimal_merge.dylib | FileCheck %s --check-prefixes=MERGE_CATS + +############ Test merging multiple categories into the base class ############ +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o merge_base_class_minimal.o merge_base_class_minimal.s +# RUN: %lld -arch arm64 -dylib -o merge_base_class_minimal_yes_merge.dylib -objc_category_merging merge_base_class_minimal.o merge_cat_minimal.o +# RUN: %lld -arch arm64 -dylib -o merge_base_class_minimal_no_merge.dylib merge_base_class_minimal.o merge_cat_minimal.o + +# RUN: llvm-objdump --objc-meta-data --macho merge_base_class_minimal_no_merge.dylib | FileCheck %s --check-prefixes=NO_MERGE_INTO_BASE +# RUN: llvm-objdump --objc-meta-data --macho merge_base_class_minimal_yes_merge.dylib | FileCheck %s --check-prefixes=YES_MERGE_INTO_BASE + +############ Test merging swift category into the base class ############ +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o MyBaseClassSwiftExtension.o MyBaseClassSwiftExtension.s +# RUN: %lld -no_objc_relative_method_lists -arch arm64 -dylib -o merge_base_class_swift_minimal_yes_merge.dylib -objc_category_merging MyBaseClassSwiftExtension.o merge_base_class_minimal.o +# RUN: llvm-objdump --objc-meta-data --macho merge_base_class_swift_minimal_yes_merge.dylib | FileCheck %s --check-prefixes=YES_MERGE_INTO_BASE_SWIFT + +############ Test merging skipped due to invalid category name ############ +# Modify __OBJC_$_CATEGORY_MyBaseClass_$_Category01's name to point to L_OBJC_IMAGE_INFO+3 +# RUN: awk '/^__OBJC_\$_CATEGORY_MyBaseClass_\$_Category01:/ { print; getline; sub(/^[ \t]*\.quad[ \t]+l_OBJC_CLASS_NAME_$/, "\t.quad\tL_OBJC_IMAGE_INFO+3"); print; next } { print }' merge_cat_minimal.s > merge_cat_minimal_bad_name.s + +# Assemble the modified source +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o merge_cat_minimal_bad_name.o merge_cat_minimal_bad_name.s + +# Run lld and check for the specific warning +# RUN: %no-fatal-warnings-lld -arch arm64 -dylib -objc_category_merging -o merge_cat_minimal_merge.dylib a64_fakedylib.dylib merge_cat_minimal_bad_name.o 2>&1 | FileCheck %s --check-prefix=MERGE_WARNING + +# Check that lld emitted the warning about skipping category merging +MERGE_WARNING: warning: ObjC category merging skipped for class symbol' _OBJC_CLASS_$_MyBaseClass' + +#### Check merge categories enabled ### +# Check that the original categories are not there +MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01 +MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + +# Check that the merged cateogry is there, in the correct format +MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass(Category01|Category02) +MERGE_CATS-NEXT: name {{.*}} Category01|Category02 +MERGE_CATS: instanceMethods +MERGE_CATS-NEXT: entsize 12 (relative) +MERGE_CATS-NEXT: count 2 +MERGE_CATS-NEXT: name {{.*}} cat01_InstanceMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp {{.*}} -[MyBaseClass(Category01) cat01_InstanceMethod] +MERGE_CATS-NEXT: name {{.*}} cat02_InstanceMethod +MERGE_CATS-NEXT: types {{.*}} v16@0:8 +MERGE_CATS-NEXT: imp {{.*}} -[MyBaseClass(Category02) cat02_InstanceMethod] +MERGE_CATS-NEXT: classMethods 0x0 +MERGE_CATS-NEXT: protocols 0x0 +MERGE_CATS-NEXT: instanceProperties 0x0 + +#### Check merge categories disabled ### +# Check that the merged category is not there +NO_MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass(Category01|Category02) + +# Check that the original categories are there +NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category01 +NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + + +#### Check merge cateogires into base class is disabled #### +NO_MERGE_INTO_BASE: __OBJC_$_CATEGORY_MyBaseClass_$_Category01 +NO_MERGE_INTO_BASE: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + +#### Check merge cateogires into base class is enabled and categories are merged into base class #### +YES_MERGE_INTO_BASE-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01 +YES_MERGE_INTO_BASE-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + +YES_MERGE_INTO_BASE: _OBJC_CLASS_$_MyBaseClass +YES_MERGE_INTO_BASE-NEXT: _OBJC_METACLASS_$_MyBaseClass +YES_MERGE_INTO_BASE: baseMethods +YES_MERGE_INTO_BASE-NEXT: entsize 12 (relative) +YES_MERGE_INTO_BASE-NEXT: count 3 +YES_MERGE_INTO_BASE-NEXT: name {{.*}} cat01_InstanceMethod +YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16@0:8 +YES_MERGE_INTO_BASE-NEXT: imp {{.*}} -[MyBaseClass(Category01) cat01_InstanceMethod] +YES_MERGE_INTO_BASE-NEXT: name {{.*}} cat02_InstanceMethod +YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16@0:8 +YES_MERGE_INTO_BASE-NEXT: imp {{.*}} -[MyBaseClass(Category02) cat02_InstanceMethod] +YES_MERGE_INTO_BASE-NEXT: name {{.*}} baseInstanceMethod +YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16@0:8 +YES_MERGE_INTO_BASE-NEXT: imp {{.*}} -[MyBaseClass baseInstanceMethod] + + +#### Check merge swift category into base class ### +YES_MERGE_INTO_BASE_SWIFT: _OBJC_CLASS_$_MyBaseClass +YES_MERGE_INTO_BASE_SWIFT-NEXT: _OBJC_METACLASS_$_MyBaseClass +YES_MERGE_INTO_BASE_SWIFT: baseMethods +YES_MERGE_INTO_BASE_SWIFT-NEXT: entsize 24 +YES_MERGE_INTO_BASE_SWIFT-NEXT: count 2 +YES_MERGE_INTO_BASE_SWIFT-NEXT: name {{.*}} swiftMethod +YES_MERGE_INTO_BASE_SWIFT-NEXT: types {{.*}} v16@0:8 +YES_MERGE_INTO_BASE_SWIFT-NEXT: imp _$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyFTo +YES_MERGE_INTO_BASE_SWIFT-NEXT: name {{.*}} baseInstanceMethod +YES_MERGE_INTO_BASE_SWIFT-NEXT: types {{.*}} v16@0:8 +YES_MERGE_INTO_BASE_SWIFT-NEXT: imp -[MyBaseClass baseInstanceMethod] + + +#--- a64_fakedylib.s + + .section __DATA,__objc_data + .globl _OBJC_CLASS_$_MyBaseClass +_OBJC_CLASS_$_MyBaseClass: + .quad 0 + +#--- merge_cat_minimal.s + +; ================== Generated from ObjC: ================== +; __attribute__((objc_root_class)) +; @interface MyBaseClass +; - (void)baseInstanceMethod; +; @end +; +; @interface MyBaseClass(Category01) +; - (void)cat01_InstanceMethod; +; @end +; +; @implementation MyBaseClass(Category01) +; - (void)cat01_InstanceMethod {} +; @end +; +; @interface MyBaseClass(Category02) +; - (void)cat02_InstanceMethod; +; @end +; +; @implementation MyBaseClass(Category02) +; - (void)cat02_InstanceMethod {} +; @end +; ================== Generated from ObjC: ================== + + .section __TEXT,__text,regular,pure_instructions + .p2align 2 ; -- Begin function -[MyBaseClass(Category01) cat01_InstanceMethod] +"-[MyBaseClass(Category01) cat01_InstanceMethod]": ; @"\01-[MyBaseClass(Category01) cat01_InstanceMethod]" + .cfi_startproc + ret + .cfi_endproc + ; -- End function + .p2align 2 ; -- Begin function -[MyBaseClass(Category02) cat02_InstanceMethod] +"-[MyBaseClass(Category02) cat02_InstanceMethod]": ; @"\01-[MyBaseClass(Category02) cat02_InstanceMethod]" + .cfi_startproc + ret + .cfi_endproc + ; -- End function + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: ; @OBJC_CLASS_NAME_ + .asciz "Category01" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: ; @OBJC_METH_VAR_NAME_ + .asciz "cat01_InstanceMethod" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: ; @OBJC_METH_VAR_TYPE_ + .asciz "v16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01" +__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category01) cat01_InstanceMethod]" + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category01" +__OBJC_$_CATEGORY_MyBaseClass_$_Category01: + .quad l_OBJC_CLASS_NAME_ + .quad _OBJC_CLASS_$_MyBaseClass + .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .long 64 ; 0x40 + .space 4 + .section __DATA,__objc_const +l_OBJC_CLASS_NAME_.1: ; @OBJC_CLASS_NAME_.1 + .asciz "Category02" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.2: ; @OBJC_METH_VAR_NAME_.2 + .asciz "cat02_InstanceMethod" + .section __DATA,__objc_const + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02" +__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02: + .long 24 ; 0x18 + .long 1 ; 0x1 + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass(Category02) cat02_InstanceMethod]" + .p2align 3, 0x0 ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category02" +__OBJC_$_CATEGORY_MyBaseClass_$_Category02: + .quad l_OBJC_CLASS_NAME_.1 + .quad _OBJC_CLASS_$_MyBaseClass + .quad __OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .long 64 ; 0x40 + .space 4 + .section __DATA,__objc_catlist,regular,no_dead_strip + .p2align 3, 0x0 ; @"OBJC_LABEL_CATEGORY_$" +l_OBJC_LABEL_CATEGORY_$: + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category01 + .quad __OBJC_$_CATEGORY_MyBaseClass_$_Category02 + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 96 +.subsections_via_symbols + +.addrsig +.addrsig_sym __OBJC_$_CATEGORY_MyBaseClass_$_Category01 + +#--- merge_base_class_minimal.s +; clang -c merge_base_class_minimal.mm -O3 -target arm64-apple-macos -arch arm64 -S -o merge_base_class_minimal.s +; ================== Generated from ObjC: ================== +; __attribute__((objc_root_class)) +; @interface MyBaseClass +; - (void)baseInstanceMethod; +; @end +; +; @implementation MyBaseClass +; - (void)baseInstanceMethod {} +; @end +; ================== Generated from ObjC ================== + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 11, 0 + .p2align 2 +"-[MyBaseClass baseInstanceMethod]": + .cfi_startproc +; %bb.0: + ret + .cfi_endproc + .section __DATA,__objc_data + .globl _OBJC_CLASS_$_MyBaseClass + .p2align 3, 0x0 +_OBJC_CLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad 0 + .quad 0 + .quad 0 + .quad __OBJC_CLASS_RO_$_MyBaseClass + .globl _OBJC_METACLASS_$_MyBaseClass + .p2align 3, 0x0 +_OBJC_METACLASS_$_MyBaseClass: + .quad _OBJC_METACLASS_$_MyBaseClass + .quad _OBJC_CLASS_$_MyBaseClass + .quad 0 + .quad 0 + .quad __OBJC_METACLASS_RO_$_MyBaseClass + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: + .asciz "MyBaseClass" + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_METACLASS_RO_$_MyBaseClass: + .long 3 + .long 40 + .long 40 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: + .asciz "baseInstanceMethod" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: + .asciz "v16@0:8" + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_INSTANCE_METHODS_MyBaseClass: + .long 24 + .long 1 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyBaseClass baseInstanceMethod]" + .p2align 3, 0x0 +__OBJC_CLASS_RO_$_MyBaseClass: + .long 2 + .long 0 + .long 0 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_MyBaseClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_MyBaseClass + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 64 +.subsections_via_symbols + + +#--- MyBaseClassSwiftExtension.s +; xcrun -sdk macosx swiftc -emit-assembly MyBaseClassSwiftExtension.swift -import-objc-header YourProject-Bridging-Header.h -o MyBaseClassSwiftExtension.s +; ================== Generated from Swift: ================== +; import Foundation +; extension MyBaseClass { +; @objc func swiftMethod() { +; } +; } +; ================== Generated from Swift =================== + .private_extern _$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyF + .globl _$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyF + .p2align 2 +_$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyF: + .cfi_startproc + mov w0, #0 + ret + .cfi_endproc + + .p2align 2 +_$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyFTo: + .cfi_startproc + mov w0, #0 + ret + .cfi_endproc + + .section __TEXT,__cstring,cstring_literals + .p2align 4, 0x0 +l_.str.25.MyBaseClassSwiftExtension: + .asciz "MyBaseClassSwiftExtension" + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(swiftMethod)": + .asciz "swiftMethod" + + .section __TEXT,__cstring,cstring_literals +"l_.str.7.v16@0:8": + .asciz "v16@0:8" + + .section __DATA,__objc_data + .p2align 3, 0x0 +__CATEGORY_INSTANCE_METHODS_MyBaseClass_$_MyBaseClassSwiftExtension: + .long 24 + .long 1 + .quad "L_selector_data(swiftMethod)" + .quad "l_.str.7.v16@0:8" + .quad _$sSo11MyBaseClassC0abC14SwiftExtensionE11swiftMethodyyFTo + + .section __DATA,__objc_const + .p2align 3, 0x0 +__CATEGORY_MyBaseClass_$_MyBaseClassSwiftExtension: + .quad l_.str.25.MyBaseClassSwiftExtension + .quad _OBJC_CLASS_$_MyBaseClass + .quad __CATEGORY_INSTANCE_METHODS_MyBaseClass_$_MyBaseClassSwiftExtension + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .long 60 + .space 4 + + .section __DATA,__objc_catlist,regular,no_dead_strip + .p2align 3, 0x0 +_objc_categories: + .quad __CATEGORY_MyBaseClass_$_MyBaseClassSwiftExtension + + .no_dead_strip _main + .no_dead_strip l_entry_point + +.subsections_via_symbols diff --git a/wild/tests/lld-macho/objc-category-merging-swift-class-ext.s b/wild/tests/lld-macho/objc-category-merging-swift-class-ext.s new file mode 100644 index 000000000..f6461e13a --- /dev/null +++ b/wild/tests/lld-macho/objc-category-merging-swift-class-ext.s @@ -0,0 +1,442 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; mkdir %t && cd %t + +############ Test swift category merging into @objc class, with protocol ############ +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o cat_swift.o %s +# RUN: %lld -arch arm64 -dylib -o cat_swift.dylib cat_swift.o -objc_category_merging +# RUN: llvm-objdump --objc-meta-data --macho cat_swift.dylib | FileCheck %s --check-prefixes=CHECK-MERGE + + +; CHECK-MERGE: Contents of (__DATA_CONST,__objc_classlist) section + +; CHECK-MERGE-NEXT: [[#%x,]] 0x[[#%x,]] _OBJC_CLASS_$__TtC11MyTestClass11MyTestClass +; CHECK-MERGE-NEXT: isa 0x[[#%x,]] _OBJC_METACLASS_$__TtC11MyTestClass11MyTestClass +; CHECK-MERGE-NEXT: superclass 0x0 +; CHECK-MERGE-NEXT: cache 0x0 +; CHECK-MERGE-NEXT: vtable 0x0 +; CHECK-MERGE-NEXT: data 0x[[#%x,]] (struct class_ro_t *) Swift class +; CHECK-MERGE-NEXT: flags 0x80 +; CHECK-MERGE-NEXT: instanceStart 8 +; CHECK-MERGE-NEXT: instanceSize 8 +; CHECK-MERGE-NEXT: reserved 0x0 +; CHECK-MERGE-NEXT: ivarLayout 0x0 +; CHECK-MERGE-NEXT: name 0x[[#%x,]] _TtC11MyTestClass11MyTestClass +; CHECK-MERGE-NEXT: baseMethods 0x[[#%x,]] (struct method_list_t *) +; CHECK-MERGE-NEXT: entsize 24 +; CHECK-MERGE-NEXT: count 1 +; CHECK-MERGE-NEXT: name 0x[[#%x,]] init +; CHECK-MERGE-NEXT: types 0x[[#%x,]] @16@0:8 +; CHECK-MERGE-NEXT: imp _$s11MyTestClassAACABycfcTo +; CHECK-MERGE-NEXT: baseProtocols 0x0 +; CHECK-MERGE-NEXT: ivars 0x0 +; CHECK-MERGE-NEXT: weakIvarLayout 0x0 +; CHECK-MERGE-NEXT: baseProperties 0x0 +; CHECK-MERGE-NEXT: Meta Class +; CHECK-MERGE-NEXT: isa 0x0 +; CHECK-MERGE-NEXT: superclass 0x0 +; CHECK-MERGE-NEXT: cache 0x0 +; CHECK-MERGE-NEXT: vtable 0x0 +; CHECK-MERGE-NEXT: data 0x[[#%x,]] (struct class_ro_t *) +; CHECK-MERGE-NEXT: flags 0x81 RO_META +; CHECK-MERGE-NEXT: instanceStart 40 +; CHECK-MERGE-NEXT: instanceSize 40 +; CHECK-MERGE-NEXT: reserved 0x0 +; CHECK-MERGE-NEXT: ivarLayout 0x0 +; CHECK-MERGE-NEXT: name 0x[[#%x,]] _TtC11MyTestClass11MyTestClass +; CHECK-MERGE-NEXT: baseMethods 0x0 (struct method_list_t *) +; CHECK-MERGE-NEXT: baseProtocols 0x0 +; CHECK-MERGE-NEXT: ivars 0x0 +; CHECK-MERGE-NEXT: weakIvarLayout 0x0 +; CHECK-MERGE-NEXT: baseProperties 0x0 + + +; ================== Generated from Swift: ================== +;; > xcrun swiftc --version +;; swift-driver version: 1.109.2 Apple Swift version 6.0 (swiftlang-6.0.0.3.300 clang-1600.0.20.10) +;; > xcrun swiftc -S MyTestClass.swift -o MyTestClass.s +;; +; import Foundation +; +; protocol MyProtocol { +; func protocolMethod() +; } +; +; @objc class MyTestClass: NSObject, MyProtocol { +; func protocolMethod() { +; } +; } +; +; extension MyTestClass { +; public func extensionMethod() { +; } +; } +; ================== Generated from Swift: ================== + + + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 11, 0 sdk_version 10, 0 + .globl _main + .p2align 2 +_main: + .cfi_startproc + mov w0, #0 + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAAC14protocolMethodyyF + .globl _$s11MyTestClassAAC14protocolMethodyyF + .p2align 2 +_$s11MyTestClassAAC14protocolMethodyyF: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAACABycfC + .globl _$s11MyTestClassAACABycfC + .p2align 2 +_$s11MyTestClassAACABycfC: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAACABycfc + .globl _$s11MyTestClassAACABycfc + .p2align 2 +_$s11MyTestClassAACABycfc: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAACMa + .globl _$s11MyTestClassAACMa + .p2align 2 +_$s11MyTestClassAACMa: + ret + + .p2align 2 +_$s11MyTestClassAACABycfcTo: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAACfD + .globl _$s11MyTestClassAACfD + .p2align 2 +_$s11MyTestClassAACfD: + .cfi_startproc + ret + .cfi_endproc + + .p2align 2 +_$s11MyTestClassAACAA0A8ProtocolA2aCP14protocolMethodyyFTW: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11MyTestClassAAC15extensionMethodyyF + .globl _$s11MyTestClassAAC15extensionMethodyyF + .p2align 2 +_$s11MyTestClassAAC15extensionMethodyyF: + .cfi_startproc + ret + .cfi_endproc + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(init)": + .asciz "init" + + .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip + .p2align 3, 0x0 +"L_selector(init)": + .quad "L_selector_data(init)" + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(dealloc)": + .asciz "dealloc" + + .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip + .p2align 3, 0x0 +"L_selector(dealloc)": + .quad "L_selector_data(dealloc)" + + .private_extern _$s11MyTestClassAACAA0A8ProtocolAAMc + .section __TEXT,__const + .globl _$s11MyTestClassAACAA0A8ProtocolAAMc + .p2align 2, 0x0 +_$s11MyTestClassAACAA0A8ProtocolAAMc: + .long _$s11MyTestClass0A8ProtocolMp-_$s11MyTestClassAACAA0A8ProtocolAAMc + .long (_$s11MyTestClassAACMn-_$s11MyTestClassAACAA0A8ProtocolAAMc)-4 + .long (_$s11MyTestClassAACAA0A8ProtocolAAWP-_$s11MyTestClassAACAA0A8ProtocolAAMc)-8 + .long 0 + + .private_extern _$s11MyTestClassAACAA0A8ProtocolAAWP + .section __DATA,__const + .globl _$s11MyTestClassAACAA0A8ProtocolAAWP + .p2align 3, 0x0 +_$s11MyTestClassAACAA0A8ProtocolAAWP: + .quad _$s11MyTestClassAACAA0A8ProtocolAAMc + .quad _$s11MyTestClassAACAA0A8ProtocolA2aCP14protocolMethodyyFTW + + .section __TEXT,__swift5_entry,regular,no_dead_strip + .p2align 2, 0x0 +l_entry_point: + .long _main-l_entry_point + .long 0 + + .private_extern "_symbolic $s11MyTestClass0A8ProtocolP" + .section __TEXT,__swift5_typeref + .globl "_symbolic $s11MyTestClass0A8ProtocolP" + .weak_definition "_symbolic $s11MyTestClass0A8ProtocolP" + .p2align 1, 0x0 +"_symbolic $s11MyTestClass0A8ProtocolP": + .ascii "$s11MyTestClass0A8ProtocolP" + .byte 0 + + .section __TEXT,__swift5_fieldmd + .p2align 2, 0x0 +_$s11MyTestClass0A8Protocol_pMF: + .long "_symbolic $s11MyTestClass0A8ProtocolP"-_$s11MyTestClass0A8Protocol_pMF + .long 0 + .short 4 + .short 12 + .long 0 + + .section __TEXT,__const +l_.str.11.MyTestClass: + .asciz "MyTestClass" + + .private_extern _$s11MyTestClassMXM + .section __TEXT,__constg_swiftt + .globl _$s11MyTestClassMXM + .weak_definition _$s11MyTestClassMXM + .p2align 2, 0x0 +_$s11MyTestClassMXM: + .long 0 + .long 0 + .long (l_.str.11.MyTestClass-_$s11MyTestClassMXM)-8 + + .section __TEXT,__const +l_.str.10.MyProtocol: + .asciz "MyProtocol" + + .private_extern _$s11MyTestClass0A8ProtocolMp + .section __TEXT,__constg_swiftt + .globl _$s11MyTestClass0A8ProtocolMp + .p2align 2, 0x0 +_$s11MyTestClass0A8ProtocolMp: + .long 65603 + .long (_$s11MyTestClassMXM-_$s11MyTestClass0A8ProtocolMp)-4 + .long (l_.str.10.MyProtocol-_$s11MyTestClass0A8ProtocolMp)-8 + .long 0 + .long 1 + .long 0 + .long 17 + .long 0 + + .private_extern _OBJC_METACLASS_$__TtC11MyTestClass11MyTestClass + .section __DATA,__data + .globl _OBJC_METACLASS_$__TtC11MyTestClass11MyTestClass + .p2align 3, 0x0 +_OBJC_METACLASS_$__TtC11MyTestClass11MyTestClass: + .quad _OBJC_METACLASS_$_NSObject + .quad _OBJC_METACLASS_$_NSObject + .quad __objc_empty_cache + .quad 0 + .quad __METACLASS_DATA__TtC11MyTestClass11MyTestClass + + .section __TEXT,__cstring,cstring_literals + .p2align 4, 0x0 +l_.str.30._TtC11MyTestClass11MyTestClass: + .asciz "_TtC11MyTestClass11MyTestClass" + + .section __DATA,__objc_const + .p2align 3, 0x0 +__METACLASS_DATA__TtC11MyTestClass11MyTestClass: + .long 129 + .long 40 + .long 40 + .long 0 + .quad 0 + .quad l_.str.30._TtC11MyTestClass11MyTestClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__cstring,cstring_literals +"l_.str.7.@16@0:8": + .asciz "@16@0:8" + + .section __DATA,__objc_data + .p2align 3, 0x0 +__INSTANCE_METHODS__TtC11MyTestClass11MyTestClass: + .long 24 + .long 1 + .quad "L_selector_data(init)" + .quad "l_.str.7.@16@0:8" + .quad _$s11MyTestClassAACABycfcTo + + .p2align 3, 0x0 +__DATA__TtC11MyTestClass11MyTestClass: + .long 128 + .long 8 + .long 8 + .long 0 + .quad 0 + .quad l_.str.30._TtC11MyTestClass11MyTestClass + .quad __INSTANCE_METHODS__TtC11MyTestClass11MyTestClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .private_extern "_symbolic So8NSObjectC" + .section __TEXT,__swift5_typeref + .globl "_symbolic So8NSObjectC" + .weak_definition "_symbolic So8NSObjectC" + .p2align 1, 0x0 +"_symbolic So8NSObjectC": + .ascii "So8NSObjectC" + .byte 0 + + .private_extern _$s11MyTestClassAACMn + .section __TEXT,__constg_swiftt + .globl _$s11MyTestClassAACMn + .p2align 2, 0x0 +_$s11MyTestClassAACMn: + .long 2147483728 + .long (_$s11MyTestClassMXM-_$s11MyTestClassAACMn)-4 + .long (l_.str.11.MyTestClass-_$s11MyTestClassAACMn)-8 + .long (_$s11MyTestClassAACMa-_$s11MyTestClassAACMn)-12 + .long (_$s11MyTestClassAACMF-_$s11MyTestClassAACMn)-16 + .long ("_symbolic So8NSObjectC"-_$s11MyTestClassAACMn)-20 + .long 3 + .long 11 + .long 1 + .long 0 + .long 10 + .long 10 + .long 1 + .long 16 + .long (_$s11MyTestClassAAC14protocolMethodyyF-_$s11MyTestClassAACMn)-56 + + .section __DATA,__objc_data + .p2align 3, 0x0 +_$s11MyTestClassAACMf: + .quad 0 + .quad _$s11MyTestClassAACfD + .quad _$sBOWV + .quad _OBJC_METACLASS_$__TtC11MyTestClass11MyTestClass + .quad _OBJC_CLASS_$_NSObject + .quad __objc_empty_cache + .quad 0 + .quad __DATA__TtC11MyTestClass11MyTestClass+2 + .long 0 + .long 0 + .long 8 + .short 7 + .short 0 + .long 112 + .long 24 + .quad _$s11MyTestClassAACMn + .quad 0 + .quad _$s11MyTestClassAAC14protocolMethodyyF + + .private_extern "_symbolic _____ 11MyTestClassAAC" + .section __TEXT,__swift5_typeref + .globl "_symbolic _____ 11MyTestClassAAC" + .weak_definition "_symbolic _____ 11MyTestClassAAC" + .p2align 1, 0x0 +"_symbolic _____ 11MyTestClassAAC": + .byte 1 + .long (_$s11MyTestClassAACMn-"_symbolic _____ 11MyTestClassAAC")-1 + .byte 0 + + .section __TEXT,__swift5_fieldmd + .p2align 2, 0x0 +_$s11MyTestClassAACMF: + .long "_symbolic _____ 11MyTestClassAAC"-_$s11MyTestClassAACMF + .long ("_symbolic So8NSObjectC"-_$s11MyTestClassAACMF)-4 + .short 7 + .short 12 + .long 0 + + .section __TEXT,__swift5_protos + .p2align 2, 0x0 +l_$s11MyTestClass0A8ProtocolHr: + .long _$s11MyTestClass0A8ProtocolMp-l_$s11MyTestClass0A8ProtocolHr + + .section __TEXT,__swift5_proto + .p2align 2, 0x0 +l_$s11MyTestClassAACAA0A8ProtocolAAHc: + .long _$s11MyTestClassAACAA0A8ProtocolAAMc-l_$s11MyTestClassAACAA0A8ProtocolAAHc + + .section __TEXT,__swift5_types + .p2align 2, 0x0 +l_$s11MyTestClassAACHn: + .long _$s11MyTestClassAACMn-l_$s11MyTestClassAACHn + + .private_extern ___swift_reflection_version + .section __TEXT,__const + .globl ___swift_reflection_version + .weak_definition ___swift_reflection_version + .p2align 1, 0x0 +___swift_reflection_version: + .short 3 + + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 +_objc_classes_$s11MyTestClassAACN: + .quad _$s11MyTestClassAACN + + .no_dead_strip _main + .no_dead_strip l_entry_point + .no_dead_strip _$s11MyTestClass0A8Protocol_pMF + .no_dead_strip _$s11MyTestClassAACMF + .no_dead_strip __swift_FORCE_LOAD_$_swiftFoundation_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftDarwin_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftObjectiveC_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftCoreFoundation_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftDispatch_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftXPC_$_MyTestClass + .no_dead_strip __swift_FORCE_LOAD_$_swiftIOKit_$_MyTestClass + .no_dead_strip l_$s11MyTestClass0A8ProtocolHr + .no_dead_strip l_$s11MyTestClassAACAA0A8ProtocolAAHc + .no_dead_strip l_$s11MyTestClassAACHn + .no_dead_strip ___swift_reflection_version + .no_dead_strip _objc_classes_$s11MyTestClassAACN + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 100665152 + + .globl _$s11MyTestClass0A8ProtocolTL + .private_extern _$s11MyTestClass0A8ProtocolTL + .alt_entry _$s11MyTestClass0A8ProtocolTL +.set _$s11MyTestClass0A8ProtocolTL, (_$s11MyTestClass0A8ProtocolMp+24)-8 + .globl _$s11MyTestClassAAC14protocolMethodyyFTq + .private_extern _$s11MyTestClassAAC14protocolMethodyyFTq + .alt_entry _$s11MyTestClassAAC14protocolMethodyyFTq +.set _$s11MyTestClassAAC14protocolMethodyyFTq, _$s11MyTestClassAACMn+52 + .globl _$s11MyTestClassAACN + .private_extern _$s11MyTestClassAACN + .alt_entry _$s11MyTestClassAACN +.set _$s11MyTestClassAACN, _$s11MyTestClassAACMf+24 + .globl _OBJC_CLASS_$__TtC11MyTestClass11MyTestClass + .private_extern _OBJC_CLASS_$__TtC11MyTestClass11MyTestClass +.set _OBJC_CLASS_$__TtC11MyTestClass11MyTestClass, _$s11MyTestClassAACN + .weak_reference __swift_FORCE_LOAD_$_swiftFoundation + .weak_reference __swift_FORCE_LOAD_$_swiftDarwin + .weak_reference __swift_FORCE_LOAD_$_swiftObjectiveC + .weak_reference __swift_FORCE_LOAD_$_swiftCoreFoundation + .weak_reference __swift_FORCE_LOAD_$_swiftDispatch + .weak_reference __swift_FORCE_LOAD_$_swiftXPC + .weak_reference __swift_FORCE_LOAD_$_swiftIOKit +.subsections_via_symbols + +_OBJC_CLASS_$_NSObject: +_OBJC_METACLASS_$_NSObject: +__objc_empty_cache: +_$sBOWV: + .quad 0 diff --git a/wild/tests/lld-macho/objc-category-merging-swift.s b/wild/tests/lld-macho/objc-category-merging-swift.s new file mode 100644 index 000000000..7a084d931 --- /dev/null +++ b/wild/tests/lld-macho/objc-category-merging-swift.s @@ -0,0 +1,410 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; mkdir %t && cd %t + +############ Test merging multiple categories into a single category ############ +## Apply category merging to swiftc code just make sure we can handle addends +## and don't erase category names for swift -- in order to not crash +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o cat_swift.o %s +# RUN: %lld -arch arm64 -dylib -o cat_swift.dylib cat_swift.o -objc_category_merging -no_objc_relative_method_lists +# RUN: llvm-objdump --objc-meta-data --macho cat_swift.dylib | FileCheck %s --check-prefixes=CHECK-MERGE + +; CHECK-MERGE: Contents of (__DATA_CONST,__objc_classlist) section +; CHECK-MERGE-NEXT: _$s11SimpleClassAACN +; CHECK-MERGE-NEXT: isa {{.+}} _OBJC_METACLASS_$__TtC11SimpleClass11SimpleClass +; CHECK-MERGE-NEXT: superclass 0x0 +; CHECK-MERGE-NEXT: cache 0x0 +; CHECK-MERGE-NEXT: vtable 0x0 +; CHECK-MERGE-NEXT: data {{.+}} (struct class_ro_t *) Swift class +; CHECK-MERGE-NEXT: flags 0x80 +; CHECK-MERGE-NEXT: instanceStart 8 +; CHECK-MERGE-NEXT: instanceSize 8 +; CHECK-MERGE-NEXT: reserved 0x0 +; CHECK-MERGE-NEXT: ivarLayout 0x0 +; CHECK-MERGE-NEXT: name {{.+}} _TtC11SimpleClass11SimpleClass +; CHECK-MERGE-NEXT: baseMethods {{.+}} (struct method_list_t *) +; CHECK-MERGE-NEXT: entsize 24 +; CHECK-MERGE-NEXT: count 3 +; CHECK-MERGE-NEXT: name {{.+}} categoryInstanceMethod +; CHECK-MERGE-NEXT: types {{.+}} q16@0:8 +; CHECK-MERGE-NEXT: imp _$s11SimpleClassAAC22categoryInstanceMethodSiyFTo +; CHECK-MERGE-NEXT: name {{.+}} baseClassInstanceMethod +; CHECK-MERGE-NEXT: types {{.+}} i16@0:8 +; CHECK-MERGE-NEXT: imp _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTo +; CHECK-MERGE-NEXT: name {{.+}} init +; CHECK-MERGE-NEXT: types {{.+}} @16@0:8 +; CHECK-MERGE-NEXT: imp _$s11SimpleClassAACABycfcTo +; CHECK-MERGE-NEXT: baseProtocols 0x0 +; CHECK-MERGE-NEXT: ivars 0x0 +; CHECK-MERGE-NEXT: weakIvarLayout 0x0 +; CHECK-MERGE-NEXT: baseProperties 0x0 +; CHECK-MERGE-NEXT: Meta Class +; CHECK-MERGE-NEXT: isa 0x0 +; CHECK-MERGE-NEXT: superclass 0x0 +; CHECK-MERGE-NEXT: cache 0x0 +; CHECK-MERGE-NEXT: vtable 0x0 +; CHECK-MERGE-NEXT: data {{.+}} (struct class_ro_t *) +; CHECK-MERGE-NEXT: flags 0x81 RO_META +; CHECK-MERGE-NEXT: instanceStart 40 +; CHECK-MERGE-NEXT: instanceSize 40 +; CHECK-MERGE-NEXT: reserved 0x0 +; CHECK-MERGE-NEXT: ivarLayout 0x0 +; CHECK-MERGE-NEXT: name {{.+}} _TtC11SimpleClass11SimpleClass +; CHECK-MERGE-NEXT: baseMethods 0x0 (struct method_list_t *) +; CHECK-MERGE-NEXT: baseProtocols 0x0 +; CHECK-MERGE-NEXT: ivars 0x0 +; CHECK-MERGE-NEXT: weakIvarLayout 0x0 +; CHECK-MERGE-NEXT: baseProperties 0x0 +; CHECK-MERGE-NEXT: Contents of (__DATA_CONST,__objc_imageinfo) section +; CHECK-MERGE-NEXT: version 0 +; CHECK-MERGE-NEXT: flags 0x740 OBJC_IMAGE_HAS_CATEGORY_CLASS_PROPERTIES Swift 5 or later + +; ================== Generated from Swift: ================== +;; > xcrun swiftc --version +;; swift-driver version: 1.109.2 Apple Swift version 6.0 (swiftlang-6.0.0.3.300 clang-1600.0.20.10) +;; > xcrun swiftc -S SimpleClass.swift -o SimpleClass.s +; import Foundation +; @objc class SimpleClass: NSObject { +; @objc func baseClassInstanceMethod() -> Int32 { +; return 2 +; } +; } +; extension SimpleClass { +; @objc func categoryInstanceMethod() -> Int { +; return 3 +; } +; } + +; ================== Generated from Swift: ================== + .section __TEXT,__text,regular,pure_instructions + .build_version macos, 11, 0 sdk_version 12, 0 + .globl _main + .p2align 2 +_main: + .cfi_startproc + mov w0, #0 + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyF + .globl _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyF + .p2align 2 +_$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyF: + .cfi_startproc + ret + .cfi_endproc + + .p2align 2 +_$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTo: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAACABycfC + .globl _$s11SimpleClassAACABycfC + .p2align 2 +_$s11SimpleClassAACABycfC: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAACABycfc + .globl _$s11SimpleClassAACABycfc + .p2align 2 +_$s11SimpleClassAACABycfc: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAACMa + .globl _$s11SimpleClassAACMa + .p2align 2 +_$s11SimpleClassAACMa: + ret + + .p2align 2 +_$s11SimpleClassAACABycfcTo: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAACfD + .globl _$s11SimpleClassAACfD + .p2align 2 +_$s11SimpleClassAACfD: + .cfi_startproc + ret + .cfi_endproc + + .private_extern _$s11SimpleClassAAC22categoryInstanceMethodSiyF + .globl _$s11SimpleClassAAC22categoryInstanceMethodSiyF + .p2align 2 +_$s11SimpleClassAAC22categoryInstanceMethodSiyF: + .cfi_startproc + ret + .cfi_endproc + + .p2align 2 +_$s11SimpleClassAAC22categoryInstanceMethodSiyFTo: + .cfi_startproc + ret + .cfi_endproc + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(init)": + .asciz "init" + + .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip + .p2align 3, 0x0 +"L_selector(init)": + .quad "L_selector_data(init)" + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(dealloc)": + .asciz "dealloc" + + .section __DATA,__objc_selrefs,literal_pointers,no_dead_strip + .p2align 3, 0x0 +"L_selector(dealloc)": + .quad "L_selector_data(dealloc)" + + .section __TEXT,__swift5_entry,regular,no_dead_strip + .p2align 2, 0x0 +l_entry_point: + .long _main-l_entry_point + .long 0 + + .private_extern _OBJC_METACLASS_$__TtC11SimpleClass11SimpleClass + .section __DATA,__data + .globl _OBJC_METACLASS_$__TtC11SimpleClass11SimpleClass + .p2align 3, 0x0 +_OBJC_METACLASS_$__TtC11SimpleClass11SimpleClass: + .quad _OBJC_METACLASS_$_NSObject + .quad _OBJC_METACLASS_$_NSObject + .quad __objc_empty_cache + .quad 0 + .quad __METACLASS_DATA__TtC11SimpleClass11SimpleClass + + .section __TEXT,__cstring,cstring_literals + .p2align 4, 0x0 +l_.str.30._TtC11SimpleClass11SimpleClass: + .asciz "_TtC11SimpleClass11SimpleClass" + + .section __DATA,__objc_const + .p2align 3, 0x0 +__METACLASS_DATA__TtC11SimpleClass11SimpleClass: + .long 129 + .long 40 + .long 40 + .long 0 + .quad 0 + .quad l_.str.30._TtC11SimpleClass11SimpleClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(baseClassInstanceMethod)": + .asciz "baseClassInstanceMethod" + + .section __TEXT,__cstring,cstring_literals +"l_.str.7.i16@0:8": + .asciz "i16@0:8" + +"l_.str.7.@16@0:8": + .asciz "@16@0:8" + + .section __DATA,__objc_data + .p2align 3, 0x0 +__INSTANCE_METHODS__TtC11SimpleClass11SimpleClass: + .long 24 + .long 2 + .quad "L_selector_data(baseClassInstanceMethod)" + .quad "l_.str.7.i16@0:8" + .quad _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTo + .quad "L_selector_data(init)" + .quad "l_.str.7.@16@0:8" + .quad _$s11SimpleClassAACABycfcTo + + .p2align 3, 0x0 +__DATA__TtC11SimpleClass11SimpleClass: + .long 128 + .long 8 + .long 8 + .long 0 + .quad 0 + .quad l_.str.30._TtC11SimpleClass11SimpleClass + .quad __INSTANCE_METHODS__TtC11SimpleClass11SimpleClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__const +l_.str.11.SimpleClass: + .asciz "SimpleClass" + + .private_extern _$s11SimpleClassMXM + .section __TEXT,__constg_swiftt + .globl _$s11SimpleClassMXM + .weak_definition _$s11SimpleClassMXM + .p2align 2, 0x0 +_$s11SimpleClassMXM: + .long 0 + .long 0 + .long (l_.str.11.SimpleClass-_$s11SimpleClassMXM)-8 + + .private_extern "_symbolic So8NSObjectC" + .section __TEXT,__swift5_typeref + .globl "_symbolic So8NSObjectC" + .weak_definition "_symbolic So8NSObjectC" + .p2align 1, 0x0 +"_symbolic So8NSObjectC": + .ascii "So8NSObjectC" + .byte 0 + + .private_extern _$s11SimpleClassAACMn + .section __TEXT,__constg_swiftt + .globl _$s11SimpleClassAACMn + .p2align 2, 0x0 +_$s11SimpleClassAACMn: + .long 2147483728 + .long (_$s11SimpleClassMXM-_$s11SimpleClassAACMn)-4 + .long (l_.str.11.SimpleClass-_$s11SimpleClassAACMn)-8 + .long (_$s11SimpleClassAACMa-_$s11SimpleClassAACMn)-12 + .long (_$s11SimpleClassAACMF-_$s11SimpleClassAACMn)-16 + .long ("_symbolic So8NSObjectC"-_$s11SimpleClassAACMn)-20 + .long 3 + .long 11 + .long 1 + .long 0 + .long 10 + .long 10 + .long 1 + .long 16 + .long (_$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyF-_$s11SimpleClassAACMn)-56 + + .section __DATA,__objc_data + .p2align 3, 0x0 +_$s11SimpleClassAACMf: + .quad 0 + .quad _$s11SimpleClassAACfD + .quad _$sBOWV + .quad _OBJC_METACLASS_$__TtC11SimpleClass11SimpleClass + .quad _OBJC_CLASS_$_NSObject + .quad __objc_empty_cache + .quad 0 + .quad __DATA__TtC11SimpleClass11SimpleClass+2 + .long 0 + .long 0 + .long 8 + .short 7 + .short 0 + .long 112 + .long 24 + .quad _$s11SimpleClassAACMn + .quad 0 + .quad _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyF + + .private_extern "_symbolic _____ 11SimpleClassAAC" + .section __TEXT,__swift5_typeref + .globl "_symbolic _____ 11SimpleClassAAC" + .weak_definition "_symbolic _____ 11SimpleClassAAC" + .p2align 1, 0x0 +"_symbolic _____ 11SimpleClassAAC": + .byte 1 + .long (_$s11SimpleClassAACMn-"_symbolic _____ 11SimpleClassAAC")-1 + .byte 0 + + .section __TEXT,__swift5_fieldmd + .p2align 2, 0x0 +_$s11SimpleClassAACMF: + .long "_symbolic _____ 11SimpleClassAAC"-_$s11SimpleClassAACMF + .long ("_symbolic So8NSObjectC"-_$s11SimpleClassAACMF)-4 + .short 7 + .short 12 + .long 0 + + .section __TEXT,__objc_methname,cstring_literals +"L_selector_data(categoryInstanceMethod)": + .asciz "categoryInstanceMethod" + + .section __TEXT,__cstring,cstring_literals +"l_.str.7.q16@0:8": + .asciz "q16@0:8" + + .section __DATA,__objc_data + .p2align 3, 0x0 +__CATEGORY_INSTANCE_METHODS__TtC11SimpleClass11SimpleClass_$_SimpleClass: + .long 24 + .long 1 + .quad "L_selector_data(categoryInstanceMethod)" + .quad "l_.str.7.q16@0:8" + .quad _$s11SimpleClassAAC22categoryInstanceMethodSiyFTo + + .section __DATA,__objc_const + .p2align 3, 0x0 +__CATEGORY__TtC11SimpleClass11SimpleClass_$_SimpleClass: + .quad l_.str.11.SimpleClass + .quad _$s11SimpleClassAACMf+24 + .quad __CATEGORY_INSTANCE_METHODS__TtC11SimpleClass11SimpleClass_$_SimpleClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .long 60 + .space 4 + + .section __TEXT,__swift5_types + .p2align 2, 0x0 +l_$s11SimpleClassAACHn: + .long _$s11SimpleClassAACMn-l_$s11SimpleClassAACHn + + .private_extern ___swift_reflection_version + .section __TEXT,__const + .globl ___swift_reflection_version + .weak_definition ___swift_reflection_version + .p2align 1, 0x0 +___swift_reflection_version: + .short 3 + + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 +_objc_classes_$s11SimpleClassAACN: + .quad _$s11SimpleClassAACN + + .section __DATA,__objc_catlist,regular,no_dead_strip + .p2align 3, 0x0 +_objc_categories: + .quad __CATEGORY__TtC11SimpleClass11SimpleClass_$_SimpleClass + + .no_dead_strip _main + .no_dead_strip l_entry_point + .no_dead_strip _$s11SimpleClassAACMF + .no_dead_strip l_$s11SimpleClassAACHn + .no_dead_strip ___swift_reflection_version + .no_dead_strip _objc_classes_$s11SimpleClassAACN + .no_dead_strip _objc_categories + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 100665152 + + .globl _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTq + .private_extern _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTq + .alt_entry _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTq +.set _$s11SimpleClassAAC04baseB14InstanceMethods5Int32VyFTq, _$s11SimpleClassAACMn+52 + .globl _$s11SimpleClassAACN + .private_extern _$s11SimpleClassAACN + .alt_entry _$s11SimpleClassAACN +.set _$s11SimpleClassAACN, _$s11SimpleClassAACMf+24 + .globl _OBJC_CLASS_$__TtC11SimpleClass11SimpleClass + .private_extern _OBJC_CLASS_$__TtC11SimpleClass11SimpleClass +.subsections_via_symbols + +_OBJC_CLASS_$_NSObject: +_OBJC_METACLASS_$_NSObject: +__objc_empty_cache: +_$sBOWV: + .quad 0 diff --git a/wild/tests/lld-macho/objc-methname.s b/wild/tests/lld-macho/objc-methname.s new file mode 100644 index 000000000..3d0647297 --- /dev/null +++ b/wild/tests/lld-macho/objc-methname.s @@ -0,0 +1,44 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/strings.s -o %t/strings.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o + +# RUN: %lld -arch arm64 -lSystem -o %t.out %t/strings.o %t/main.o --no-deduplicate-strings + +# RUN: llvm-otool -vs __TEXT __cstring %t.out | FileCheck %s --check-prefix=CSTRING +# RUN: llvm-otool -vs __TEXT __objc_methname %t.out | FileCheck %s --check-prefix=METHNAME + +# RUN: %lld -arch arm64 -lSystem -o %t/duplicates %t/strings.o %t/strings.o %t/main.o + +# RUN: llvm-otool -vs __TEXT __cstring %t/duplicates | FileCheck %s --check-prefix=CSTRING +# RUN: llvm-otool -vs __TEXT __objc_methname %t/duplicates | FileCheck %s --check-prefix=METHNAME + +# CSTRING: Contents of (__TEXT,__cstring) section +# CSTRING-NEXT: existing-cstring +# CSTRING-EMPTY: + +# METHNAME: Contents of (__TEXT,__objc_methname) section +# METHNAME-NEXT: existing_methname +# METHNAME-NEXT: synthetic_methname +# METHNAME-EMPTY: + +#--- strings.s +.cstring +.p2align 2 + .asciz "existing-cstring" + +.section __TEXT,__objc_methname,cstring_literals + .asciz "existing_methname" + +#--- main.s +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$existing_methname + bl _objc_msgSend$synthetic_methname + ret diff --git a/wild/tests/lld-macho/objc-relative-method-lists-simple.s b/wild/tests/lld-macho/objc-relative-method-lists-simple.s new file mode 100644 index 000000000..c8646f596 --- /dev/null +++ b/wild/tests/lld-macho/objc-relative-method-lists-simple.s @@ -0,0 +1,258 @@ +# REQUIRES: aarch64 +# UNSUPPORTED: target=arm{{.*}}-unknown-linux-gnueabihf +# RUN: rm -rf %t; split-file %s %t && cd %t + +## Compile a64_rel_dylib.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos10.15 -o a64_rel_dylib.o a64_simple_class.s + +## Test arm64 + relative method lists +# RUN: %no-lsystem-lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_REL + +## Test arm64 + relative method lists + dead-strip +# RUN: %no-lsystem-lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -dead_strip +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_REL + +## Test arm64 + traditional method lists (no relative offsets) +# RUN: %no-lsystem-lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -no_objc_relative_method_lists +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_NO_REL + +## Test arm64 + relative method lists by explicitly adding `-objc_relative_method_lists`. +# RUN: %lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -platform_version macOS 10.15 10.15 -objc_relative_method_lists +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_REL + +## Test arm64 + no relative method lists by default. +# RUN: %lld a64_rel_dylib.o -o a64_rel_dylib.dylib -map a64_rel_dylib.map -dylib -arch arm64 -platform_version macOS 10.15 10.15 +# RUN: llvm-objdump --macho --objc-meta-data a64_rel_dylib.dylib | FileCheck %s --check-prefix=CHK_NO_REL + + +CHK_REL: Contents of (__DATA_CONST,__objc_classlist) section +CHK_REL-NEXT: _OBJC_CLASS_$_MyClass +CHK_REL: baseMethods +CHK_REL-NEXT: entsize 12 (relative) +CHK_REL-NEXT: count 3 +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_00 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_00] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_01 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_01] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) instance_method_02 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) -[MyClass instance_method_02] + +CHK_REL: Meta Class +CHK_REL-NEXT: isa 0x{{[0-9a-f]*}} _OBJC_METACLASS_$_MyClass +CHK_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_REL-NEXT: entsize 12 (relative) +CHK_REL-NEXT: count 3 +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_00 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_00] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_01 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_01] +CHK_REL-NEXT: name 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) class_method_02 +CHK_REL-NEXT: types 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) v16@0:8 +CHK_REL-NEXT: imp 0x{{[0-9a-f]*}} (0x{{[0-9a-f]*}}) +[MyClass class_method_02] + + +CHK_NO_REL-NOT: (relative) + +CHK_NO_REL: Contents of (__DATA_CONST,__objc_classlist) section +CHK_NO_REL-NEXT: _OBJC_CLASS_$_MyClass + +CHK_NO_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_NO_REL-NEXT: entsize 24 +CHK_NO_REL-NEXT: count 3 +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_00 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_00] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_01 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_01] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} instance_method_02 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp -[MyClass instance_method_02] + + +CHK_NO_REL: Meta Class +CHK_NO_REL-NEXT: _OBJC_METACLASS_$_MyClass + +CHK_NO_REL: baseMethods 0x{{[0-9a-f]*}} (struct method_list_t *) +CHK_NO_REL-NEXT: entsize 24 +CHK_NO_REL-NEXT: count 3 +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_00 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_00] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_01 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_01] +CHK_NO_REL-NEXT: name 0x{{[0-9a-f]*}} class_method_02 +CHK_NO_REL-NEXT: types 0x{{[0-9a-f]*}} v16@0:8 +CHK_NO_REL-NEXT: imp +[MyClass class_method_02] + + +######################## Generate a64_simple_class.s ######################### +# clang -c simple_class.mm -s -o a64_simple_class.s -target arm64-apple-macos -arch arm64 -Oz + +######################## simple_class.mm ######################## +# __attribute__((objc_root_class)) +# @interface MyClass +# - (void)instance_method_00; +# - (void)instance_method_01; +# - (void)instance_method_02; +# + (void)class_method_00; +# + (void)class_method_01; +# + (void)class_method_02; +# @end +# +# @implementation MyClass +# - (void)instance_method_00 {} +# - (void)instance_method_01 {} +# - (void)instance_method_02 {} +# + (void)class_method_00 {} +# + (void)class_method_01 {} +# + (void)class_method_02 {} +# @end +# +# void *_objc_empty_cache; +# void *_objc_empty_vtable; +# + +#--- objc-macros.s +.macro .objc_selector_def name + .p2align 2 +"\name": + .cfi_startproc + ret + .cfi_endproc +.endm + +#--- a64_simple_class.s +.include "objc-macros.s" + +.section __TEXT,__text,regular,pure_instructions +.build_version macos, 10, 15 + +.objc_selector_def "-[MyClass instance_method_00]" +.objc_selector_def "-[MyClass instance_method_01]" +.objc_selector_def "-[MyClass instance_method_02]" + +.objc_selector_def "+[MyClass class_method_00]" +.objc_selector_def "+[MyClass class_method_01]" +.objc_selector_def "+[MyClass class_method_02]" + +.globl __objc_empty_vtable +.zerofill __DATA,__common,__objc_empty_vtable,8,3 +.section __DATA,__objc_data +.globl _OBJC_CLASS_$_MyClass +.p2align 3, 0x0 + +_OBJC_CLASS_$_MyClass: + .quad _OBJC_METACLASS_$_MyClass + .quad 0 + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_CLASS_RO_$_MyClass + .globl _OBJC_METACLASS_$_MyClass + .p2align 3, 0x0 + +_OBJC_METACLASS_$_MyClass: + .quad _OBJC_METACLASS_$_MyClass + .quad _OBJC_CLASS_$_MyClass + .quad __objc_empty_cache + .quad __objc_empty_vtable + .quad __OBJC_METACLASS_RO_$_MyClass + + .section __TEXT,__objc_classname,cstring_literals +l_OBJC_CLASS_NAME_: + .asciz "MyClass" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_: + .asciz "class_method_00" + .section __TEXT,__objc_methtype,cstring_literals +l_OBJC_METH_VAR_TYPE_: + .asciz "v16@0:8" + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.1: + .asciz "class_method_01" +l_OBJC_METH_VAR_NAME_.2: + .asciz "class_method_02" + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_CLASS_METHODS_MyClass: + .long 24 + .long 3 + .quad l_OBJC_METH_VAR_NAME_ + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_00]" + .quad l_OBJC_METH_VAR_NAME_.1 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_01]" + .quad l_OBJC_METH_VAR_NAME_.2 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "+[MyClass class_method_02]" + .p2align 3, 0x0 + +__OBJC_METACLASS_RO_$_MyClass: + .long 3 + .long 40 + .long 40 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_CLASS_METHODS_MyClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + + .section __TEXT,__objc_methname,cstring_literals +l_OBJC_METH_VAR_NAME_.3: + .asciz "instance_method_00" +l_OBJC_METH_VAR_NAME_.4: + .asciz "instance_method_01" +l_OBJC_METH_VAR_NAME_.5: + .asciz "instance_method_02" + + .section __DATA,__objc_const + .p2align 3, 0x0 +__OBJC_$_INSTANCE_METHODS_MyClass: + .long 24 + .long 3 + .quad l_OBJC_METH_VAR_NAME_.3 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_00]" + .quad l_OBJC_METH_VAR_NAME_.4 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_01]" + .quad l_OBJC_METH_VAR_NAME_.5 + .quad l_OBJC_METH_VAR_TYPE_ + .quad "-[MyClass instance_method_02]" + .p2align 3, 0x0 + +__OBJC_CLASS_RO_$_MyClass: + .long 2 + .long 0 + .long 0 + .space 4 + .quad 0 + .quad l_OBJC_CLASS_NAME_ + .quad __OBJC_$_INSTANCE_METHODS_MyClass + .quad 0 + .quad 0 + .quad 0 + .quad 0 + .globl __objc_empty_cache + +.zerofill __DATA,__common,__objc_empty_cache,8,3 + .section __DATA,__objc_classlist,regular,no_dead_strip + .p2align 3, 0x0 +l_OBJC_LABEL_CLASS_$: + .quad _OBJC_CLASS_$_MyClass + .section __DATA,__objc_imageinfo,regular,no_dead_strip +L_OBJC_IMAGE_INFO: + .long 0 + .long 64 +.subsections_via_symbols diff --git a/wild/tests/lld-macho/objc-selrefs.s b/wild/tests/lld-macho/objc-selrefs.s new file mode 100644 index 000000000..eebe7c647 --- /dev/null +++ b/wild/tests/lld-macho/objc-selrefs.s @@ -0,0 +1,81 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/explicit-selrefs-1.s -o %t/explicit-selrefs-1.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/explicit-selrefs-2.s -o %t/explicit-selrefs-2.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/implicit-selrefs.s -o %t/implicit-selrefs.o + +# RUN: %lld -dylib -arch arm64 -lSystem -o %t/explicit-only-no-icf \ +# RUN: %t/explicit-selrefs-1.o %t/explicit-selrefs-2.o -no_fixup_chains +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/explicit-only-no-icf | \ +# RUN: FileCheck %s --check-prefix=EXPLICIT-NO-ICF + +## NOTE: ld64 always dedups the selrefs unconditionally, but we only do it when +## ICF is enabled. +# RUN: %lld -dylib -arch arm64 -lSystem -o %t/explicit-only-with-icf \ +# RUN: %t/explicit-selrefs-1.o %t/explicit-selrefs-2.o -no_fixup_chains +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/explicit-only-with-icf \ +# RUN: | FileCheck %s --check-prefix=EXPLICIT-WITH-ICF + +# SELREFS: Contents of (__DATA,__objc_selrefs) section +# SELREFS-NEXT: __TEXT:__objc_methname:foo +# SELREFS-NEXT: __TEXT:__objc_methname:bar +# SELREFS-NEXT: __TEXT:__objc_methname:foo +# SELREFS-NEXT: __TEXT:__objc_methname:length +# SELREFS-EMPTY: + +# RUN: %lld -dylib -arch arm64 -lSystem --icf=all -o %t/explicit-and-implicit \ +# RUN: %t/explicit-selrefs-1.o %t/explicit-selrefs-2.o %t/implicit-selrefs.o \ +# RUN: -no_fixup_chains +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/explicit-and-implicit \ +# RUN: | FileCheck %s --check-prefix=EXPLICIT-AND-IMPLICIT + +# EXPLICIT-NO-ICF: Contents of (__DATA,__objc_selrefs) section +# EXPLICIT-NO-ICF-NEXT: __TEXT:__objc_methname:foo +# EXPLICIT-NO-ICF-NEXT: __TEXT:__objc_methname:bar +# EXPLICIT-NO-ICF-NEXT: __TEXT:__objc_methname:bar +# EXPLICIT-NO-ICF-NEXT: __TEXT:__objc_methname:foo + +# EXPLICIT-WITH-ICF: Contents of (__DATA,__objc_selrefs) section +# EXPLICIT-WITH-ICF-NEXT: __TEXT:__objc_methname:foo +# EXPLICIT-WITH-ICF-NEXT: __TEXT:__objc_methname:bar + +# EXPLICIT-AND-IMPLICIT: Contents of (__DATA,__objc_selrefs) section +# EXPLICIT-AND-IMPLICIT-NEXT: __TEXT:__objc_methname:foo +# EXPLICIT-AND-IMPLICIT-NEXT: __TEXT:__objc_methname:bar +# EXPLICIT-AND-IMPLICIT-NEXT: __TEXT:__objc_methname:length + +#--- explicit-selrefs-1.s +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 + .quad lselref1 + .quad lselref2 + .quad lselref2 + +#--- explicit-selrefs-2.s +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 + .quad lselref1 + +#--- implicit-selrefs.s +.text +.globl _objc_msgSend +.p2align 2 +_objc_msgSend: + ret + +.p2align 2 +_sender: + bl _objc_msgSend$length + bl _objc_msgSend$foo + ret diff --git a/wild/tests/lld-macho/order-file-cstring-tailmerge.s b/wild/tests/lld-macho/order-file-cstring-tailmerge.s new file mode 100644 index 000000000..20a4d162c --- /dev/null +++ b/wild/tests/lld-macho/order-file-cstring-tailmerge.s @@ -0,0 +1,56 @@ +; REQUIRES: aarch64 +; RUN: rm -rf %t && split-file %s %t + +; RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o +; RUN: %lld -dylib -arch arm64 --no-tail-merge-strings -order_file %t/orderfile.txt %t/a.o -o - | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s +; RUN: %lld -dylib -arch arm64 --tail-merge-strings -order_file %t/orderfile.txt %t/a.o -o - | llvm-nm --numeric-sort --format=just-symbols - | FileCheck %s --check-prefix=MERGED + +; CHECK: _str2 +; CHECK: _str1 +; CHECK: _superstr2 +; CHECK: _superstr3 +; CHECK: _superstr1 +; CHECK: _str3 + +; str1 has a higher priority than superstr1, so str1 must be ordered before +; str3, even though superstr1 is before superstr3 in the orderfile. + +; MERGED: _superstr2 +; MERGED: _str2 +; MERGED: _superstr1 +; MERGED: _str1 +; MERGED: _superstr3 +; MERGED: _str3 + +;--- a.s +.cstring + _superstr1: +.asciz "superstr1" + _str1: +.asciz "str1" + _superstr2: +.asciz "superstr2" + _str2: +.asciz "str2" + _superstr3: +.asciz "superstr3" + _str3: +.asciz "str3" + +; TODO: We could use update_test_body.py to generate the hashes for the +; orderfile. Unfortunately, it seems that LLVM has a different hash +; implementation than the xxh64sum tool. See +; DeduplicatedCStringSection::getStringOffset() for hash details. +; +; while IFS="" read -r line; do +; echo -n $line | xxh64sum | awk '{printf "CSTR;%010d", and(strtonum("0x"$1), 0x7FFFFFFF)}' +; echo " # $line" +; done < orderfile.txt.template + +;--- orderfile.txt +CSTR;1236462241 # str2 +CSTR;1526669509 # str1 +CSTR;1563550684 # superstr2 +CSTR;1044337806 # superstr3 +CSTR;262417687 # superstr1 +CSTR;717161398 # str3 diff --git a/wild/tests/lld-macho/order-file-cstring.s b/wild/tests/lld-macho/order-file-cstring.s new file mode 100644 index 000000000..ca3c32bb1 --- /dev/null +++ b/wild/tests/lld-macho/order-file-cstring.s @@ -0,0 +1,230 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/test.s -o %t/test.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/more-cstrings.s -o %t/more-cstrings.o + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/test-0 %t/test.o %t/more-cstrings.o +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-0 | FileCheck %s --check-prefix=ORIGIN_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-0 | FileCheck %s --check-prefix=ORIGIN_SEC + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/test-1 %t/test.o %t/more-cstrings.o -order_file %t/ord-1 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-1 | FileCheck %s --check-prefix=ONE_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-1 | FileCheck %s --check-prefix=ONE_SEC + +# RUN: %lld --no-deduplicate-strings -arch arm64 -lSystem -e _main -o %t/test-1-dup %t/test.o %t/more-cstrings.o -order_file %t/ord-1 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-1-dup | FileCheck %s --check-prefix=ONE_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-1-dup | FileCheck %s --check-prefix=ONE_SEC + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/test-2 %t/test.o %t/more-cstrings.o -order_file %t/ord-2 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-2 | FileCheck %s --check-prefix=TWO_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-2 | FileCheck %s --check-prefix=TWO_SEC + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/test-3 %t/test.o %t/more-cstrings.o -order_file %t/ord-3 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-3 | FileCheck %s --check-prefix=THREE_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-3 | FileCheck %s --check-prefix=THREE_SEC + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/test-4 %t/test.o %t/more-cstrings.o -order_file %t/ord-4 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/test-4 | FileCheck %s --check-prefix=FOUR_SYM +# RUN: llvm-objdump --macho --section="__TEXT,__cstring" %t/test-4 | FileCheck %s --check-prefix=FOUR_SEC +# RUN: llvm-readobj --string-dump=__cstring %t/test-4 | FileCheck %s --check-prefix=FOUR_SEC_ESCAPE + +# We expect: +# 1) Covered cstring symbols to be reordered +# 2) the rest of the cstring symbols remain in the original relative order within the cstring section + +# ORIGIN_SYM: _local_foo1 +# ORIGIN_SYM: _globl_foo2 +# ORIGIN_SYM: _local_foo2 +# ORIGIN_SYM: _bar +# ORIGIN_SYM: _baz +# ORIGIN_SYM: _baz_dup +# ORIGIN_SYM: _bar2 +# ORIGIN_SYM: _globl_foo3 + +# ORIGIN_SEC: foo1 +# ORIGIN_SEC: foo2 +# ORIGIN_SEC: bar +# ORIGIN_SEC: baz +# ORIGIN_SEC: bar2 +# ORIGIN_SEC: foo3 + +# original order, but only parital covered +#--- ord-1 +#foo2 +CSTR;1433942677 +#bar +CSTR;0x2032D362 +#bar2 +CSTR;1496286555 +#foo3 +CSTR;0x501BCC31 + +# ONE_SYM-DAG: _globl_foo2 +# ONE_SYM-DAG: _local_foo2 +# ONE_SYM: _bar +# ONE_SYM: _bar2 +# ONE_SYM: _globl_foo3 +# ONE_SYM: _local_foo1 +# ONE_SYM: _baz +# ONE_SYM: _baz_dup + +# ONE_SEC: foo2 +# ONE_SEC: bar +# ONE_SEC: bar2 +# ONE_SEC: foo3 +# ONE_SEC: foo1 +# ONE_SEC: baz + + +# TWO_SYM: _globl_foo2 +# TWO_SYM: _local_foo2 +# TWO_SYM: _local_foo1 +# TWO_SYM: _baz +# TWO_SYM: _baz_dup +# TWO_SYM: _bar +# TWO_SYM: _bar2 +# TWO_SYM: _globl_foo3 + +# TWO_SEC: foo2 +# TWO_SEC: foo1 +# TWO_SEC: baz +# TWO_SEC: bar +# TWO_SEC: bar2 +# TWO_SEC: foo3 + + +# THREE_SYM: _local_foo1 +# THREE_SYM: _baz +# THREE_SYM: _baz_dup +# THREE_SYM: _bar +# THREE_SYM: _bar2 +# THREE_SYM: _globl_foo2 +# THREE_SYM: _local_foo2 +# THREE_SYM: _globl_foo3 + +# THREE_SEC: foo1 +# THREE_SEC: baz +# THREE_SEC: bar +# THREE_SEC: bar2 +# THREE_SEC: foo2 +# THREE_SEC: foo3 + + +# FOUR_SYM: _local_escape_white_space +# FOUR_SYM: _globl_foo2 +# FOUR_SYM: _local_foo2 +# FOUR_SYM: _local_escape +# FOUR_SYM: _globl_foo3 +# FOUR_SYM: _bar +# FOUR_SYM: _local_foo1 +# FOUR_SYM: _baz +# FOUR_SYM: _baz_dup +# FOUR_SYM: _bar2 + +# FOUR_SEC: \t\n +# FOUR_SEC: foo2 +# FOUR_SEC: @\"NSDictionary\" +# FOUR_SEC: foo3 +# FOUR_SEC: bar +# FOUR_SEC: foo1 +# FOUR_SEC: baz +# FOUR_SEC: bar2 + +# FOUR_SEC_ESCAPE: .. +# FOUR_SEC_ESCAPE: foo2 +# FOUR_SEC_ESCAPE: @"NSDictionary" +# FOUR_SEC_ESCAPE: foo3 +# FOUR_SEC_ESCAPE: bar +# FOUR_SEC_ESCAPE: foo1 +# FOUR_SEC_ESCAPE: baz +# FOUR_SEC_ESCAPE: bar2 + + +# change order, parital covered +#--- ord-2 +#foo2 +CSTR;1433942677 +#foo1 +CSTR;1663475769 +#baz +CSTR;862947621 +#bar +CSTR;540201826 +#bar2 +CSTR;1496286555 + +# change order, parital covered, with mismatches, duplicates +#--- ord-3 +foo2222 +CSTR;0x11111111 +#bar (mismatched cpu and file name) +fakeCPU:fake-file-name.o:CSTR;540201826 +#not a hash +CSTR;xxx +#foo1 +CSTR;1663475769 +#baz +CSTR;862947621 +#bar +CSTR;540201826 +#bar2 +CSTR;1496286555 +#baz +CSTR;862947621 + +# test escape strings +#--- ord-4 +#\t\n +CSTR;1035903177 +#foo2 +CSTR;0x55783A95 +#@\"NSDictionary\" +CSTR;1202669430 +#foo3 +CSTR;1343999025 +#bar +CSTR;0x2032D362 + + +#--- test.s +.text +.globl _main + +_main: + ret + +.cstring +.p2align 2 +_local_foo1: + .asciz "foo1" +_local_foo2: + .asciz "foo2" +L_.foo1_dup: + .asciz "foo1" +L_.foo2_dup: + .asciz "foo2" +_local_escape: + .asciz "@\"NSDictionary\"" +_local_escape_white_space: + .asciz "\t\n" + +_bar: + .asciz "bar" +_baz: + .asciz "baz" +_bar2: + .asciz "bar2" +_baz_dup: + .asciz "baz" + +.subsections_via_symbols + +#--- more-cstrings.s +.globl _globl_foo1, _globl_foo3 +.cstring +.p2align 4 +_globl_foo3: + .asciz "foo3" +_globl_foo2: + .asciz "foo2" diff --git a/wild/tests/lld-macho/order-file-strip-hashes.s b/wild/tests/lld-macho/order-file-strip-hashes.s new file mode 100644 index 000000000..f843e607a --- /dev/null +++ b/wild/tests/lld-macho/order-file-strip-hashes.s @@ -0,0 +1,96 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t && split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/a.s -o %t/a.o + +# RUN: %lld -arch arm64 -lSystem -e _main -o %t/a.out %t/a.o -order_file %t/ord-1 +# RUN: llvm-nm --numeric-sort --format=just-symbols %t/a.out | FileCheck %s + +#--- a.s +.text +.globl _main, A, _B, C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + +_main: + ret +A: + ret +F: + add w0, w0, #3 + bl C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + ret +C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222: + add w0, w0, #2 + bl A + ret +D: + add w0, w0, #2 + bl B + ret +B: + add w0, w0, #1 + bl A + ret +E: + add w0, w0, #2 + bl C.__uniq.111111111111111111111111111111111111111.llvm.2222222222222222222 + ret + +.section __DATA,__objc_const +# test multiple symbols at the same address, which will be alphabetic sorted based symbol names +_OBJC_$_CATEGORY_CLASS_METHODS_Foo_$_Cat2: + .quad 789 + +_OBJC_$_CATEGORY_SOME_$_FOLDED: +_OBJC_$_CATEGORY_Foo_$_Cat1: +_ALPHABETIC_SORT_FIRST: + .quad 123 + +_OBJC_$_CATEGORY_Foo_$_Cat2: + .quad 222 + +_OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1: + .quad 456 + +.section __DATA,__objc_data +_OBJC_CLASS_$_Foo: + .quad 123 + +_OBJC_CLASS_$_Bar.llvm.1234: + .quad 456 + +_OBJC_CLASS_$_Baz: + .quad 789 + +_OBJC_CLASS_$_Baz2: + .quad 999 + +.section __DATA,__objc_classrefs +.quad _OBJC_CLASS_$_Foo +.quad _OBJC_CLASS_$_Bar.llvm.1234 +.quad _OBJC_CLASS_$_Baz + +.subsections_via_symbols + + +#--- ord-1 +# change order, parital covered +A +B +C.__uniq.555555555555555555555555555555555555555.llvm.6666666666666666666 +_OBJC_CLASS_$_Baz +_OBJC_CLASS_$_Bar.__uniq.12345 +_OBJC_CLASS_$_Foo.__uniq.123.llvm.123456789 +_OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1 +_OBJC_$_CATEGORY_Foo_$_Cat1.llvm.1234567 + +# .text +# CHECK: A +# CHECK: B +# CHECK: C +# .section __DATA,__objc_const +# CHECK: _OBJC_$_CATEGORY_INSTANCE_METHODS_Foo_$_Cat1 +# CHECK: _OBJC_$_CATEGORY_Foo_$_Cat1 +# .section __DATA,__objc_data +# CHECK: _OBJC_CLASS_$_Baz +# CHECK: _OBJC_CLASS_$_Bar +# CHECK: _OBJC_CLASS_$_Foo diff --git a/wild/tests/lld-macho/order-file.s b/wild/tests/lld-macho/order-file.s new file mode 100644 index 000000000..e0ca735ab --- /dev/null +++ b/wild/tests/lld-macho/order-file.s @@ -0,0 +1,188 @@ +# REQUIRES: x86 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/foo.s -o %t/foo.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/abs.s -o %t/abs.o +# RUN: llvm-ar rcs %t/foo.a %t/foo.o + +# FOO-FIRST: <_bar>: +# FOO-FIRST: <_main>: + +# FOO-SECOND: <_main>: +# FOO-SECOND: <_bar>: + +# RUN: %lld -lSystem -o %t/test-1 %t/test.o %t/foo.o -order_file %t/ord-1 +# RUN: llvm-objdump -d %t/test-1 | FileCheck %s --check-prefix=FOO-FIRST +## Output should be the same regardless of the command-line order of object files +# RUN: %lld -lSystem -o %t/test-1 %t/foo.o %t/test.o -order_file %t/ord-1 +# RUN: llvm-objdump -d %t/test-1 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-2 %t/test.o %t/foo.o -order_file %t/ord-2 +# RUN: llvm-objdump -d %t/test-2 | FileCheck %s --check-prefix=FOO-SECOND +# RUN: %lld -lSystem -o %t/test-2 %t/foo.o %t/test.o -order_file %t/ord-2 +# RUN: llvm-objdump -d %t/test-2 | FileCheck %s --check-prefix=FOO-SECOND + +# RUN: %lld -lSystem -o %t/test-file-match %t/test.o %t/foo.o -order_file %t/ord-file-match +# RUN: llvm-objdump -d %t/test-file-match | FileCheck %s --check-prefix=FOO-FIRST +## Output should be the same regardless of the command-line order of object files +# RUN: %lld -lSystem -o %t/test-file-match %t/foo.o %t/test.o -order_file %t/ord-file-match +# RUN: llvm-objdump -d %t/test-file-match | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-file-nomatch %t/test.o %t/foo.o -order_file %t/ord-file-nomatch +# RUN: llvm-objdump -d %t/test-file-nomatch | FileCheck %s --check-prefix=FOO-SECOND +# RUN: %lld -lSystem -o %t/test-file-nomatch %t/foo.o %t/test.o -order_file %t/ord-file-nomatch +# RUN: llvm-objdump -d %t/test-file-nomatch | FileCheck %s --check-prefix=FOO-SECOND + +# RUN: %lld -lSystem -o %t/test-arch-match %t/test.o %t/foo.o -order_file %t/ord-arch-match +# RUN: llvm-objdump -d %t/test-arch-match | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-arch-match %t/foo.o %t/test.o -order_file %t/ord-arch-match +# RUN: llvm-objdump -d %t/test-arch-match | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-arch-nomatch %t/test.o %t/foo.o -order_file %t/ord-arch-nomatch +# RUN: llvm-objdump -d %t/test-arch-nomatch | FileCheck %s --check-prefix=FOO-SECOND +# RUN: %lld -lSystem -o %t/test-arch-nomatch %t/foo.o %t/test.o -order_file %t/ord-arch-nomatch +# RUN: llvm-objdump -d %t/test-arch-nomatch | FileCheck %s --check-prefix=FOO-SECOND + +# RUN: %lld -lSystem -o %t/test-arch-match %t/test.o %t/foo.o -order_file %t/ord-arch-match +# RUN: llvm-objdump -d %t/test-arch-match | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-arch-match %t/foo.o %t/test.o -order_file %t/ord-arch-match +# RUN: llvm-objdump -d %t/test-arch-match | FileCheck %s --check-prefix=FOO-FIRST + +## Test archives + +# RUN: %lld -lSystem -o %t/test-archive-1 %t/test.o %t/foo.a -order_file %t/ord-1 +# RUN: llvm-objdump -d %t/test-archive-1 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-archive-1 %t/foo.a %t/test.o -order_file %t/ord-1 +# RUN: llvm-objdump -d %t/test-archive-1 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-archive-file-no-match %t/test.o %t/foo.a -order_file %t/ord-file-match +# RUN: llvm-objdump -d %t/test-archive-file-no-match | FileCheck %s --check-prefix=FOO-SECOND +# RUN: %lld -lSystem -o %t/test-archive %t/foo.a %t/test.o -order_file %t/ord-file-match +# RUN: llvm-objdump -d %t/test-archive-file-no-match | FileCheck %s --check-prefix=FOO-SECOND + +# RUN: %lld -lSystem -o %t/test-archive-1 %t/test.o %t/foo.a -order_file %t/ord-archive-match +# RUN: llvm-objdump -d %t/test-archive-1 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-archive-1 %t/foo.a %t/test.o -order_file %t/ord-archive-match +# RUN: llvm-objdump -d %t/test-archive-1 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-archive-file-no-match %t/test.o %t/foo.a -order_file %t/ord-file-nomatch +# RUN: llvm-objdump -d %t/test-archive-file-no-match | FileCheck %s --check-prefix=FOO-SECOND +# RUN: %lld -lSystem -o %t/test-archive %t/foo.a %t/test.o -order_file %t/ord-file-nomatch +# RUN: llvm-objdump -d %t/test-archive-file-no-match | FileCheck %s --check-prefix=FOO-SECOND + +## The following tests check that if an address is matched by multiple order +## file entries, it should always use the lowest-ordered match. + +# RUN: %lld -lSystem -o %t/test-1 %t/test.o %t/foo.o -order_file %t/ord-multiple-1 +# RUN: llvm-objdump -d %t/test-1 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-1 %t/foo.o %t/test.o -order_file %t/ord-multiple-1 +# RUN: llvm-objdump -d %t/test-1 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-2 %t/test.o %t/foo.o -order_file %t/ord-multiple-2 +# RUN: llvm-objdump -d %t/test-2 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-2 %t/foo.o %t/test.o -order_file %t/ord-multiple-2 +# RUN: llvm-objdump -d %t/test-2 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-3 %t/test.o %t/foo.o -order_file %t/ord-multiple-3 +# RUN: llvm-objdump -d %t/test-3 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-3 %t/foo.o %t/test.o -order_file %t/ord-multiple-3 +# RUN: llvm-objdump -d %t/test-3 | FileCheck %s --check-prefix=FOO-FIRST + +# RUN: %lld -lSystem -o %t/test-4 %t/test.o %t/foo.o -order_file %t/ord-multiple-4 +# RUN: llvm-objdump -d %t/test-4 | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-4 %t/foo.o %t/test.o -order_file %t/ord-multiple-4 +# RUN: llvm-objdump -d %t/test-4 | FileCheck %s --check-prefix=FOO-FIRST + +## -[Foo doFoo:andBar:] and _bar both point to the same location. When both +## symbols appear in an order file, the location in question should be ordered +## according to the lowest-ordered symbol that references it. + +# RUN: %lld -lSystem -o %t/test-alias %t/test.o %t/foo.o -order_file %t/ord-alias +# RUN: llvm-objdump -d %t/test-alias | FileCheck %s --check-prefix=FOO-FIRST +# RUN: %lld -lSystem -o %t/test-alias %t/foo.o %t/test.o -order_file %t/ord-alias +# RUN: llvm-objdump -d %t/test-alias | FileCheck %s --check-prefix=FOO-FIRST + +## Absolute in symbols in order files make no sense. Just ignore them. +# RUN: %lld -lSystem -dylib -o %t/test-abs %t/abs.o -order_file %t/ord-abs + +#--- ord-1 +-[Foo doFoo:andBar:] # just a comment +_main # another comment + +#--- ord-2 +_main # just a comment +-[Foo doFoo:andBar:] # another comment + +#--- ord-file-match +foo.o:-[Foo doFoo:andBar:] +_main + +#--- ord-archive-match +foo.a(foo.o):-[Foo doFoo:andBar:] +_main + +#--- ord-file-nomatch +bar.o:-[Foo doFoo:andBar:] +_main +-[Foo doFoo:andBar:] + +#--- ord-arch-match +x86_64:-[Foo doFoo:andBar:] +_main + +#--- ord-arch-nomatch +arm64:-[Foo doFoo:andBar:] +_main +-[Foo doFoo:andBar:] + +#--- ord-arch-file-match +x86_64:bar.o:-[Foo doFoo:andBar:] +_main + +#--- ord-multiple-1 +-[Foo doFoo:andBar:] +_main +foo.o:-[Foo doFoo:andBar:] + +#--- ord-multiple-2 +foo.o:-[Foo doFoo:andBar:] +_main +-[Foo doFoo:andBar:] + +#--- ord-multiple-3 +-[Foo doFoo:andBar:] +_main +-[Foo doFoo:andBar:] + +#--- ord-multiple-4 +foo.o:-[Foo doFoo:andBar:] +_main +foo.o:-[Foo doFoo:andBar:] + +#--- ord-alias +_bar +_main +-[Foo doFoo:andBar:] + +#--- ord-abs +_abs + +#--- foo.s +.globl "-[Foo doFoo:andBar:]" +"-[Foo doFoo:andBar:]": +_bar: + ret + +#--- test.s +.globl _main + +_main: + callq "-[Foo doFoo:andBar:]" + ret + +.section __DWARF,__debug_aranges,regular,debug +ltmp1: + .byte 0 + +#--- abs.s +_abs = 42 diff --git a/wild/tests/lld-macho/pagezero.s b/wild/tests/lld-macho/pagezero.s new file mode 100644 index 000000000..684249f65 --- /dev/null +++ b/wild/tests/lld-macho/pagezero.s @@ -0,0 +1,37 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; mkdir %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/x86_64.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %s -o %t/arm64_32.o + +# RUN: %lld -lSystem -arch x86_64 -o %t/x86_64 %t/x86_64.o -pagezero_size 100000 +# RUN: llvm-readobj --macho-segment %t/x86_64 | FileCheck %s -D#VMSIZE=0x100000 -D#SIZE=72 + +# RUN: %lld-watchos -lSystem -arch arm64_32 -o %t/arm64_32 %t/arm64_32.o -pagezero_size 100000 +# RUN: llvm-readobj --macho-segment %t/arm64_32 | FileCheck %s -D#VMSIZE=0x100000 -D#SIZE=56 + +# RUN: %lld -lSystem -arch x86_64 -o %t/zero %t/x86_64.o -pagezero_size 0 +# RUN: llvm-readobj --macho-segment %t/zero | FileCheck %s --check-prefix=CHECK-ZERO -D#VMSIZE=0x1000 -D#SIZE=152 + +# RUN: %no-fatal-warnings-lld -lSystem -arch x86_64 -o %t/x86_64-misalign %t/x86_64.o -pagezero_size 1001 2>&1 | FileCheck %s --check-prefix=LINK -D#SIZE=0x1000 +# RUN: llvm-readobj --macho-segment %t/x86_64-misalign | FileCheck %s -D#VMSIZE=0x1000 -D#SIZE=72 + +# RUN: %no-fatal-warnings-lld-watchos -lSystem -arch arm64_32 -o %t/arm64_32-misalign-4K %t/arm64_32.o -pagezero_size 1001 2>&1 | FileCheck %s --check-prefix=LINK -D#SIZE=0x0 +# RUN: llvm-readobj --macho-segment %t/arm64_32-misalign-4K | FileCheck %s --check-prefix=CHECK-ZERO -D#VMSIZE=0x4000 -D#SIZE=124 + +# RUN: %no-fatal-warnings-lld-watchos -lSystem -arch arm64_32 -o %t/arm64_32-misalign-16K %t/arm64_32.o -pagezero_size 4001 2>&1 | FileCheck %s --check-prefix=LINK -D#SIZE=0x4000 +# RUN: llvm-readobj --macho-segment %t/arm64_32-misalign-16K | FileCheck %s -D#VMSIZE=0x4000 -D#SIZE=56 + +# LINK: warning: __PAGEZERO size is not page aligned, rounding down to 0x[[#%x,SIZE]] + +# CHECK: Name: __PAGEZERO +# CHECK-NEXT: Size: [[#%d,SIZE]] +# CHECK-NEXT: vmaddr: 0x0 +# CHECK-NEXT: vmsize: 0x[[#%x,VMSIZE]] + +# CHECK-ZERO: Name: __TEXT +# CHECK-ZERO-NEXT: Size: [[#%d,SIZE]] +# CHECK-ZERO-NEXT: vmaddr: 0x0 +# CHECK-ZERO-NEXT: vmsize: 0x[[#%x,VMSIZE]] + +.globl _main +_main: diff --git a/wild/tests/lld-macho/reexport-with-symlink.s b/wild/tests/lld-macho/reexport-with-symlink.s new file mode 100644 index 000000000..c9cde5bc4 --- /dev/null +++ b/wild/tests/lld-macho/reexport-with-symlink.s @@ -0,0 +1,75 @@ +# REQUIRES: aarch64 +# UNSUPPORTED: system-windows +# RUN: rm -rf %t; split-file %s %t +# RUN: ln -s Versions/A/Developer %t/Developer/Library/Frameworks/Developer.framework/ +# RUN: llvm-mc -filetype obj -triple arm64-apple-macos11.0 %t/test.s -o %t/test.o +# RUN: %lld -arch arm64 -platform_version macos 11.0 11.0 -o %t/test -framework Developer -F %t/Developer/Library/Frameworks -L %t/Developer/usr/lib %t/test.o -t | FileCheck %s + +# CHECK: {{.*}}/Developer/Library/Frameworks/Developer.framework/Developer +# CHECK: {{.*}}/Developer/usr/lib/libDeveloperSupport.tbd(@rpath/libDeveloperSupport.dylib) +# CHECK-NOT: {{.*}}/Developer/Library/Frameworks/Developer.framework/Versions/A/Developer + +#--- Developer/Library/Frameworks/Developer.framework/Versions/A/Developer +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/Developer.framework/Developer" + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcPublic"] + } + } + ] + } +} +#--- Developer/usr/lib/libDeveloperSupport.tbd +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/libDeveloperSupport.dylib" + } + ], + "reexported_libraries": [ + { + "names": [ + "@rpath/Developer.framework/Versions/A/Developer" + ] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcSupport"] + } + } + ] + } +} +#--- test.s +.text +.globl _main +.linker_option "-lDeveloperSupport" + +_main: + ret + +.data + .quad _funcPublic + .quad _funcSupport diff --git a/wild/tests/lld-macho/reexport-without-rpath.s b/wild/tests/lld-macho/reexport-without-rpath.s new file mode 100644 index 000000000..a204c140c --- /dev/null +++ b/wild/tests/lld-macho/reexport-without-rpath.s @@ -0,0 +1,121 @@ +# REQUIRES: aarch64 +# Windows does not support rpath +# UNSUPPORTED: system-windows +# RUN: rm -rf %t; split-file %s %t +# RUN: ln -s Versions/A/Developer %t/Developer/Library/Frameworks/Developer.framework/ +# RUN: ln -s Versions/A/DeveloperCore %t/Developer/Library/PrivateFrameworks/DeveloperCore.framework/ +# RUN: llvm-mc -filetype obj -triple arm64-apple-macos11.0 %t/test.s -o %t/test.o +# RUN: %lld -arch arm64 -platform_version macos 11.0 11.0 -o %t/test -framework Developer -F %t/Developer/Library/Frameworks -L %t/Developer/usr/lib %t/test.o +# RUN: llvm-objdump --bind --no-show-raw-insn -d %t/test | FileCheck %s +# CHECK: Bind table: +# CHECK-DAG: __DATA __data {{.*}} pointer 0 Developer _funcPublic +# CHECK-DAG: __DATA __data {{.*}} pointer 0 Developer _funcCore +# CHECK-DAG: __DATA __data {{.*}} pointer 0 libDeveloperSupport _funcSupport + +#--- Developer/Library/Frameworks/Developer.framework/Versions/A/Developer +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/Developer.framework/Versions/A/Developer" + } + ], + "rpaths": [ + { + "paths": [ + "@loader_path/../../../../PrivateFrameworks/" + ] + } + ], + "reexported_libraries": [ + { + "names": [ + "@rpath/DeveloperCore.framework/Versions/A/DeveloperCore" + ] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcPublic"] + } + } + ] + } +} +#--- Developer/Library/PrivateFrameworks/DeveloperCore.framework/Versions/A/DeveloperCore +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/DeveloperCore.framework/Versions/A/DeveloperCore" + } + ], + "allowable_clients": [ + { + "clients": ["Developer"] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcCore"] + } + } + ] + } +} +#--- Developer/usr/lib/libDeveloperSupport.tbd +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/libDeveloperSupport.dylib" + } + ], + "reexported_libraries": [ + { + "names": [ + "@rpath/Developer.framework/Versions/A/Developer" + ] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcSupport"] + } + } + ] + } +} +#--- test.s +.text +.globl _main +.linker_option "-lDeveloperSupport" + +_main: + ret + +.data + .quad _funcPublic + .quad _funcCore + .quad _funcSupport diff --git a/wild/tests/lld-macho/reloc-subtractor.s b/wild/tests/lld-macho/reloc-subtractor.s new file mode 100644 index 000000000..215593c22 --- /dev/null +++ b/wild/tests/lld-macho/reloc-subtractor.s @@ -0,0 +1,74 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/x86_64.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/test.s -o %t/arm64.o +# RUN: %lld -lSystem %t/x86_64.o -o %t/x86_64 -order_file %t/order-file +# RUN: llvm-objdump --syms --full-contents --rebase %t/x86_64 | FileCheck %s +# RUN: %lld -arch arm64 -lSystem %t/arm64.o -o %t/arm64 -order_file %t/order-file +# RUN: llvm-objdump --syms --full-contents --rebase %t/arm64 | FileCheck %s + +# CHECK-LABEL: SYMBOL TABLE: +# CHECK: {{0*}}[[#%x, SUB1ADDR:]] l {{.*}} __DATA,bar _sub1 +# CHECK: {{0*}}[[#%x, SUB2ADDR:]] l {{.*}} __DATA,bar _sub2 +# CHECK: {{0*}}[[#%x, SUB3ADDR:]] l {{.*}} __DATA,bar _sub3 +# CHECK: {{0*}}[[#%x, SUB4ADDR:]] l {{.*}} __DATA,bar _sub4 +# CHECK: {{0*}}[[#%x, SUB5ADDR:]] l {{.*}} __DATA,bar _sub5 +# CHECK-LABEL: Contents of section __DATA,bar: +# CHECK: [[#SUB1ADDR]] 10000000 +# CHECK-NEXT: [[#SUB2ADDR]] f2ffffff +# CHECK-NEXT: [[#SUB3ADDR]] 14000000 00000000 +# CHECK-NEXT: [[#SUB4ADDR]] f6ffffff ffffffff +# CHECK-NEXT: [[#SUB5ADDR]] f1ffffff ffffffff +# CHECK: Rebase table: +# CHECK-NEXT: segment section address type +# CHECK-EMPTY: + +#--- test.s + +.globl _main, _subtrahend_1, _subtrahend_2, _minuend1, _minuend2 + +.section __DATA,foo + .space 16 +L_.minuend: + .space 16 + +.section __DATA,bar +_sub1: + .long _minuend_1 - _subtrahend_1 + .space 12 +_sub2: + .long _minuend_2 - _subtrahend_2 + 2 + .space 12 +_sub3: + .quad _minuend_1 - _subtrahend_1 + 4 + .space 8 +_sub4: + .quad _minuend_2 - _subtrahend_2 + 6 + .space 8 +_sub5: + .quad L_.minuend - _subtrahend_1 + 1 + .space 8 + +_minuend_1: + .space 16 +_minuend_2: + .space 16 +_subtrahend_1: + .space 16 +_subtrahend_2: + .space 16 + +.text +.p2align 2 +_main: + ret + +.subsections_via_symbols + +#--- order-file +## Reorder the symbols to make sure that the addends are being associated with +## the minuend (and not the subtrahend) relocation. +_subtrahend_1 +_minuend_1 +_minuend_2 +_subtrahend_2 diff --git a/wild/tests/lld-macho/section-order.s b/wild/tests/lld-macho/section-order.s new file mode 100644 index 000000000..7a0b6f799 --- /dev/null +++ b/wild/tests/lld-macho/section-order.s @@ -0,0 +1,58 @@ +# REQUIRES: x86 +## Check that section ordering follows from input file ordering. +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/1.s -o %t/1.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/2.s -o %t/2.o +# RUN: %lld -dylib %t/1.o %t/2.o -o %t/12 +# RUN: %lld -dylib %t/2.o %t/1.o -o %t/21 +# RUN: %lld -dylib %t/2.o %t/1.o -o %t/synth-section-order \ +# RUN: -add_empty_section __TEXT __objc_stubs \ +# RUN: -add_empty_section __TEXT __init_offsets \ +# RUN: -add_empty_section __TEXT __stubs \ +# RUN: -add_empty_section __TEXT __stub_helper \ +# RUN: -add_empty_section __TEXT __unwind_info \ +# RUN: -add_empty_section __TEXT __eh_frame \ +# RUN: -add_empty_section __DATA __objc_selrefs +# RUN: llvm-objdump --macho --section-headers %t/12 | FileCheck %s --check-prefix=CHECK-12 +# RUN: llvm-objdump --macho --section-headers %t/21 | FileCheck %s --check-prefix=CHECK-21 +# RUN: llvm-objdump --macho --section-headers %t/synth-section-order | FileCheck %s --check-prefix=CHECK-SYNTHETIC-ORDER + +# CHECK-12: __text +# CHECK-12-NEXT: foo +# CHECK-12-NEXT: bar +# CHECK-12-NEXT: __cstring + +# CHECK-21: __text +## `foo` always sorts next to `__text` since it's a code section +## and needs to be adjacent for arm64 thunk calculations +# CHECK-21-NEXT: foo +# CHECK-21-NEXT: __cstring +# CHECK-21-NEXT: bar + +# CHECK-SYNTHETIC-ORDER: __text +# CHECK-SYNTHETIC-ORDER-NEXT: foo +# CHECK-SYNTHETIC-ORDER-NEXT: __stubs +# CHECK-SYNTHETIC-ORDER-NEXT: __stub_helper +# CHECK-SYNTHETIC-ORDER-NEXT: __objc_stubs +# CHECK-SYNTHETIC-ORDER-NEXT: __init_offsets +# CHECK-SYNTHETIC-ORDER-NEXT: __cstring +# CHECK-SYNTHETIC-ORDER-NEXT: bar +# CHECK-SYNTHETIC-ORDER-NEXT: __unwind_info +# CHECK-SYNTHETIC-ORDER-NEXT: __eh_frame +# CHECK-SYNTHETIC-ORDER-NEXT: __objc_selrefs + +#--- 1.s +.section __TEXT,foo + .space 1 +.section __TEXT,bar + .space 1 +.cstring + .asciz "" + +#--- 2.s +.cstring + .asciz "" +.section __TEXT,bar + .space 1 +.section __TEXT,foo,regular,pure_instructions + .space 1 diff --git a/wild/tests/lld-macho/segments.s b/wild/tests/lld-macho/segments.s new file mode 100644 index 000000000..b167813d4 --- /dev/null +++ b/wild/tests/lld-macho/segments.s @@ -0,0 +1,73 @@ +# REQUIRES: x86, aarch64 +# RUN: rm -rf %t; mkdir -p %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t/x86_64.o +# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-watchos %s -o %t/arm64-32.o +# RUN: %lld -o %t/x86_64 %t/x86_64.o +# RUN: %lld-watchos -o %t/arm64_32 %t/arm64-32.o + +# RUN: llvm-readobj --macho-segment %t/x86_64 > %t/x86_64.out +# RUN: echo "Total file size" >> %t/x86_64.out +# RUN: wc -c %t/x86_64 >> %t/x86_64.out +# RUN: FileCheck %s -DSUFFIX=_64 -DPAGEZERO_SIZE=0x100000000 -DTEXT_ADDR=0x100000000 < %t/x86_64.out + +# RUN: llvm-readobj --macho-segment %t/arm64_32 > %t/arm64-32.out +# RUN: echo "Total file size" >> %t/arm64-32.out +# RUN: wc -c %t/arm64_32 >> %t/arm64-32.out +# RUN: FileCheck %s -DSUFFIX= -DPAGEZERO_SIZE=0x4000 -DTEXT_ADDR=0x4000 < %t/arm64-32.out + +## These two segments must always be present at the start of an executable. +# CHECK-NOT: Segment { +# CHECK: Segment { +# CHECK-NEXT: Cmd: LC_SEGMENT[[SUFFIX]]{{$}} +# CHECK-NEXT: Name: __PAGEZERO +# CHECK-NEXT: Size: +# CHECK-NEXT: vmaddr: 0x0 +# CHECK-NEXT: vmsize: [[PAGEZERO_SIZE]] +# CHECK-NEXT: fileoff: 0 +# CHECK-NEXT: filesize: 0 +## The kernel won't execute a binary with the wrong protections for __PAGEZERO. +# CHECK-NEXT: maxprot: --- +# CHECK-NEXT: initprot: --- +# CHECK-NEXT: nsects: 0 +# CHECK-NEXT: flags: 0x0 +# CHECK-NEXT: } +# CHECK-NEXT: Segment { +# CHECK-NEXT: Cmd: LC_SEGMENT[[SUFFIX]]{{$}} +# CHECK-NEXT: Name: __TEXT +# CHECK-NEXT: Size: +# CHECK-NEXT: vmaddr: [[TEXT_ADDR]] +# CHECK-NEXT: vmsize: +## dyld3 assumes that the __TEXT segment starts from the file header +# CHECK-NEXT: fileoff: 0 +# CHECK-NEXT: filesize: +# CHECK-NEXT: maxprot: r-x +# CHECK-NEXT: initprot: r-x +# CHECK-NEXT: nsects: 1 +# CHECK-NEXT: flags: 0x0 +# CHECK-NEXT: } + +## Check that we handle max-length names correctly. +# CHECK: Cmd: LC_SEGMENT[[SUFFIX]]{{$}} +# CHECK-NEXT: Name: maxlen_16ch_name + +## This segment must always be present at the end of an executable, and cover +## its last byte. +# CHECK: Name: __LINKEDIT +# CHECK-NEXT: Size: +# CHECK-NEXT: vmaddr: +# CHECK-NEXT: vmsize: +# CHECK-NEXT: fileoff: [[#%u, LINKEDIT_OFF:]] +# CHECK-NEXT: filesize: [[#%u, LINKEDIT_SIZE:]] +# CHECK-NEXT: maxprot: r-- +# CHECK-NEXT: initprot: r-- +# CHECK-NOT: Cmd: LC_SEGMENT[[SUFFIX]]{{$}} + +# CHECK-LABEL: Total file size +# CHECK-NEXT: [[#%u, LINKEDIT_OFF + LINKEDIT_SIZE]] + +.text +.global _main +_main: + ret + +.section maxlen_16ch_name,foo diff --git a/wild/tests/lld-macho/skip-platform-checks.s b/wild/tests/lld-macho/skip-platform-checks.s new file mode 100644 index 000000000..bcd82d59d --- /dev/null +++ b/wild/tests/lld-macho/skip-platform-checks.s @@ -0,0 +1,12 @@ +# REQUIRES: x86, aarch64 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-iossimulator %s -o %t.o +## This should succeed even though libsystem_kernel.dylib has a mismatched platform. +# RUN: %no-arg-lld -lSystem -arch x86_64 -platform_version ios-simulator 14.0 15.0 \ +# RUN: -syslibroot %S/Inputs/iPhoneSimulator.sdk %t.o -o %t +# RUN: llvm-objdump --macho --bind %t | FileCheck %s +# CHECK: __DATA_CONST __got 0x100001000 pointer 0 libSystem dyld_stub_binder + +.globl _main +_main: + callq ___fsync + ret diff --git a/wild/tests/lld-macho/tapi-link-by-arch.s b/wild/tests/lld-macho/tapi-link-by-arch.s new file mode 100644 index 000000000..d78b2ea83 --- /dev/null +++ b/wild/tests/lld-macho/tapi-link-by-arch.s @@ -0,0 +1,19 @@ +# REQUIRES: x86, aarch64 + +# RUN: mkdir -p %t +# RUN: llvm-mc -filetype obj -triple arm64-apple-ios14.4 %s -o %t/arm64-ios.o +# RUN: not %no-arg-lld -dylib -arch arm64 -platform_version ios 14.4 15.0 -o /dev/null \ +# RUN: -lSystem %S/Inputs/libStubLink.tbd %t/arm64-ios.o 2>&1 | FileCheck %s + +# RUN: llvm-mc -filetype obj -triple x86_64-apple-iossimulator14.4 %s -o %t/x86_64-sim.o +# RUN: not %no-arg-lld -dylib -arch x86_64 -platform_version ios-simulator 14.4 15.0 -o /dev/null \ +# RUN: -lSystem %S/Inputs/libStubLink.tbd %t/x86_64-sim.o 2>&1 | FileCheck %s + +# RUN: llvm-mc -filetype obj -triple arm64-apple-iossimulator14.4 %s -o %t/arm64-sim.o +# RUN: %no-arg-lld -dylib -arch arm64 -platform_version ios-simulator 14.4 15.0 -o \ +# RUN: /dev/null %S/Inputs/libStubLink.tbd %t/arm64-sim.o + +# CHECK: error: undefined symbol: _arm64_sim_only + +.data +.quad _arm64_sim_only diff --git a/wild/tests/lld-macho/tapi-rpath.s b/wild/tests/lld-macho/tapi-rpath.s new file mode 100644 index 000000000..23187e797 --- /dev/null +++ b/wild/tests/lld-macho/tapi-rpath.s @@ -0,0 +1,89 @@ +# REQUIRES: aarch64 +# Windows does not support rpath +# UNSUPPORTED: system-windows +# RUN: rm -rf %t; split-file %s %t +# RUN: ln -s Versions/A/Developer %t/Developer/Library/Frameworks/Developer.framework/ +# RUN: ln -s Versions/A/DeveloperCore %t/Developer/Library/PrivateFrameworks/DeveloperCore.framework/ +# RUN: llvm-mc -filetype obj -triple arm64-apple-macos11.0 %t/test.s -o %t/test.o +# RUN: %lld -arch arm64 -platform_version macos 11.0 11.0 -o %t/test -framework Developer -F %t/Developer/Library/Frameworks %t/test.o + +# RUN: llvm-objdump --bind --no-show-raw-insn -d %t/test | FileCheck %s +# CHECK: Bind table: +# CHECK-DAG: __DATA __data {{.*}} pointer 0 Developer _funcPublic +# CHECK-DAG: __DATA __data {{.*}} pointer 0 Developer _funcCore + +#--- Developer/Library/Frameworks/Developer.framework/Versions/A/Developer +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/Developer.framework/Versions/A/Developer" + } + ], + "rpaths": [ + { + "paths": [ + "@loader_path/../../../../PrivateFrameworks/" + ] + } + ], + "reexported_libraries": [ + { + "names": [ + "@rpath/DeveloperCore.framework/Versions/A/DeveloperCore" + ] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcPublic"] + } + } + ] + } +} +#--- Developer/Library/PrivateFrameworks/DeveloperCore.framework/Versions/A/DeveloperCore +{ + "tapi_tbd_version": 5, + "main_library": { + "target_info": [ + { + "target": "arm64-macos" + } + ], + "install_names": [ + { + "name": "@rpath/DeveloperCore.framework/Versions/A/DeveloperCore" + } + ], + "allowable_clients": [ + { + "clients": ["Developer"] + } + ], + "exported_symbols": [ + { + "text": { + "global": ["_funcCore"] + } + } + ] + } +} +#--- test.s +.text +.globl _main + +_main: + ret + +.data + .quad _funcPublic + .quad _funcCore diff --git a/wild/tests/lld-macho/tlv.s b/wild/tests/lld-macho/tlv.s new file mode 100644 index 000000000..e71fe7698 --- /dev/null +++ b/wild/tests/lld-macho/tlv.s @@ -0,0 +1,132 @@ +# REQUIRES: x86 +# RUN: rm -rf %t; split-file %s %t +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/regular.s -o %t/regular.o +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/tbss.s -o %t/tbss.o + +# RUN: %lld -lSystem -no_pie -o %t/regular-no-pie %t/regular.o +# RUN: llvm-otool -hv %t/regular-no-pie | FileCheck %s --check-prefix=HEADER +# RUN: llvm-objdump -d --bind --rebase %t/regular-no-pie | FileCheck %s --check-prefixes=REG,LINKEDIT +# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular-no-pie | \ +# RUN: FileCheck %s --check-prefix=REG-TLVP + +# RUN: %lld -lSystem %t/regular.o -o %t/regular-pie +# RUN: llvm-otool -hv %t/regular-pie | FileCheck %s --check-prefix=HEADER +# RUN: llvm-objdump -d --bind --rebase %t/regular-pie | FileCheck %s --check-prefixes=REG,LINKEDIT +# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular-pie | \ +# RUN: FileCheck %s --check-prefix=REG-TLVP + +# RUN: %lld -lSystem %t/tbss.o -o %t/tbss -e _f +# RUN: llvm-objdump -d --bind --rebase %t/tbss | FileCheck %s --check-prefixes=TBSS,LINKEDIT +# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/tbss | \ +# RUN: FileCheck %s --check-prefix=TBSS-TLVP + +# RUN: %lld -lSystem %t/regular.o %t/tbss.o -o %t/regular-and-tbss +# RUN: llvm-objdump -d --bind --rebase %t/regular-and-tbss | FileCheck %s --check-prefixes=REG,TBSS,LINKEDIT +# RUN: llvm-objdump --macho --section=__DATA,__thread_vars %t/regular-and-tbss | \ +# RUN: FileCheck %s --check-prefix=REG-TBSS-TLVP +# RUN: llvm-objdump --section-headers %t/regular-and-tbss | FileCheck %s --check-prefix=SECTIONS + +## Check that we always put __thread_bss immediately after __thread_data, +## regardless of the order of the input files. +# RUN: %lld -lSystem %t/tbss.o %t/regular.o -o %t/regular-and-tbss +# RUN: llvm-objdump --section-headers %t/regular-and-tbss | FileCheck %s --check-prefix=SECTIONS + +# HEADER: MH_HAS_TLV_DESCRIPTORS + +# REG: <_main>: +# REG-NEXT: leaq {{.*}}(%rip), %rax ## {{.*}} <_foo> +# REG-NEXT: leaq {{.*}}(%rip), %rax ## {{.*}} <_bar> +# REG-NEXT: retq + +# TBSS: <_f>: +# TBSS-NEXT: leaq {{.*}}(%rip), %rax ## {{.*}} <_baz> +# TBSS-NEXT: leaq {{.*}}(%rip), %rax ## {{.*}} <_qux> +# TBSS-NEXT: leaq {{.*}}(%rip), %rax ## {{.*}} <_hoge> +# TBSS-NEXT: retq + +# REG-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TLVP-NEXT: 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 + +# TBSS-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 + +# REG-TBSS-TLVP: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +# REG-TBSS-TLVP-NEXT: 30 00 00 00 00 00 00 00 + +## Make sure we don't emit rebase opcodes for relocations in __thread_vars. +# LINKEDIT: Rebase table: +# LINKEDIT-NEXT: segment section address type +# LINKEDIT-EMPTY: +# LINKEDIT-NEXT: Bind table: +# LINKEDIT: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap +# LINKEDIT: __DATA __thread_vars 0x{{[0-9a-f]*}} pointer 0 libSystem __tlv_bootstrap + +## Make sure we have an odd number of tlv vars, and that the __thread_vars +## section starts 16-bytes aligned. This is the setup required for __thread_data +## not to be automatically 16-bytes aligned, ensuring the linker does its +## expected job of aligning _hoge$tlv$init. +# SECTIONS: __thread_vars {{[0-9]+}}8 {{[0-9]+}}0 +# SECTIONS: __thread_data +# SECTIONS: more_thread_data +# SECTIONS-NEXT: __thread_bss + +#--- regular.s +.globl _main +_main: + mov _foo@TLVP(%rip), %rax + mov _bar@TLVP(%rip), %rax + ret + +.section __DATA,__thread_data,thread_local_regular +_foo$tlv$init: + .quad 123 + +.section __DATA,more_thread_data,thread_local_regular +_bar$tlv$init: + .quad 123 + +.section __DATA,__thread_vars,thread_local_variables +.globl _foo, _bar +_foo: + .quad __tlv_bootstrap + .quad 0 + .quad _foo$tlv$init +_bar: + .quad __tlv_bootstrap + .quad 0 + .quad _bar$tlv$init + +#--- tbss.s + +.globl _f +_f: + mov _baz@TLVP(%rip), %rax + mov _qux@TLVP(%rip), %rax + mov _hoge@TLVP(%rip), %rax + ret + +.tbss _baz$tlv$init, 8, 3 +.tbss _qux$tlv$init, 8, 3 +.tbss _hoge$tlv$init, 16, 4 + +.section __DATA,__thread_vars,thread_local_variables +_baz: + .quad __tlv_bootstrap + .quad 0 + .quad _baz$tlv$init +_qux: + .quad __tlv_bootstrap + .quad 0 + .quad _qux$tlv$init +_hoge: + .quad __tlv_bootstrap + .quad 0 + .quad _hoge$tlv$init diff --git a/wild/tests/lld_macho_tests.rs b/wild/tests/lld_macho_tests.rs new file mode 100644 index 000000000..06a4e960d --- /dev/null +++ b/wild/tests/lld_macho_tests.rs @@ -0,0 +1,141 @@ +//! Test runner for lld MachO assembly tests. +//! +//! These tests are adapted from LLVM lld's MachO test suite +//! (Apache License 2.0 with LLVM Exceptions). +//! +//! Each test assembles a .s file, links with Wild, and verifies +//! the output binary is structurally valid and codesigns cleanly. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn wild_binary_path() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_wild")) +} + +fn lld_tests_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/lld-macho") +} + +fn collect_tests(tests: &mut Vec) { + let wild_bin = wild_binary_path(); + let test_dir = lld_tests_dir(); + + for entry in std::fs::read_dir(&test_dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension().map_or(true, |e| e != "s") { + continue; + } + let content = std::fs::read_to_string(&path).unwrap(); + + // Skip tests that need split-file (multi-file tests) + if content.contains("split-file") { + continue; + } + + // Only run aarch64/arm64 tests + if content.contains("REQUIRES: x86") || content.contains("REQUIRES: i386") { + continue; + } + + // Extract linker flags from RUN lines + let is_dylib = content.contains("-dylib"); + + let test_name = path.file_stem().unwrap().to_string_lossy().to_string(); + let wild = wild_bin.clone(); + let test_path = path.clone(); + + tests.push( + libtest_mimic::Trial::test(format!("lld-macho/{test_name}"), move || { + run_lld_test(&wild, &test_path, is_dylib).map_err(Into::into) + }) + .with_ignored_flag( + // Known failures — ignore until fixed + test_name == "arm64-relocs" + || test_name == "objc-category-merging-erase-objc-name-test" + ), + ); + } +} + +fn run_lld_test( + wild_bin: &Path, + test_path: &Path, + is_dylib: bool, +) -> Result<(), String> { + let build_dir = std::env::temp_dir().join("wild-lld-tests"); + std::fs::create_dir_all(&build_dir).map_err(|e| format!("mkdir: {e}"))?; + + let stem = test_path.file_stem().unwrap().to_string_lossy(); + let obj_path = build_dir.join(format!("{stem}.o")); + let out_path = build_dir.join(format!("{stem}.out")); + + // Strip comment lines and assemble + let content = std::fs::read_to_string(test_path) + .map_err(|e| format!("read: {e}"))?; + let clean: String = content + .lines() + .filter(|l| !l.starts_with('#')) + .collect::>() + .join("\n"); + let clean_path = build_dir.join(format!("{stem}.clean.s")); + std::fs::write(&clean_path, &clean).map_err(|e| format!("write: {e}"))?; + + // Assemble + let asm = Command::new("clang") + .args(["-c", "-target", "arm64-apple-macos"]) + .arg(&clean_path) + .arg("-o") + .arg(&obj_path) + .output() + .map_err(|e| format!("clang: {e}"))?; + if !asm.status.success() { + let stderr = String::from_utf8_lossy(&asm.stderr); + // Some tests have intentional assembly errors + if stderr.contains("error:") { + return Ok(()); // Skip tests with asm errors + } + return Err(format!("Assembly failed:\n{stderr}")); + } + + // Link with Wild + let mut cmd = Command::new(wild_bin); + cmd.arg(&obj_path); + if is_dylib { + cmd.arg("-dylib"); + } + cmd.args(["-arch", "arm64", "-lSystem", "-o"]) + .arg(&out_path) + .env("WILD_VALIDATE_OUTPUT", "1"); + + let link = cmd.output().map_err(|e| format!("wild: {e}"))?; + if !link.status.success() { + let stderr = String::from_utf8_lossy(&link.stderr); + // Check if test expects a link error + if content.contains("error:") || content.contains("not-allowed") { + return Ok(()); // Expected failure + } + return Err(format!("Link failed:\n{stderr}")); + } + + // Verify output is valid: codesign check + let verify = Command::new("codesign") + .args(["-vv"]) + .arg(&out_path) + .output() + .map_err(|e| format!("codesign: {e}"))?; + if !verify.status.success() { + let stderr = String::from_utf8_lossy(&verify.stderr); + return Err(format!("Codesign verification failed:\n{stderr}")); + } + + Ok(()) +} + +fn main() { + let mut tests = Vec::new(); + collect_tests(&mut tests); + let args = libtest_mimic::Arguments::from_args(); + libtest_mimic::run(&args, tests).exit(); +} From 932de9aaaf3eb5ea413dd2b8196b2cba43438dff Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 08:25:44 +0100 Subject: [PATCH 20/21] feat: add sold Mach-O test suite (MIT) Import 134 shell tests from bluewhalesystems/sold (archived). Tests compile C/C++ via clang, link with Wild via --ld-path=./ld64, and verify output. 36 pass, 98 ignored (categorized by reason). Signed-off-by: Giles Cope --- wild/Cargo.toml | 5 + wild/tests/sold-macho/LICENSE.md | 21 ++ wild/tests/sold-macho/README.md | 33 +++ wild/tests/sold-macho/S.sh | 17 ++ wild/tests/sold-macho/U.sh | 10 + wild/tests/sold-macho/Z.sh | 9 + wild/tests/sold-macho/add-ast-path.sh | 12 + wild/tests/sold-macho/add-empty-section.sh | 11 + wild/tests/sold-macho/adhoc-codesign.sh | 20 ++ wild/tests/sold-macho/all-load.sh | 21 ++ .../tests/sold-macho/application-extension.sh | 32 +++ .../sold-macho/application-extension2.sh | 17 ++ wild/tests/sold-macho/archive.sh | 31 +++ wild/tests/sold-macho/baserel.sh | 16 ++ wild/tests/sold-macho/basic.sh | 11 + wild/tests/sold-macho/bind-at-load.sh | 21 ++ wild/tests/sold-macho/bss.sh | 16 ++ wild/tests/sold-macho/bundle.sh | 12 + wild/tests/sold-macho/comdat.sh | 28 +++ wild/tests/sold-macho/common-alignment.sh | 22 ++ wild/tests/sold-macho/common.inc | 40 ++++ wild/tests/sold-macho/common.sh | 27 +++ wild/tests/sold-macho/cstring.sh | 21 ++ wild/tests/sold-macho/data-in-code-info.sh | 16 ++ wild/tests/sold-macho/data-reloc.sh | 23 ++ wild/tests/sold-macho/dead-strip-dylibs.sh | 20 ++ wild/tests/sold-macho/dead-strip-dylibs2.sh | 26 +++ wild/tests/sold-macho/dead-strip-dylibs3.sh | 47 ++++ wild/tests/sold-macho/dead-strip.sh | 27 +++ wild/tests/sold-macho/debuginfo.sh | 28 +++ wild/tests/sold-macho/dependency-info.sh | 12 + wild/tests/sold-macho/dlinfo.sh | 23 ++ wild/tests/sold-macho/duplicate-error.sh | 14 ++ wild/tests/sold-macho/dylib.sh | 27 +++ wild/tests/sold-macho/eh-frame.sh | 18 ++ wild/tests/sold-macho/entry.sh | 17 ++ .../exception-in-static-initializer.sh | 26 +++ wild/tests/sold-macho/exception.sh | 16 ++ wild/tests/sold-macho/export-dynamic.sh | 24 ++ .../tests/sold-macho/exported-symbols-list.sh | 43 ++++ wild/tests/sold-macho/filepath.sh | 20 ++ wild/tests/sold-macho/filepath2.sh | 20 ++ wild/tests/sold-macho/fixup-chains-addend.sh | 28 +++ .../tests/sold-macho/fixup-chains-addend64.sh | 26 +++ .../sold-macho/fixup-chains-os-version.sh | 16 ++ .../fixup-chains-unaligned-error.sh | 19 ++ wild/tests/sold-macho/fixup-chains.sh | 18 ++ wild/tests/sold-macho/flat-namespace.sh | 29 +++ wild/tests/sold-macho/force-load.sh | 26 +++ wild/tests/sold-macho/framework.sh | 21 ++ .../sold-macho/headerpad-max-install-names.sh | 8 + wild/tests/sold-macho/headerpad.sh | 11 + wild/tests/sold-macho/hello.sh | 19 ++ wild/tests/sold-macho/hello2.sh | 14 ++ wild/tests/sold-macho/hello3.sh | 14 ++ wild/tests/sold-macho/hello4.sh | 16 ++ wild/tests/sold-macho/hello5.sh | 19 ++ wild/tests/sold-macho/hidden-l.sh | 33 +++ wild/tests/sold-macho/indirect-symtab.sh | 10 + .../sold-macho/init-offsets-fixup-chains.sh | 17 ++ wild/tests/sold-macho/init-offsets.sh | 24 ++ .../install-name-executable-path.sh | 30 +++ .../sold-macho/install-name-loader-path.sh | 40 ++++ wild/tests/sold-macho/install-name-rpath.sh | 40 ++++ wild/tests/sold-macho/install-name.sh | 9 + wild/tests/sold-macho/lazy-ptr-optimize.sh | 22 ++ wild/tests/sold-macho/lc-build-version.sh | 9 + wild/tests/sold-macho/lc-linker-option.sh | 9 + wild/tests/sold-macho/lib1.sh | 16 ++ wild/tests/sold-macho/libunwind.sh | 51 +++++ .../sold-macho/linker-optimization-hints.sh | 38 ++++ wild/tests/sold-macho/literals.sh | 14 ++ wild/tests/sold-macho/llvm-section.sh | 10 + .../tests/sold-macho/lto-dead-strip-dylibs.sh | 12 + wild/tests/sold-macho/lto.sh | 12 + wild/tests/sold-macho/macos-version-min.sh | 11 + wild/tests/sold-macho/map.sh | 24 ++ .../sold-macho/mark-dead-strippable-dylib.sh | 21 ++ wild/tests/sold-macho/merge-scope.sh | 25 +++ wild/tests/sold-macho/missing-error.sh | 13 ++ wild/tests/sold-macho/needed-framework.sh | 27 +++ wild/tests/sold-macho/needed-l.sh | 18 ++ wild/tests/sold-macho/no-compact-unwind.sh | 16 ++ wild/tests/sold-macho/no-function-starts.sh | 13 ++ wild/tests/sold-macho/objc-selector.sh | 13 ++ wild/tests/sold-macho/objc.sh | 22 ++ wild/tests/sold-macho/object-path-lto.sh | 13 ++ wild/tests/sold-macho/order-file.sh | 32 +++ wild/tests/sold-macho/oso-prefix.sh | 18 ++ wild/tests/sold-macho/pagezero-size.sh | 22 ++ wild/tests/sold-macho/pagezero-size2.sh | 12 + wild/tests/sold-macho/pagezero-size3.sh | 13 ++ wild/tests/sold-macho/platform-version.sh | 12 + wild/tests/sold-macho/print-dependencies.sh | 21 ++ wild/tests/sold-macho/private-extern.sh | 12 + wild/tests/sold-macho/private-symbols.sh | 12 + wild/tests/sold-macho/reexport-l.sh | 38 ++++ wild/tests/sold-macho/reexport-library.sh | 38 ++++ wild/tests/sold-macho/reproducibility.sh | 17 ++ wild/tests/sold-macho/reproducible.sh | 9 + wild/tests/sold-macho/response-file.sh | 5 + wild/tests/sold-macho/rpath.sh | 12 + wild/tests/sold-macho/search-dylibs-first.sh | 35 +++ wild/tests/sold-macho/search-paths-first.sh | 35 +++ wild/tests/sold-macho/sectcreate.sh | 15 ++ wild/tests/sold-macho/stack-size.sh | 12 + wild/tests/sold-macho/start-stop-symbol.sh | 26 +++ wild/tests/sold-macho/strip.sh | 13 ++ .../sold-macho/subsections-via-symbols.sh | 39 ++++ wild/tests/sold-macho/syslibroot.sh | 16 ++ wild/tests/sold-macho/tbd-add.sh | 31 +++ wild/tests/sold-macho/tbd-hide.sh | 31 +++ wild/tests/sold-macho/tbd-install-name.sh | 35 +++ wild/tests/sold-macho/tbd-previous.sh | 35 +++ wild/tests/sold-macho/tbd-reexport.sh | 54 +++++ wild/tests/sold-macho/tbd.sh | 41 ++++ wild/tests/sold-macho/tls-dylib.sh | 23 ++ wild/tests/sold-macho/tls-mismatch.sh | 19 ++ wild/tests/sold-macho/tls-mismatch2.sh | 19 ++ wild/tests/sold-macho/tls.sh | 22 ++ wild/tests/sold-macho/tls2.sh | 22 ++ wild/tests/sold-macho/umbrella.sh | 9 + wild/tests/sold-macho/undef.sh | 21 ++ wild/tests/sold-macho/undefined.sh | 10 + .../sold-macho/unexported-symbols-list.sh | 43 ++++ wild/tests/sold-macho/universal.sh | 21 ++ wild/tests/sold-macho/unkown-tbd-target.sh | 29 +++ wild/tests/sold-macho/uuid.sh | 24 ++ wild/tests/sold-macho/uuid2.sh | 15 ++ wild/tests/sold-macho/version.sh | 15 ++ wild/tests/sold-macho/w.sh | 22 ++ wild/tests/sold-macho/weak-def-archive.sh | 39 ++++ wild/tests/sold-macho/weak-def-dylib.sh | 29 +++ wild/tests/sold-macho/weak-def-ref.sh | 18 ++ wild/tests/sold-macho/weak-def.sh | 26 +++ wild/tests/sold-macho/weak-l.sh | 27 +++ wild/tests/sold-macho/weak-undef.sh | 20 ++ wild/tests/sold-macho/x.sh | 17 ++ wild/tests/sold_macho_tests.rs | 212 ++++++++++++++++++ 139 files changed, 3189 insertions(+) create mode 100644 wild/tests/sold-macho/LICENSE.md create mode 100644 wild/tests/sold-macho/README.md create mode 100755 wild/tests/sold-macho/S.sh create mode 100755 wild/tests/sold-macho/U.sh create mode 100755 wild/tests/sold-macho/Z.sh create mode 100755 wild/tests/sold-macho/add-ast-path.sh create mode 100755 wild/tests/sold-macho/add-empty-section.sh create mode 100755 wild/tests/sold-macho/adhoc-codesign.sh create mode 100755 wild/tests/sold-macho/all-load.sh create mode 100755 wild/tests/sold-macho/application-extension.sh create mode 100755 wild/tests/sold-macho/application-extension2.sh create mode 100755 wild/tests/sold-macho/archive.sh create mode 100755 wild/tests/sold-macho/baserel.sh create mode 100755 wild/tests/sold-macho/basic.sh create mode 100755 wild/tests/sold-macho/bind-at-load.sh create mode 100755 wild/tests/sold-macho/bss.sh create mode 100755 wild/tests/sold-macho/bundle.sh create mode 100755 wild/tests/sold-macho/comdat.sh create mode 100755 wild/tests/sold-macho/common-alignment.sh create mode 100644 wild/tests/sold-macho/common.inc create mode 100755 wild/tests/sold-macho/common.sh create mode 100755 wild/tests/sold-macho/cstring.sh create mode 100755 wild/tests/sold-macho/data-in-code-info.sh create mode 100755 wild/tests/sold-macho/data-reloc.sh create mode 100755 wild/tests/sold-macho/dead-strip-dylibs.sh create mode 100755 wild/tests/sold-macho/dead-strip-dylibs2.sh create mode 100755 wild/tests/sold-macho/dead-strip-dylibs3.sh create mode 100755 wild/tests/sold-macho/dead-strip.sh create mode 100755 wild/tests/sold-macho/debuginfo.sh create mode 100755 wild/tests/sold-macho/dependency-info.sh create mode 100755 wild/tests/sold-macho/dlinfo.sh create mode 100755 wild/tests/sold-macho/duplicate-error.sh create mode 100755 wild/tests/sold-macho/dylib.sh create mode 100755 wild/tests/sold-macho/eh-frame.sh create mode 100755 wild/tests/sold-macho/entry.sh create mode 100755 wild/tests/sold-macho/exception-in-static-initializer.sh create mode 100755 wild/tests/sold-macho/exception.sh create mode 100755 wild/tests/sold-macho/export-dynamic.sh create mode 100755 wild/tests/sold-macho/exported-symbols-list.sh create mode 100755 wild/tests/sold-macho/filepath.sh create mode 100755 wild/tests/sold-macho/filepath2.sh create mode 100755 wild/tests/sold-macho/fixup-chains-addend.sh create mode 100755 wild/tests/sold-macho/fixup-chains-addend64.sh create mode 100755 wild/tests/sold-macho/fixup-chains-os-version.sh create mode 100755 wild/tests/sold-macho/fixup-chains-unaligned-error.sh create mode 100755 wild/tests/sold-macho/fixup-chains.sh create mode 100755 wild/tests/sold-macho/flat-namespace.sh create mode 100755 wild/tests/sold-macho/force-load.sh create mode 100755 wild/tests/sold-macho/framework.sh create mode 100755 wild/tests/sold-macho/headerpad-max-install-names.sh create mode 100755 wild/tests/sold-macho/headerpad.sh create mode 100755 wild/tests/sold-macho/hello.sh create mode 100755 wild/tests/sold-macho/hello2.sh create mode 100755 wild/tests/sold-macho/hello3.sh create mode 100755 wild/tests/sold-macho/hello4.sh create mode 100755 wild/tests/sold-macho/hello5.sh create mode 100755 wild/tests/sold-macho/hidden-l.sh create mode 100755 wild/tests/sold-macho/indirect-symtab.sh create mode 100755 wild/tests/sold-macho/init-offsets-fixup-chains.sh create mode 100755 wild/tests/sold-macho/init-offsets.sh create mode 100755 wild/tests/sold-macho/install-name-executable-path.sh create mode 100755 wild/tests/sold-macho/install-name-loader-path.sh create mode 100755 wild/tests/sold-macho/install-name-rpath.sh create mode 100755 wild/tests/sold-macho/install-name.sh create mode 100755 wild/tests/sold-macho/lazy-ptr-optimize.sh create mode 100755 wild/tests/sold-macho/lc-build-version.sh create mode 100755 wild/tests/sold-macho/lc-linker-option.sh create mode 100755 wild/tests/sold-macho/lib1.sh create mode 100755 wild/tests/sold-macho/libunwind.sh create mode 100755 wild/tests/sold-macho/linker-optimization-hints.sh create mode 100755 wild/tests/sold-macho/literals.sh create mode 100755 wild/tests/sold-macho/llvm-section.sh create mode 100755 wild/tests/sold-macho/lto-dead-strip-dylibs.sh create mode 100755 wild/tests/sold-macho/lto.sh create mode 100755 wild/tests/sold-macho/macos-version-min.sh create mode 100755 wild/tests/sold-macho/map.sh create mode 100755 wild/tests/sold-macho/mark-dead-strippable-dylib.sh create mode 100755 wild/tests/sold-macho/merge-scope.sh create mode 100755 wild/tests/sold-macho/missing-error.sh create mode 100755 wild/tests/sold-macho/needed-framework.sh create mode 100755 wild/tests/sold-macho/needed-l.sh create mode 100755 wild/tests/sold-macho/no-compact-unwind.sh create mode 100755 wild/tests/sold-macho/no-function-starts.sh create mode 100755 wild/tests/sold-macho/objc-selector.sh create mode 100755 wild/tests/sold-macho/objc.sh create mode 100755 wild/tests/sold-macho/object-path-lto.sh create mode 100755 wild/tests/sold-macho/order-file.sh create mode 100755 wild/tests/sold-macho/oso-prefix.sh create mode 100755 wild/tests/sold-macho/pagezero-size.sh create mode 100755 wild/tests/sold-macho/pagezero-size2.sh create mode 100755 wild/tests/sold-macho/pagezero-size3.sh create mode 100755 wild/tests/sold-macho/platform-version.sh create mode 100755 wild/tests/sold-macho/print-dependencies.sh create mode 100755 wild/tests/sold-macho/private-extern.sh create mode 100755 wild/tests/sold-macho/private-symbols.sh create mode 100755 wild/tests/sold-macho/reexport-l.sh create mode 100755 wild/tests/sold-macho/reexport-library.sh create mode 100755 wild/tests/sold-macho/reproducibility.sh create mode 100644 wild/tests/sold-macho/reproducible.sh create mode 100755 wild/tests/sold-macho/response-file.sh create mode 100755 wild/tests/sold-macho/rpath.sh create mode 100755 wild/tests/sold-macho/search-dylibs-first.sh create mode 100755 wild/tests/sold-macho/search-paths-first.sh create mode 100755 wild/tests/sold-macho/sectcreate.sh create mode 100755 wild/tests/sold-macho/stack-size.sh create mode 100755 wild/tests/sold-macho/start-stop-symbol.sh create mode 100755 wild/tests/sold-macho/strip.sh create mode 100755 wild/tests/sold-macho/subsections-via-symbols.sh create mode 100755 wild/tests/sold-macho/syslibroot.sh create mode 100755 wild/tests/sold-macho/tbd-add.sh create mode 100755 wild/tests/sold-macho/tbd-hide.sh create mode 100755 wild/tests/sold-macho/tbd-install-name.sh create mode 100755 wild/tests/sold-macho/tbd-previous.sh create mode 100755 wild/tests/sold-macho/tbd-reexport.sh create mode 100755 wild/tests/sold-macho/tbd.sh create mode 100755 wild/tests/sold-macho/tls-dylib.sh create mode 100755 wild/tests/sold-macho/tls-mismatch.sh create mode 100755 wild/tests/sold-macho/tls-mismatch2.sh create mode 100755 wild/tests/sold-macho/tls.sh create mode 100755 wild/tests/sold-macho/tls2.sh create mode 100755 wild/tests/sold-macho/umbrella.sh create mode 100755 wild/tests/sold-macho/undef.sh create mode 100755 wild/tests/sold-macho/undefined.sh create mode 100755 wild/tests/sold-macho/unexported-symbols-list.sh create mode 100755 wild/tests/sold-macho/universal.sh create mode 100755 wild/tests/sold-macho/unkown-tbd-target.sh create mode 100755 wild/tests/sold-macho/uuid.sh create mode 100755 wild/tests/sold-macho/uuid2.sh create mode 100755 wild/tests/sold-macho/version.sh create mode 100755 wild/tests/sold-macho/w.sh create mode 100755 wild/tests/sold-macho/weak-def-archive.sh create mode 100755 wild/tests/sold-macho/weak-def-dylib.sh create mode 100755 wild/tests/sold-macho/weak-def-ref.sh create mode 100755 wild/tests/sold-macho/weak-def.sh create mode 100755 wild/tests/sold-macho/weak-l.sh create mode 100755 wild/tests/sold-macho/weak-undef.sh create mode 100755 wild/tests/sold-macho/x.sh create mode 100644 wild/tests/sold_macho_tests.rs diff --git a/wild/Cargo.toml b/wild/Cargo.toml index 7435f655d..f5b5c7636 100644 --- a/wild/Cargo.toml +++ b/wild/Cargo.toml @@ -27,6 +27,11 @@ name = "lld_macho_tests" path = "tests/lld_macho_tests.rs" harness = false +[[test]] +name = "sold_macho_tests" +path = "tests/sold_macho_tests.rs" +harness = false + [dependencies] libwild = { path = "../libwild", version = "0.8.0" } diff --git a/wild/tests/sold-macho/LICENSE.md b/wild/tests/sold-macho/LICENSE.md new file mode 100644 index 000000000..ae2ecfbef --- /dev/null +++ b/wild/tests/sold-macho/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Rui Ueyama + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/wild/tests/sold-macho/README.md b/wild/tests/sold-macho/README.md new file mode 100644 index 000000000..df3a00b3e --- /dev/null +++ b/wild/tests/sold-macho/README.md @@ -0,0 +1,33 @@ +# sold Mach-O Test Suite + +Tests adapted from the [sold](https://github.com/bluewhalesystems/sold) +Mach-O linker by Rui Ueyama (Blue Whale Systems). + +## Source + + + +## License + +MIT License (Copyright 2023 Rui Ueyama) -- see [LICENSE.md](LICENSE.md). + +## Format + +Each test is a bash script that: + +1. Compiles C/C++ source via heredocs using `$CC` +2. Links with `$CC --ld-path=./ld64` (the test runner symlinks Wild as `ld64`) +3. Runs the output binary and verifies behavior (usually via `grep -q`) + +The `common.inc` file sets up `$CC`, `$CXX`, trap handlers, and `$t` (temp dir). + +## Running + +```sh +cargo test --test sold_macho_tests +``` + +## Note + +The sold repository is archived and no longer maintained. This is a +complete snapshot of its Mach-O test suite as of the final commit. diff --git a/wild/tests/sold-macho/S.sh b/wild/tests/sold-macho/S.sh new file mode 100755 index 000000000..56db433de --- /dev/null +++ b/wild/tests/sold-macho/S.sh @@ -0,0 +1,17 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/a.c +#include +void hello() { printf("Hello world\n"); } +int main(){ hello(); } +EOF + +$CC -o $t/a.o -c -g $t/a.c + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -g +nm -pa $t/exe1 | grep -qw OSO + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -g -Wl,-S +nm -pa $t/exe2 > $t/log2 +! grep -qw OSO $t/log2 || false diff --git a/wild/tests/sold-macho/U.sh b/wild/tests/sold-macho/U.sh new file mode 100755 index 000000000..7303ea70d --- /dev/null +++ b/wild/tests/sold-macho/U.sh @@ -0,0 +1,10 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log 2>&1 +grep -q 'library not found: -lSystem' $t/log diff --git a/wild/tests/sold-macho/add-ast-path.sh b/wild/tests/sold-macho/add-ast-path.sh new file mode 100755 index 000000000..d8a286958 --- /dev/null +++ b/wild/tests/sold-macho/add-ast-path.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -B. -o $t/exe1 $t/a.o -Wl,-adhoc_codesign +otool -l $t/exe1 | grep -q LC_CODE_SIGNATURE +$t/exe1 | grep -Fq 'Hello world' + +$CC --ld-path=./ld64 -B. -o $t/exe2 $t/a.o -Wl,-no_adhoc_codesign +otool -l $t/exe2 > $t/log2 +! grep -q LC_CODE_SIGNATURE $t/log2 || false +grep -q LC_UUID $t/log2 +! grep -q 'uuid 00000000-0000-0000-0000-000000000000' $t/log2 || false diff --git a/wild/tests/sold-macho/all-load.sh b/wild/tests/sold-macho/all-load.sh new file mode 100755 index 000000000..cc737129f --- /dev/null +++ b/wild/tests/sold-macho/all-load.sh @@ -0,0 +1,21 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/a.tbd +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos, arm64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: '/usr/frameworks/SomeFramework.framework/SomeFramework' +current-version: 0000 +compatibility-version: 150 +flags: [ not_app_extension_safe ] +exports: + - targets: [ x86_64-macos, arm64-macos ] + symbols: [ _foo ] +... +EOF + +cat <& $t/log1 +! grep -q 'application extension' $t/log1 || false + +$CC --ld-path=./ld64 -o $t/exe1 $t/b.o $t/a.tbd -Wl,-application_extension >& $t/log2 +grep -q 'application extension' $t/log2 diff --git a/wild/tests/sold-macho/application-extension2.sh b/wild/tests/sold-macho/application-extension2.sh new file mode 100755 index 000000000..c374a787b --- /dev/null +++ b/wild/tests/sold-macho/application-extension2.sh @@ -0,0 +1,17 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat <& $t/log + +grep -q 'not safe for use in application extensions' $t/log diff --git a/wild/tests/sold-macho/archive.sh b/wild/tests/sold-macho/archive.sh new file mode 100755 index 000000000..8d0284b43 --- /dev/null +++ b/wild/tests/sold-macho/archive.sh @@ -0,0 +1,31 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +void hello() { + printf("Hello world\n"); +} +EOF + +cat < + +char msg[] = "Hello world\n"; +char *p = msg; + +int main() { + printf("%s", p); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/basic.sh b/wild/tests/sold-macho/basic.sh new file mode 100755 index 000000000..f47ad19b1 --- /dev/null +++ b/wild/tests/sold-macho/basic.sh @@ -0,0 +1,11 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < $t/log +! grep -q _hello $t/log || false diff --git a/wild/tests/sold-macho/bss.sh b/wild/tests/sold-macho/bss.sh new file mode 100755 index 000000000..2971cc33a --- /dev/null +++ b/wild/tests/sold-macho/bss.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +static int foo[100]; + +int main() { + foo[1] = 5; + printf("%d %d %p\n", foo[0], foo[1], foo); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q '^0 5 ' diff --git a/wild/tests/sold-macho/bundle.sh b/wild/tests/sold-macho/bundle.sh new file mode 100755 index 000000000..c96a95a8b --- /dev/null +++ b/wild/tests/sold-macho/bundle.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/bundle $t/a.o -Wl,-bundle +file $t/exe | grep -qi bundle diff --git a/wild/tests/sold-macho/comdat.sh b/wild/tests/sold-macho/comdat.sh new file mode 100755 index 000000000..e1658a66f --- /dev/null +++ b/wild/tests/sold-macho/comdat.sh @@ -0,0 +1,28 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +struct T { + T() { std::cout << "foo "; } +}; +T x; +EOF + +cat < +struct T { + T() { std::cout << "foo "; } +}; +T y; +EOF + +cat < +int main() { + std::cout << "bar\n"; +} +EOF + +$CXX --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o $t/c.o +$t/exe | grep -q '^foo foo bar$' diff --git a/wild/tests/sold-macho/common-alignment.sh b/wild/tests/sold-macho/common-alignment.sh new file mode 100755 index 000000000..f324ad2c1 --- /dev/null +++ b/wild/tests/sold-macho/common-alignment.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +#include + +extern int foo; +extern int bar; + +int main() { + printf("%lu %lu\n", (uintptr_t)&foo % 4, (uintptr_t)&bar % 4096); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o +$t/exe | grep -q '^0 0$' diff --git a/wild/tests/sold-macho/common.inc b/wild/tests/sold-macho/common.inc new file mode 100644 index 000000000..d079120a9 --- /dev/null +++ b/wild/tests/sold-macho/common.inc @@ -0,0 +1,40 @@ +# -*- mode: sh -*- + +# Make sure all commands print out messages in English +export LC_ALL=C + +ARCH="${ARCH:-$(uname -m)}" +CC="cc -arch $ARCH" +CXX="c++ -arch $ARCH" + +# Common functions +test_cflags() { + echo 'int main() {}' | $CC "$@" -o /dev/null -xc - >& /dev/null +} + +skip() { + echo skipped + trap - EXIT + exit 0 +} + +on_error() { + code=$? + echo "command failed: $1: $BASH_COMMAND" + trap - EXIT + exit $code +} + +on_exit() { + echo OK + exit 0 +} + +trap 'on_error $LINENO' ERR +trap on_exit EXIT + +# Print out the startup message +testname=$(basename "$0" .sh) +echo -n "Testing $testname ... " +t=out/test/macho/$ARCH/$testname +mkdir -p $t diff --git a/wild/tests/sold-macho/common.sh b/wild/tests/sold-macho/common.sh new file mode 100755 index 000000000..b0aaec557 --- /dev/null +++ b/wild/tests/sold-macho/common.sh @@ -0,0 +1,27 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +extern int foo; +extern int bar; +static int baz[10000]; + +int main() { + printf("%d %d %d\n", foo, bar, baz[0]); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o $t/c.o +$t/exe | grep -q '^0 5 0$' diff --git a/wild/tests/sold-macho/cstring.sh b/wild/tests/sold-macho/cstring.sh new file mode 100755 index 000000000..71b555cdc --- /dev/null +++ b/wild/tests/sold-macho/cstring.sh @@ -0,0 +1,21 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +extern const char *x; +const char *y = "Hello world\n"; +const char *z = "Howdy world\n"; + +int main() { + printf("%d %d\n", x == y, y == z); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o +$t/exe | grep -q '^1 0$' diff --git a/wild/tests/sold-macho/data-in-code-info.sh b/wild/tests/sold-macho/data-in-code-info.sh new file mode 100755 index 000000000..48a2f494f --- /dev/null +++ b/wild/tests/sold-macho/data-in-code-info.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log3 +! grep -q DATA_IN_CODE $t/log3 || false diff --git a/wild/tests/sold-macho/data-reloc.sh b/wild/tests/sold-macho/data-reloc.sh new file mode 100755 index 000000000..be3495987 --- /dev/null +++ b/wild/tests/sold-macho/data-reloc.sh @@ -0,0 +1,23 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int a = 5; +int *b = &a; + +void print() { + printf("%d %d\n", a, *b); +} +EOF + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/a.o + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < $t/log +! grep -q libfoo.dylib $t/log || false diff --git a/wild/tests/sold-macho/dead-strip-dylibs2.sh b/wild/tests/sold-macho/dead-strip-dylibs2.sh new file mode 100755 index 000000000..ede38fec8 --- /dev/null +++ b/wild/tests/sold-macho/dead-strip-dylibs2.sh @@ -0,0 +1,26 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +mkdir -p $t/Foo.framework + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-F$t -Wl,-framework,Foo +otool -l $t/exe | grep -A3 'cmd LC_LOAD_DYLIB' | grep -Fq Foo.framework/Foo + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-F$t -Wl,-framework,Foo \ + -Wl,-dead_strip_dylibs +otool -l $t/exe | grep -A3 'cmd LC_LOAD_DYLIB' >& $t/log +! grep -Fq Foo.framework/Foo $t/log || false diff --git a/wild/tests/sold-macho/dead-strip-dylibs3.sh b/wild/tests/sold-macho/dead-strip-dylibs3.sh new file mode 100755 index 000000000..6d46e9a15 --- /dev/null +++ b/wild/tests/sold-macho/dead-strip-dylibs3.sh @@ -0,0 +1,47 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat > $t/libfoo.tbd < +int main() { printf("Hello world\n"); } +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -L$t -Wl,-lfoo +objdump --macho --dylibs-used $t/exe > $t/log +grep -q libfoo.dylib $t/log +! grep -q libbar.dylib $t/log || false diff --git a/wild/tests/sold-macho/dead-strip.sh b/wild/tests/sold-macho/dead-strip.sh new file mode 100755 index 000000000..4e5bfbe8b --- /dev/null +++ b/wild/tests/sold-macho/dead-strip.sh @@ -0,0 +1,27 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +char msg1[] = "Hello world"; +char msg2[] = "Howdy world"; + +void hello() { + printf("%s\n", msg1); +} + +void howdy() { + printf("%s\n", msg2); +} + +int main() { + hello(); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-dead_strip +$t/exe | grep -q 'Hello world' +otool -tVj $t/exe > $t/log +grep -q 'hello:' $t/log +! grep -q 'howdy:' $t/log || false diff --git a/wild/tests/sold-macho/debuginfo.sh b/wild/tests/sold-macho/debuginfo.sh new file mode 100755 index 000000000..56031b480 --- /dev/null +++ b/wild/tests/sold-macho/debuginfo.sh @@ -0,0 +1,28 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/a.c +#include +extern char *msg; +void hello() { printf("Hello world\n"); } +EOF + +$CC -o $t/a.o -c -g $t/a.c + +cat < $t/b.c +char *msg = "Hello world\n"; +void hello(); +int main() { hello(); } +EOF + +$CC -o $t/b.o -c -g $t/b.c + +rm -f $t/c.a +ar cru $t/c.a $t/b.o + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/c.a -g + +$t/exe | grep -q 'Hello world' + +lldb -o 'b main' -o run -o list -o quit $t/exe | \ + grep -Eq '^-> 3\s+int main\(\) { hello\(\); }' diff --git a/wild/tests/sold-macho/dependency-info.sh b/wild/tests/sold-macho/dependency-info.sh new file mode 100755 index 000000000..4469c47e2 --- /dev/null +++ b/wild/tests/sold-macho/dependency-info.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +#include + +int main(int argc, char **argv) { + Dl_info info; + + if (!dladdr((char *)main + 4, &info)) { + printf("dladdr failed\n"); + return 1; + } + + printf("fname=%s fbase=%p sname=%s saddr=%p\n", + info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr); + return 0; +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q sname=main diff --git a/wild/tests/sold-macho/duplicate-error.sh b/wild/tests/sold-macho/duplicate-error.sh new file mode 100755 index 000000000..713de1be4 --- /dev/null +++ b/wild/tests/sold-macho/duplicate-error.sh @@ -0,0 +1,14 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log || false +grep -q 'duplicate symbol: .*/b.o: .*/a.o: _hello' $t/log diff --git a/wild/tests/sold-macho/dylib.sh b/wild/tests/sold-macho/dylib.sh new file mode 100755 index 000000000..2b40a7cfd --- /dev/null +++ b/wild/tests/sold-macho/dylib.sh @@ -0,0 +1,27 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +char world[] = "world"; + +char *hello() { + return "Hello"; +} +EOF + +$CC --ld-path=./ld64 -o $t/b.dylib -shared $t/a.o + +cat < + +char *hello(); +extern char world[]; + +int main() { + printf("%s %s\n", hello(), world); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/c.o $t/b.dylib +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/eh-frame.sh b/wild/tests/sold-macho/eh-frame.sh new file mode 100755 index 000000000..5e45bb7e6 --- /dev/null +++ b/wild/tests/sold-macho/eh-frame.sh @@ -0,0 +1,18 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +[ $CXX -xc -femit-dwarf-unwind=always /dev/null 2> /dev/null ] || skip + +cat < + +int hello() { + printf("Hello world\n"); + return 0; +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-e,_hello +$t/exe | grep -q 'Hello world' + +! $CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-e,no_such_symbol 2> $t/log || false +grep -q 'undefined entry point symbol: no_such_symbol' $t/log diff --git a/wild/tests/sold-macho/exception-in-static-initializer.sh b/wild/tests/sold-macho/exception-in-static-initializer.sh new file mode 100755 index 000000000..0f3b1b26b --- /dev/null +++ b/wild/tests/sold-macho/exception-in-static-initializer.sh @@ -0,0 +1,26 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +class Error : public std::exception { +public: + const char *what() const noexcept override { + return "ERROR STRING"; + } +}; + +static int foo() { + throw Error(); + return 1; +} + +static inline int bar = foo(); + +int main() {} +EOF + +$CXX --ld-path=./ld64 -o $t/exe $t/a.o +( set +e; $t/exe; true ) >& $t/log +grep -q 'terminating with uncaught exception of type Error: ERROR STRING' $t/log diff --git a/wild/tests/sold-macho/exception.sh b/wild/tests/sold-macho/exception.sh new file mode 100755 index 000000000..452d3c79e --- /dev/null +++ b/wild/tests/sold-macho/exception.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +void hello() { + printf("Hello world\n"); +} + +int main() { + hello(); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -flto -Wl,-no_fixup_chains +$t/exe1 | grep -q 'Hello world' +nm -g $t/exe1 > $t/log1 +! grep -q _hello $t/log1 || false + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -flto -Wl,-no_fixup_chains -Wl,-export_dynamic +$t/exe2 | grep -q 'Hello world' +nm -g $t/exe2 > $t/log2 +grep -q _hello $t/log2 diff --git a/wild/tests/sold-macho/exported-symbols-list.sh b/wild/tests/sold-macho/exported-symbols-list.sh new file mode 100755 index 000000000..bd74bc956 --- /dev/null +++ b/wild/tests/sold-macho/exported-symbols-list.sh @@ -0,0 +1,43 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/list +_foo +_a* +EOF + +$CC --ld-path=./ld64 -shared -o $t/c.dylib $t/a.o + +objdump --macho --exports-trie $t/c.dylib > $t/log1 +grep -q _foo $t/log1 +! grep -q _bar $t/log1 || false +grep -q _baz $t/log1 +grep -q _abc $t/log1 +grep -q _xyz $t/log1 + +$CC --ld-path=./ld64 -shared -o $t/d.dylib $t/a.o \ + -Wl,-exported_symbols_list,$t/list + +objdump --macho --exports-trie $t/d.dylib > $t/log2 +grep -q _foo $t/log2 +! grep -q _bar $t/log2 || false +! grep -q _baz $t/log2 || false +grep -q _abc $t/log2 +! grep -q _xyz $t/log2 || false + +$CC --ld-path=./ld64 -shared -o $t/e.dylib $t/a.o -Wl,-exported_symbol,_foo + +objdump --macho --exports-trie $t/e.dylib > $t/log3 +grep -q _foo $t/log3 +! grep -q _bar $t/log3 || false +! grep -q _baz $t/log3 || false +! grep -q _abc $t/log3 || false +! grep -q _xyz $t/log3 || false diff --git a/wild/tests/sold-macho/filepath.sh b/wild/tests/sold-macho/filepath.sh new file mode 100755 index 000000000..cebf53b29 --- /dev/null +++ b/wild/tests/sold-macho/filepath.sh @@ -0,0 +1,20 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { printf("Hello world\n"); } +EOF + +cat < $t/filelist +$t/a.o +$t/b.o +EOF + +$CC --ld-path=./ld64 -o $t/exe -Wl,-filelist,$t/filelist +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/filepath2.sh b/wild/tests/sold-macho/filepath2.sh new file mode 100755 index 000000000..bfdd89b95 --- /dev/null +++ b/wild/tests/sold-macho/filepath2.sh @@ -0,0 +1,20 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { printf("Hello world\n"); } +EOF + +cat < $t/filelist +a.o +b.o +EOF + +$CC --ld-path=./ld64 -o $t/exe -Xlinker -filelist -Xlinker $t/filelist,$t +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/fixup-chains-addend.sh b/wild/tests/sold-macho/fixup-chains-addend.sh new file mode 100755 index 000000000..3b07848eb --- /dev/null +++ b/wild/tests/sold-macho/fixup-chains-addend.sh @@ -0,0 +1,28 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +extern int arr[5]; + +int *p1 = arr + (1 << 10); + +int main() { + printf("%d %d\n", arr[0], *(p1 - (1 << 10))); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/c.o $t/b.dylib -Wl,-fixup_chains +$t/exe1 +$t/exe1 | grep -q '^1 1$' + +$CC --ld-path=./ld64 -o $t/exe2 $t/c.o $t/b.dylib -Wl,-no_fixup_chains +$t/exe2 +$t/exe2 | grep -q '^1 1$' diff --git a/wild/tests/sold-macho/fixup-chains-addend64.sh b/wild/tests/sold-macho/fixup-chains-addend64.sh new file mode 100755 index 000000000..64099d256 --- /dev/null +++ b/wild/tests/sold-macho/fixup-chains-addend64.sh @@ -0,0 +1,26 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +extern int arr[5]; + +int *p1 = arr + (1LL << 40); + +int main() { + printf("%d %d\n", arr[0], *(p1 - (1LL << 40))); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/c.o $t/b.dylib -Wl,-fixup_chains +$t/exe1 | grep -q '^1 1$' + +$CC --ld-path=./ld64 -o $t/exe2 $t/c.o $t/b.dylib -Wl,-no_fixup_chains +$t/exe2 | grep -q '^1 1$' diff --git a/wild/tests/sold-macho/fixup-chains-os-version.sh b/wild/tests/sold-macho/fixup-chains-os-version.sh new file mode 100755 index 000000000..3b1fef868 --- /dev/null +++ b/wild/tests/sold-macho/fixup-chains-os-version.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -Wl,-platform_version,macos,11,11 +otool -l $t/exe1 > $t/log1 +! grep -q LC_DYLD_CHAINED_FIXUPS %t/log1 || false + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -Wl,-platform_version,macos,13,13 +otool -l $t/exe2 | grep -q LC_DYLD_CHAINED_FIXUPS diff --git a/wild/tests/sold-macho/fixup-chains-unaligned-error.sh b/wild/tests/sold-macho/fixup-chains-unaligned-error.sh new file mode 100755 index 000000000..fdc213a92 --- /dev/null +++ b/wild/tests/sold-macho/fixup-chains-unaligned-error.sh @@ -0,0 +1,19 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat <& $t/log +grep -Fq '/a.o(__DATA,__data): unaligned base relocation' $t/log diff --git a/wild/tests/sold-macho/fixup-chains.sh b/wild/tests/sold-macho/fixup-chains.sh new file mode 100755 index 000000000..a25efa023 --- /dev/null +++ b/wild/tests/sold-macho/fixup-chains.sh @@ -0,0 +1,18 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { printf("Hello world\n"); } +EOF + +cat < +void hello() { printf("Hello world\n"); } +void foo() { hello(); } +EOF + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/a.o -Wl,-flat_namespace + +objdump --macho --bind --lazy-bind $t/b.dylib | grep -Eq 'flat-namespace\s+_hello' +objdump --macho --bind --lazy-bind $t/b.dylib | grep -Eq 'flat-namespace\s+_printf' + +cat < +void hello() { printf("interposed\n"); } +EOF + +$CC --ld-path=./ld64 -shared -o $t/d.dylib $t/c.o + +cat < +void foo(); +int main() { foo(); } +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/e.o $t/d.dylib $t/b.dylib +$t/exe | grep -q interposed diff --git a/wild/tests/sold-macho/force-load.sh b/wild/tests/sold-macho/force-load.sh new file mode 100755 index 000000000..eb3b9d8a2 --- /dev/null +++ b/wild/tests/sold-macho/force-load.sh @@ -0,0 +1,26 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log +grep -q 'D _foo$' $t/log +! grep -q 'D _bar$' $t/log || false diff --git a/wild/tests/sold-macho/framework.sh b/wild/tests/sold-macho/framework.sh new file mode 100755 index 000000000..d31801466 --- /dev/null +++ b/wild/tests/sold-macho/framework.sh @@ -0,0 +1,21 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +mkdir -p $t/Foo.framework + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < + +int main() { + printf("Hello"); + puts(" world"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/hello3.sh b/wild/tests/sold-macho/hello3.sh new file mode 100755 index 000000000..7e418a623 --- /dev/null +++ b/wild/tests/sold-macho/hello3.sh @@ -0,0 +1,14 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int main() { + printf("Hello"); + fprintf(stdout, " world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/hello4.sh b/wild/tests/sold-macho/hello4.sh new file mode 100755 index 000000000..41df723eb --- /dev/null +++ b/wild/tests/sold-macho/hello4.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int main() { + printf("Hello"); + fprintf(stdout, " world\n"); + fprintf(stderr, "Hello stderr\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe 2> /dev/null | grep -q 'Hello world' +$t/exe 2>&1 > /dev/null | grep -q 'Hello stderr' diff --git a/wild/tests/sold-macho/hello5.sh b/wild/tests/sold-macho/hello5.sh new file mode 100755 index 000000000..1e98ae12e --- /dev/null +++ b/wild/tests/sold-macho/hello5.sh @@ -0,0 +1,19 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +extern char msg[]; + +int main() { + printf("%s\n", msg); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/hidden-l.sh b/wild/tests/sold-macho/hidden-l.sh new file mode 100755 index 000000000..d9271f7cf --- /dev/null +++ b/wild/tests/sold-macho/hidden-l.sh @@ -0,0 +1,33 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log +grep -q ' _foo$' $t/log +! grep -q ' _bar$' $t/log || false +grep -q ' _baz$' $t/log diff --git a/wild/tests/sold-macho/indirect-symtab.sh b/wild/tests/sold-macho/indirect-symtab.sh new file mode 100755 index 000000000..93e7e9730 --- /dev/null +++ b/wild/tests/sold-macho/indirect-symtab.sh @@ -0,0 +1,10 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { printf("Hello world\n"); } +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +objdump --macho --indirect-symbols $t/exe | grep -q _printf diff --git a/wild/tests/sold-macho/init-offsets-fixup-chains.sh b/wild/tests/sold-macho/init-offsets-fixup-chains.sh new file mode 100755 index 000000000..30c413325 --- /dev/null +++ b/wild/tests/sold-macho/init-offsets-fixup-chains.sh @@ -0,0 +1,17 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int foo() { std::cout << "foo "; return 3; } +int x = foo(); +int main() {} +EOF + +# -fixup_chains implies -init_offsets +$CXX --ld-path=./ld64 -o $t/exe1 $t/a.o -Wl,-no_fixup_chains +objdump -h $t/exe1 > $t/log1 +! grep -q __init_offsets $t/log1 || false + +$CXX --ld-path=./ld64 -o $t/exe2 $t/a.o -Wl,-fixup_chains +objdump -h $t/exe2 | grep -q __init_offsets diff --git a/wild/tests/sold-macho/init-offsets.sh b/wild/tests/sold-macho/init-offsets.sh new file mode 100755 index 000000000..3a56da107 --- /dev/null +++ b/wild/tests/sold-macho/init-offsets.sh @@ -0,0 +1,24 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int foo() { std::cout << "foo "; return 3; } + +int x = foo(); +EOF + +cat < + +int bar() { std::cout << "bar "; return 5; } +int y = bar(); + +int main() { + std::cout << "main\n"; +} +EOF + +$CXX --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o -Wl,-init_offsets +objdump -h $t/exe | grep -Eq '__init_offsets\s+00000008\s' +$t/exe | grep -q 'foo bar main' diff --git a/wild/tests/sold-macho/install-name-executable-path.sh b/wild/tests/sold-macho/install-name-executable-path.sh new file mode 100755 index 000000000..e6925caed --- /dev/null +++ b/wild/tests/sold-macho/install-name-executable-path.sh @@ -0,0 +1,30 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +void *foo() { + return printf; +} + +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q 'Hello world' + +objdump --macho --bind $t/exe | grep -q _printf + +objdump --macho --lazy-bind $t/exe > $t/log +! grep -q _printf $t/log || false diff --git a/wild/tests/sold-macho/lc-build-version.sh b/wild/tests/sold-macho/lc-build-version.sh new file mode 100755 index 000000000..ccec93f66 --- /dev/null +++ b/wild/tests/sold-macho/lc-build-version.sh @@ -0,0 +1,9 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() {} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o diff --git a/wild/tests/sold-macho/lib1.sh b/wild/tests/sold-macho/lib1.sh new file mode 100755 index 000000000..9971181ef --- /dev/null +++ b/wild/tests/sold-macho/lib1.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < + +#include +#include + +static id exception_processor(id exception) { + unw_context_t context; + unw_getcontext(&context); + + unw_cursor_t cursor; + unw_init_local(&cursor, &context); + + do { + unw_proc_info_t frame_info; + if (unw_get_proc_info(&cursor, &frame_info) != UNW_ESUCCESS) { + NSLog(@"unw_get_proc_info failed"); + continue; + } + + char proc_name[64] = ""; + unw_word_t offset; + unw_get_proc_name(&cursor, proc_name, sizeof(proc_name), &offset); + + NSLog(@"proc_name=%s has_handler=%d", proc_name, frame_info.handler != 0); + } while (unw_step(&cursor) > 0); + + return exception; +} + +void throw_exception() { + [NSException raise:@"foo" format:@"bar"]; +} + +int main(int argc, char **argv) { + objc_setExceptionPreprocessor(&exception_processor); + @try { + throw_exception(); + } @catch (id exception) { + NSLog(@"caught an exception"); + } + return 0; +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -framework Foundation +$t/exe 2>&1 | grep -q 'proc_name=objc_exception_throw has_handler=0' +$t/exe 2>&1 | grep -q 'proc_name=main has_handler=1' diff --git a/wild/tests/sold-macho/linker-optimization-hints.sh b/wild/tests/sold-macho/linker-optimization-hints.sh new file mode 100755 index 000000000..5361796e6 --- /dev/null +++ b/wild/tests/sold-macho/linker-optimization-hints.sh @@ -0,0 +1,38 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +char x1 = -1; +short x2 = -1; +int x3 = -1; +long x4 = -1; +int x5[] = {0, 1, 2, 3}; +long x6[] = {0, 1, 2, 3}; + +void hello() { + printf("Hello world "); +} +EOF + +cat < + +void hello(); + +extern char x1; +extern short x2; +extern int x3; +extern long x4; +extern int x5[]; +extern long x6[]; + +int main() { + hello(); + printf("%d %d %d %ld %d %ld\n", x1, x2, x3, x4, x5[2], x6[3]); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o +$t/exe | grep -q 'Hello world -1 -1 -1 -1 2 3' diff --git a/wild/tests/sold-macho/literals.sh b/wild/tests/sold-macho/literals.sh new file mode 100755 index 000000000..1058dd3ef --- /dev/null +++ b/wild/tests/sold-macho/literals.sh @@ -0,0 +1,14 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { printf("Hello world\n"); } +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/lto-dead-strip-dylibs.sh b/wild/tests/sold-macho/lto-dead-strip-dylibs.sh new file mode 100755 index 000000000..651cbc224 --- /dev/null +++ b/wild/tests/sold-macho/lto-dead-strip-dylibs.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + std::cout << "Hello world\n"; +} +EOF + +$CXX --ld-path=./ld64 -o $t/exe $t/a.o -flto -dead_strip_dylibs +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/lto.sh b/wild/tests/sold-macho/lto.sh new file mode 100755 index 000000000..db6772284 --- /dev/null +++ b/wild/tests/sold-macho/lto.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -flto +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/macos-version-min.sh b/wild/tests/sold-macho/macos-version-min.sh new file mode 100755 index 000000000..5a150fdb0 --- /dev/null +++ b/wild/tests/sold-macho/macos-version-min.sh @@ -0,0 +1,11 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log +grep -q 'platform 1' $t/log +grep -q 'minos 10.9' $t/log diff --git a/wild/tests/sold-macho/map.sh b/wild/tests/sold-macho/map.sh new file mode 100755 index 000000000..fd67ac68a --- /dev/null +++ b/wild/tests/sold-macho/map.sh @@ -0,0 +1,24 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < +void hello() { printf("Hello world\n"); } +EOF + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/a.o +$CC --ld-path=./ld64 -shared -o $t/c.dylib $t/a.o -Wl,-mark_dead_strippable_dylib + +cat < $t/log2 +! grep -Fq c.dylib $t/log2 || false diff --git a/wild/tests/sold-macho/merge-scope.sh b/wild/tests/sold-macho/merge-scope.sh new file mode 100755 index 000000000..deab197f4 --- /dev/null +++ b/wild/tests/sold-macho/merge-scope.sh @@ -0,0 +1,25 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log || false +grep -q 'undefined symbol: .*\.o: _foo' $t/log diff --git a/wild/tests/sold-macho/needed-framework.sh b/wild/tests/sold-macho/needed-framework.sh new file mode 100755 index 000000000..c4aa24b89 --- /dev/null +++ b/wild/tests/sold-macho/needed-framework.sh @@ -0,0 +1,27 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +mkdir -p $t/Foo.framework + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-F$t -Wl,-needed_framework,Foo \ + -Wl,-dead_strip_dylibs +otool -l $t/exe | grep -A3 'cmd LC_LOAD_DYLIB' | grep -Fq Foo.framework/Foo + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-F$t -Wl,-framework,Foo \ + -Wl,-dead_strip_dylibs +otool -l $t/exe | grep -A3 'cmd LC_LOAD_DYLIB' >& $t/log +! grep -Fq Foo.framework/Foo $t/log || false diff --git a/wild/tests/sold-macho/needed-l.sh b/wild/tests/sold-macho/needed-l.sh new file mode 100755 index 000000000..1e00333f7 --- /dev/null +++ b/wild/tests/sold-macho/needed-l.sh @@ -0,0 +1,18 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < $t/log +! grep -q LC_FUNCTION_STARTS $t/log || false diff --git a/wild/tests/sold-macho/objc-selector.sh b/wild/tests/sold-macho/objc-selector.sh new file mode 100755 index 000000000..74585aa5f --- /dev/null +++ b/wild/tests/sold-macho/objc-selector.sh @@ -0,0 +1,13 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + NSProcessInfo *info = [NSProcessInfo processInfo]; + NSLog(@"processName: %@", [info processName]); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -framework foundation -Wl,-ObjC +$t/exe 2>&1 | grep -Fq 'processName: exe' diff --git a/wild/tests/sold-macho/objc.sh b/wild/tests/sold-macho/objc.sh new file mode 100755 index 000000000..477000e57 --- /dev/null +++ b/wild/tests/sold-macho/objc.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +@interface MyClass : NSObject +@end +@implementation MyClass +@end +EOF + +ar rcs $t/b.a $t/a.o + +cat < $t/log 2>&1 +grep -q _OBJC_CLASS_ $t/log diff --git a/wild/tests/sold-macho/object-path-lto.sh b/wild/tests/sold-macho/object-path-lto.sh new file mode 100755 index 000000000..958b144b4 --- /dev/null +++ b/wild/tests/sold-macho/object-path-lto.sh @@ -0,0 +1,13 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -flto -Wl,-object_path_lto,$t/obj +$t/exe | grep -q 'Hello world' +otool -l $t/obj > /dev/null diff --git a/wild/tests/sold-macho/order-file.sh b/wild/tests/sold-macho/order-file.sh new file mode 100755 index 000000000..cf1edcd62 --- /dev/null +++ b/wild/tests/sold-macho/order-file.sh @@ -0,0 +1,32 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int main(); + +void print() { + printf("%d\n", (char *)print < (char *)main); +} + +int main() { + print(); +} +EOF + +cat < $t/order1 +_print +_main +EOF + +cat < $t/order2 +_main +_print +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -Wl,-order_file,$t/order1 +$t/exe1 | grep -q '^1$' + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -Wl,-order_file,$t/order2 +$t/exe2 | grep -q '^0$' diff --git a/wild/tests/sold-macho/oso-prefix.sh b/wild/tests/sold-macho/oso-prefix.sh new file mode 100755 index 000000000..ec66cbf0c --- /dev/null +++ b/wild/tests/sold-macho/oso-prefix.sh @@ -0,0 +1,18 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -g +nm -pa $t/exe1 | grep -q 'OSO /' + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -g -Wl,-oso_prefix,. +nm -pa $t/exe2 | grep -Eq 'OSO out' + +$CC --ld-path=./ld64 -o $t/exe3 $t/a.o -g -Wl,-oso_prefix,"`pwd`/" +nm -pa $t/exe3 | grep -Eq 'OSO out' diff --git a/wild/tests/sold-macho/pagezero-size.sh b/wild/tests/sold-macho/pagezero-size.sh new file mode 100755 index 000000000..3dff195ff --- /dev/null +++ b/wild/tests/sold-macho/pagezero-size.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +[ "`uname -p`" = arm ] && { echo skipped; exit; } + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o + +otool -l $t/exe | grep -A5 'segname __PAGEZERO' | \ + grep -q 'vmsize 0x0000000100000000' + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-pagezero_size,0x10000 +$t/exe | grep -q 'Hello world' + +otool -l $t/exe | grep -A5 'segname __PAGEZERO' | \ + grep -q 'vmsize 0x0000000000010000' diff --git a/wild/tests/sold-macho/pagezero-size2.sh b/wild/tests/sold-macho/pagezero-size2.sh new file mode 100755 index 000000000..2388dd879 --- /dev/null +++ b/wild/tests/sold-macho/pagezero-size2.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +! $CC --ld-path=./ld64 -shared -o $t/b.dylib $t/a.o -Wl,-pagezero_size,0x1000 >& $t/log +grep -Fq ' -pagezero_size option can only be used when linking a main executable' $t/log diff --git a/wild/tests/sold-macho/pagezero-size3.sh b/wild/tests/sold-macho/pagezero-size3.sh new file mode 100755 index 000000000..6e80dc14f --- /dev/null +++ b/wild/tests/sold-macho/pagezero-size3.sh @@ -0,0 +1,13 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/a.o +otool -l $t/b.dylib > $t/log +! grep -q 'segname: __PAGEZERO' $t/log || false diff --git a/wild/tests/sold-macho/platform-version.sh b/wild/tests/sold-macho/platform-version.sh new file mode 100755 index 000000000..7d4f619d0 --- /dev/null +++ b/wild/tests/sold-macho/platform-version.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log +grep -Fq 'minos 13.5' $t/log +grep -Fq 'sdk 12.0' $t/log diff --git a/wild/tests/sold-macho/print-dependencies.sh b/wild/tests/sold-macho/print-dependencies.sh new file mode 100755 index 000000000..3f7e7d3ec --- /dev/null +++ b/wild/tests/sold-macho/print-dependencies.sh @@ -0,0 +1,21 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < $t/log + +grep -Eq '/a\.o\t.*libSystem\S+\tu\t_printf' $t/log +grep -Eq '/b\.o\t.*a.o\tu\t_hello' $t/log diff --git a/wild/tests/sold-macho/private-extern.sh b/wild/tests/sold-macho/private-extern.sh new file mode 100755 index 000000000..ec65ae680 --- /dev/null +++ b/wild/tests/sold-macho/private-extern.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log +grep -q _foo $t/log +! grep -q _bar $t/log || false diff --git a/wild/tests/sold-macho/private-symbols.sh b/wild/tests/sold-macho/private-symbols.sh new file mode 100755 index 000000000..bb636ac67 --- /dev/null +++ b/wild/tests/sold-macho/private-symbols.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { printf("Hello world\n"); } +int main() { hello(); } +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +objdump --macho --syms $t/exe > $t/log +! grep ' ltmp' $t/log || false diff --git a/wild/tests/sold-macho/reexport-l.sh b/wild/tests/sold-macho/reexport-l.sh new file mode 100755 index 000000000..40c89254d --- /dev/null +++ b/wild/tests/sold-macho/reexport-l.sh @@ -0,0 +1,38 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +cp $t/exe $t/exe1 + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +cp $t/exe $t/exe2 + +diff $t/exe1 $t/exe2 diff --git a/wild/tests/sold-macho/reproducible.sh b/wild/tests/sold-macho/reproducible.sh new file mode 100644 index 000000000..34bada6af --- /dev/null +++ b/wild/tests/sold-macho/reproducible.sh @@ -0,0 +1,9 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/rsp +./ld64 @$t/rsp | grep -q Usage diff --git a/wild/tests/sold-macho/rpath.sh b/wild/tests/sold-macho/rpath.sh new file mode 100755 index 000000000..fe7626a09 --- /dev/null +++ b/wild/tests/sold-macho/rpath.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log + +grep -A3 'cmd LC_RPATH' $t/log | grep -q 'path foo' +grep -A3 'cmd LC_RPATH' $t/log | grep -q 'path @bar' diff --git a/wild/tests/sold-macho/search-dylibs-first.sh b/wild/tests/sold-macho/search-dylibs-first.sh new file mode 100755 index 000000000..2c0682e0b --- /dev/null +++ b/wild/tests/sold-macho/search-dylibs-first.sh @@ -0,0 +1,35 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void say() { + printf("Hello\n"); +} +EOF + +cat < +void say() { + printf("Howdy\n"); +} +EOF + +cat < +void say() { + printf("Hello\n"); +} +EOF + +cat < +void say() { + printf("Howdy\n"); +} +EOF + +cat < $t/contents + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -Wl,-sectcreate,__TEXT,__foo,$t/contents + +otool -l $t/exe | grep -A3 'sectname __foo' > $t/log +grep -q 'segname __TEXT' $t/log +grep -q 'segname __TEXT' $t/log +grep -q 'size 0x0*7$' $t/log diff --git a/wild/tests/sold-macho/stack-size.sh b/wild/tests/sold-macho/stack-size.sh new file mode 100755 index 000000000..85cd1a582 --- /dev/null +++ b/wild/tests/sold-macho/stack-size.sh @@ -0,0 +1,12 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +#include + +extern char a __asm("section$start$__TEXT$__text"); +extern char b __asm("section$end$__TEXT$__text"); + +extern char c __asm("section$start$__TEXT$__foo"); +extern char d __asm("section$end$__TEXT$__foo"); + +extern char e __asm("section$start$__FOO$__foo"); +extern char f __asm("section$end$__FOO$__foo"); + +extern char g __asm("segment$start$__TEXT"); +extern char h __asm("segment$end$__TEXT"); + +int main() { + printf("%p %p %p %p %p %p %p %p\n", &a, &b, &c, &d, &e, &f, &g, &h); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe > /dev/null diff --git a/wild/tests/sold-macho/strip.sh b/wild/tests/sold-macho/strip.sh new file mode 100755 index 000000000..f9e002c62 --- /dev/null +++ b/wild/tests/sold-macho/strip.sh @@ -0,0 +1,13 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +strip $t/exe +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/subsections-via-symbols.sh b/wild/tests/sold-macho/subsections-via-symbols.sh new file mode 100755 index 000000000..3f4617519 --- /dev/null +++ b/wild/tests/sold-macho/subsections-via-symbols.sh @@ -0,0 +1,39 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +void fn1(); +void fn2(); +void fn3(); +void fn4(); + +int main() { + printf("%lu %lu\n", (char *)fn2 - (char *)fn1, (char *)fn4 - (char *)fn3); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o $t/b.o $t/c.o +$t/exe | grep -q '^16 1$' diff --git a/wild/tests/sold-macho/syslibroot.sh b/wild/tests/sold-macho/syslibroot.sh new file mode 100755 index 000000000..6b5fde43e --- /dev/null +++ b/wild/tests/sold-macho/syslibroot.sh @@ -0,0 +1,16 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +mkdir -p $t/foo/bar + +cat < $t/libfoo.tbd <<'EOF' +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos, arm64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: '/foo' +current-version: 0 +compatibility-version: 0 +exports: + - targets: [ x86_64-macos, arm64-macos ] + symbols: [ '$ld$add$os14.0$_foo' ] +... +EOF + +cat <& /dev/null || false + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/libfoo.tbd $t/a.o \ + -Wl,-platform_version,macos,14.0,13.0 >& /dev/null diff --git a/wild/tests/sold-macho/tbd-hide.sh b/wild/tests/sold-macho/tbd-hide.sh new file mode 100755 index 000000000..652350872 --- /dev/null +++ b/wild/tests/sold-macho/tbd-hide.sh @@ -0,0 +1,31 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat > $t/libfoo.tbd <<'EOF' +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos, arm64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: '/foo' +current-version: 0 +compatibility-version: 0 +exports: + - targets: [ x86_64-macos, arm64-macos ] + symbols: [ '$ld$hide$os25.0$_foo', _foo ] +... +EOF + +cat <& /dev/null + +! $CC --ld-path=./ld64 -o $t/exe $t/libfoo.tbd $t/a.o \ + -Wl,-platform_version,macos,25.0,21.0 >& /dev/null || false diff --git a/wild/tests/sold-macho/tbd-install-name.sh b/wild/tests/sold-macho/tbd-install-name.sh new file mode 100755 index 000000000..c841fbf8f --- /dev/null +++ b/wild/tests/sold-macho/tbd-install-name.sh @@ -0,0 +1,35 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat > $t/libfoo.tbd <<'EOF' +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos, arm64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: '/foo' +current-version: 0 +compatibility-version: 0 +exports: + - targets: [ x86_64-macos, arm64-macos ] + symbols: [ '$ld$install_name$os25.0$/bar', _foo ] +... +EOF + +cat <& /dev/null + +otool -L $t/exe1 | grep -q /foo + +$CC --ld-path=./ld64 -o $t/exe2 $t/libfoo.tbd $t/a.o \ + -Wl,-platform_version,macos,25.0,21.0 >& /dev/null + +otool -L $t/exe2 | grep -q /bar diff --git a/wild/tests/sold-macho/tbd-previous.sh b/wild/tests/sold-macho/tbd-previous.sh new file mode 100755 index 000000000..81c229b58 --- /dev/null +++ b/wild/tests/sold-macho/tbd-previous.sh @@ -0,0 +1,35 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat > $t/libfoo.tbd <<'EOF' +--- !tapi-tbd +tbd-version: 4 +targets: [ x86_64-macos, arm64-macos ] +uuids: + - target: x86_64-macos + value: 00000000-0000-0000-0000-000000000000 + - target: arm64-macos + value: 00000000-0000-0000-0000-000000000000 +install-name: '/foo' +current-version: 0 +compatibility-version: 0 +exports: + - targets: [ x86_64-macos, arm64-macos ] + symbols: [ '$ld$previous$/bar$$1$10.0$15.0$$', _foo ] +... +EOF + +cat < /dev/null + +otool -L $t/b.dylib | grep -q /foo + +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/libfoo.tbd $t/a.o \ + -Wl,-platform_version,macos,14.0,14.0 2> /dev/null + +otool -L $t/b.dylib | grep -q /bar diff --git a/wild/tests/sold-macho/tbd-reexport.sh b/wild/tests/sold-macho/tbd-reexport.sh new file mode 100755 index 000000000..c3aaae6b7 --- /dev/null +++ b/wild/tests/sold-macho/tbd-reexport.sh @@ -0,0 +1,54 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +mkdir -p $t/libs/SomeFramework.framework/ + +cat > $t/libs/SomeFramework.framework/SomeFramework.tbd < $t/libs/SomeFramework.framework/SomeFramework.tbd < + +extern _Thread_local int foo; +extern _Thread_local int bar; + +int main() { + printf("%d %d\n", foo, bar); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/c.o $t/b.dylib +$t/exe | grep -q '^0 5$' diff --git a/wild/tests/sold-macho/tls-mismatch.sh b/wild/tests/sold-macho/tls-mismatch.sh new file mode 100755 index 000000000..5582d7a65 --- /dev/null +++ b/wild/tests/sold-macho/tls-mismatch.sh @@ -0,0 +1,19 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat <& $t/log1 +grep -Fq 'illegal thread local variable reference to regular symbol `_a`' $t/log1 + +! $CC --ld-path=./ld64 -o $t/exe2 $t/a.o $t/c.o >& $t/log2 +grep -Fq 'illegal thread local variable reference to regular symbol `_a`' $t/log2 diff --git a/wild/tests/sold-macho/tls-mismatch2.sh b/wild/tests/sold-macho/tls-mismatch2.sh new file mode 100755 index 000000000..c28bf36b1 --- /dev/null +++ b/wild/tests/sold-macho/tls-mismatch2.sh @@ -0,0 +1,19 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat <& $t/log1 +grep -Fq 'illegal thread local variable reference to regular symbol `_a`' $t/log1 + +! $CC --ld-path=./ld64 -o $t/exe2 $t/a.o $t/c.o >& $t/log2 +grep -Fq 'illegal thread local variable reference to regular symbol `_a`' $t/log2 diff --git a/wild/tests/sold-macho/tls.sh b/wild/tests/sold-macho/tls.sh new file mode 100755 index 000000000..39869417a --- /dev/null +++ b/wild/tests/sold-macho/tls.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int a = 3; +extern _Thread_local int b; +extern _Thread_local int c; + +int main() { + printf("%d %d %d\n", a, b, c); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.dylib $t/b.o +$t/exe | grep -q '^3 0 5$' diff --git a/wild/tests/sold-macho/tls2.sh b/wild/tests/sold-macho/tls2.sh new file mode 100755 index 000000000..3af412d3a --- /dev/null +++ b/wild/tests/sold-macho/tls2.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +# For some reason, this test fails only on GitHub CI. +[ "$GITHUB_ACTIONS" = true ] && { echo skipped; exit; } + +cat < + +_Thread_local int a; +static _Thread_local int b = 5; +static _Thread_local int *c; + +int main() { + b = 5; + c = &b; + printf("%d %d %d\n", a, b, *c); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o +$t/exe | grep -q '^0 5 5$' diff --git a/wild/tests/sold-macho/umbrella.sh b/wild/tests/sold-macho/umbrella.sh new file mode 100755 index 000000000..b8a23b713 --- /dev/null +++ b/wild/tests/sold-macho/umbrella.sh @@ -0,0 +1,9 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/log1 +! grep -q _foo $t/log1 || false + +$CC --ld-path=./ld64 -o $t/exe2 $t/b.a $t/c.o -Wl,-u,_foo +nm $t/exe2 > $t/log2 +grep -q _foo $t/log2 diff --git a/wild/tests/sold-macho/undefined.sh b/wild/tests/sold-macho/undefined.sh new file mode 100755 index 000000000..6403b6aeb --- /dev/null +++ b/wild/tests/sold-macho/undefined.sh @@ -0,0 +1,10 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < $t/list +_foo +_a* +EOF + +$CC --ld-path=./ld64 -shared -o $t/c.dylib $t/a.o + +objdump --macho --exports-trie $t/c.dylib > $t/log1 +grep -q _foo $t/log1 +! grep -q _bar $t/log1 || false +grep -q _baz $t/log1 +grep -q _abc $t/log1 +grep -q _xyz $t/log1 + +$CC --ld-path=./ld64 -shared -o $t/d.dylib $t/a.o \ + -Wl,-unexported_symbols_list,$t/list + +objdump --macho --exports-trie $t/d.dylib > $t/log2 +! grep -q _foo $t/log2 || false +! grep -q _bar $t/log2 || false +grep -q _baz $t/log2 || false +! grep -q _abc $t/log2 || false +grep -q _xyz $t/log2 + +$CC --ld-path=./ld64 -shared -o $t/e.dylib $t/a.o -Wl,-unexported_symbol,_foo + +objdump --macho --exports-trie $t/e.dylib > $t/log3 +! grep -q _foo $t/log3 || false +! grep -q _bar $t/log3 || false +grep -q _baz $t/log3 +grep -q _abc $t/log3 +grep -q _xyz $t/log3 diff --git a/wild/tests/sold-macho/universal.sh b/wild/tests/sold-macho/universal.sh new file mode 100755 index 000000000..1021ff9de --- /dev/null +++ b/wild/tests/sold-macho/universal.sh @@ -0,0 +1,21 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +lipo $t/a.o -create -output $t/fat.o + +cat < $t/b.tbd < +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o -Wl,-final_output,exe1 +otool -l $t/exe1 | grep -q LC_UUID + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -Wl,-final_output,exe1 +otool -l $t/exe2 | grep -q LC_UUID + +diff -q $t/exe1 $t/exe2 > /dev/null + +$CC --ld-path=./ld64 -o $t/exe3 $t/a.o -Wl,-no_uuid +otool -l $t/exe3 > $t/log3 +! grep -q LC_UUID $t/log3 || false + +$CC --ld-path=./ld64 -o $t/exe4 $t/a.o -Wl,-random_uuid +otool -l $t/exe4 | grep -q LC_UUID diff --git a/wild/tests/sold-macho/uuid2.sh b/wild/tests/sold-macho/uuid2.sh new file mode 100755 index 000000000..08e8ca01c --- /dev/null +++ b/wild/tests/sold-macho/uuid2.sh @@ -0,0 +1,15 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -B. -o $t/exe1 $t/a.o -Wl,-adhoc_codesign +$CC --ld-path=./ld64 -B. -o $t/exe2 $t/a.o -Wl,-adhoc_codesign + +[ "$(otool -l $t/exe1 | grep 'uuid ')" != "$(otool -l $t/exe2 | grep 'uuid ')" ] diff --git a/wild/tests/sold-macho/version.sh b/wild/tests/sold-macho/version.sh new file mode 100755 index 000000000..19a3ffcab --- /dev/null +++ b/wild/tests/sold-macho/version.sh @@ -0,0 +1,15 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +./ld64 -v | grep -q '[ms]old' + +cat < + +int main() { + printf("Hello world\n"); +} +EOF + +$CC --ld-path=./ld64 -Wl,-v -o $t/exe $t/a.o | grep -q '[ms]old' +$t/exe | grep -q 'Hello world' diff --git a/wild/tests/sold-macho/w.sh b/wild/tests/sold-macho/w.sh new file mode 100755 index 000000000..bc954fe3f --- /dev/null +++ b/wild/tests/sold-macho/w.sh @@ -0,0 +1,22 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat <& $t/log1 + +grep -q warning $t/log1 + +$CC --ld-path=./ld64 -shared -o $t/d.so $t/b.so $t/c.o \ + -Wl,-application_extension -Wl,-w >& $t/log2 + +! grep -q warning $t/log2 || false diff --git a/wild/tests/sold-macho/weak-def-archive.sh b/wild/tests/sold-macho/weak-def-archive.sh new file mode 100755 index 000000000..8e69dc41a --- /dev/null +++ b/wild/tests/sold-macho/weak-def-archive.sh @@ -0,0 +1,39 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int foo() __attribute__((weak)); +int foo() { return 42; } + +int main() { + printf("foo=%d\n", foo()); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe1 $t/b.a $t/c.o +$t/exe1 | grep -q '^foo=42$' + +cat < + +int foo() __attribute__((weak)); +int foo() { return 42; } +int bar(); + +int main() { + printf("foo=%d bar=%d\n", foo(), bar()); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe2 $t/b.a $t/d.o +$t/exe2 | grep -q '^foo=3 bar=5$' diff --git a/wild/tests/sold-macho/weak-def-dylib.sh b/wild/tests/sold-macho/weak-def-dylib.sh new file mode 100755 index 000000000..6f97b1c2f --- /dev/null +++ b/wild/tests/sold-macho/weak-def-dylib.sh @@ -0,0 +1,29 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int foo() __attribute((weak)); + +int main() { + printf("%d\n", foo ? foo() : 42); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/b.dylib $t/c.o +$t/exe | grep -q '^3$' + +$CC -c -o $t/d.o -xc /dev/null +$CC --ld-path=./ld64 -shared -o $t/b.dylib $t/d.o +$t/exe | grep -q '^42$' diff --git a/wild/tests/sold-macho/weak-def-ref.sh b/wild/tests/sold-macho/weak-def-ref.sh new file mode 100755 index 000000000..135bce1a2 --- /dev/null +++ b/wild/tests/sold-macho/weak-def-ref.sh @@ -0,0 +1,18 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +struct Foo { + Foo() { std::cout << "foo\n"; } +}; + +Foo x; + +int main() {} +EOF + +$CXX --ld-path=./ld64 -o $t/exe $t/a.o +objdump --macho --exports-trie $t/exe > $t/log +! grep -q __ZN3FooC1Ev $t/log || false diff --git a/wild/tests/sold-macho/weak-def.sh b/wild/tests/sold-macho/weak-def.sh new file mode 100755 index 000000000..0eb62b15c --- /dev/null +++ b/wild/tests/sold-macho/weak-def.sh @@ -0,0 +1,26 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < + +int foo() __attribute__((weak)); + +int foo() { + return 3; +} + +int main() { + printf("%d\n", foo()); +} +EOF + +cat < +void hello() { + printf("Hello world\n"); +} +EOF + +cat < +void hello() __attribute__((weak_import)); + +int main() { + if (hello) + hello(); + else + printf("hello is missing\n"); +} +EOF + +$CC --ld-path=./ld64 -o $t/exe $t/a.o -L$t -Wl,-weak-lfoo +$t/exe | grep -q 'Hello world' + +rm $t/libfoo.dylib +$t/exe | grep -q 'hello is missing' diff --git a/wild/tests/sold-macho/weak-undef.sh b/wild/tests/sold-macho/weak-undef.sh new file mode 100755 index 000000000..e174c57b8 --- /dev/null +++ b/wild/tests/sold-macho/weak-undef.sh @@ -0,0 +1,20 @@ +#!/bin/bash +. $(dirname $0)/common.inc + +cat < +int foo() __attribute__((weak)); +int main() { + printf("%d\n", foo ? foo() : 5); +} +EOF + +cat < $t/a.c +#include +static void hello() { printf("Hello world\n"); } +int main(){ hello(); } +EOF + +$CC -o $t/a.o -c $t/a.c + +$CC --ld-path=./ld64 -o $t/exe1 $t/a.o +nm $t/exe1 | grep -qw _hello + +$CC --ld-path=./ld64 -o $t/exe2 $t/a.o -Wl,-x +nm $t/exe2 > $t/log2 +! grep -qw _hello $t/log2 || false diff --git a/wild/tests/sold_macho_tests.rs b/wild/tests/sold_macho_tests.rs new file mode 100644 index 000000000..72f1b2d63 --- /dev/null +++ b/wild/tests/sold_macho_tests.rs @@ -0,0 +1,212 @@ +//! Test runner for sold (bluewhalesystems/sold) Mach-O shell tests. +//! +//! These tests are adapted from the sold linker's Mach-O test suite (MIT License). +//! +//! Each test is a bash script that compiles C/C++ code, links with the linker +//! under test (via `--ld-path=./ld64`), and verifies the output. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +fn wild_binary_path() -> PathBuf { + PathBuf::from(env!("CARGO_BIN_EXE_wild")) +} + +fn sold_tests_dir() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/sold-macho") +} + +fn collect_tests(tests: &mut Vec) { + let wild_bin = wild_binary_path(); + let test_dir = sold_tests_dir(); + + // Create a working directory with ld64 symlink + let work_dir = std::env::temp_dir().join("wild-sold-tests"); + std::fs::create_dir_all(&work_dir).unwrap(); + let ld64_link = work_dir.join("ld64"); + let _ = std::fs::remove_file(&ld64_link); + std::os::unix::fs::symlink(&wild_bin, &ld64_link).unwrap(); + + for entry in std::fs::read_dir(&test_dir).unwrap() { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension().map_or(true, |e| e != "sh") { + continue; + } + + let test_name = path.file_stem().unwrap().to_string_lossy().to_string(); + let test_path = path.clone(); + let wd = work_dir.clone(); + + let ignored = should_ignore(&test_name); + + tests.push( + libtest_mimic::Trial::test(format!("sold-macho/{test_name}"), move || { + run_sold_test(&test_path, &wd).map_err(Into::into) + }) + .with_ignored_flag(ignored), + ); + } +} + +fn should_ignore(name: &str) -> bool { + // Tests that don't use --ld-path (invoke ./ld64 directly without cc) + const DIRECT_LD64: &[&str] = &["response-file"]; + + // Tests that use flags/features Wild doesn't support yet + const UNSUPPORTED_FLAGS: &[&str] = &[ + "bind-at-load", // -Wl,-bind_at_load -Wl,-no_fixup_chains + "flat-namespace", // -flat_namespace + "undefined", // -undefined warning + "U", // -U (dynamic lookup) + "umbrella", // -umbrella + "mark-dead-strippable-dylib", // -mark_dead_strippable_dylib + "application-extension", // -application_extension + "application-extension2", // -application_extension + "exported-symbols-list", // -exported_symbols_list + "unexported-symbols-list", // -unexported_symbols_list + "export-dynamic", // -export_dynamic + "merge-scope", // visibility merging + "force-load", // -force_load + "all-load", // -all_load + "hidden-l", // -hidden-l + "needed-l", // -needed-l + "needed-framework", // -needed_framework + "weak-l", // -weak-l + "weak-undef", // -U / weak undefined + "weak-def-dylib", // dylib weak defs + "reexport-l", // -reexport-l + "reexport-library", // -reexport_library + "install-name", // -install_name + "install-name-executable-path", // @executable_path + "install-name-loader-path", // @loader_path + "install-name-rpath", // @rpath + "rpath", // -rpath + "search-paths-first", // -search_paths_first + "search-dylibs-first", // -search_dylibs_first + "sectcreate", // -sectcreate + "order-file", // -order_file + "stack-size", // -stack_size + "map", // -map + "dependency-info", // -dependency_info + "print-dependencies", // -print_dependency_info + "macos-version-min", // -macos_version_min + "platform-version", // -platform_version + "S", // -S (strip debug) + "strip", // strip tool compatibility + "no-function-starts", // -no_function_starts + "data-in-code-info", // LC_DATA_IN_CODE + "subsections-via-symbols", // -subsections_via_symbols + "add-ast-path", // -add_ast_path + "add-empty-section", // -add_empty_section + "pagezero-size2", // -pagezero_size variations + "pagezero-size3", // -pagezero_size variations + "oso-prefix", // -oso_prefix + "start-stop-symbol", // __start_/__stop_ sections + "framework", // -framework (non-system) + ]; + + // Tests requiring LTO + const LTO: &[&str] = &["lto", "lto-dead-strip-dylibs", "object-path-lto"]; + + // Validation/correctness bugs in Wild to fix + const WILD_BUGS: &[&str] = &[ + "dylib", // n_value outside section range + "tls", // TLV descriptor offset validation + "tls-dylib", // TLS across dylibs + "tls-mismatch", // TLS type mismatch errors + "tls-mismatch2", // TLS type mismatch errors + "common", // common symbols + "common-alignment", // common symbol alignment + "cstring", // cstring dedup/merging + "duplicate-error", // duplicate symbol errors + "missing-error", // undefined symbol error format + "undef", // undefined symbol handling + "entry", // -e / custom entry point + "fixup-chains-addend", // fixup chain addends + "fixup-chains-addend64", // 64-bit fixup chain addends + "fixup-chains-unaligned-error", // unaligned fixup error + "data-reloc", // data relocations + "exception-in-static-initializer", // init func exceptions + "indirect-symtab", // indirect symbol table + "init-offsets", // __mod_init_func offsets + "init-offsets-fixup-chains", // init offsets + fixup chains + "literals", // literal section merging + "libunwind", // libunwind integration + "objc-selector", // ObjC selector refs + "debuginfo", // debug info pass-through + "filepath", // N_SO stab entries + "filepath2", // N_SO stab entries + ]; + + // x86_64-specific tests + const X86_ONLY: &[&str] = &["eh-frame"]; + + // Tests that invoke ld64 directly (not through cc --ld-path) + const NO_LD_PATH: &[&str] = &["objc"]; + + // .tbd parsing features not yet supported + const TBD: &[&str] = &[ + "tbd", + "tbd-add", + "tbd-hide", + "tbd-install-name", + "tbd-previous", + "tbd-reexport", + "unkown-tbd-target", + ]; + + // Load command / output format checks + const OUTPUT_FORMAT: &[&str] = &[ + "lc-build-version", // LC_BUILD_VERSION tool field + "uuid", // LC_UUID + "uuid2", // LC_UUID reproducibility + "version", // -current_version / -compatibility_version + "w", // -w (suppress warnings) + "x", // -x (no local symbols) + "Z", // -Z (no default search paths) + "adhoc-codesign", // codesign hash verification + "dead-strip-dylibs", // -dead_strip_dylibs + "dead-strip-dylibs2", // -dead_strip_dylibs + ]; + + DIRECT_LD64.contains(&name) + || UNSUPPORTED_FLAGS.contains(&name) + || LTO.contains(&name) + || WILD_BUGS.contains(&name) + || X86_ONLY.contains(&name) + || NO_LD_PATH.contains(&name) + || TBD.contains(&name) + || OUTPUT_FORMAT.contains(&name) +} + +fn run_sold_test(test_path: &Path, work_dir: &Path) -> Result<(), String> { + let output = Command::new("bash") + .arg(test_path) + .current_dir(work_dir) + .env("WILD_VALIDATE_OUTPUT", "1") + .output() + .map_err(|e| format!("bash: {e}"))?; + + if !output.status.success() { + let stdout = String::from_utf8_lossy(&output.stdout); + let stderr = String::from_utf8_lossy(&output.stderr); + let mut msg = format!("Test failed with status {}\n", output.status); + if !stdout.is_empty() { + msg.push_str(&format!("stdout:\n{stdout}\n")); + } + if !stderr.is_empty() { + msg.push_str(&format!("stderr:\n{stderr}\n")); + } + return Err(msg); + } + + Ok(()) +} + +fn main() { + let mut tests = Vec::new(); + collect_tests(&mut tests); + let args = libtest_mimic::Arguments::from_args(); + libtest_mimic::run(&args, tests).exit(); +} From 907bd7a5a1184b258d34afe84123b00aaaf93d78 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Thu, 9 Apr 2026 08:29:46 +0100 Subject: [PATCH 21/21] chore: fmt Signed-off-by: Giles Cope --- wild/tests/lld_macho_tests.rs | 14 +-- wild/tests/sold_macho_tests.rs | 167 +++++++++++++++++---------------- 2 files changed, 89 insertions(+), 92 deletions(-) diff --git a/wild/tests/lld_macho_tests.rs b/wild/tests/lld_macho_tests.rs index 06a4e960d..96ef3ac2e 100644 --- a/wild/tests/lld_macho_tests.rs +++ b/wild/tests/lld_macho_tests.rs @@ -6,7 +6,8 @@ //! Each test assembles a .s file, links with Wild, and verifies //! the output binary is structurally valid and codesigns cleanly. -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::path::PathBuf; use std::process::Command; fn wild_binary_path() -> PathBuf { @@ -53,17 +54,13 @@ fn collect_tests(tests: &mut Vec) { .with_ignored_flag( // Known failures — ignore until fixed test_name == "arm64-relocs" - || test_name == "objc-category-merging-erase-objc-name-test" + || test_name == "objc-category-merging-erase-objc-name-test", ), ); } } -fn run_lld_test( - wild_bin: &Path, - test_path: &Path, - is_dylib: bool, -) -> Result<(), String> { +fn run_lld_test(wild_bin: &Path, test_path: &Path, is_dylib: bool) -> Result<(), String> { let build_dir = std::env::temp_dir().join("wild-lld-tests"); std::fs::create_dir_all(&build_dir).map_err(|e| format!("mkdir: {e}"))?; @@ -72,8 +69,7 @@ fn run_lld_test( let out_path = build_dir.join(format!("{stem}.out")); // Strip comment lines and assemble - let content = std::fs::read_to_string(test_path) - .map_err(|e| format!("read: {e}"))?; + let content = std::fs::read_to_string(test_path).map_err(|e| format!("read: {e}"))?; let clean: String = content .lines() .filter(|l| !l.starts_with('#')) diff --git a/wild/tests/sold_macho_tests.rs b/wild/tests/sold_macho_tests.rs index 72f1b2d63..0afc5c501 100644 --- a/wild/tests/sold_macho_tests.rs +++ b/wild/tests/sold_macho_tests.rs @@ -5,7 +5,8 @@ //! Each test is a bash script that compiles C/C++ code, links with the linker //! under test (via `--ld-path=./ld64`), and verifies the output. -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::path::PathBuf; use std::process::Command; fn wild_binary_path() -> PathBuf { @@ -55,55 +56,55 @@ fn should_ignore(name: &str) -> bool { // Tests that use flags/features Wild doesn't support yet const UNSUPPORTED_FLAGS: &[&str] = &[ - "bind-at-load", // -Wl,-bind_at_load -Wl,-no_fixup_chains - "flat-namespace", // -flat_namespace - "undefined", // -undefined warning - "U", // -U (dynamic lookup) - "umbrella", // -umbrella - "mark-dead-strippable-dylib", // -mark_dead_strippable_dylib - "application-extension", // -application_extension - "application-extension2", // -application_extension - "exported-symbols-list", // -exported_symbols_list - "unexported-symbols-list", // -unexported_symbols_list - "export-dynamic", // -export_dynamic - "merge-scope", // visibility merging - "force-load", // -force_load - "all-load", // -all_load - "hidden-l", // -hidden-l - "needed-l", // -needed-l - "needed-framework", // -needed_framework - "weak-l", // -weak-l - "weak-undef", // -U / weak undefined - "weak-def-dylib", // dylib weak defs - "reexport-l", // -reexport-l - "reexport-library", // -reexport_library - "install-name", // -install_name + "bind-at-load", // -Wl,-bind_at_load -Wl,-no_fixup_chains + "flat-namespace", // -flat_namespace + "undefined", // -undefined warning + "U", // -U (dynamic lookup) + "umbrella", // -umbrella + "mark-dead-strippable-dylib", // -mark_dead_strippable_dylib + "application-extension", // -application_extension + "application-extension2", // -application_extension + "exported-symbols-list", // -exported_symbols_list + "unexported-symbols-list", // -unexported_symbols_list + "export-dynamic", // -export_dynamic + "merge-scope", // visibility merging + "force-load", // -force_load + "all-load", // -all_load + "hidden-l", // -hidden-l + "needed-l", // -needed-l + "needed-framework", // -needed_framework + "weak-l", // -weak-l + "weak-undef", // -U / weak undefined + "weak-def-dylib", // dylib weak defs + "reexport-l", // -reexport-l + "reexport-library", // -reexport_library + "install-name", // -install_name "install-name-executable-path", // @executable_path "install-name-loader-path", // @loader_path - "install-name-rpath", // @rpath - "rpath", // -rpath - "search-paths-first", // -search_paths_first - "search-dylibs-first", // -search_dylibs_first - "sectcreate", // -sectcreate - "order-file", // -order_file - "stack-size", // -stack_size - "map", // -map - "dependency-info", // -dependency_info - "print-dependencies", // -print_dependency_info - "macos-version-min", // -macos_version_min - "platform-version", // -platform_version - "S", // -S (strip debug) - "strip", // strip tool compatibility - "no-function-starts", // -no_function_starts - "data-in-code-info", // LC_DATA_IN_CODE - "subsections-via-symbols", // -subsections_via_symbols - "add-ast-path", // -add_ast_path - "add-empty-section", // -add_empty_section - "pagezero-size2", // -pagezero_size variations - "pagezero-size3", // -pagezero_size variations - "oso-prefix", // -oso_prefix - "start-stop-symbol", // __start_/__stop_ sections - "framework", // -framework (non-system) + "install-name-rpath", // @rpath + "rpath", // -rpath + "search-paths-first", // -search_paths_first + "search-dylibs-first", // -search_dylibs_first + "sectcreate", // -sectcreate + "order-file", // -order_file + "stack-size", // -stack_size + "map", // -map + "dependency-info", // -dependency_info + "print-dependencies", // -print_dependency_info + "macos-version-min", // -macos_version_min + "platform-version", // -platform_version + "S", // -S (strip debug) + "strip", // strip tool compatibility + "no-function-starts", // -no_function_starts + "data-in-code-info", // LC_DATA_IN_CODE + "subsections-via-symbols", // -subsections_via_symbols + "add-ast-path", // -add_ast_path + "add-empty-section", // -add_empty_section + "pagezero-size2", // -pagezero_size variations + "pagezero-size3", // -pagezero_size variations + "oso-prefix", // -oso_prefix + "start-stop-symbol", // __start_/__stop_ sections + "framework", // -framework (non-system) ]; // Tests requiring LTO @@ -111,32 +112,32 @@ fn should_ignore(name: &str) -> bool { // Validation/correctness bugs in Wild to fix const WILD_BUGS: &[&str] = &[ - "dylib", // n_value outside section range - "tls", // TLV descriptor offset validation - "tls-dylib", // TLS across dylibs - "tls-mismatch", // TLS type mismatch errors - "tls-mismatch2", // TLS type mismatch errors - "common", // common symbols - "common-alignment", // common symbol alignment - "cstring", // cstring dedup/merging - "duplicate-error", // duplicate symbol errors - "missing-error", // undefined symbol error format - "undef", // undefined symbol handling - "entry", // -e / custom entry point - "fixup-chains-addend", // fixup chain addends - "fixup-chains-addend64", // 64-bit fixup chain addends - "fixup-chains-unaligned-error", // unaligned fixup error - "data-reloc", // data relocations + "dylib", // n_value outside section range + "tls", // TLV descriptor offset validation + "tls-dylib", // TLS across dylibs + "tls-mismatch", // TLS type mismatch errors + "tls-mismatch2", // TLS type mismatch errors + "common", // common symbols + "common-alignment", // common symbol alignment + "cstring", // cstring dedup/merging + "duplicate-error", // duplicate symbol errors + "missing-error", // undefined symbol error format + "undef", // undefined symbol handling + "entry", // -e / custom entry point + "fixup-chains-addend", // fixup chain addends + "fixup-chains-addend64", // 64-bit fixup chain addends + "fixup-chains-unaligned-error", // unaligned fixup error + "data-reloc", // data relocations "exception-in-static-initializer", // init func exceptions - "indirect-symtab", // indirect symbol table - "init-offsets", // __mod_init_func offsets - "init-offsets-fixup-chains", // init offsets + fixup chains - "literals", // literal section merging - "libunwind", // libunwind integration - "objc-selector", // ObjC selector refs - "debuginfo", // debug info pass-through - "filepath", // N_SO stab entries - "filepath2", // N_SO stab entries + "indirect-symtab", // indirect symbol table + "init-offsets", // __mod_init_func offsets + "init-offsets-fixup-chains", // init offsets + fixup chains + "literals", // literal section merging + "libunwind", // libunwind integration + "objc-selector", // ObjC selector refs + "debuginfo", // debug info pass-through + "filepath", // N_SO stab entries + "filepath2", // N_SO stab entries ]; // x86_64-specific tests @@ -158,16 +159,16 @@ fn should_ignore(name: &str) -> bool { // Load command / output format checks const OUTPUT_FORMAT: &[&str] = &[ - "lc-build-version", // LC_BUILD_VERSION tool field - "uuid", // LC_UUID - "uuid2", // LC_UUID reproducibility - "version", // -current_version / -compatibility_version - "w", // -w (suppress warnings) - "x", // -x (no local symbols) - "Z", // -Z (no default search paths) - "adhoc-codesign", // codesign hash verification - "dead-strip-dylibs", // -dead_strip_dylibs - "dead-strip-dylibs2", // -dead_strip_dylibs + "lc-build-version", // LC_BUILD_VERSION tool field + "uuid", // LC_UUID + "uuid2", // LC_UUID reproducibility + "version", // -current_version / -compatibility_version + "w", // -w (suppress warnings) + "x", // -x (no local symbols) + "Z", // -Z (no default search paths) + "adhoc-codesign", // codesign hash verification + "dead-strip-dylibs", // -dead_strip_dylibs + "dead-strip-dylibs2", // -dead_strip_dylibs ]; DIRECT_LD64.contains(&name)