Keep the chunks' layout helpers in their chunk modules

Some helpers of passes.rs were about one chunk: finish_unwind_info
encodes __unwind_info again once every segment is placed, init_func
makes an __init_offsets entry of a symbol, resolve_entry took the
address LC_MAIN and LC_UNIXTHREAD name, and resolve_stub_binder finds
the helper legacy LINKEDIT's stub helper jumps to. They are
unwind_info::finish_unwind_info, InitFunc::new and
stub_helper::resolve_stub_binder, beside ensure_stub_binder, now, and
the entry point's address is chunks/mod.rs's entry_addr, as in mold,
which the load commands ask rather than a Context field that a pass
set after layout. An undefined entry point is still reported by a
pass, check_entry_point, where the driver ran resolve_entry.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Rui UeyamaandClaude Opus 5.5 committed 2026-10-08 18:27:31 +09:00
1 parent 309b8cd8df
commit b6418cfff5
7 files changed
+79 -70

No files matched your search

+13
View File
@@ -25,6 +25,19 @@ pub enum InitFunc {
Imported(SymbolId),
}
impl InitFunc {
/// The initializer symbol `id` is. One dyld binds, or an absolute
/// one, has no offset in the image: the link fails as it is written
/// (see copy_buf).
pub fn new<E: Target>(ctx: &Context<E>, id: SymbolId) -> Self {
let sym = &ctx.symbols[id];
match sym.input_section() {
Some(isec) => InitFunc::Local(ctx.isecs.resolve(isec as usize), sym.value),
None => InitFunc::Imported(id),
}
}
}
impl InitOffsetsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__init_offsets");
+20 -4
View File
@@ -679,16 +679,32 @@ fn create_string_cmd(kind: u32, path: &[u8]) -> Vec<u8> {
append_string(to_vec(&cmd), path)
}
/// The address of the entry point symbol, or 0 for an image without
/// one, or for one that is undefined (see passes::check_entry_point).
fn entry_addr<E: Target>(ctx: &Context<E>) -> u64 {
if !ctx.args.has_entry_point() {
return 0;
}
match ctx.symbols.lookup(&ctx.args.entry) {
// An entry point in a dylib (an app extension's
// _NSExtensionMain): LC_MAIN must point into __TEXT, so it
// names the symbol's stub, as ld64 does.
Some(id) if ctx.symbols[id].is_imported() => ctx.symbols[id].stub_addr(ctx),
Some(id) if ctx.symbols[id].is_defined() => ctx.symbols[id].addr(ctx),
_ => 0,
}
}
fn create_main_cmd<E: Target>(ctx: &Context<E>) -> Vec<u8> {
// The entry point is a file offset into __TEXT, whose file offset
// is zero. The layout sizes the command before the entry point has
// an address (0) - also when it lays the segments out again, with
// __TEXT placed by the first round.
// its final address - also when it lays the segments out again,
// with __TEXT placed by the first round.
let text = ctx.segments.iter().find(|s| s.name == b"__TEXT").unwrap();
let cmd = EntryPointCommand {
cmd: LC_MAIN,
cmdsize: size_of::<EntryPointCommand>() as u32,
entryoff: ctx.entry_addr.saturating_sub(text.cmd.vmaddr),
entryoff: entry_addr(ctx).saturating_sub(text.cmd.vmaddr),
stacksize: ctx.args.stack_size,
};
to_vec(&cmd)
@@ -721,7 +737,7 @@ fn create_unixthread_cmd<E: Target>(ctx: &Context<E>) -> Vec<u8> {
}
buf.resize(size, 0);
let pc = 16 + E::THREAD_STATE_PC_OFFSET;
buf[pc..pc + 8].copy_from_slice(&ctx.entry_addr.to_le_bytes());
buf[pc..pc + 8].copy_from_slice(&entry_addr(ctx).to_le_bytes());
if let Some(stack) = ctx.segments.iter().find(|seg| seg.name == b"__UNIXSTACK") {
let sp = 16 + E::THREAD_STATE_SP_OFFSET;
buf[sp..sp + 8].copy_from_slice(&(stack.cmd.vmaddr + stack.cmd.vmsize).to_le_bytes());
+14 -1
View File
@@ -65,7 +65,7 @@ pub fn entry_offset<E: Target>(ctx: &Context<E>, idx: u32) -> u64 {
/// synthesized. Once, on the first stub.
pub fn ensure_stub_binder<E: Target>(ctx: &mut Context<E>) {
// Legacy LINKEDIT's helper enters dyld through crt1.o's
// dyld_stub_binding_helper instead (see passes::resolve_stub_binder).
// dyld_stub_binding_helper instead (see resolve_stub_binder).
if ctx.stub_helper.dyld_stub_binder.is_some() || ctx.args.legacy_linkedit {
return;
}
@@ -80,6 +80,19 @@ pub fn ensure_stub_binder<E: Target>(ctx: &mut Context<E>) {
ctx.extra_local_syms.push((b"__dyld_private", isec));
}
/// Finds the helper legacy LINKEDIT's stub helper entries jump to. It
/// binds no dyld_stub_binder: its entries go to dyld_stub_binding_helper,
/// which crt1.o, dylib1.o or bundle1.o defines; no dylib exports it.
/// (Otherwise dyld_stub_binder is bound once a stub needs it; see
/// ensure_stub_binder.)
pub fn resolve_stub_binder<E: Target>(ctx: &mut Context<E>) {
if ctx.args.legacy_linkedit {
let id = ctx.symbols.lookup(b"dyld_stub_binding_helper");
let id = id.filter(|&id| ctx.symbols[id].input_section().is_some());
ctx.stub_helper.binding_helper = id;
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
ctx.stub_helper.hdr.size =
header_size(ctx) + ctx.stubs.lazy.len() as u64 * E::STUB_HELPER_ENTRY_SIZE;
+19 -1
View File
@@ -4,7 +4,7 @@
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_sections::{InputSection, UnwindRecord};
use crate::macho::*;
@@ -56,6 +56,24 @@ pub fn compute_size<E: Target>(ctx: &mut Context<E>) -> u64 {
size
}
/// __unwind_info is encoded as __TEXT is laid out, when only __TEXT's
/// addresses are final. If it covers code or LSDAs in other segments
/// too, this encodes it again now every segment has its address.
/// Returns false if that encoding needs more room than __TEXT left the
/// section; the layout is then done again with that much room (a
/// smaller one leaves zeros after it).
pub fn finish_unwind_info<E: Target>(ctx: &mut Context<E>) -> bool {
if !ctx.chunks.contains(&ChunkId::UnwindInfo) || !covers_other_segments(ctx) {
return true;
}
let size = compute_size(ctx);
if size > ctx.unwind_info.hdr.size {
ctx.unwind_info.min_size = size;
return false;
}
true
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = &ctx.unwind_info;
debug_assert!(sec.contents.len() as u64 <= sec.hdr.size);
-3
View File
@@ -244,8 +244,6 @@ pub struct Context<E: Target> {
pub pointers32: std::sync::Mutex<Vec<(u32, u32)>>,
/// The output's UUID, computed from its contents.
pub uuid: std::sync::Mutex<[u8; 16]>,
/// The resolved address of the entry point symbol.
pub entry_addr: u64,
/// The -init function, when LC_ROUTINES_64 names it (the image has
/// no __init_offsets to run it first from).
pub init_routine: Option<SymbolId>,
@@ -338,7 +336,6 @@ impl<E: Target> Context<E> {
text_relocs: std::sync::Mutex::new(Vec::new()),
pointers32: std::sync::Mutex::new(Vec::new()),
uuid: std::sync::Mutex::new([0; 16]),
entry_addr: 0,
init_routine: None,
output_size: 0,
timers,
+2 -2
View File
@@ -247,7 +247,7 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
objc::merge_objc_categories(&mut ctx);
// Synthetic stubs and unwind data can introduce library references
// (notably dyld_stub_binder). Establish them before pruning dylibs.
passes::resolve_stub_binder(&mut ctx);
chunks::stub_helper::resolve_stub_binder(&mut ctx);
crate::lazy_load::bind_dyld_lazy_load(&mut ctx);
timed!("dead_strip_dylibs", passes::dead_strip_dylibs(&mut ctx));
passes::bind_private_reexports_to_image(&mut ctx);
@@ -301,7 +301,7 @@ pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
// of the parallel __LINKEDIT task group.
timed!("set_osec_offsets", passes::set_osec_offsets(&mut ctx));
passes::fix_synthetic_symbols(&mut ctx);
passes::resolve_entry(&mut ctx);
passes::check_entry_point(&ctx);
crate::error::checkpoint();
crate::mapfile::write_dependency_info(&ctx);
crate::mapfile::print_map(&ctx);
+11 -59
View File
@@ -1011,7 +1011,7 @@ pub fn convert_init_offsets<E: Target>(ctx: &mut Context<E>) {
}
// ld-prime makes it the first of the initializer offsets.
if let Some(id) = init {
let func = init_func(ctx, id);
let func = InitFunc::new(ctx, id);
ctx.init_offsets.init_funcs.push(func);
}
// ld-prime runs the hook for the classes of mergeable libraries (see
@@ -1028,7 +1028,7 @@ pub fn convert_init_offsets<E: Target>(ctx: &mut Context<E>) {
let obj = ctx.isecs[i].file as usize;
for rel in initializer_relocs(ctx, i) {
let func = match rel.target() {
RelocTarget::Sym(idx) => init_func(ctx, ctx.objs[obj].symbols[idx as usize]),
RelocTarget::Sym(idx) => InitFunc::new(ctx, ctx.objs[obj].symbols[idx as usize]),
RelocTarget::Section(isec) => {
InitFunc::Local(ctx.isecs.resolve(isec as usize), rel.addend as u64)
}
@@ -1039,17 +1039,6 @@ pub fn convert_init_offsets<E: Target>(ctx: &mut Context<E>) {
}
}
/// The initializer symbol `id` is. One dyld binds, or an absolute
/// one, has no offset in the image: the link fails as it is written
/// (see init_offsets::copy_buf).
fn init_func<E: Target>(ctx: &Context<E>, id: crate::symbol::SymbolId) -> InitFunc {
let sym = &ctx.symbols[id];
match sym.input_section() {
Some(isec) => InitFunc::Local(ctx.isecs.resolve(isec as usize), sym.value),
None => InitFunc::Imported(id),
}
}
/// The function -init names, if it is defined. An undefined one is
/// reported with the other initial undefines.
fn init_function<E: Target>(ctx: &Context<E>) -> Option<crate::symbol::SymbolId> {
@@ -4650,8 +4639,9 @@ fn mach_header_addr<E: Target>(ctx: &Context<E>) -> u64 {
/// moved once all are sized - all but the mach header's segment
/// (__TEXT), whose address is known up front (mach_header_addr) and
/// whose __unwind_info encodes the final addresses of its functions
/// (and of the others once they are placed: finish_unwind_info).
/// __LINKEDIT comes last: its tables read every other address.
/// (and of the others once they are placed: see
/// unwind_info::finish_unwind_info). __LINKEDIT comes last: its tables
/// read every other address.
pub fn set_osec_offsets<E: Target>(ctx: &mut Context<E>) {
let linkedit = ctx.segments.len() - 1;
debug_assert_eq!(ctx.segments[linkedit].name, b"__LINKEDIT");
@@ -4804,11 +4794,11 @@ fn lay_out_segments<E: Target>(ctx: &mut Context<E>) -> u64 {
/// Lays out every segment but __LINKEDIT as lay_out_segments does, and
/// again until __unwind_info fits the room __TEXT leaves it (see
/// finish_unwind_info). Returns the file offset past them.
/// unwind_info::finish_unwind_info). Returns the file offset past them.
fn lay_out_segments_with_unwind_info<E: Target>(ctx: &mut Context<E>) -> u64 {
loop {
let fileoff = lay_out_segments(ctx);
if finish_unwind_info(ctx) {
if chunks::unwind_info::finish_unwind_info(ctx) {
return fileoff;
}
}
@@ -4843,26 +4833,6 @@ fn segment_start_align<E: Target>(ctx: &Context<E>, seg_idx: usize) -> u64 {
ctx.args.segment_align.max(1 << first)
}
/// __unwind_info is encoded as __TEXT is laid out, when only __TEXT's
/// addresses are final. If it covers code or LSDAs in other segments
/// too, this encodes it again now every segment has its address.
/// Returns false if that encoding needs more room than __TEXT left the
/// section; the layout is then done again with that much room (a
/// smaller one leaves zeros after it).
fn finish_unwind_info<E: Target>(ctx: &mut Context<E>) -> bool {
if !ctx.chunks.contains(&ChunkId::UnwindInfo)
|| !chunks::unwind_info::covers_other_segments(ctx)
{
return true;
}
let size = chunks::unwind_info::compute_size(ctx);
if size > ctx.unwind_info.hdr.size {
ctx.unwind_info.min_size = size;
return false;
}
true
}
/// Lays out a segment's chunks from file offset `fileoff` and address
/// `vmaddr`, and returns the file offset past the segment.
fn layout_segment<E: Target>(
@@ -5374,19 +5344,14 @@ fn build_fixups<E: Target>(ctx: &Context<E>) -> Fixups {
Fixups::Classic { rebase, bind, weak_bind, lazy_bind, lazy_offsets }
}
/// Resolves the entry point symbol.
pub fn resolve_entry<E: Target>(ctx: &mut Context<E>) {
/// Reports an entry point symbol that is undefined (see
/// chunks::entry_addr).
pub fn check_entry_point<E: Target>(ctx: &Context<E>) {
if !ctx.args.has_entry_point() {
return;
}
match ctx.symbols.lookup(&ctx.args.entry) {
// An entry point in a dylib (an app extension's
// _NSExtensionMain): LC_MAIN must point into __TEXT, so it
// names the symbol's stub, as ld64 does.
Some(id) if ctx.symbols[id].is_imported() => {
ctx.entry_addr = ctx.symbols[id].stub_addr(ctx)
}
Some(id) if ctx.symbols[id].is_defined() => ctx.entry_addr = ctx.symbols[id].addr(ctx),
Some(id) if ctx.symbols[id].is_imported() || ctx.symbols[id].is_defined() => {}
_ => {
error!(
"undefined symbol for entry point: {}",
@@ -5410,19 +5375,6 @@ pub fn add_entry_stub<E: Target>(ctx: &mut Context<E>) {
}
}
/// Finds the helper legacy LINKEDIT's stub helper entries jump to. It
/// binds no dyld_stub_binder: its entries go to dyld_stub_binding_helper,
/// which crt1.o, dylib1.o or bundle1.o defines; no dylib exports it.
/// (Otherwise dyld_stub_binder is bound once a stub needs it; see
/// chunks::stub_helper::ensure_stub_binder.)
pub fn resolve_stub_binder<E: Target>(ctx: &mut Context<E>) {
if ctx.args.legacy_linkedit {
let id = ctx.symbols.lookup(b"dyld_stub_binding_helper");
let id = id.filter(|&id| ctx.symbols[id].input_section().is_some());
ctx.stub_helper.binding_helper = id;
}
}
/// Computes the UUID that identifies the build and writes it into the
/// header's LC_UUID, and returns the SHA256 hashes of every 4KiB page
/// before the code signature at `sig_start`, which the signature is