From bc98b76f3beb3ac2aabd1c2c9a2505a2f477dff9 Mon Sep 17 00:00:00 2001 From: Rui Ueyama Date: Thu, 8 Oct 2026 18:05:39 +0900 Subject: [PATCH] Keep the internal object's synthetic inputs beside ObjectFile::internal What the link synthesizes into the internal object was added from four places: Context's add_synthetic_section and add_data_word, objc.rs's add_placed_isec and add_data_blob with the DataBlob and DataField records they make, and delay_init's add_cstring. A data blob is no Objective-C record alone: bundle_hook's class table, the lazy loads' flags and __dyld_private are blobs too. mold's internal object is an ObjectFile that ObjectFile::internal makes, in input_files.rs; these are functions of input_files.rs beside it now. They add subsections to the link's arena and blobs to its list, not to the object alone, so they take the context rather than being the object's methods. Their callers bind the section header they add to a local first. Co-Authored-By: Claude Opus 5.5 --- macho/bundle_hook.rs | 9 ++- macho/chunks/output_section.rs | 2 +- macho/chunks/rebase_info.rs | 5 +- macho/chunks/split_info.rs | 4 +- macho/chunks/stub_helper.rs | 3 +- macho/context.rs | 36 +--------- macho/delay_init.rs | 25 +------ macho/dtrace.rs | 6 +- macho/input_files.rs | 127 +++++++++++++++++++++++++++++++++ macho/lazy_load.rs | 4 +- macho/make_mergeable/objc.rs | 3 +- macho/objc.rs | 88 +++-------------------- macho/passes.rs | 13 ++-- 13 files changed, 167 insertions(+), 158 deletions(-) diff --git a/macho/bundle_hook.rs b/macho/bundle_hook.rs index c15d134c3..b938052e9 100644 --- a/macho/bundle_hook.rs +++ b/macho/bundle_hook.rs @@ -23,10 +23,10 @@ use crate::arch::Target; use crate::cmdline::Args; use crate::context::Context; -use crate::input_files::FileId; +use crate::input_files::{DataField, FileId, add_data_blob}; use crate::macho::*; use crate::mapped_file::MappedFile; -use crate::objc::{DataField, ObjcRef}; +use crate::objc::ObjcRef; use crate::symbol::SymbolId; /// The hook, built by c/build-bundle-hook.sh. @@ -143,8 +143,7 @@ pub fn create_class_table(ctx: &mut Context) { /// struct entry. Returns its subsection. fn add_table(ctx: &mut Context, libraries: &[(Vec, Vec)]) -> u32 { let names = libraries.iter().flat_map(|(name, _)| [&name[..], b"\0"].concat()).collect(); - let names = - crate::objc::add_data_blob(ctx, b"__data", S_REGULAR, vec![DataField::Bytes(names)]); + let names = add_data_blob(ctx, b"__data", S_REGULAR, vec![DataField::Bytes(names)]); let count: usize = libraries.iter().map(|(_, classes)| classes.len()).sum(); let mut fields = vec![DataField::Bytes((count as u64).to_le_bytes().to_vec())]; let mut name_off = 0; @@ -155,7 +154,7 @@ fn add_table(ctx: &mut Context, libraries: &[(Vec, Vec(ctx: &Context, locs: &[u64]) -> Vec { } /// The (address, target) of every pointer field of the synthesized -/// records (see objc::DataBlob). +/// records (see input_files::DataBlob). fn data_blob_fields(ctx: &Context) -> Vec<(u64, ObjcRef)> { let mut out = Vec::new(); for b in &ctx.data_blobs { diff --git a/macho/chunks/split_info.rs b/macho/chunks/split_info.rs index 8a8b89770..58d066263 100644 --- a/macho/chunks/split_info.rs +++ b/macho/chunks/split_info.rs @@ -23,10 +23,10 @@ use crate::arch::{SplitRef, Target}; use crate::chunks::init_offsets::InitFunc; use crate::chunks::{ChunkHeader, ChunkId, delay_init, objc_stubs, stub_helper, stubs}; use crate::context::Context; -use crate::input_files::FileId; +use crate::input_files::{DataField, FileId}; use crate::input_sections::{InputSection, Reloc, RelocTarget}; use crate::macho_consts::*; -use crate::objc::{DataField, ObjcRef}; +use crate::objc::ObjcRef; use crate::symbol::SymbolId; use crate::util::encode_uleb; diff --git a/macho/chunks/stub_helper.rs b/macho/chunks/stub_helper.rs index 6aa7665af..735946878 100644 --- a/macho/chunks/stub_helper.rs +++ b/macho/chunks/stub_helper.rs @@ -6,6 +6,7 @@ use crate::arch::Target; use crate::chunks::ChunkHeader; use crate::context::Context; +use crate::input_files::add_data_word; use crate::macho::*; use crate::symbol::SymbolId; @@ -74,7 +75,7 @@ pub fn ensure_stub_binder(ctx: &mut Context) { ctx.symbols[id].set_used(true); crate::chunks::got::add_got_symbol(ctx, id); ctx.stub_helper.dyld_stub_binder = Some(id); - let isec = ctx.add_data_word(8); + let isec = add_data_word(ctx, 8); ctx.stub_helper.dyld_private_isec = isec; ctx.extra_local_syms.push((b"__dyld_private", isec)); } diff --git a/macho/context.rs b/macho/context.rs index 77ca95482..d4484bef0 100644 --- a/macho/context.rs +++ b/macho/context.rs @@ -40,9 +40,6 @@ use crate::chunks::{ }; use crate::cmdline::Args; use crate::input_files::{DylibFile, FileId, ObjectFile}; -use crate::input_sections::InputSection; -use crate::macho::{MachSection, bytes_to_name}; -use crate::objc::{DataBlob, DataField}; use crate::symbol::{SymbolId, SymbolTable}; use crate::util::perf::Timers; @@ -204,7 +201,7 @@ pub struct Context { /// Objective-C data records the linker synthesized (see /// merge_objc_categories) and the table of bundle_hook, each placed /// as the tail of the output section it names. - pub data_blobs: Vec, + pub data_blobs: Vec, /// Local symbols the linker names itself, on synthesized data: /// ld64's __OBJC_$_INSTANCE_METHODS_Foo(A|B) on a merged method /// list, and the like. (name, subsection). @@ -437,37 +434,6 @@ impl Context { self.args.dead_strip || (!self.args.relocatable && !self.lto_inputs.is_empty()) } - /// Adds a section the linker synthesizes to the internal object, - /// returning the (file, shndx) pair a subsection standing for it - /// carries. - pub fn add_synthetic_section(&mut self, hdr: crate::macho::MachSection) -> (u32, u32) { - let file = self.internal_obj.expect("internal object not created yet"); - let hdrs = self.objs[file].sect_hdrs.to_mut(); - hdrs.push(hdr); - (file as u32, (hdrs.len() - 1) as u32) - } - - /// Synthesizes a zero word of `size` bytes, aligned to its size, in - /// __DATA,__data (after the inputs'), and returns its subsection. - pub fn add_data_word(&mut self, size: u32) -> u32 { - let p2align = size.trailing_zeros() as u8; - let (file, shndx) = self.add_synthetic_section(MachSection { - sectname: bytes_to_name(b"__data"), - segname: bytes_to_name(b"__DATA"), - p2align: p2align as u32, - flags: 0, - ..Default::default() - }); - self.isecs.push(InputSection { - flags: InputSection::flags_placed(), - ..InputSection::new(file, shndx, p2align, size, &[]) - }); - let isec = (self.isecs.len() - 1) as u32; - let fields = vec![DataField::Bytes(vec![0; size as usize])]; - self.data_blobs.push(DataBlob { sect: b"__data", isec, fields }); - isec - } - /// Binds a symbol the linker's own code calls (dyld_stub_binder, /// __dyld_lazy_load), unless something in the link defines it, to /// the first loaded dylib that exports it - or, if none does and diff --git a/macho/delay_init.rs b/macho/delay_init.rs index dbc828f74..84d2927b7 100644 --- a/macho/delay_init.rs +++ b/macho/delay_init.rs @@ -10,9 +10,8 @@ use crate::arch::{LazyRef, Target}; use crate::chunks::delay_init::{DelayHelper, DelayStub, DelayUse, DlopenHelper}; use crate::context::Context; -use crate::input_files::FileId; +use crate::input_files::{FileId, add_cstring, add_data_word}; use crate::lazy_load::{LazyUseSite, import_uses, load_helper_name}; -use crate::macho::*; use crate::symbol::SymbolId; use crate::util::leak_bytes; @@ -96,7 +95,7 @@ fn create_dlopen_helpers( let leaf = install_name.rsplit(|&c| c == b'/').next().unwrap_or(&install_name); let name = leak_bytes([b"_dlopenHelper$", leaf].concat()); let flag_name = leak_bytes([b"_dlopenHelperFlag$", leaf].concat()); - let flag = ctx.add_data_word(4); + let flag = add_data_word(ctx, 4); ctx.extra_local_syms.push((flag_name, flag)); let string = add_cstring(ctx, &install_name); dlopen_of.insert(install_name.clone(), i as u32); @@ -107,26 +106,6 @@ fn create_dlopen_helpers( dlopen_of } -/// Synthesizes a C string in __TEXT,__cstring, after the inputs', and -/// returns its subsection. -fn add_cstring(ctx: &mut Context, s: &[u8]) -> u32 { - let (file, shndx) = ctx.add_synthetic_section(MachSection { - sectname: bytes_to_name(b"__cstring"), - segname: bytes_to_name(b"__TEXT"), - flags: S_CSTRING_LITERALS, - ..Default::default() - }); - let mut bytes = s.to_vec(); - bytes.push(0); - let bytes: &'static [u8] = Vec::leak(bytes); - use crate::input_sections::InputSection; - ctx.isecs.push(InputSection { - flags: InputSection::flags_alive_no_modulus(), - ..InputSection::new(file, shndx, 0, bytes.len() as u32, bytes) - }); - (ctx.isecs.len() - 1) as u32 -} - /// Makes a stub for each symbol something calls, by name, which jumps /// through the symbol's __got slot. fn create_delay_stubs( diff --git a/macho/dtrace.rs b/macho/dtrace.rs index 68dbbad63..a42a4510a 100644 --- a/macho/dtrace.rs +++ b/macho/dtrace.rs @@ -30,6 +30,7 @@ use rayon::prelude::*; use crate::arch::Target; use crate::context::Context; use crate::error::raw; +use crate::input_files::add_synthetic_section; use crate::input_sections::{InputSection, Reloc, RelocTarget}; use crate::macho::*; use crate::symbol::SymbolId; @@ -238,12 +239,13 @@ fn section_name(provider: &[u8], taken: &[Vec]) -> Vec { fn add_dof_section(ctx: &mut Context, name: &[u8], dof: Dof, sites: &[&Site]) { let mut sectname = [0; 16]; sectname[..name.len()].copy_from_slice(name); - let (file, shndx) = ctx.add_synthetic_section(MachSection { + let hdr = MachSection { sectname, segname: bytes_to_name(b"__TEXT"), flags: S_DTRACE_DOF, ..Default::default() - }); + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); let id = ctx.isecs.len() as u32; let relocs = &mut ctx.objs[file as usize].relocs; let rel_offset = relocs.len() as u32; diff --git a/macho/input_files.rs b/macho/input_files.rs index a0b0dfb37..4ea07d1c9 100644 --- a/macho/input_files.rs +++ b/macho/input_files.rs @@ -19,6 +19,7 @@ use crate::input_sections::{ }; use crate::macho::*; use crate::mapped_file::{MappedFile, unreadable_file}; +use crate::objc::ObjcRef; use crate::symbol::{Symbol, SymbolId}; use crate::tapi; use crate::tapi::{LdDirectives, LdSymbols, MovedExport, interpret_ld_symbols}; @@ -297,6 +298,132 @@ impl ObjectFile { } } +/// Adds a section the linker synthesizes to the internal object, +/// returning the (file, shndx) pair a subsection standing for it +/// carries. +pub fn add_synthetic_section(ctx: &mut Context, hdr: MachSection) -> (u32, u32) { + let file = ctx.internal_obj.expect("internal object not created yet"); + let hdrs = ctx.objs[file].sect_hdrs.to_mut(); + hdrs.push(hdr); + (file as u32, (hdrs.len() - 1) as u32) +} + +/// Appends a live synthetic subsection of `sect`, a section of the +/// internal object as add_synthetic_section returns it, and returns +/// it. Its output section and offset are set by hand (IS_PLACED), not +/// by create_output_sections; `offset` is its offset if already known. +pub(crate) fn add_placed_isec( + ctx: &mut Context, + sect: (u32, u32), + p2align: u8, + size: u64, + offset: u64, +) -> u32 { + let (file, shndx) = sect; + ctx.isecs.push(InputSection { + offset: offset as u32, + flags: InputSection::flags_placed(), + ..InputSection::new(file, shndx, p2align, size as u32, &[]) + }); + (ctx.isecs.len() - 1) as u32 +} + +/// A field of a synthesized data record. +#[derive(Clone, Debug)] +pub enum DataField { + Bytes(Vec), + /// An 8-byte pointer, rebased at load (or null), or bound if to an + /// import. + Ptr(ObjcRef), +} + +/// A synthesized data record (an Objective-C one, or the table of +/// bundle_hook), placed in the tail of the output section `sect` +/// (mapped to its segment like an input section of that name) as the +/// synthetic subsection `isec`. +#[derive(Debug)] +pub struct DataBlob { + pub sect: &'static [u8], + pub isec: u32, + pub fields: Vec, +} + +impl DataBlob { + pub fn size(&self) -> u64 { + self.fields + .iter() + .map(|f| match f { + DataField::Bytes(b) => b.len() as u64, + DataField::Ptr(_) => 8, + }) + .sum() + } +} + +/// Appends a synthesized record to the tail of __DATA,`sect` (a section +/// with the given flags) and returns its subsection. +pub(crate) fn add_data_blob( + ctx: &mut Context, + sect: &'static [u8], + flags: u32, + fields: Vec, +) -> u32 { + let hdr = MachSection { + sectname: bytes_to_name(sect), + segname: bytes_to_name(b"__DATA"), + p2align: 3, + flags, + ..Default::default() + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); + let blob = DataBlob { sect, isec: 0, fields }; + let isec = add_placed_isec(ctx, (file, shndx), 3, blob.size(), 0); + ctx.data_blobs.push(DataBlob { isec, ..blob }); + isec +} + +/// Synthesizes a zero word of `size` bytes, aligned to its size, in +/// __DATA,__data (after the inputs'), and returns its subsection. +pub fn add_data_word(ctx: &mut Context, size: u32) -> u32 { + let p2align = size.trailing_zeros() as u8; + let hdr = MachSection { + sectname: bytes_to_name(b"__data"), + segname: bytes_to_name(b"__DATA"), + p2align: p2align as u32, + flags: 0, + ..Default::default() + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); + ctx.isecs.push(InputSection { + flags: InputSection::flags_placed(), + ..InputSection::new(file, shndx, p2align, size, &[]) + }); + let isec = (ctx.isecs.len() - 1) as u32; + let fields = vec![DataField::Bytes(vec![0; size as usize])]; + ctx.data_blobs.push(DataBlob { sect: b"__data", isec, fields }); + isec +} + +/// Synthesizes a C string in __TEXT,__cstring, after the inputs', and +/// returns its subsection. +pub(crate) fn add_cstring(ctx: &mut Context, s: &[u8]) -> u32 { + let hdr = MachSection { + sectname: bytes_to_name(b"__cstring"), + segname: bytes_to_name(b"__TEXT"), + flags: S_CSTRING_LITERALS, + ..Default::default() + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); + let mut bytes = s.to_vec(); + bytes.push(0); + let bytes: &'static [u8] = Vec::leak(bytes); + ctx.isecs.push(InputSection { + flags: InputSection::flags_alive_no_modulus(), + ..InputSection::new(file, shndx, 0, bytes.len() as u32, bytes) + }); + (ctx.isecs.len() - 1) as u32 +} + /// Whether a label is one a compiler or assembler makes for itself: an /// assembler temporary (L...) or a linker-private label (l...) - the /// compiler's lCPI0_0 constant-pool and l_.str string labels, the arm64 diff --git a/macho/lazy_load.rs b/macho/lazy_load.rs index d7573da53..833b4ee6f 100644 --- a/macho/lazy_load.rs +++ b/macho/lazy_load.rs @@ -13,7 +13,7 @@ use crate::chunks::lazy_helpers::{LazyHelper, LazyUse}; use crate::chunks::lazy_load_info::{LazyDylib, record_size}; use crate::context::Context; use crate::error::raw; -use crate::input_files::FileId; +use crate::input_files::{FileId, add_data_word}; use crate::symbol::SymbolId; use crate::util::leak_bytes; @@ -168,7 +168,7 @@ fn create_lazy_load_slots( let mut slots = Vec::new(); let mut offset = 0; for d in used { - let flag = ctx.add_data_word(4); + let flag = add_data_word(ctx, 4); let install_name = &ctx.dylibs[d].install_name; let leaf = install_name.rsplit(|&c| c == b'/').next().unwrap_or(install_name); let name = leak_bytes([b"_lazyLoadFlag$", leaf].concat()); diff --git a/macho/make_mergeable/objc.rs b/macho/make_mergeable/objc.rs index 65c21b560..c9e764774 100644 --- a/macho/make_mergeable/objc.rs +++ b/macho/make_mergeable/objc.rs @@ -9,8 +9,9 @@ use hashbrown::HashSet; use super::{Builder, Content, OutEntry, OutFixup, To, record_size}; use crate::arch::Target; +use crate::input_files::DataField; use crate::mergeable::{ctype, fk, kind, scope, standard_content_type}; -use crate::objc::{DataField, ObjcRef}; +use crate::objc::ObjcRef; impl Builder<'_, E> { /// The entries of the metadata the link made: the selector diff --git a/macho/objc.rs b/macho/objc.rs index dba2196dc..60fdc8125 100644 --- a/macho/objc.rs +++ b/macho/objc.rs @@ -23,7 +23,10 @@ use rayon::prelude::*; use crate::arch::Target; use crate::context::Context; -use crate::input_files::{FileId, redirect_symbols_to_replacements}; +use crate::input_files::{ + DataField, FileId, add_data_blob, add_placed_isec, add_synthetic_section, + redirect_symbols_to_replacements, +}; use crate::input_sections::{InputSection, Reloc, RelocTarget}; use crate::macho::*; use crate::symbol::{NEEDS_GOT, NEEDS_STUB}; @@ -60,15 +63,6 @@ pub struct ObjcMethList { pub methods: Vec, } -/// A field of a synthesized Objective-C data record. -#[derive(Clone, Debug)] -pub enum DataField { - Bytes(Vec), - /// An 8-byte pointer, rebased at load (or null), or bound if to an - /// import. - Ptr(ObjcRef), -} - impl ObjcRef { /// The import a reference is to, which dyld binds. pub fn import(self, ctx: &Context) -> Option { @@ -90,80 +84,17 @@ impl ObjcRef { } } -/// A synthesized data record (an Objective-C one, or the table of -/// bundle_hook), placed in the tail of the output section `sect` -/// (mapped to its segment like an input section of that name) as the -/// synthetic subsection `isec`. -#[derive(Debug)] -pub struct DataBlob { - pub sect: &'static [u8], - pub isec: u32, - pub fields: Vec, -} - -impl DataBlob { - pub fn size(&self) -> u64 { - self.fields - .iter() - .map(|f| match f { - DataField::Bytes(b) => b.len() as u64, - DataField::Ptr(_) => 8, - }) - .sum() - } -} - -/// Appends a live synthetic subsection of `sect`, a section of the -/// internal object as add_synthetic_section returns it, and returns -/// it. Its output section and offset are set by hand (IS_PLACED), not -/// by create_output_sections; `offset` is its offset if already known. -fn add_placed_isec( - ctx: &mut Context, - sect: (u32, u32), - p2align: u8, - size: u64, - offset: u64, -) -> u32 { - let (file, shndx) = sect; - ctx.isecs.push(InputSection { - offset: offset as u32, - flags: InputSection::flags_placed(), - ..InputSection::new(file, shndx, p2align, size as u32, &[]) - }); - (ctx.isecs.len() - 1) as u32 -} - -/// Appends a synthesized record to the tail of __DATA,`sect` (a section -/// with the given flags) and returns its subsection. -pub(crate) fn add_data_blob( - ctx: &mut Context, - sect: &'static [u8], - flags: u32, - fields: Vec, -) -> u32 { - let hdr = ctx.add_synthetic_section(MachSection { - sectname: bytes_to_name(sect), - segname: bytes_to_name(b"__DATA"), - p2align: 3, - flags, - ..Default::default() - }); - let blob = DataBlob { sect, isec: 0, fields }; - let isec = add_placed_isec(ctx, hdr, 3, blob.size(), 0); - ctx.data_blobs.push(DataBlob { isec, ..blob }); - isec -} - /// A new __TEXT,__objc_methlist section of the internal object, for /// method lists rewritten in the relative form. fn add_methlist_section(ctx: &mut Context) -> (u32, u32) { - ctx.add_synthetic_section(MachSection { + let hdr = MachSection { sectname: bytes_to_name(b"__objc_methlist"), segname: bytes_to_name(b"__TEXT"), p2align: 2, flags: S_REGULAR, ..Default::default() - }) + }; + add_synthetic_section(ctx, hdr) } /// Appends a method list in the relative form, at `*offset` in `sect` @@ -779,13 +710,14 @@ fn add_classref_stand_ins( if kept.is_empty() { return; } - let (file, shndx) = ctx.add_synthetic_section(MachSection { + let hdr = MachSection { sectname: bytes_to_name(b"__got"), segname: bytes_to_name(b"__DATA"), p2align: 3, flags: S_NON_LAZY_SYMBOL_POINTERS, ..Default::default() - }); + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); for (slot, class) in kept { ctx.isecs.push(InputSection { output_section: crate::chunks::ChunkId::Got.pack(), diff --git a/macho/passes.rs b/macho/passes.rs index b4eba42b6..fb7bd5b88 100644 --- a/macho/passes.rs +++ b/macho/passes.rs @@ -23,12 +23,11 @@ use crate::error::RawPath; use crate::error::raw; use crate::fatal; use crate::input_files; -use crate::input_files::{FileId, ObjcImageInfo, SymbolSlots}; +use crate::input_files::{DataBlob, FileId, ObjcImageInfo, SymbolSlots, add_synthetic_section}; use crate::input_files::{is_class_or_protocol_ref_name, standard_section_flags}; use crate::input_sections::{InputSection, InputSectionId, NO_REPLACEMENT, RelocTarget}; use crate::macho::*; use crate::mapped_file::MappedFile; -use crate::objc::DataBlob; use crate::symbol::{NEEDS_GOT, NEEDS_STUB, Symbol, SymbolId}; use crate::symbol_moves::{Move, MoveOption}; use crate::util::worker_local::WorkerLocal; @@ -1217,14 +1216,15 @@ pub fn convert_common_symbols(ctx: &mut Context) { (size.next_power_of_two().trailing_zeros() as u8).min(ctx.args.max_default_common_align) }; - let (file, shndx) = ctx.add_synthetic_section(MachSection { + let hdr = MachSection { sectname: bytes_to_name(b"__common"), segname: bytes_to_name(b"__DATA"), size, p2align: p2align as u32, flags: S_ZEROFILL, ..Default::default() - }); + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); ctx.isecs.push(InputSection::new(file, shndx, p2align, size as u32, &[])); let sym = &mut ctx.symbols[i]; @@ -3649,12 +3649,13 @@ fn add_sectcreate_isec( data: &'static [u8], ) -> u32 { let sc = &ctx.args.sectcreate[i]; - let (file, shndx) = ctx.add_synthetic_section(MachSection { + let hdr = MachSection { sectname: bytes_to_name(&sc.sectname), segname: bytes_to_name(&sc.segname), size: data.len() as u64, ..Default::default() - }); + }; + let (file, shndx) = add_synthetic_section(ctx, hdr); let id = ctx.isecs.len() as u32; ctx.isecs.push(InputSection { output_section: ChunkId::Output(osec).pack(),