Make the symbol predicates Symbol methods, as mold has them

What dyld does with a symbol - whether it is an import of a lazy or
delay-init dylib, a TLV descriptor, a weak definition dyld may
coalesce, an interposable export, whether dyld binds its slots and
pointers, by what ordinal, and whether a GOT load of it relaxes - was
asked of Context with the symbol's id. mold asks the symbol, with the
context: Symbol::is_local(ctx), is_pcrel_linktime_const(ctx) and the
like. These are Symbol methods taking the context now;
is_absolute_symbol is is_absolute, mold's name, sym_bind_ordinal is
bind_ordinal, and is_dtrace_pointer_target, which looks at the symbol
alone, takes no context. Where a caller has the symbol at hand, it
asks it directly, and the scans over the whole table iterate the
symbols.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Rui UeyamaandClaude Opus 5.5 committed 2026-10-08 17:48:37 +09:00
1 parent a3f8b6275b
commit ce2f56bec8
22 files changed
+328 -313

No files matched your search

+12 -10
View File
@@ -1250,13 +1250,13 @@ impl Target for Arm64 {
let file = isec.file as usize;
for rel in isec_relocs_of(&ctx.objs, isec) {
let Some(id) = ctx.reloc_target_sym(file, rel) else { continue };
let sym = &ctx.symbols[id];
// A lazy dylib's symbols take no stub or GOT slot; the image
// reaches them through the helpers of
// lazy_load::create_lazy_loads.
if ctx.is_lazy_import(id) {
if sym.is_lazy_import(ctx) {
continue;
}
let sym = &ctx.symbols[id];
// Thread-locals live behind __thread_vars descriptors, so the
// reference kind must agree with the symbol: a TLV load of
@@ -1265,7 +1265,7 @@ impl Target for Arm64 {
// descriptor as data. ld64 rejects both directions.
let is_tlv_reloc =
matches!(rel.ty, ARM64_RELOC_TLVP_LOAD_PAGE21 | ARM64_RELOC_TLVP_LOAD_PAGEOFF12);
if is_tlv_reloc != ctx.is_tlv(id) {
if is_tlv_reloc != sym.is_tlv(ctx) {
error!("illegal thread local variable reference to regular symbol `{sym}`");
}
@@ -1275,8 +1275,8 @@ impl Target for Arm64 {
// through its stub; a delay-init dylib's, through its
// stub of delay_init::create_delay_init instead.
ARM64_RELOC_BRANCH26 => {
if !ctx.is_delay_import(id)
&& (ctx.binds_as_import(id) || ctx.binds_weak_lookup(id))
if !sym.is_delay_import(ctx)
&& (sym.binds_as_import(ctx) || sym.binds_weak_lookup(ctx))
{
sym.add_flags(NEEDS_STUB);
}
@@ -1291,7 +1291,7 @@ impl Target for Arm64 {
| ARM64_RELOC_GOT_LOAD_PAGEOFF12
| ARM64_RELOC_TLVP_LOAD_PAGE21
| ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
if !ctx.can_relax_got(id) {
if !sym.can_relax_got(ctx) {
sym.add_flags(NEEDS_GOT);
}
}
@@ -1322,8 +1322,9 @@ impl Target for Arm64 {
ctx.check_text_reloc(isec_id, rels, i, p);
// An imported symbol's address is written by dyld,
// via a bind record (an interposable export's too).
let imported =
ctx.reloc_target_sym(obj, r).is_some_and(|id| ctx.binds_as_import(id));
let imported = ctx
.reloc_target_sym(obj, r)
.is_some_and(|id| ctx.symbols[id].binds_as_import(ctx));
if imported {
// The slot is filled by dyld.
} else if ctx.reloc_target_is_tls(obj, r) {
@@ -1431,7 +1432,8 @@ impl Target for Arm64 {
// address from __got.
ARM64_RELOC_GOT_LOAD_PAGE21 | ARM64_RELOC_TLVP_LOAD_PAGE21 => {
let id = ctx.reloc_target_sym(obj, r).unwrap();
let t = if ctx.can_relax_got(id) { s } else { ctx.sym_got_addr(id) };
let t =
if ctx.symbols[id].can_relax_got(ctx) { s } else { ctx.sym_got_addr(id) };
check_adrp(ctx, isec_id, r, p, t.wrapping_add_signed(a));
write_adrp(loc, t.wrapping_add_signed(a), p);
}
@@ -1440,7 +1442,7 @@ impl Target for Arm64 {
// any other instruction.
ARM64_RELOC_GOT_LOAD_PAGEOFF12 | ARM64_RELOC_TLVP_LOAD_PAGEOFF12 => {
let id = ctx.reloc_target_sym(obj, r).unwrap();
if !ctx.can_relax_got(id) {
if !ctx.symbols[id].can_relax_got(ctx) {
let g = ctx.sym_got_addr(id);
if let Err(size) = write_add_ldst(loc, g.wrapping_add_signed(a)) {
report_ldst_alignment(ctx, isec_id, r, size);
+1 -1
View File
@@ -152,7 +152,7 @@ pub trait Target: Copy + Default + Send + Sync + 'static {
/// The relocation types that load a symbol's address from its GOT
/// slot, or a thread-local's descriptor's from its slot, and that
/// relax to the address itself when dyld fills no slot (see
/// Context::can_relax_got).
/// Symbol::can_relax_got).
const RELOC_GOT_LOADS: &'static [u8];
/// The explicit-addend relocation type, for targets that have one.
const RELOC_ADDEND: u8;
+13 -10
View File
@@ -570,17 +570,17 @@ impl Target for X86_64 {
let file = isec.file as usize;
for rel in isec_relocs_of(&ctx.objs, isec) {
let Some(id) = ctx.reloc_target_sym(file, rel) else { continue };
let sym = &ctx.symbols[id];
// A lazy dylib's symbols take no stub or GOT slot; the image
// reaches them through the helpers of
// lazy_load::create_lazy_loads.
if ctx.is_lazy_import(id) {
if sym.is_lazy_import(ctx) {
continue;
}
let sym = &ctx.symbols[id];
// A TLV load must load a thread-local, and only a TLV load
// may, as on arm64.
if (rel.ty == X86_64_RELOC_TLV) != ctx.is_tlv(id) {
if (rel.ty == X86_64_RELOC_TLV) != sym.is_tlv(ctx) {
error!("illegal thread local variable reference to regular symbol `{sym}`");
}
@@ -597,17 +597,17 @@ impl Target for X86_64 {
X86_64_RELOC_BRANCH
if ctx.args.is_kext()
&& !ctx.args.kexts_use_stubs
&& !ctx.binds_weak_lookup(id) => {}
&& !sym.binds_weak_lookup(ctx) => {}
// Otherwise, as on arm64.
X86_64_RELOC_BRANCH => {
if !ctx.is_delay_import(id)
&& (ctx.binds_as_import(id) || ctx.binds_weak_lookup(id))
if !sym.is_delay_import(ctx)
&& (sym.binds_as_import(ctx) || sym.binds_weak_lookup(ctx))
{
sym.add_flags(NEEDS_STUB);
}
}
X86_64_RELOC_GOT_LOAD | X86_64_RELOC_TLV => {
if !ctx.can_relax_got(id) {
if !sym.can_relax_got(ctx) {
sym.add_flags(NEEDS_GOT);
}
}
@@ -635,7 +635,9 @@ impl Target for X86_64 {
// before the fixup, so it is rewritten before the slice
// below is taken.
let relaxed_got_load = matches!(r.ty, X86_64_RELOC_GOT_LOAD | X86_64_RELOC_TLV)
&& ctx.reloc_target_sym(obj, r).is_some_and(|id| ctx.can_relax_got(id));
&& ctx
.reloc_target_sym(obj, r)
.is_some_and(|id| ctx.symbols[id].can_relax_got(ctx));
if relaxed_got_load {
match r.offset.checked_sub(2).map(|i| &mut buf[i as usize]) {
Some(op) if *op == 0x8b => *op = 0x8d,
@@ -703,8 +705,9 @@ impl Target for X86_64 {
}
X86_64_RELOC_UNSIGNED => {
ctx.check_text_reloc(isec_id, rels, i, p);
let imported =
ctx.reloc_target_sym(obj, r).is_some_and(|id| ctx.binds_pointer(id));
let imported = ctx
.reloc_target_sym(obj, r)
.is_some_and(|id| ctx.symbols[id].binds_pointer(ctx));
if imported {
// The slot is filled by dyld. It keeps the
// addend, to which a legacy LINKEDIT external
+4 -3
View File
@@ -35,7 +35,7 @@ pub fn build<E: Target>(ctx: &Context<E>) -> Vec<u8> {
// GOT slots for imported symbols.
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if ctx.binds_as_import(id) {
if ctx.symbols[id].binds_as_import(ctx) {
binds.push((ctx.got.slot_addr(i), id, 0));
}
}
@@ -48,7 +48,8 @@ pub fn build<E: Target>(ctx: &Context<E>) -> Vec<u8> {
}
for (addr, rel) in rebase_info::pointer_relocs(ctx, isec) {
if let Some(id) = ctx.reloc_target_sym(isec.file as usize, rel)
&& (ctx.binds_as_import(id) || ctx.is_dtrace_pointer_target(id))
&& (ctx.symbols[id].binds_as_import(ctx)
|| ctx.symbols[id].is_dtrace_pointer_target())
{
binds.push((addr, id, rel.addend));
}
@@ -63,7 +64,7 @@ pub fn build<E: Target>(ctx: &Context<E>) -> Vec<u8> {
}
// ld64 lists the binds by library, symbol, addend, then address, so
// each library and symbol is set once.
let ordinal = |id| ctx.sym_bind_ordinal(id);
let ordinal = |id: crate::symbol::SymbolId| ctx.symbols[id].bind_ordinal(ctx);
binds.sort_by(|a, b| {
(ordinal(a.1), ctx.symbols[a.1].name(), a.2, a.0).cmp(&(
ordinal(b.1),
+17 -11
View File
@@ -284,15 +284,16 @@ fn write_imports<E: Target>(
/// under -flat_namespace and the image itself (0) otherwise.
fn import_ordinal<E: Target>(ctx: &Context<E>, sym: SymbolId, bits: u32) -> u64 {
let special = |ordinal: i32| (ordinal as i64 as u64) & ((1u64 << bits) - 1);
match ctx.symbols[sym].file() {
Some(FileId::Dylib(dylib)) if !ctx.binds_weak_lookup(sym) => {
let sym = &ctx.symbols[sym];
match sym.file() {
Some(FileId::Dylib(dylib)) if !sym.binds_weak_lookup(ctx) => {
chained_import_ordinal(ctx, dylib, bits)
}
_ if ctx.binds_to_self(sym) => BIND_SPECIAL_DYLIB_SELF as u64,
_ if ctx.is_interposable_export(sym) && !ctx.binds_weak_lookup(sym) => {
_ if sym.binds_to_self(ctx) => BIND_SPECIAL_DYLIB_SELF as u64,
_ if sym.is_interposable_export(ctx) && !sym.binds_weak_lookup(ctx) => {
special(ctx.export_bind_ordinal())
}
_ if ctx.is_dtrace_pointer_target(sym) => special(BIND_SPECIAL_DYLIB_FLAT_LOOKUP),
_ if sym.is_dtrace_pointer_target() => special(BIND_SPECIAL_DYLIB_FLAT_LOOKUP),
_ => special(BIND_SPECIAL_DYLIB_WEAK_LOOKUP),
}
}
@@ -445,11 +446,16 @@ fn collect_fixups<E: Target>(ctx: &Context<E>) -> (Vec<Fixup>, Vec<(u32, u64)>)
let unaligned = &unaligned;
rebase_info::pointer_relocs(ctx, isec).filter_map(move |(addr, rel)| {
let fixup = match ctx.reloc_target_sym(isec.file as usize, rel) {
Some(id) if ctx.is_absolute_symbol(id) && !ctx.binds_at_runtime(id) => None,
Some(id)
if ctx.binds_at_runtime(id)
|| ctx.binds_to_self(id)
|| ctx.is_dtrace_pointer_target(id) =>
if ctx.symbols[id].is_absolute(ctx)
&& !ctx.symbols[id].binds_at_runtime(ctx) =>
{
None
}
Some(id)
if ctx.symbols[id].binds_at_runtime(ctx)
|| ctx.symbols[id].binds_to_self(ctx)
|| ctx.symbols[id].is_dtrace_pointer_target() =>
{
Some((addr, Some(id), rel.addend as u64))
}
@@ -465,10 +471,10 @@ fn collect_fixups<E: Target>(ctx: &Context<E>) -> (Vec<Fixup>, Vec<(u32, u64)>)
.collect();
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if ctx.is_absolute_symbol(id) && !ctx.binds_at_runtime(id) {
if ctx.symbols[id].is_absolute(ctx) && !ctx.symbols[id].binds_at_runtime(ctx) {
continue;
}
let sym = Some(id).filter(|&id| ctx.binds_at_runtime(id));
let sym = Some(id).filter(|&id| ctx.symbols[id].binds_at_runtime(ctx));
let slot = ctx.got.slot_addr(i);
fixups.push((slot, sym, 0));
}
+2 -2
View File
@@ -174,10 +174,10 @@ fn exports<E: Target>(
if sym.is_weak_def() {
flags |= EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
}
if ctx.is_tlv(id) {
if sym.is_tlv(ctx) {
flags |= EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL;
}
let addr = if ctx.is_absolute_symbol(id) {
let addr = if sym.is_absolute(ctx) {
flags |= EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE;
ctx.sym_addr(id)
} else {
+3 -3
View File
@@ -37,7 +37,7 @@ impl Default for ExternRelocsSection {
/// Collects the GOT slots and data pointers that hold an import's
/// address, and the calls to one (on x86-64, which has no stubs for a
/// kext), and sizes the table. In legacy LINKEDIT, the data pointers
/// dyld binds (see Context::binds_pointer).
/// dyld binds (see Symbol::binds_pointer).
pub fn build<E: Target>(ctx: &mut Context<E>) {
let got = &ctx.got;
let mut vec: Vec<(u64, SymbolId, bool)> = Vec::new();
@@ -48,8 +48,8 @@ pub fn build<E: Target>(ctx: &mut Context<E>) {
.map(|(i, &id)| (got.slot_addr(i), id, false)),
);
}
let binds = |id| match ctx.args.legacy_linkedit {
true => ctx.binds_pointer(id),
let binds = |id: SymbolId| match ctx.args.legacy_linkedit {
true => ctx.symbols[id].binds_pointer(ctx),
false => ctx.symbols[id].is_imported(),
};
for isec in ctx.isecs.iter() {
+1 -1
View File
@@ -73,7 +73,7 @@ pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
// address all the same, as ld-prime writes it.
let binds = |id: crate::symbol::SymbolId| match ctx.args.legacy_linkedit {
true => ctx.symbols[id].is_imported(),
false => ctx.binds_as_import(id),
false => ctx.symbols[id].binds_as_import(ctx),
};
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if !binds(id) {
+5 -2
View File
@@ -52,7 +52,9 @@ pub fn sections<E: Target>(ctx: &Context<E>) -> impl Iterator<Item = ChunkId> +
fn entries<E: Target>(ctx: &Context<E>, id: ChunkId) -> Vec<Option<SymbolId>> {
let got = &ctx.got;
let got_slots = |syms: &[SymbolId]| -> Vec<Option<SymbolId>> {
syms.iter().map(|&id| Some(id).filter(|&id| ctx.binds_at_runtime(id))).collect()
syms.iter()
.map(|&id| Some(id).filter(|&id| ctx.symbols[id].binds_at_runtime(ctx)))
.collect()
};
match id {
ChunkId::Stubs => ctx.stubs.symbols.iter().map(|&id| Some(id)).collect(),
@@ -84,7 +86,8 @@ fn input_slots<E: Target>(ctx: &Context<E>, isec: usize) -> Vec<Option<SymbolId>
(slots.into_iter())
.map(|rel| {
let RelocTarget::Sym(idx) = rel?.target() else { return None };
Some(ctx.objs[obj].symbols[idx as usize]).filter(|&id| ctx.binds_at_runtime(id))
Some(ctx.objs[obj].symbols[idx as usize])
.filter(|&id| ctx.symbols[id].binds_at_runtime(ctx))
})
.collect()
}
+1 -1
View File
@@ -48,7 +48,7 @@ pub fn build<E: Target>(ctx: &Context<E>) -> (Vec<u8>, Vec<u32>) {
let flags = if sym.is_weak_ref() { BIND_SYMBOL_FLAGS_WEAK_IMPORT } else { 0 };
let ops = [
Op::SegOffset(seg, off),
Op::Dylib(ctx.sym_bind_ordinal(id)),
Op::Dylib(sym.bind_ordinal(ctx)),
Op::Symbol(sym.name(), flags),
Op::Bind,
];
+3 -3
View File
@@ -1006,10 +1006,10 @@ fn mach_header_flags<E: Target>(ctx: &Context<E>) -> u32 {
// ld-prime's eyes, referenced from within the image or not (a
// dylib whose only weak definition nothing calls still gets
// 0x118085), since another image's copy may replace it.
if (0..ctx.symbols.syms.len()).into_par_iter().any(|i| ctx.exports_weak_def(i as u32)) {
if ctx.symbols.syms.par_iter().any(|sym| sym.exports_weak_def(ctx)) {
flags |= MH_WEAK_DEFINES | MH_BINDS_TO_WEAK;
}
if (0..ctx.symbols.syms.len()).into_par_iter().any(|i| ctx.overrides_weak_export(i as u32)) {
if ctx.symbols.syms.par_iter().any(|sym| sym.overrides_weak_export(ctx)) {
flags |= MH_WEAK_DEFINES;
}
// -bind_at_load makes the stubs bind through the GOT instead of
@@ -1048,7 +1048,7 @@ fn binds_to_weak<E: Target>(ctx: &Context<E>) -> bool {
_ => false,
});
uses_weak_export
|| ctx.chained_fixups.imports.iter().any(|&(id, _)| ctx.binds_weak_lookup(id))
|| ctx.chained_fixups.imports.iter().any(|&(id, _)| ctx.symbols[id].binds_weak_lookup(ctx))
|| !ctx.weak_bind_info.contents.is_empty()
}
+5 -4
View File
@@ -57,10 +57,10 @@ pub fn rebase_locations<E: Target>(ctx: &Context<E>) -> Vec<u64> {
for (addr, rel) in pointer_relocs(ctx, isec) {
// Pointers to thread-local data are thread-pointer-relative
// offsets, not addresses, so they are not rebased.
let target = ctx.reloc_target_sym(isec.file as usize, rel);
let target = ctx.reloc_target_sym(isec.file as usize, rel).map(|id| &ctx.symbols[id]);
let imported =
target.is_some_and(|id| ctx.binds_pointer(id) || ctx.is_dtrace_pointer_target(id));
let absolute = target.is_some_and(|id| ctx.is_absolute_symbol(id));
target.is_some_and(|sym| sym.binds_pointer(ctx) || sym.is_dtrace_pointer_target());
let absolute = target.is_some_and(|sym| sym.is_absolute(ctx));
if !imported && !absolute && !ctx.reloc_target_is_tls(isec.file as usize, rel) {
locs.push(addr);
}
@@ -88,7 +88,8 @@ pub fn rebase_locations<E: Target>(ctx: &Context<E>) -> Vec<u64> {
// binds the others by name.)
if !ctx.args.legacy_linkedit {
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if !ctx.binds_as_import(id) && !ctx.is_absolute_symbol(id) {
let sym = &ctx.symbols[id];
if !sym.binds_as_import(ctx) && !sym.is_absolute(ctx) {
locs.push(ctx.got.slot_addr(i));
}
}
+5 -4
View File
@@ -307,12 +307,13 @@ impl<'a, E: Target> Places<'a, E> {
}
RelocTarget::Sym(idx) => ctx.objs[isec.file as usize].symbols[idx as usize],
};
if r.ty == E::RELOC_GOTPC || E::RELOC_GOT_LOADS.contains(&r.ty) && !ctx.can_relax_got(id) {
let sym = &ctx.symbols[id];
if r.ty == E::RELOC_GOTPC || E::RELOC_GOT_LOADS.contains(&r.ty) && !sym.can_relax_got(ctx) {
return Some(self.got_slot(id));
}
if r.is_func_call::<E>() {
if ctx.is_interposable(id)
&& let Some(stub) = ctx.symbols[id].stub_idx(&ctx.symbols)
if sym.is_interposable(ctx)
&& let Some(stub) = sym.stub_idx(&ctx.symbols)
{
return Some(self.chunk(ChunkId::Stubs, stubs::entry_offset::<E>(stub)));
}
@@ -404,7 +405,7 @@ impl<'a, E: Target> Places<'a, E> {
let has = |id| ctx.chunks.contains(&id);
if has(ChunkId::Stubs) {
for (i, &id) in ctx.stubs.symbols.iter().enumerate() {
let slot = if ctx.args.lazy_binding && !ctx.binds_weak_lookup(id) {
let slot = if ctx.args.lazy_binding && !ctx.symbols[id].binds_weak_lookup(ctx) {
let lazy = ctx.stubs.lazy.binary_search(&(i as u32)).unwrap();
self.chunk_addr(ChunkId::LazyPtrs, ctx.lazy_ptrs.slot_addr(lazy))
} else {
+1 -1
View File
@@ -49,7 +49,7 @@ pub fn add_symbol<E: Target>(ctx: &mut Context<E>, id: SymbolId) {
}
ctx.symbols.aux_mut(id).stub_idx = ctx.stubs.symbols.len() as u32;
ctx.stubs.symbols.push(id);
if ctx.args.lazy_binding && !ctx.binds_weak_lookup(id) {
if ctx.args.lazy_binding && !ctx.symbols[id].binds_weak_lookup(ctx) {
crate::chunks::stub_helper::ensure_stub_binder(ctx);
} else {
crate::chunks::got::add_got_symbol(ctx, id);
+7 -3
View File
@@ -42,7 +42,10 @@ pub fn build<E: Target>(ctx: &Context<E>) -> Vec<u8> {
// A -static image calls its weak definitions directly, but under
// -no_fixup_chains ld-prime still lists the slots holding their
// addresses, as the chains do.
let binds_weak = |id| ctx.binds_weak_lookup(id) || ctx.is_weak_coalesced(id);
let binds_weak = |id: crate::symbol::SymbolId| {
let sym = &ctx.symbols[id];
sym.binds_weak_lookup(ctx) || sym.is_weak_coalesced(ctx)
};
let mut binds: Vec<(crate::symbol::SymbolId, u64)> = Vec::new();
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
@@ -65,8 +68,9 @@ pub fn build<E: Target>(ctx: &Context<E>) -> Vec<u8> {
// Strong definitions overriding a dylib's weak export are listed
// first, by name, flagged non-weak, with no location: dyld then
// knows this image's copy wins coalescing.
let mut overrides: Vec<crate::symbol::SymbolId> =
(0..ctx.symbols.syms.len() as u32).filter(|&i| ctx.overrides_weak_export(i)).collect();
let mut overrides: Vec<crate::symbol::SymbolId> = (0..ctx.symbols.syms.len() as u32)
.filter(|&i| ctx.symbols[i].overrides_weak_export(ctx))
.collect();
if binds.is_empty() && overrides.is_empty() {
return Vec::new();
}
+7 -237
View File
@@ -589,48 +589,12 @@ impl<E: Target> Context<E> {
self.stubs.hdr.addr + crate::chunks::stubs::entry_offset::<E>(idx)
}
/// True for a symbol of a dylib whose initializers wait for the
/// image's first use of it (see delay_init::create_delay_init).
pub fn is_delay_import(&self, id: SymbolId) -> bool {
match self.symbols[id].file() {
Some(FileId::Dylib(d)) => d != u32::MAX && self.dylibs[d as usize].delay_init.is_some(),
_ => false,
}
}
/// True for a symbol of a dylib dyld loads lazily (see
/// lazy_load::create_lazy_loads).
pub fn is_lazy_import(&self, id: SymbolId) -> bool {
match self.symbols[id].file() {
Some(FileId::Dylib(d)) => d != u32::MAX && self.dylibs[d as usize].is_lazy,
_ => false,
}
}
/// True if the symbol resolves to a TLV descriptor: a definition in a
/// S_THREAD_LOCAL_VARIABLES section, or a dylib export listed as
/// thread-local - sold's Symbol::is_tlv. Symbols left to runtime
/// lookup pass as either.
pub fn is_tlv(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
match sym.file() {
Some(FileId::Obj(_)) => sym.input_section().is_some_and(|isec| {
self.hdr_of(&self.isecs[isec as usize]).section_type()
== crate::macho::S_THREAD_LOCAL_VARIABLES
}),
Some(FileId::Dylib(d)) => {
d != u32::MAX && self.dylibs[d as usize].tlv_exports.contains(sym.name())
}
_ => false,
}
}
/// The address of the pointer slot stub `i` (for symbol `id`)
/// jumps through: its lazy pointer, or its GOT slot. A weak
/// definition of this image always goes through its GOT slot (the
/// lazy binder cannot do weak lookup), as in ld64.
pub fn stub_ptr_addr(&self, i: usize, id: SymbolId) -> u64 {
if self.args.lazy_binding && !self.binds_weak_lookup(id) {
if self.args.lazy_binding && !self.symbols[id].binds_weak_lookup(self) {
let slot = self.stubs.lazy.binary_search(&(i as u32)).unwrap();
self.lazy_ptrs.slot_addr(slot)
} else {
@@ -638,100 +602,6 @@ impl<E: Target> Context<E> {
}
}
/// True for a weak definition of this image that dyld may replace
/// with another image's copy at load time: an exported (neither
/// private nor auto-hidden) weak definition from an object. ld64
/// routes every reference to such a symbol through a slot dyld
/// binds by weak lookup - a GOT entry, a stub, a data pointer -
/// so that C++'s one-definition rule holds across images (an
/// inline function's static local is one variable, not one per
/// dylib). In a relocatable output the references stay relocations.
/// Nor does another image's copy replace one of dyld's own: dyld
/// fixes itself up before it loads any image, and by rebases alone.
/// (ld-prime reaches them directly from code too, but binds a
/// pointer to one in data by weak lookup, a bind dyld could not
/// carry out.)
pub fn is_weak_coalesced(&self, id: SymbolId) -> bool {
if self.args.relocatable || self.args.is_dylinker() {
return false;
}
let sym = &self.symbols[id];
matches!(sym.file(), Some(FileId::Obj(_)))
&& sym.is_weak_def()
&& sym.is_extern()
&& !sym.is_private_extern()
}
/// True for a live weak definition the image exports, which another
/// image's copy may replace at load time (and so for which ld-prime
/// sets MH_WEAK_DEFINES): not an auto-hidden or private extern one,
/// nor one -dead_strip removed.
pub fn exports_weak_def(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
sym.is_weak_def()
&& sym.is_extern()
&& !sym.is_private_extern()
&& sym.input_section().is_some_and(|isec| self.isecs[isec as usize].is_alive())
}
/// True for a definition the image exports that dyld binds the
/// image's own references to by name, as it binds imports, so that
/// another image can interpose it: each export of a -flat_namespace
/// dylib or bundle, by flat lookup (an image loaded before it may),
/// and one -interposable or -interposable_list names in any image
/// dyld loads (but dyld), to the image itself. ld64 calls it through
/// a stub, loads it from a GOT slot and binds the pointers to it in
/// data (initializer and Objective-C metadata pointers too) instead
/// of rebasing them. ld-prime binds a weak definition so too,
/// besides by weak lookup (with chained fixups by weak lookup
/// alone). A -flat_namespace executable's references to its own
/// definitions stay direct - it comes first in the flat search
/// order anyway - as do dyld's (ld-prime crashes linking one) and
/// those to a sectionless symbol: an absolute one, or one that marks
/// the image's layout.
pub fn is_interposable_export(&self, id: SymbolId) -> bool {
let args = &self.args;
let sym = &self.symbols[id];
let flat = args.flat_namespace
&& matches!(args.output_type, crate::macho::MH_DYLIB | crate::macho::MH_BUNDLE);
let listed = !args.relocatable
&& !args.without_dyld()
&& !args.is_dylinker()
&& args.interposable.as_ref().is_some_and(|g| g.find(sym.name()) != -1);
(flat || listed)
&& matches!(sym.file(), Some(FileId::Obj(_)))
&& sym.input_section().is_some()
&& sym.is_extern()
&& !sym.is_private_extern()
}
/// True if dyld binds the slots referring to this symbol as it
/// binds an import's, by name from the bind stream: an import, or an
/// interposable export.
pub fn binds_as_import(&self, id: SymbolId) -> bool {
self.symbols[id].is_imported() || self.is_interposable_export(id)
}
/// True if dyld binds a pointer in data to this symbol rather than
/// sliding it: one to an import's (see binds_as_import), or, in
/// legacy LINKEDIT (Args::legacy_linkedit), to one of the image's
/// coalescable weak definitions, which an external relocation
/// binds by name. LC_DYLD_INFO slides that one and weak-binds it.
pub fn binds_pointer(&self, id: SymbolId) -> bool {
self.binds_as_import(id) || (self.args.legacy_linkedit && self.is_weak_coalesced(id))
}
/// The library ordinal a bind of this symbol names: its dylib's, or
/// for an interposable export, the flat lookup under
/// -flat_namespace, else the image itself.
pub fn sym_bind_ordinal(&self, id: SymbolId) -> i32 {
match self.symbols[id].file() {
Some(FileId::Dylib(dylib)) => self.bind_ordinal(dylib),
_ if self.is_dtrace_pointer_target(id) => crate::macho::BIND_SPECIAL_DYLIB_FLAT_LOOKUP,
_ => self.export_bind_ordinal(),
}
}
/// The library ordinal an interposable export binds with.
pub fn export_bind_ordinal(&self) -> i32 {
match self.args.flat_namespace {
@@ -740,112 +610,12 @@ impl<E: Target> Context<E> {
}
}
/// True for a definition of this image that dyld may replace with
/// another image's at load time, so that its references go through
/// slots dyld binds and its calls through its stub: a weak
/// definition subject to coalescing, or an interposable export.
pub fn is_interposable(&self, id: SymbolId) -> bool {
self.is_weak_coalesced(id) || self.is_interposable_export(id)
}
/// True for a DTrace symbol (see dtrace), never defined, which a
/// pointer in data binds by flat lookup, as ld-prime has it: no
/// import, it takes no stub, GOT slot or symbol table entry.
pub fn is_dtrace_pointer_target(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
sym.file().is_none() && crate::dtrace::is_dtrace_symbol(sym.name())
}
/// True if dyld fills the references to this symbol: an import, or
/// a definition it may interpose.
pub fn binds_at_runtime(&self, id: SymbolId) -> bool {
self.symbols[id].is_imported() || self.is_interposable(id)
}
/// An input's N_ABS definition has no section and never slides.
/// Sectionless symbols in the internal object instead describe the
/// image (its header and layout boundaries), so their values slide.
pub fn is_absolute_symbol(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
sym.input_section().is_none()
&& matches!(sym.file(), Some(FileId::Obj(obj)) if !self.is_internal(obj as usize))
}
/// A GOT load relaxes to a PC-relative address computation unless
/// dyld fills the slot - an import or an interposable export, or a
/// weak definition it binds by weak lookup, which a -static image's
/// code never is - or the target is an absolute constant: the
/// instruction slides but the value does not.
pub fn can_relax_got(&self, id: SymbolId) -> bool {
!self.binds_as_import(id) && !self.binds_weak_lookup(id) && !self.is_absolute_symbol(id)
}
/// True for a definition this image exports that some dylib in the
/// link exports as a weak definition: the program's own operator
/// new overriding libc++'s. dyld must let it win coalescing, so
/// the image is marked WEAK_DEFINES and, with classic dyld info,
/// the symbol is listed in the weak_bind stream as a non-weak
/// definition (ld64 does both).
pub fn overrides_weak_export(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
matches!(sym.file(), Some(FileId::Obj(_)))
&& sym.is_extern()
&& !sym.is_private_extern()
&& !sym.is_weak_def()
&& self.dylibs.iter().any(|d| d.weak_exports.contains(sym.name()))
}
/// True if dyld resolves this symbol by weak lookup - searching
/// every loaded image for the coalesced definition - rather than
/// in one dylib: a coalescable weak definition of this image, or
/// an import that its dylib exports as a weak definition (libc++'s
/// operator new and delete, which a program may override). ld64
/// binds both with library ordinal -3, never lazily, and lists
/// them in the classic weak_bind stream.
pub fn binds_weak_lookup(&self, id: SymbolId) -> bool {
// A static image has no dyld to perform runtime weak lookup, so a
// call to a weakly-defined symbol in the image binds directly.
// ld64 emits neither a stub nor a weak bind for it (the stock
// XNU kernel has no stubs and an empty weak bind table). Nor
// does a kext's but in the shared region, where it calls and
// takes its weak definitions through the GOT as ld-prime links
// an arm64 kext.
if self.args.static_link || (self.args.is_kext() && !self.args.shared_region) {
return false;
}
if self.is_weak_coalesced(id) {
return true;
}
let sym = &self.symbols[id];
match sym.file() {
Some(FileId::Dylib(d)) if d != u32::MAX => {
self.dylibs[d as usize].weak_exports.contains(sym.name())
}
_ => false,
}
}
/// True for an exported Objective-C class (or metaclass) of a dylib
/// bound for the shared region, whose pointers ld-prime writes as
/// binds to the image itself rather than rebases, with chained
/// fixups: the cache builder may redirect them to a class that
/// replaces this one.
pub fn binds_to_self(&self, id: SymbolId) -> bool {
let sym = &self.symbols[id];
self.args.shared_region
&& self.args.output_type == crate::macho::MH_DYLIB
&& self.use_chained_fixups()
&& matches!(sym.file(), Some(FileId::Obj(_)))
&& sym.is_extern()
&& !sym.is_private_extern()
&& (sym.name().starts_with(b"_OBJC_CLASS_$_")
|| sym.name().starts_with(b"_OBJC_METACLASS_$_"))
}
/// The address a branch to `id` targets: the symbol's stub when it
/// has one and dyld may redirect it, else the symbol itself.
pub fn branch_target_addr(&self, id: SymbolId) -> u64 {
if self.is_interposable(id) && self.symbols[id].stub_idx(&self.symbols).is_some() {
if self.symbols[id].is_interposable(self)
&& self.symbols[id].stub_idx(&self.symbols).is_some()
{
self.sym_stub_addr(id)
} else {
self.sym_addr(id)
@@ -1009,9 +779,9 @@ impl<E: Target> Context<E> {
let file = self.isecs[isec].file as usize;
let rel = &rels[i];
let slides = self.args.pie || self.args.output_type != crate::macho::MH_EXECUTE;
let needs_fixup = match self.reloc_target_sym(file, rel) {
Some(id) if self.binds_at_runtime(id) || self.binds_to_self(id) => true,
Some(id) if self.is_absolute_symbol(id) => false,
let needs_fixup = match self.reloc_target_sym(file, rel).map(|id| &self.symbols[id]) {
Some(sym) if sym.binds_at_runtime(self) || sym.binds_to_self(self) => true,
Some(sym) if sym.is_absolute(self) => false,
_ => slides && !self.reloc_target_is_tls(file, rel),
};
if needs_fixup {
+1 -1
View File
@@ -53,7 +53,7 @@ pub fn create_delay_init<E: Target>(ctx: &mut Context<E>) {
/// among them an __objc_classrefs slot of a class, which only a link
/// below macOS 15 keeps (see objc::fold_objc_classrefs).
fn delay_uses<E: Target>(ctx: &Context<E>) -> Vec<LazyUseSite> {
let uses = import_uses(ctx, |id| ctx.is_delay_import(id));
let uses = import_uses(ctx, |id| ctx.symbols[id].is_delay_import(ctx));
for &(isec, _, id, how) in &uses {
if how == LazyRef::Unsupported {
let (sym, subsec) = (&ctx.symbols[id], ctx.subsec_name(isec as usize));
+5 -6
View File
@@ -25,9 +25,8 @@ pub fn bind_dyld_lazy_load<E: Target>(ctx: &mut Context<E>) {
return;
}
let ctx_ref: &Context<E> = ctx;
let uses_lazy = (0..ctx.symbols.syms.len() as SymbolId)
.into_par_iter()
.any(|id| ctx_ref.symbols[id].is_used() && ctx_ref.is_lazy_import(id));
let uses_lazy =
ctx_ref.symbols.syms.par_iter().any(|sym| sym.is_used() && sym.is_lazy_import(ctx_ref));
if uses_lazy && let Some(id) = ctx.bind_linker_import(b"__dyld_lazy_load") {
ctx.symbols[id].set_is_used(true);
}
@@ -102,7 +101,7 @@ pub(crate) fn import_uses<E: Target>(
/// The references to lazy dylibs' symbols from live subsections, in
/// input order, once the ones ld-prime refuses are reported.
fn lazy_uses<E: Target>(ctx: &Context<E>) -> Vec<LazyUseSite> {
let uses = import_uses(ctx, |id| ctx.is_lazy_import(id));
let uses = import_uses(ctx, |id| ctx.symbols[id].is_lazy_import(ctx));
for &(isec, _, id, how) in &uses {
if how == LazyRef::Unsupported {
let sym = &ctx.symbols[id];
@@ -123,8 +122,8 @@ fn lazy_uses<E: Target>(ctx: &Context<E>) -> Vec<LazyUseSite> {
// launch, and is refused alike. (ld-prime leaves a personality's
// slot zero.)
for &id in ctx.stubs.symbols.iter().chain(&ctx.got.got_syms) {
if ctx.is_lazy_import(id) {
let sym = &ctx.symbols[id];
let sym = &ctx.symbols[id];
if sym.is_lazy_import(ctx) {
crate::error!("use of '{sym}' in 'anon' cannot be lazy loaded.");
}
}
+4 -3
View File
@@ -508,10 +508,11 @@ pub fn drop_dead_objc_stubs<E: Target>(ctx: &mut Context<E>) {
/// (passes::scan_relocations makes the slot or the stub.)
pub fn scan_objc_stubs<E: Target>(ctx: &mut Context<E>) {
if let Some(id) = ctx.objc_stubs.msgsend_sym {
let sym = &ctx.symbols[id];
if !ctx.args.objc_stubs_small {
ctx.symbols[id].add_flags(NEEDS_GOT);
} else if ctx.binds_as_import(id) || ctx.binds_weak_lookup(id) {
ctx.symbols[id].add_flags(NEEDS_STUB);
sym.add_flags(NEEDS_GOT);
} else if sym.binds_as_import(ctx) || sym.binds_weak_lookup(ctx) {
sym.add_flags(NEEDS_STUB);
}
}
+7 -6
View File
@@ -1856,11 +1856,12 @@ pub fn check_weak_exports<E: Target>(ctx: &Context<E>) {
let mut found: Vec<(&[u8], bool)> = (0..ctx.symbols.syms.len() as u32)
.into_par_iter()
.filter_map(|id| {
let defined_here = matches!(ctx.symbols[id].file(), Some(FileId::Obj(_)));
if defined_here && ctx.exports_weak_def(id) {
Some((ctx.symbols[id].name(), false))
} else if ctx.overrides_weak_export(id) {
Some((ctx.symbols[id].name(), true))
let sym = &ctx.symbols[id];
let defined_here = matches!(sym.file(), Some(FileId::Obj(_)));
if defined_here && sym.exports_weak_def(ctx) {
Some((sym.name(), false))
} else if sym.overrides_weak_export(ctx) {
Some((sym.name(), true))
} else {
None
}
@@ -2675,7 +2676,7 @@ pub fn scan_relocations<E: Target>(ctx: &mut Context<E>) {
/// after it make.
pub fn finish_stubs<E: Target>(ctx: &mut Context<E>) {
let stubs = &ctx.stubs.symbols;
let lazy = |id| !ctx.binds_weak_lookup(id);
let lazy = |id: SymbolId| !ctx.symbols[id].binds_weak_lookup(ctx);
let lazy_stubs: Vec<u32> = match ctx.args.lazy_binding {
true => (0..stubs.len() as u32).filter(|&i| lazy(stubs[i as usize])).collect(),
false => Vec::new(),
+223
View File
@@ -348,6 +348,229 @@ impl Symbol {
self.got_idx(symbols).is_some()
}
/// True for a symbol of a dylib whose initializers wait for the
/// image's first use of it (see delay_init::create_delay_init).
pub fn is_delay_import<E: Target>(&self, ctx: &Context<E>) -> bool {
match self.file() {
Some(FileId::Dylib(d)) => d != u32::MAX && ctx.dylibs[d as usize].delay_init.is_some(),
_ => false,
}
}
/// True for a symbol of a dylib dyld loads lazily (see
/// lazy_load::create_lazy_loads).
pub fn is_lazy_import<E: Target>(&self, ctx: &Context<E>) -> bool {
match self.file() {
Some(FileId::Dylib(d)) => d != u32::MAX && ctx.dylibs[d as usize].is_lazy,
_ => false,
}
}
/// True if the symbol resolves to a TLV descriptor: a definition in a
/// S_THREAD_LOCAL_VARIABLES section, or a dylib export listed as
/// thread-local - sold's Symbol::is_tlv. Symbols left to runtime
/// lookup pass as either.
pub fn is_tlv<E: Target>(&self, ctx: &Context<E>) -> bool {
match self.file() {
Some(FileId::Obj(_)) => self.input_section().is_some_and(|isec| {
ctx.hdr_of(&ctx.isecs[isec as usize]).section_type()
== crate::macho::S_THREAD_LOCAL_VARIABLES
}),
Some(FileId::Dylib(d)) => {
d != u32::MAX && ctx.dylibs[d as usize].tlv_exports.contains(self.name())
}
_ => false,
}
}
/// True for a weak definition of this image that dyld may replace
/// with another image's copy at load time: an exported (neither
/// private nor auto-hidden) weak definition from an object. ld64
/// routes every reference to such a symbol through a slot dyld
/// binds by weak lookup - a GOT entry, a stub, a data pointer -
/// so that C++'s one-definition rule holds across images (an
/// inline function's static local is one variable, not one per
/// dylib). In a relocatable output the references stay relocations.
/// Nor does another image's copy replace one of dyld's own: dyld
/// fixes itself up before it loads any image, and by rebases alone.
/// (ld-prime reaches them directly from code too, but binds a
/// pointer to one in data by weak lookup, a bind dyld could not
/// carry out.)
pub fn is_weak_coalesced<E: Target>(&self, ctx: &Context<E>) -> bool {
if ctx.args.relocatable || ctx.args.is_dylinker() {
return false;
}
matches!(self.file(), Some(FileId::Obj(_)))
&& self.is_weak_def()
&& self.is_extern()
&& !self.is_private_extern()
}
/// True for a live weak definition the image exports, which another
/// image's copy may replace at load time (and so for which ld-prime
/// sets MH_WEAK_DEFINES): not an auto-hidden or private extern one,
/// nor one -dead_strip removed.
pub fn exports_weak_def<E: Target>(&self, ctx: &Context<E>) -> bool {
self.is_weak_def()
&& self.is_extern()
&& !self.is_private_extern()
&& self.input_section().is_some_and(|isec| ctx.isecs[isec as usize].is_alive())
}
/// True for a definition the image exports that dyld binds the
/// image's own references to by name, as it binds imports, so that
/// another image can interpose it: each export of a -flat_namespace
/// dylib or bundle, by flat lookup (an image loaded before it may),
/// and one -interposable or -interposable_list names in any image
/// dyld loads (but dyld), to the image itself. ld64 calls it through
/// a stub, loads it from a GOT slot and binds the pointers to it in
/// data (initializer and Objective-C metadata pointers too) instead
/// of rebasing them. ld-prime binds a weak definition so too,
/// besides by weak lookup (with chained fixups by weak lookup
/// alone). A -flat_namespace executable's references to its own
/// definitions stay direct - it comes first in the flat search
/// order anyway - as do dyld's (ld-prime crashes linking one) and
/// those to a sectionless symbol: an absolute one, or one that marks
/// the image's layout.
pub fn is_interposable_export<E: Target>(&self, ctx: &Context<E>) -> bool {
let args = &ctx.args;
let flat = args.flat_namespace
&& matches!(args.output_type, crate::macho::MH_DYLIB | crate::macho::MH_BUNDLE);
let listed = !args.relocatable
&& !args.without_dyld()
&& !args.is_dylinker()
&& args.interposable.as_ref().is_some_and(|g| g.find(self.name()) != -1);
(flat || listed)
&& matches!(self.file(), Some(FileId::Obj(_)))
&& self.input_section().is_some()
&& self.is_extern()
&& !self.is_private_extern()
}
/// True if dyld binds the slots referring to this symbol as it
/// binds an import's, by name from the bind stream: an import, or an
/// interposable export.
pub fn binds_as_import<E: Target>(&self, ctx: &Context<E>) -> bool {
self.is_imported() || self.is_interposable_export(ctx)
}
/// True if dyld binds a pointer in data to this symbol rather than
/// sliding it: one to an import's (see binds_as_import), or, in
/// legacy LINKEDIT (Args::legacy_linkedit), to one of the image's
/// coalescable weak definitions, which an external relocation
/// binds by name. LC_DYLD_INFO slides that one and weak-binds it.
pub fn binds_pointer<E: Target>(&self, ctx: &Context<E>) -> bool {
self.binds_as_import(ctx) || (ctx.args.legacy_linkedit && self.is_weak_coalesced(ctx))
}
/// The library ordinal a bind of this symbol names: its dylib's, or
/// for an interposable export, the flat lookup under
/// -flat_namespace, else the image itself.
pub fn bind_ordinal<E: Target>(&self, ctx: &Context<E>) -> i32 {
match self.file() {
Some(FileId::Dylib(dylib)) => ctx.bind_ordinal(dylib),
_ if self.is_dtrace_pointer_target() => crate::macho::BIND_SPECIAL_DYLIB_FLAT_LOOKUP,
_ => ctx.export_bind_ordinal(),
}
}
/// True for a definition of this image that dyld may replace with
/// another image's at load time, so that its references go through
/// slots dyld binds and its calls through its stub: a weak
/// definition subject to coalescing, or an interposable export.
pub fn is_interposable<E: Target>(&self, ctx: &Context<E>) -> bool {
self.is_weak_coalesced(ctx) || self.is_interposable_export(ctx)
}
/// True for a DTrace symbol (see dtrace), never defined, which a
/// pointer in data binds by flat lookup, as ld-prime has it: no
/// import, it takes no stub, GOT slot or symbol table entry.
pub fn is_dtrace_pointer_target(&self) -> bool {
self.file().is_none() && crate::dtrace::is_dtrace_symbol(self.name())
}
/// True if dyld fills the references to this symbol: an import, or
/// a definition it may interpose.
pub fn binds_at_runtime<E: Target>(&self, ctx: &Context<E>) -> bool {
self.is_imported() || self.is_interposable(ctx)
}
/// An input's N_ABS definition has no section and never slides.
/// Sectionless symbols in the internal object instead describe the
/// image (its header and layout boundaries), so their values slide.
pub fn is_absolute<E: Target>(&self, ctx: &Context<E>) -> bool {
self.input_section().is_none()
&& matches!(self.file(), Some(FileId::Obj(obj)) if !ctx.is_internal(obj as usize))
}
/// A GOT load relaxes to a PC-relative address computation unless
/// dyld fills the slot - an import or an interposable export, or a
/// weak definition it binds by weak lookup, which a -static image's
/// code never is - or the target is an absolute constant: the
/// instruction slides but the value does not.
pub fn can_relax_got<E: Target>(&self, ctx: &Context<E>) -> bool {
!self.binds_as_import(ctx) && !self.binds_weak_lookup(ctx) && !self.is_absolute(ctx)
}
/// True for a definition this image exports that some dylib in the
/// link exports as a weak definition: the program's own operator
/// new overriding libc++'s. dyld must let it win coalescing, so
/// the image is marked WEAK_DEFINES and, with classic dyld info,
/// the symbol is listed in the weak_bind stream as a non-weak
/// definition (ld64 does both).
pub fn overrides_weak_export<E: Target>(&self, ctx: &Context<E>) -> bool {
matches!(self.file(), Some(FileId::Obj(_)))
&& self.is_extern()
&& !self.is_private_extern()
&& !self.is_weak_def()
&& ctx.dylibs.iter().any(|d| d.weak_exports.contains(self.name()))
}
/// True if dyld resolves this symbol by weak lookup - searching
/// every loaded image for the coalesced definition - rather than
/// in one dylib: a coalescable weak definition of this image, or
/// an import that its dylib exports as a weak definition (libc++'s
/// operator new and delete, which a program may override). ld64
/// binds both with library ordinal -3, never lazily, and lists
/// them in the classic weak_bind stream.
pub fn binds_weak_lookup<E: Target>(&self, ctx: &Context<E>) -> bool {
// A static image has no dyld to perform runtime weak lookup, so a
// call to a weakly-defined symbol in the image binds directly.
// ld64 emits neither a stub nor a weak bind for it (the stock
// XNU kernel has no stubs and an empty weak bind table). Nor
// does a kext's but in the shared region, where it calls and
// takes its weak definitions through the GOT as ld-prime links
// an arm64 kext.
if ctx.args.static_link || (ctx.args.is_kext() && !ctx.args.shared_region) {
return false;
}
if self.is_weak_coalesced(ctx) {
return true;
}
match self.file() {
Some(FileId::Dylib(d)) if d != u32::MAX => {
ctx.dylibs[d as usize].weak_exports.contains(self.name())
}
_ => false,
}
}
/// True for an exported Objective-C class (or metaclass) of a dylib
/// bound for the shared region, whose pointers ld-prime writes as
/// binds to the image itself rather than rebases, with chained
/// fixups: the cache builder may redirect them to a class that
/// replaces this one.
pub fn binds_to_self<E: Target>(&self, ctx: &Context<E>) -> bool {
ctx.args.shared_region
&& ctx.args.output_type == crate::macho::MH_DYLIB
&& ctx.use_chained_fixups()
&& matches!(self.file(), Some(FileId::Obj(_)))
&& self.is_extern()
&& !self.is_private_extern()
&& (self.name().starts_with(b"_OBJC_CLASS_$_")
|| self.name().starts_with(b"_OBJC_METACLASS_$_"))
}
/// The address of a thunk entry for the symbol that a branch at `pc`
/// can reach, if it has one - mold's thunk_addr.
#[inline]
+1 -1
View File
@@ -392,7 +392,7 @@ fn needs_thunk<E: Target>(ctx: &Context<E>, reach: &Reach, p: u64, id: SymbolId)
let sym = &ctx.symbols[id];
let stub = sym.stub_idx(&ctx.symbols).is_some();
let side = match sym.file() {
_ if stub && ctx.is_interposable(id) => reach.stubs,
_ if stub && sym.is_interposable(ctx) => reach.stubs,
Some(FileId::Dylib(_)) if stub => reach.stubs,
Some(FileId::Obj(_)) => match sym.input_section() {
Some(target) => {