From 7c4fa05d10b0cfd36712e2319e4234e349331bea Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 13 Jul 2026 15:58:42 +0200 Subject: [PATCH] Add Mach-O section parsing Mach-O sections have a type and a number of attributes, and are grouped in segments. There is a canonical textual representation that allows specifying this with the assembly .section directive (mostly defined by LLVM's source). Cranelift supported this with `DataDescription::custom_segment_section` by letting the user implement the parsing themselves, but this complicates the API (and will require breaking changes if we want to support specifying e.g. the stub size). Instead, `DataDescription::custom_section` is now just a `String`, and `cranelift-object` implements the parsing of the section name into its components. This is the same "layering" that LLVM has chosen, and it seems to work well there. This makes it easy for `rustc_cranelift_backend` to implement Rust's `#[link_section = ...]` to support things such as the `ctor` crate. --- cranelift/jit/src/backend.rs | 2 +- cranelift/module/src/data_context.rs | 17 ++- cranelift/object/src/backend.rs | 219 +++++++++++++++++++++++++-- 3 files changed, 218 insertions(+), 20 deletions(-) diff --git a/cranelift/jit/src/backend.rs b/cranelift/jit/src/backend.rs index 6bfed6078f71..82cfed4422a0 100644 --- a/cranelift/jit/src/backend.rs +++ b/cranelift/jit/src/backend.rs @@ -584,7 +584,7 @@ impl Module for JITModule { data_decls: _, function_relocs: _, data_relocs: _, - custom_segment_section: _, + custom_section: _, align, used: _, } = data; diff --git a/cranelift/module/src/data_context.rs b/cranelift/module/src/data_context.rs index f3e79a830e33..40fd4c78f775 100644 --- a/cranelift/module/src/data_context.rs +++ b/cranelift/module/src/data_context.rs @@ -60,8 +60,11 @@ pub struct DataDescription { pub function_relocs: Vec<(CodeOffset, ir::FuncRef)>, /// Data addresses to write at specified offsets. pub data_relocs: Vec<(CodeOffset, ir::GlobalValue, Addend)>, - /// Object file segment, section and Mach-O flags. - pub custom_segment_section: Option<(String, String, u32)>, + /// Object file section. + /// + /// Tries to support the same format as LLVM: + /// . + pub custom_section: Option, /// Alignment in bytes. `None` means that the default alignment of the /// respective module should be used. pub align: Option, @@ -79,7 +82,7 @@ impl DataDescription { data_decls: PrimaryMap::new(), function_relocs: vec![], data_relocs: vec![], - custom_segment_section: None, + custom_section: None, align: None, used: false, } @@ -92,7 +95,7 @@ impl DataDescription { self.data_decls.clear(); self.function_relocs.clear(); self.data_relocs.clear(); - self.custom_segment_section = None; + self.custom_section = None; self.align = None; self.used = false; } @@ -111,9 +114,9 @@ impl DataDescription { self.init = Init::Bytes { contents }; } - /// Override the segment/section for data, only supported on Object backend - pub fn set_segment_section(&mut self, seg: &str, sec: &str, macho_flags: u32) { - self.custom_segment_section = Some((seg.to_owned(), sec.to_owned(), macho_flags)) + /// Override the section for data, only supported on Object backend + pub fn set_custom_section(&mut self, section: &str) { + self.custom_section = Some(section.to_owned()); } /// Set the alignment for data. The alignment must be a power of two. diff --git a/cranelift/object/src/backend.rs b/cranelift/object/src/backend.rs index 4d6789a7ad91..b8d7eb9c44b5 100644 --- a/cranelift/object/src/backend.rs +++ b/cranelift/object/src/backend.rs @@ -15,11 +15,12 @@ use object::write::{ Object, Relocation, SectionId, StandardSection, Symbol, SymbolId, SymbolSection, }; use object::{ - RelocationEncoding, RelocationFlags, RelocationKind, SectionFlags, SectionKind, SymbolFlags, - SymbolKind, SymbolScope, elf, + BinaryFormat, RelocationEncoding, RelocationFlags, RelocationKind, SectionFlags, SectionKind, + SymbolFlags, SymbolKind, SymbolScope, elf, }; use std::collections::HashMap; use std::collections::hash_map::Entry; +use std::fmt::Write as _; use std::mem; use target_lexicon::{PointerWidth, Triple}; @@ -514,7 +515,7 @@ impl Module for ObjectModule { data_decls: _, function_relocs: _, data_relocs: _, - ref custom_segment_section, + ref custom_section, align, used, } = data; @@ -529,7 +530,7 @@ impl Module for ObjectModule { .map(|record| self.process_reloc(&record)) .collect::>(); - let section = if custom_segment_section.is_none() { + let section = if custom_section.is_none() { let section_kind = if let Init::Zeros { .. } = *init { if decl.tls { StandardSection::UninitializedTls @@ -560,10 +561,12 @@ impl Module for ObjectModule { "Custom section not supported for TLS" ))); } - let (seg, sec, macho_flags) = &custom_segment_section.as_ref().unwrap(); + let (segment, section, macho_flags) = + parse_section(custom_section.as_ref().unwrap(), self.object.format()) + .map_err(ModuleError::Backend)?; let section = self.object.add_section( - seg.clone().into_bytes(), - sec.clone().into_bytes(), + segment.to_string().into_bytes(), + section.to_string().into_bytes(), if decl.writable { SectionKind::Data } else if relocs.is_empty() { @@ -582,13 +585,11 @@ impl Module for ObjectModule { // with how we set these, to ensure we set the section // type properly). assert_eq!(*flags, 0); - *flags = *macho_flags; + *flags = macho_flags; } _ => { - if *macho_flags != 0 { - return Err(cranelift_module::ModuleError::Backend(anyhow::anyhow!( - "unsupported Mach-O flags for this platform: {macho_flags:?}" - ))); + if macho_flags != 0 { + unreachable!("unsupported Mach-O flags for this platform: {macho_flags:?}"); } } } @@ -1176,3 +1177,197 @@ struct ObjectRelocRecord { flags: RelocationFlags, addend: Addend, } + +fn parse_section( + section: &str, + binary_format: BinaryFormat, +) -> Result<(&str, &str, u32), anyhow::Error> { + match binary_format { + // See https://github.com/llvm/llvm-project/blob/main/llvm/lib/MC/MCSectionMachO.cpp + BinaryFormat::MachO => { + let mut parts = section.split(','); + + let section_err = |msg| { + Err(anyhow!( + "section `{section}` is not valid for Mach-O target: {msg}" + )) + }; + + let segment_name = parts.next().unwrap(); + if segment_name.len() > 16 { + return section_err("segment name larger than 16 bytes"); + } + + let Some(section_name) = parts.next() else { + return section_err("must be segment and section separated by comma"); + }; + if section_name.len() > 16 { + return section_err("section name larger than 16 bytes"); + } + + let section_type = parts.next().unwrap_or("regular"); + + // The custom Mach-O section flags. This is the section type + // (8 bits) packed together with the attributes (24 bits). + let mut macho_flags = if let Some((_, val)) = MACHO_SECTION_TYPES + .iter() + .find(|(name, _)| *name == section_type) + { + *val + } else { + let types = list_valid_values(MACHO_SECTION_TYPES); + return section_err(&format!( + "unsupported section type `{section_type}`, valid values are {types}" + )); + }; + + if let Some(section_attributes) = parts.next() { + for attr in section_attributes.split('+') { + macho_flags |= if let Some((_, val)) = MACHO_SECTION_ATTRIBUTES + .iter() + .find(|(name, _)| *name == attr) + { + *val + } else { + let attributes = list_valid_values(MACHO_SECTION_ATTRIBUTES); + return section_err(&format!( + "unsupported section attribute `{attr}`, valid values are {attributes}" + )); + }; + } + } + + if parts.next().is_some() { + return section_err("too many components"); + } + + Ok((segment_name, section_name, macho_flags)) + } + // Otherwise, assume no segment and flags. + _ => Ok(("", section, 0)), + } +} + +// We support the same custom section type / attrs naming as LLVM: +// +// +// +// See also the Mac OS X Assembler Reference: +// +#[rustfmt::skip] +const MACHO_SECTION_TYPES: &[(&str, u32)] = { + use object::macho::*; + &[ + ("regular", S_REGULAR), + ("zerofill", S_ZEROFILL), + ("cstring_literals", S_CSTRING_LITERALS), + ("4byte_literals", S_4BYTE_LITERALS), + ("8byte_literals", S_8BYTE_LITERALS), + ("literal_pointers", S_LITERAL_POINTERS), + ("non_lazy_symbol_pointers", S_NON_LAZY_SYMBOL_POINTERS), + ("lazy_symbol_pointers", S_LAZY_SYMBOL_POINTERS), + // ("symbol_stubs", S_SYMBOL_STUBS) (requires extra param stub size) + ("mod_init_funcs", S_MOD_INIT_FUNC_POINTERS), + ("mod_term_funcs", S_MOD_TERM_FUNC_POINTERS), + ("coalesced", S_COALESCED), + // S_GB_ZEROFILL (not supported by LLVM) + ("interposing", S_INTERPOSING), + ("16byte_literals", S_16BYTE_LITERALS), + // S_DTRACE_DOF (not supported by LLVM) + // S_LAZY_DYLIB_SYMBOL_POINTERS (not supported by LLVM) + ("thread_local_regular", S_THREAD_LOCAL_REGULAR), + ("thread_local_zerofill", S_THREAD_LOCAL_ZEROFILL), + ("thread_local_variables", S_THREAD_LOCAL_VARIABLES), + ("thread_local_variable_pointers", S_THREAD_LOCAL_VARIABLE_POINTERS), + ("thread_local_init_function_pointers", S_THREAD_LOCAL_INIT_FUNCTION_POINTERS), + // S_INIT_FUNC_OFFSETS (not supported by LLVM) + ] +}; + +const MACHO_SECTION_ATTRIBUTES: &[(&str, u32)] = { + use object::macho::*; + &[ + ("pure_instructions", S_ATTR_PURE_INSTRUCTIONS), + ("no_toc", S_ATTR_NO_TOC), + ("strip_static_syms", S_ATTR_STRIP_STATIC_SYMS), + ("no_dead_strip", S_ATTR_NO_DEAD_STRIP), + ("live_support", S_ATTR_LIVE_SUPPORT), + ("self_modifying_code", S_ATTR_SELF_MODIFYING_CODE), + ("debug", S_ATTR_DEBUG), + // System settable attributes are not supported by LLVM: + // S_ATTR_SOME_INSTRUCTIONS + // S_ATTR_EXT_RELOC + // S_ATTR_LOC_RELOC + ] +}; + +fn list_valid_values(items: &[(&str, u32)]) -> String { + let mut items = items.iter().peekable(); + let mut result = String::new(); + if let Some((item, _)) = items.next() { + write!(&mut result, "`{item}`").unwrap(); + } + while let Some((item, _)) = items.next() { + if items.peek().is_none() { + write!(&mut result, " and `{item}`").unwrap(); + } else { + write!(&mut result, ", `{item}`").unwrap(); + } + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + use object::macho::*; + + #[test] + fn section() { + assert_eq!( + parse_section("__DATA,__mod_init_func,mod_init_funcs", BinaryFormat::MachO).unwrap(), + ("__DATA", "__mod_init_func", S_MOD_INIT_FUNC_POINTERS), + ); + assert_eq!( + parse_section( + "__OBJC,__module_info,regular,no_dead_strip", + BinaryFormat::MachO, + ) + .unwrap(), + ("__OBJC", "__module_info", S_REGULAR | S_ATTR_NO_DEAD_STRIP), + ); + + assert_eq!( + parse_section("__TEXT,__text", BinaryFormat::MachO).unwrap(), + ("__TEXT", "__text", S_REGULAR), + ); + assert_eq!( + parse_section("__TEXT,__text,regular", BinaryFormat::MachO).unwrap(), + ("__TEXT", "__text", S_REGULAR), + ); + assert_eq!( + parse_section( + "foo,bar,literal_pointers,no_toc+no_dead_strip", + BinaryFormat::MachO + ) + .unwrap(), + ( + "foo", + "bar", + S_LITERAL_POINTERS | S_ATTR_NO_TOC | S_ATTR_NO_DEAD_STRIP + ), + ); + + assert!(parse_section("foo", BinaryFormat::MachO).is_err()); + assert!(parse_section("12345678901234567,bar", BinaryFormat::MachO).is_err()); + assert!(parse_section("foo,12345678901234567", BinaryFormat::MachO).is_err()); + assert!(parse_section("foo,bar,unknown", BinaryFormat::MachO).is_err()); + assert!(parse_section("foo,bar,regular,unknown", BinaryFormat::MachO).is_err()); + assert!( + parse_section("foo,bar,regular,no_dead_strip+unknown", BinaryFormat::MachO).is_err() + ); + assert!( + parse_section("foo,bar,regular,no_dead_strip,unknown", BinaryFormat::MachO).is_err() + ); + } +}