mirror of
https://github.com/rui314/mold.git
synced 2026-11-07 09:16:04 -10:00
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:
1 parent
a3f8b6275b
commit
ce2f56bec8
22 files changed
+328
-313
No files matched your search
+12
-10
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) => {
|
||||
|
||||
Reference in new issue
Block a user