mold-macho is a Mach-O linker for macOS, written in Rust as a port of
mold. Its tree already follows mold's layout (macho/, arch/macho-*,
tests/macho/), so this merge keeps its history and commit hashes as
they are.

Where both trees have a file, this merge keeps mold's. The Mach-O
crates join the workspace, but the mold executable doesn't use them
yet; the following commits wire them in.
This commit is contained in:
Rui Ueyama committed 2026-10-09 16:50:20 +09:00
commit 5d05ee37c7
768 files changed
+83137 -1

No files matched your search

+53
View File
@@ -0,0 +1,53 @@
name: Mach-O CI
on:
push:
branches: [main]
pull_request:
# A newer push to the same branch supersedes a run still in progress.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
jobs:
test:
# The tests drive Apple's toolchain (cc, nm, otool, dyld_info, lldb),
# so they need a macOS host. An arm64 runner covers both targets:
# the harness runs every test natively and again under Rosetta.
runs-on: macos-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
- name: Install Rosetta 2
run: |
if ! arch -x86_64 /usr/bin/true 2>/dev/null; then
sudo softwareupdate --install-rosetta --agree-to-license
fi
- name: Update Rust
run: rustup update --no-self-update stable && rustup default stable
- uses: Swatinem/rust-cache@v2
# The unit tests, then the shell tests with twice the harness's
# default timeout, as mold's CI gives its slow configurations more:
# here the simulator's tests are slow, lldb's above all, whose lldb
# session alone takes ten seconds on a Mac, and the first of which
# ran past a minute on the hosted runners.
- run: cargo test --workspace --lib --bins
- run: cargo test -p mold-macho-cli --test integration -- --timeout 120
# The harness reports only which tests failed; each test's log
# and its output directory are kept under the target directory.
- name: Upload test logs
if: failure()
uses: actions/upload-artifact@v7
with:
name: test-logs
path: target/debug/mold-test/out/test/
if-no-files-found: ignore
+2
View File
@@ -1,6 +1,8 @@
/target
/out
/dist
/bench
/.claude
/.codex
*~
__pycache__/
Generated
+146
View File
@@ -27,6 +27,15 @@ dependencies = [
"cpufeatures",
]
[[package]]
name = "block-buffer"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
dependencies = [
"hybrid-array",
]
[[package]]
name = "bstr"
version = "1.13.1"
@@ -56,6 +65,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "const-oid"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
[[package]]
name = "constant_time_eq"
version = "0.4.2"
@@ -114,6 +129,26 @@ version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crypto-common"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
dependencies = [
"hybrid-array",
]
[[package]]
name = "digest"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
dependencies = [
"block-buffer",
"const-oid",
"crypto-common",
]
[[package]]
name = "either"
version = "1.18.0"
@@ -169,6 +204,21 @@ dependencies = [
"foldhash",
]
[[package]]
name = "hybrid-array"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
dependencies = [
"typenum",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "jobserver"
version = "0.1.35"
@@ -423,6 +473,46 @@ dependencies = [
"mold-elf",
]
[[package]]
name = "mold-macho"
version = "3.0.0"
dependencies = [
"cpp_demangle",
"getrandom",
"hashbrown",
"libc",
"memchr",
"memmap2",
"portable-atomic",
"rayon",
"serde_json",
"sha1",
"sha2",
"xxhash-rust",
]
[[package]]
name = "mold-macho-arm64"
version = "3.0.0"
dependencies = [
"mold-macho",
]
[[package]]
name = "mold-macho-tests"
version = "3.0.0"
dependencies = [
"libc",
"serde_json",
]
[[package]]
name = "mold-macho-x86_64"
version = "3.0.0"
dependencies = [
"mold-macho",
]
[[package]]
name = "mold-mimalloc"
version = "0.1.52"
@@ -509,6 +599,15 @@ version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
]
[[package]]
name = "serde_core"
version = "1.0.229"
@@ -529,6 +628,41 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "sha1"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "shlex"
version = "2.0.1"
@@ -546,6 +680,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -573,6 +713,12 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
[[package]]
name = "zstd"
version = "0.13.3"
+4 -1
View File
@@ -4,7 +4,7 @@
# arch/, and the executable in cli/ dispatches to them.
[workspace]
resolver = "3"
members = ["cli", "common", "elf", "arch/*", "tests/elf"]
members = ["cli", "common", "elf", "macho", "arch/*", "tests/elf", "tests/macho"]
[workspace.package]
version = "3.0.0"
@@ -40,6 +40,9 @@ mold-elf-sh4 = { path = "arch/elf-sh4", version = "=3.0.0" }
mold-elf-sh4be = { path = "arch/elf-sh4be", version = "=3.0.0" }
mold-elf-loongarch64 = { path = "arch/elf-loongarch64", version = "=3.0.0" }
mold-elf-loongarch32 = { path = "arch/elf-loongarch32", version = "=3.0.0" }
mold-macho = { path = "macho", version = "=3.0.0" }
mold-macho-arm64 = { path = "arch/macho-arm64", version = "=3.0.0" }
mold-macho-x86_64 = { path = "arch/macho-x86_64", version = "=3.0.0" }
[profile.release]
panic = "abort"
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-macho-arm64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for arm64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-macho.workspace = true
+8
View File
@@ -0,0 +1,8 @@
//! The linker instantiated for arm64. Every target has a crate like this
//! one, so that the compiler can build the targets in parallel.
/// Links for this target, or reports the target the inputs are actually
/// for.
pub fn link(cmdline: mold_macho::driver::Cmdline) -> mold_macho::driver::LinkResult {
mold_macho::driver::link::<mold_macho::arch::Arm64>(cmdline)
}
+14
View File
@@ -0,0 +1,14 @@
[package]
name = "mold-macho-x86_64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which instantiates its Mach-O linker for x86_64"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold-macho.workspace = true
+8
View File
@@ -0,0 +1,8 @@
//! The linker instantiated for x86_64. Every target has a crate like this
//! one, so that the compiler can build the targets in parallel.
/// Links for this target, or reports the target the inputs are actually
/// for.
pub fn link(cmdline: mold_macho::driver::Cmdline) -> mold_macho::driver::LinkResult {
mold_macho::driver::link::<mold_macho::arch::X86_64>(cmdline)
}
Executable
+26
View File
@@ -0,0 +1,26 @@
#!/bin/sh
# Installs the release build as $PREFIX/bin/mold and creates the
# ld64.mold symlink next to it, the name to give clang's --ld-path or
# swiftc's -use-ld. Mirrors mold's install-mold.sh, where the
# symlink is ld.mold.
set -e
PREFIX=${PREFIX:-/usr/local}
srcdir=$(CDPATH= cd "$(dirname "$0")" && pwd)
artifact_dir="${CARGO_TARGET_DIR:-$srcdir/target}/release"
if [ ! -x "$artifact_dir/mold" ]; then
echo "install.sh: release artifacts are missing" >&2
echo "Run 'cargo build --release' first." >&2
exit 1
fi
bindir="$DESTDIR$PREFIX/bin"
docdir="$DESTDIR$PREFIX/share/doc/mold-macho"
install -d "$bindir" "$docdir"
install -m 755 "$artifact_dir/mold" "$bindir"
install -m 644 "$srcdir/LICENSE" "$docdir"
ln -sf mold "$bindir/ld64.mold"
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "mold-macho"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "An internal crate of the mold linker, which implements its Mach-O linker"
license.workspace = true
repository.workspace = true
[lib]
path = "lib.rs"
[dependencies]
cpp_demangle = "0.5.1"
getrandom = "0.4.3"
hashbrown = "0.17"
libc.workspace = true
memchr = "2.8.3"
memmap2 = "0.9"
# std has no 64-bit atomics on hosts such as 32-bit PowerPC.
portable-atomic = "1"
rayon = "1.12.0"
serde_json = "1.0.151"
sha1 = "0.11"
sha2 = "0.11"
xxhash-rust = { version = "0.8", features = ["xxh3"] }
+7
View File
@@ -0,0 +1,7 @@
# Targets
This directory contains one file per instruction set. Each file defines a
type for the target it supports, and the types implement `Target` from
[`mod.rs`](mod.rs), which holds the constants and code that differ between
targets. The rest of the linker is generic over it. The crates under
[`arch/`](../../arch/) instantiate the linker for each type.
+1474
View File
File diff suppressed because it is too large. Load diff
+333
View File
@@ -0,0 +1,333 @@
//! Target descriptions.
//!
//! Every target is a zero-sized marker type implementing [`Target`], which
//! selects a CPU type and carries the constants and code that differ
//! between targets: the stub and thunk shapes, and the target-dependent
//! relocation handling. The rest of the linker is generic over it. Each
//! target is instantiated in a crate of its own under arch/.
mod arm64;
mod x86_64;
pub use arm64::Arm64;
pub use x86_64::X86_64;
use std::path::Path;
use crate::context::Context;
use crate::error::RawPath;
use crate::input_sections::{InputSection, Reloc};
use crate::macho::{MachRel, MachSection};
/// How LC_SEGMENT_SPLIT_INFO records a reference a relocation type
/// makes, when it crosses sections.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SplitRef {
/// An absolute address in data, recorded wherever it points.
Pointer,
/// The first of a SUBTRACTOR/UNSIGNED pair: a distance, recorded
/// wherever it points.
Subtractor,
/// arm64's adrp and the ldr or add under it, and its 26-bit
/// branch: recorded only when they reach another section.
Page,
PageOff,
Branch26,
/// A 32-bit PC-relative displacement: recorded when it reaches
/// another section.
PcRel32,
}
/// How a relocation reaches a symbol of a dylib dyld loads lazily, or
/// initializes at its first use (see lazy_load::create_lazy_loads and
/// delay_init::create_delay_init).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LazyRef {
/// A branch: to the symbol's call helper or stub.
Call,
/// The instruction that computes a GOT slot's address for a load
/// (arm64's adrp, x86-64's movq): it calls a load helper instead.
Load,
/// The instruction that loads from the slot the adrp above found
/// (arm64's ldr): from the symbol's __lazy_load_got slot, or a
/// delay-init dylib's symbol's __got slot as ever.
Slot,
/// x86-64's cmpq $0 of the GOT slot: it calls a compare helper
/// instead.
Cmp,
/// A reference that can't be made lazy or delayed: a pointer or a
/// difference, which dyld would have to fill in at launch, or a
/// form of instruction no helper stands in for.
Unsupported,
}
/// One form of a relocation record - its pcrel, p2size (log2 of the
/// field's size) and extern fields - as a bit in a set of the forms a
/// type takes. The fields are bits 24 to 27 of the record's second
/// word, which select the bit.
pub const fn reloc_form(pcrel: bool, p2size: u32, ext: bool) -> u16 {
1 << (pcrel as u32 | p2size << 1 | (ext as u32) << 3)
}
/// Whether a record's form is one of `forms`, a set of reloc_form bits.
/// A target lists each type's set in a match, which compiles to a
/// table: a lookup is much cheaper than testing the fields one by one.
#[inline]
pub fn has_reloc_form(r: &MachRel, forms: u16) -> bool {
forms >> ((r.bits >> 24) & 0xf) & 1 != 0
}
/// Reports relocation record `r` of section `hdr` of object `file`,
/// which the linker can't apply for the reason `what`.
#[cold]
#[inline(never)]
pub fn bad_reloc(file: &Path, hdr: &MachSection, r: &MachRel, what: &str) -> ! {
crate::fatal!(
"{}:({},{}): {what} at 0x{:x}: type={}, p2size={}, pcrel={}, extern={}",
file.raw(),
crate::error::raw(hdr.segname()),
crate::error::raw(hdr.sectname()),
r.offset,
r.ty(),
r.p2size(),
r.is_pcrel() as u8,
r.is_extern() as u8
)
}
pub trait Target: Copy + Default + Send + Sync + 'static {
const NAME: &'static str;
const CPUTYPE: u32;
const CPUSUBTYPE: u32;
const PAGE_SIZE: u64;
/// The size of one __stubs entry.
const STUB_SIZE: u64;
/// The size of the __stub_helper header (the common code that
/// enters dyld_stub_binder), and the distance from one stub's entry
/// to the next.
const STUB_HELPER_HEADER_SIZE: u64;
const STUB_HELPER_ENTRY_SIZE: u64;
/// The compact unwind encoding mode meaning "use DWARF instead".
const UNWIND_MODE_DWARF: u32;
/// The size of one __objc_stubs entry, and of one under
/// -objc_stubs_small (see Args::objc_stubs_small).
const OBJC_STUB_SIZE: u64;
const OBJC_SMALL_STUB_SIZE: u64;
/// The alignment of __lazy_helpers, and whether a symbol's call
/// helper goes through a __lazy_load_got slot of its own, apart
/// from the one its GOT loads read (ld-prime's x86-64 ones do).
const LAZY_HELPERS_P2ALIGN: u32;
const LAZY_CALL_OWN_SLOT: bool;
/// The size of a __delay_stubs entry and of a dlopen helper, and the
/// alignment of __delay_stubs and __delay_helper.
const DELAY_STUB_SIZE: u64;
const DLOPEN_HELPER_SIZE: u32;
const DELAY_P2ALIGN: u32;
/// The span a branch instruction can cover (both directions
/// together), and the size of one range-extension thunk entry.
const BRANCH_RANGE: u64;
const THUNK_SIZE: u64;
/// The relocation types for a plain absolute word, a subtraction
/// pair, and a GOT-relative pointer.
const RELOC_UNSIGNED: u8;
const RELOC_SUBTRACTOR: u8;
const RELOC_GOTPC: u8;
/// The branch relocation type: a call or a jump, which goes through
/// the target's stub if it has one - mold's R_FUNCALL.
const RELOC_BRANCH: u8;
/// 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
/// Symbol::can_relax_got).
const RELOC_GOT_LOADS: &'static [u8];
/// The explicit-addend relocation type, for targets that have one.
const RELOC_ADDEND: u8;
/// Where the linker's own code materializes an address
/// PC-relatively, for LC_SEGMENT_SPLIT_INFO: the split-info kinds
/// of one such address (arm64's adrp, then the ldr or add 4 bytes
/// on; x86-64's 32-bit displacement), and where it sits in a
/// __stubs entry (its pointer slot), the __stub_helper header
/// (__dyld_private, then dyld_stub_binder's GOT slot) and an
/// __objc_stubs entry (its selector reference, then
/// _objc_msgSend's GOT slot).
const SPLIT_PCREL_KINDS: &'static [u8];
const STUB_REF_OFF: u64;
const STUB_HELPER_REF_OFFS: [u64; 2];
const OBJC_STUB_REF_OFFS: [u64; 2];
/// The register state LC_UNIXTHREAD holds: its flavor, its size in
/// 32-bit words, and the byte offsets of the stack pointer and the
/// program counter in it.
const THREAD_STATE_FLAVOR: u32;
const THREAD_STATE_COUNT: u32;
const THREAD_STATE_SP_OFFSET: usize;
const THREAD_STATE_PC_OFFSET: usize;
/// Whether re-emitting this relocation type in a relocatable output
/// needs an explicit addend record when its addend is nonzero.
fn relocatable_needs_addend(ty: u8) -> bool;
/// The distance folded into a pcrel relocation's embedded addend
/// beyond the field itself (x86-64's SIGNED_1/2/4), which a field
/// written back for a relocatable output must leave out again.
fn reloc_bias(_ty: u8) -> i64 {
0
}
/// How LC_SEGMENT_SPLIT_INFO records a relocation type's reference.
fn split_ref(ty: u8) -> SplitRef;
/// The GOT-load type objc::fold_objc_classrefs rewrites a reference
/// to an __objc_classrefs slot into - a relocation of type `ty`
/// at `offset` of a subsection whose contents are `data` - if the
/// reference loads the slot's pointer (arm64's adrp, or the ldr
/// under it; x86-64's RIP-relative mov): the ordinary GOT-load
/// handling then loads the class from its GOT entry, or relaxes the
/// load of a class the image defines. None for a reference that
/// takes the slot's address.
fn got_load_form(ty: u8, data: &[u8], offset: u32) -> Option<u8>;
/// Which half of a two-instruction address or load a relocation
/// type is: Some(true) for the page (arm64's adrp), Some(false) for
/// the offset into it (the ldr or add that follows); None for one
/// that stands alone (x86-64's RIP-relative references).
fn page_pair_half(_ty: u8) -> Option<bool> {
None
}
/// Writes the __stubs section: for each symbol in `ctx.stubs.symbols`, a
/// jump through the symbol's __got slot. `addr` is the section's
/// address and `buf` its bytes in the output.
fn write_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// Writes the __TEXT,__stub_helper section: the header that pushes
/// __dyld_private and jumps to dyld_stub_binder through the GOT,
/// then one entry per stub that loads the stub's lazy-bind record
/// offset and branches to the header. `addr` is the section's
/// address and `buf` its bytes.
fn write_stub_helper(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// Writes legacy LINKEDIT's __stub_helper section (see
/// Args::legacy_linkedit) instead, which has no header: one entry
/// per stub that enters crt1.o's dyld_stub_binding_helper with the
/// address of the stub's lazy pointer. Only an x86-64 image has
/// legacy LINKEDIT.
fn write_legacy_stub_helper(_ctx: &Context<Self>, _addr: u64, _buf: &mut [u8]) {
unreachable!("legacy LINKEDIT on {}", Self::NAME)
}
/// Writes the __objc_stubs section: for each _objc_msgSend$<sel>
/// symbol, code that loads the selector from its __objc_selrefs
/// slot and tail-calls _objc_msgSend through the GOT.
fn write_objc_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// Writes the __lazy_helpers section: each helper of
/// `ctx.lazy_helpers.helpers` (see chunks::lazy_helpers).
fn write_lazy_helpers(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// The size of a __lazy_helpers entry of a kind.
fn lazy_helper_size(kind: crate::chunks::lazy_helpers::LazyUse) -> u32;
/// Writes the __delay_stubs section: each stub of
/// `ctx.delay_init.stubs` (see chunks::delay_init).
fn write_delay_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// Writes the __delay_helper section: the helpers for GOT loads,
/// then the dlopen helpers (see chunks::delay_init).
fn write_delay_helper(ctx: &Context<Self>, addr: u64, buf: &mut [u8]);
/// The size of a __delay_helper entry for GOT loads of a kind.
fn delay_helper_size(kind: crate::chunks::delay_init::DelayUse) -> u32;
/// Where a delay-init stub or helper refers to what, for
/// LC_SEGMENT_SPLIT_INFO: offsets in it, with the split-info kind
/// of each reference.
fn delay_refs(
code: crate::chunks::delay_init::DelayCode,
) -> Vec<(u32, u8, crate::chunks::delay_init::DelayTarget)>;
/// Where a __lazy_helpers entry of a kind refers to what, for
/// LC_SEGMENT_SPLIT_INFO: offsets in the entry, with the split-info
/// kind of each reference.
fn lazy_helper_refs(
kind: crate::chunks::lazy_helpers::LazyUse,
) -> Vec<(u32, u8, crate::chunks::lazy_helpers::LazyTarget)>;
/// How relocation `r`, of a subsection whose contents are `data`,
/// reaches a symbol of a dylib dyld loads lazily, or a delay-init
/// dylib's.
fn lazy_ref(r: &Reloc, data: &[u8]) -> LazyRef;
/// For a GOT load of such a symbol, the instruction at `offset` of
/// the subsection whose contents are `data` (see LazyRef::Load):
/// the register it loads, and whether the helper it calls must be
/// its own (arm64's frameless code: see LazyUse::Load).
fn lazy_load_site(data: &[u8], offset: u32) -> (u8, bool);
/// The name of a register a load helper fills, as ld-prime puts it
/// in the helper's symbol.
fn lazy_register_name(reg: u8) -> String;
/// Writes one range-extension thunk's entries. `addr` is the
/// thunk's address and `buf` its bytes.
fn write_thunk(
ctx: &Context<Self>,
addr: u64,
syms: &[crate::symbol::SymbolId],
buf: &mut [u8],
);
/// Converts raw relocation records of one input section, `hdr`,
/// into [`Reloc`]s, failing the link on one it can't apply (see
/// bad_reloc). Mach-O encodes addends target-dependently: some are
/// embedded in the relocated field, some are separate records.
/// `contents` is the section's bytes.
fn read_relocs(
file_name: &Path,
sections: &[MachSection],
hdr: &MachSection,
contents: &[u8],
rels: &[MachRel],
) -> Vec<Reloc>;
/// Scans the relocations of one emitted subsection and sets the
/// NEEDS_* flags of the symbols that need stubs or GOT slots, which
/// passes::scan_relocations then creates. Runs on all cores.
fn scan_relocations(ctx: &Context<Self>, isec: &InputSection);
/// Whether a relocation has the linker write a pointer, which dyld may
/// slide or bind: an 8-byte absolute address, not a term of a
/// SUBTRACTOR pair's difference.
fn is_absrel(rel: &Reloc) -> bool {
rel.ty == Self::RELOC_UNSIGNED && rel.size == 8 && !rel.is_pcrel && !rel.is_subtracted
}
/// Applies the relocations of one input section to `buf`, its bytes
/// in the output. `isec` is the subsection's arena index and `base`
/// its output address.
fn apply_reloc_alloc(
ctx: &Context<Self>,
rels: &[Reloc],
isec: usize,
base: u64,
buf: &mut [u8],
);
/// Applies LC_LINKER_OPTIMIZATION_HINT rewrites after relocation.
/// Only arm64 defines hints; the default does nothing.
fn apply_optimization_hints(_ctx: &Context<Self>, _buf: &mut [u8]) {}
}
/// The canonical name of a target named on the command line, borrowed
/// from static storage so that a restart for that target can carry it.
pub fn canonical_name(name: &str) -> Option<&'static str> {
[Arm64::NAME, X86_64::NAME].into_iter().find(|&canonical| canonical == name)
}
/// Returns the target name for a Mach-O CPU type, if we know it.
pub fn cputype_name(cputype: u32) -> Option<&'static str> {
match cputype {
Arm64::CPUTYPE => Some(Arm64::NAME),
X86_64::CPUTYPE => Some(X86_64::NAME),
_ => None,
}
}
+783
View File
@@ -0,0 +1,783 @@
//! The x86-64 target.
use std::path::Path;
use crate::arch::{SplitRef, Target, has_reloc_form, reloc_form};
use crate::chunks::delay_init::{DelayCode, DelayTarget, DelayUse};
use crate::chunks::lazy_helpers::{LazyTarget, LazyUse};
use crate::chunks::{delay_init, lazy_helpers, objc_stubs, stub_helper, stubs};
use crate::context::Context;
use crate::dtrace::SiteKind;
use crate::input_files::{ObjectFile, section_target};
use crate::input_sections::{InputSection, Reloc, RelocTarget};
use crate::macho::*;
use crate::symbol::{NEEDS_GOT, NEEDS_STUB, SymbolId};
use crate::util::{write32, write64};
use crate::{error, fatal};
#[derive(Clone, Copy, Default)]
pub struct X86_64;
/// The start of a delay-init stub or load helper at `base`, which
/// makes sure the dylib of dlopen helper `dlopen` is initialized:
/// cmpl $0, flag(%rip); jne 1f; push %rbp; mov %rsp, %rbp; call the
/// dlopen helper; pop %rbp; 1:
fn write_delay_check(ctx: &Context<X86_64>, ent: &mut [u8], base: u64, dlopen: u32) {
let flag = ctx.isecs[ctx.delay_init.dlopens[dlopen as usize].flag as usize].addr(ctx);
let helper = ctx.delay_init.dlopen_helper_addr(dlopen as usize);
ent[..19].copy_from_slice(&[
0x83, 0x3d, 0, 0, 0, 0, 0, 0x75, 0x0a, 0x55, 0x48, 0x89, 0xe5, 0xe8, 0, 0, 0, 0, 0x5d,
]);
write32(&mut ent[2..], flag.wrapping_sub(base + 7) as u32);
write32(&mut ent[14..], helper.wrapping_sub(base + 18) as u32);
}
/// "movq disp32(%rip), %reg" up to its displacement: REX.W (and REX.R
/// for %r8-%r15), the opcode, and a ModRM of `reg` and RIP-relative.
fn movq_rip(reg: u8) -> [u8; 3] {
[0x48 | (reg >> 3) << 2, 0x8b, 0x05 | (reg & 7) << 3]
}
/// A dlopen helper, but for the displacements of the leaq of the
/// install name (at 68), the call of dlopen (75) and the xchgl of the
/// flag (86).
const DLOPEN_HELPER: [u8; 153] = [
0x55, // push %rbp
0x48, 0x89, 0xe5, // mov %rsp, %rbp
0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, // push %rax ... %r9
0x48, 0x83, 0xc4, 0x80, // add $-0x80, %rsp
0xf3, 0x0f, 0x7f, 0x04, 0x24, // movdqu %xmm0, (%rsp)
0xf3, 0x0f, 0x7f, 0x4c, 0x24, 0x10, // movdqu %xmm1, 0x10(%rsp)
0xf3, 0x0f, 0x7f, 0x54, 0x24, 0x20, 0xf3, 0x0f, 0x7f, 0x5c, 0x24, 0x30, 0xf3, 0x0f, 0x7f, 0x64,
0x24, 0x40, 0xf3, 0x0f, 0x7f, 0x6c, 0x24, 0x50, 0xf3, 0x0f, 0x7f, 0x74, 0x24, 0x60, 0xf3, 0x0f,
0x7f, 0x7c, 0x24, 0x70, // movdqu %xmm7, 0x70(%rsp)
0x48, 0x8d, 0x3d, 0, 0, 0, 0, // leaq name(%rip), %rdi
0x31, 0xf6, // xorl %esi, %esi
0xe8, 0, 0, 0, 0, // call dlopen
0xb8, 0x01, 0, 0, 0, // movl $1, %eax
0x87, 0x05, 0, 0, 0, 0, // xchgl %eax, flag(%rip)
0xf3, 0x0f, 0x6f, 0x04, 0x24, // movdqu (%rsp), %xmm0
0xf3, 0x0f, 0x6f, 0x4c, 0x24, 0x10, 0xf3, 0x0f, 0x6f, 0x54, 0x24, 0x20, 0xf3, 0x0f, 0x6f, 0x5c,
0x24, 0x30, 0xf3, 0x0f, 0x6f, 0x64, 0x24, 0x40, 0xf3, 0x0f, 0x6f, 0x6c, 0x24, 0x50, 0xf3, 0x0f,
0x6f, 0x74, 0x24, 0x60, 0xf3, 0x0f, 0x6f, 0x7c, 0x24, 0x70, // movdqu 0x70(%rsp), %xmm7
0x48, 0x83, 0xec, 0x80, // sub $-0x80, %rsp
0x41, 0x59, 0x41, 0x58, 0x5f, 0x5e, 0x5b, 0x5a, 0x59, 0x58, // pop %r9 ... %rax
0x5d, // pop %rbp
0xc3, // ret
];
/// What the call or jmp of a DTrace probe site becomes, from its opcode
/// on, if relocation `r` is one (see dtrace): a nop and a 4-byte nop,
/// or for an is-enabled test "xorl %eax, %eax" (its result false) and
/// nops.
fn dtrace_site_code(
ctx: &Context<X86_64>,
file: &ObjectFile,
r: &Reloc,
) -> Option<&'static [u8; 5]> {
let id = r.sym(file)?;
match crate::dtrace::site_kind(ctx, id)? {
SiteKind::Probe => Some(&[0x90, 0x0f, 0x1f, 0x40, 0x00]),
SiteKind::IsEnabled => Some(&[0x33, 0xc0, 0x90, 0x90, 0x90]),
}
}
/// The SIGNED_K relocation types describe a pcrel field followed by K
/// more instruction bytes; the extra distance is folded into the addend
/// when reading and taken back out when writing.
fn reloc_bias(ty: u8) -> i64 {
match ty {
X86_64_RELOC_SIGNED_1 => 1,
X86_64_RELOC_SIGNED_2 => 2,
X86_64_RELOC_SIGNED_4 => 4,
_ => 0,
}
}
/// Whether a record's pcrel, p2size and extern fields are ones its type
/// takes. An assembler writes other forms for `.short sym` or
/// `.quad sym@GOTPCREL`.
#[inline]
fn is_supported(r: &MachRel) -> bool {
let forms = match r.ty() {
X86_64_RELOC_UNSIGNED => {
reloc_form(false, 2, true)
| reloc_form(false, 3, true)
| reloc_form(false, 2, false)
| reloc_form(false, 3, false)
}
X86_64_RELOC_SUBTRACTOR => reloc_form(false, 2, true) | reloc_form(false, 3, true),
X86_64_RELOC_SIGNED
| X86_64_RELOC_SIGNED_1
| X86_64_RELOC_SIGNED_2
| X86_64_RELOC_SIGNED_4 => reloc_form(true, 2, true) | reloc_form(true, 2, false),
// A one-byte branch (jmp rel8) must name a symbol.
X86_64_RELOC_BRANCH => {
reloc_form(true, 2, true) | reloc_form(true, 2, false) | reloc_form(true, 0, true)
}
X86_64_RELOC_GOT_LOAD | X86_64_RELOC_GOT | X86_64_RELOC_TLV => reloc_form(true, 2, true),
_ => 0,
};
has_reloc_form(r, forms)
}
/// The displacement a 32-bit pc-relative fixup, relocation `r` of
/// subsection `isec` at `p`, holds to reach `t` (its target, or the
/// target's stub or GOT slot): from the end of the instruction, past the
/// field and the immediate after it a SIGNED_1/2/4 counts. One that
/// doesn't fit is an error.
fn rip32_displacement(
ctx: &Context<X86_64>,
isec: &InputSection,
r: &Reloc,
p: u64,
t: u64,
) -> u32 {
let disp = t.wrapping_sub(p + 4).wrapping_sub(reloc_bias(r.ty) as u64) as i64;
if i32::try_from(disp).is_err() {
let name = r.target_name(ctx, &ctx.objs[isec.file as usize]);
let name = crate::error::raw(&name);
let msg = format_args!(
"32-bit RIP-relative reference out of range (displacement={disp}, max is +/-2GB), \
from 0x{p:08X} to 0x{t:08X} ('{name}')"
);
isec.fixup_error(ctx, r.offset, msg);
}
disp as u32
}
/// Writes a one-byte branch (jmp rel8) at `loc`, relocation `r` of
/// subsection `isec` whose address is `p`, to `t`, the address of
/// `sym`. It reaches only a definition near it in the image: ld-prime
/// gives it no stub.
fn write_branch8(
ctx: &Context<X86_64>,
isec: &InputSection,
r: &Reloc,
sym: SymbolId,
t: u64,
p: u64,
loc: &mut [u8],
) {
let sym = &ctx.symbols[sym];
let val = t.wrapping_sub(p + 1) as i64;
if sym.is_imported() {
isec.fixup_error(ctx, r.offset, format_args!("target '{sym}' does not have address"));
} else if !(-128..128).contains(&val) {
let msg = format_args!(
"8-bit branch out of range (displacement={val}, max is +/-127), \
from 0x{p:X} to 0x{t:X} ('{sym}')"
);
isec.fixup_error(ctx, r.offset, msg);
}
loc[0] = val as u8;
}
impl Target for X86_64 {
const NAME: &'static str = "x86_64";
const CPUTYPE: u32 = CPU_TYPE_X86_64;
const CPUSUBTYPE: u32 = CPU_SUBTYPE_X86_64_ALL;
const PAGE_SIZE: u64 = 4096;
const STUB_SIZE: u64 = 6;
const STUB_HELPER_HEADER_SIZE: u64 = 16;
// ld64 keeps the entries 4-byte aligned: 10 bytes of push and jmp,
// then 2 zero bytes.
const STUB_HELPER_ENTRY_SIZE: u64 = 12;
const UNWIND_MODE_DWARF: u32 = UNWIND_X86_64_MODE_DWARF;
const OBJC_STUB_SIZE: u64 = 13;
// ld-prime has no small form of x86-64's.
const OBJC_SMALL_STUB_SIZE: u64 = 13;
const LAZY_HELPERS_P2ALIGN: u32 = 0;
const LAZY_CALL_OWN_SLOT: bool = true;
const DELAY_STUB_SIZE: u64 = 25;
const DLOPEN_HELPER_SIZE: u32 = DLOPEN_HELPER.len() as u32;
const DELAY_P2ALIGN: u32 = 0;
// A 32-bit pcrel branch covers 4 GiB; x86-64 outputs never need
// thunks.
const BRANCH_RANGE: u64 = 1 << 32;
const THUNK_SIZE: u64 = 0;
const RELOC_UNSIGNED: u8 = X86_64_RELOC_UNSIGNED;
const RELOC_SUBTRACTOR: u8 = X86_64_RELOC_SUBTRACTOR;
const RELOC_GOTPC: u8 = X86_64_RELOC_GOT;
const RELOC_BRANCH: u8 = X86_64_RELOC_BRANCH;
const RELOC_GOT_LOADS: &'static [u8] = &[X86_64_RELOC_GOT_LOAD, X86_64_RELOC_TLV];
// x86-64 embeds every addend in the relocated field.
const RELOC_ADDEND: u8 = 0xff;
const SPLIT_PCREL_KINDS: &'static [u8] = &[DYLD_CACHE_ADJ_V2_DELTA_32];
const STUB_REF_OFF: u64 = 2;
const STUB_HELPER_REF_OFFS: [u64; 2] = [3, 11];
const OBJC_STUB_REF_OFFS: [u64; 2] = [3, 9];
// x86_THREAD_STATE64: rax..r15, then rip.
const THREAD_STATE_FLAVOR: u32 = 4;
const THREAD_STATE_COUNT: u32 = 42;
const THREAD_STATE_SP_OFFSET: usize = 7 * 8;
const THREAD_STATE_PC_OFFSET: usize = 16 * 8;
fn relocatable_needs_addend(_ty: u8) -> bool {
false
}
fn reloc_bias(ty: u8) -> i64 {
reloc_bias(ty)
}
fn split_ref(ty: u8) -> SplitRef {
match ty {
X86_64_RELOC_UNSIGNED => SplitRef::Pointer,
X86_64_RELOC_SUBTRACTOR => SplitRef::Subtractor,
_ => SplitRef::PcRel32,
}
}
// GOT_LOAD marks "movq sym@GOTPCREL(%rip), %reg" (opcode 0x8b,
// after a REX prefix), which a local target relaxes to lea (0x8d).
// A leaq of a slot takes its address.
fn got_load_form(ty: u8, data: &[u8], offset: u32) -> Option<u8> {
let mov = offset >= 2 && data.get(offset as usize - 2) == Some(&0x8b);
(ty == X86_64_RELOC_SIGNED && mov).then_some(X86_64_RELOC_GOT_LOAD)
}
fn write_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
for (i, &sym) in ctx.stubs.symbols.iter().enumerate() {
let off = stubs::entry_offset::<Self>(i as u32);
let ent = &mut buf[off as usize..];
let ent_addr = addr + off;
let ptr_addr = ctx.symbols[sym].stub_ptr_addr(ctx, i);
let disp = ptr_addr.wrapping_sub(ent_addr + 6) as i64;
if i32::try_from(disp).is_err() {
let p = ent_addr + 2;
crate::error!(
"stub for {}: 32-bit RIP-relative reference out of range (displacement={disp}, \
max is +/-2GB), from 0x{p:08X} to its pointer at 0x{ptr_addr:08X}",
ctx.symbols[sym]
);
}
// jmp *ptr(%rip)
ent[0] = 0xff;
ent[1] = 0x25;
write32(&mut ent[2..], disp as u32);
}
}
fn write_stub_helper(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
// The header, as ld64 emits it:
// lea __dyld_private(%rip), %r11
// push %r11
// jmp *dyld_stub_binder@GOTPCREL(%rip)
// nop
let private = ctx.isecs[ctx.stub_helper.dyld_private_isec as usize].addr(ctx);
let binder = ctx.symbols[ctx.stub_helper.dyld_stub_binder.unwrap()].got_addr(ctx);
buf[0..3].copy_from_slice(&[0x4c, 0x8d, 0x1d]);
write32(&mut buf[3..], private.wrapping_sub(addr + 7) as u32);
buf[7..9].copy_from_slice(&[0x41, 0x53]);
buf[9..11].copy_from_slice(&[0xff, 0x25]);
write32(&mut buf[11..], binder.wrapping_sub(addr + 15) as u32);
buf[15] = 0x90;
// Each entry: push $offset; jmp header; the zero padding.
let lazy_offsets = &ctx.lazy_bind_info.offsets[..ctx.stubs.lazy.len()];
for (i, &lazy_off) in lazy_offsets.iter().enumerate() {
let off = stub_helper::entry_offset(ctx, i as u32) as usize;
let ent_addr = addr + off as u64;
buf[off] = 0x68;
write32(&mut buf[off + 1..], lazy_off);
buf[off + 5] = 0xe9;
write32(&mut buf[off + 6..], addr.wrapping_sub(ent_addr + 10) as u32);
buf[off + 10..off + 12].fill(0);
}
}
/// Each entry hands dyld_stub_binding_helper the address of its lazy
/// pointer, which dyld binds by the indirect symbol table.
/// lea lazy_ptr(%rip), %r11
/// jmp dyld_stub_binding_helper
fn write_legacy_stub_helper(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
let helper = ctx.stub_helper.binding_helper.map(|id| ctx.symbols[id].addr(ctx));
if helper.is_none() {
crate::error!("stub helper: target 'dyld_stub_binding_helper' does not have address");
}
for i in 0..ctx.stubs.lazy.len() {
let off = stub_helper::entry_offset(ctx, i as u32);
let ent = &mut buf[off as usize..];
let ent_addr = addr + off;
let ptr = ctx.lazy_ptrs.slot_addr(i);
ent[0..3].copy_from_slice(&[0x4c, 0x8d, 0x1d]);
write32(&mut ent[3..], ptr.wrapping_sub(ent_addr + 7) as u32);
ent[7] = 0xe9;
write32(&mut ent[8..], helper.unwrap_or(0).wrapping_sub(ent_addr + 12) as u32);
}
}
fn write_objc_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
let msgsend_got = ctx.objc_msgsend_got_addr();
for i in 0..ctx.objc_stubs.symbols.len() {
let off = objc_stubs::entry_offset(ctx, i as u32);
let ent = &mut buf[off as usize..];
let ent_addr = addr + off;
let sel_addr = ctx.objc_stubs.selref_addr(ctx, i);
// mov sel(%rip), %rsi; jmp *_objc_msgSend@GOT(%rip), packed
// back to back as ld-prime lays them out.
ent[..13].copy_from_slice(&[0x48, 0x8b, 0x35, 0, 0, 0, 0, 0xff, 0x25, 0, 0, 0, 0]);
write32(&mut ent[3..], sel_addr.wrapping_sub(ent_addr + 7) as u32);
write32(&mut ent[9..], msgsend_got.wrapping_sub(ent_addr + 13) as u32);
}
}
fn write_lazy_helpers(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
let lazy_load = ctx.symbols[ctx.lazy_helpers.dyld_lazy_load.unwrap()].stub_addr(ctx);
let header = ctx.mach_header.hdr.addr;
for h in &ctx.lazy_helpers.helpers {
let size = Self::lazy_helper_size(h.kind) as usize;
let ent = &mut buf[h.offset as usize..h.offset as usize + size];
let base = addr + h.offset as u64;
let flag = ctx.isecs[h.flag as usize].addr(ctx);
let slot = ctx.lazy_load_got.slot_addr(h.slot);
// The call of __dyld_lazy_load(&flag, mach header), by which
// dyld finds the dylib's record, with the argument registers
// saved: push %rbp; mov %rsp, %rbp; push %rsi; push %rdi;
// lea flag(%rip), %rdi; lea header(%rip), %rsi; call; pop
// %rdi; pop %rsi; pop %rbp.
let call: [u8; 28] = [
0x55, 0x48, 0x89, 0xe5, 0x56, 0x57, 0x48, 0x8d, 0x3d, 0, 0, 0, 0, 0x48, 0x8d, 0x35,
0, 0, 0, 0, 0xe8, 0, 0, 0, 0, 0x5f, 0x5e, 0x5d,
];
// cmpl $0, flag(%rip)
ent[..7].copy_from_slice(&[0x83, 0x3d, 0, 0, 0, 0, 0]);
write32(&mut ent[2..], flag.wrapping_sub(base + 7) as u32);
let start = match h.kind {
// je 1f; 2: jmp *slot(%rip); 1: (the call); jmp 2b
LazyUse::Call => {
ent[7..15].copy_from_slice(&[0x74, 0x06, 0xff, 0x25, 0, 0, 0, 0]);
write32(&mut ent[11..], slot.wrapping_sub(base + 15) as u32);
ent[43..45].copy_from_slice(&[0xeb, 0xdc]);
15
}
// jne 1f; (the call); 1: movq slot(%rip), %reg; ret
LazyUse::Load { reg, .. } => {
ent[7..9].copy_from_slice(&[0x75, 0x1c]);
ent[37..40].copy_from_slice(&movq_rip(reg));
write32(&mut ent[40..], slot.wrapping_sub(base + 44) as u32);
ent[44] = 0xc3;
9
}
// jne 1f; (the call); 1: cmpq $0, slot(%rip); ret
LazyUse::Cmp => {
ent[7..9].copy_from_slice(&[0x75, 0x1c]);
ent[37..45].copy_from_slice(&[0x48, 0x83, 0x3d, 0, 0, 0, 0, 0]);
write32(&mut ent[40..], slot.wrapping_sub(base + 45) as u32);
ent[45] = 0xc3;
9
}
};
let at = base + start as u64;
ent[start..start + call.len()].copy_from_slice(&call);
write32(&mut ent[start + 9..], flag.wrapping_sub(at + 13) as u32);
write32(&mut ent[start + 16..], header.wrapping_sub(at + 20) as u32);
write32(&mut ent[start + 21..], lazy_load.wrapping_sub(at + 25) as u32);
}
}
fn lazy_helper_size(kind: LazyUse) -> u32 {
if kind == LazyUse::Cmp { 46 } else { 45 }
}
// cmpl $0, flag(%rip); jne 1f
// push %rbp; mov %rsp, %rbp; call dlopen helper; pop %rbp
// 1: jmp *slot(%rip)
fn write_delay_stubs(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
for (i, stub) in ctx.delay_init.stubs.iter().enumerate() {
let at = delay_init::stub_offset::<Self>(i as u32) as usize;
let ent = &mut buf[at..at + Self::DELAY_STUB_SIZE as usize];
let base = addr + at as u64;
write_delay_check(ctx, ent, base, stub.dlopen);
ent[19..21].copy_from_slice(&[0xff, 0x25]);
let slot = ctx.got.slot_addr(stub.got as usize);
write32(&mut ent[21..], slot.wrapping_sub(base + 25) as u32);
}
}
// (the check as a stub's) 1: movq slot(%rip), %reg; ret, or
// cmpq $0, slot(%rip); ret.
//
// A dlopen helper calls dlopen(install name, 0) with the argument
// registers saved, then sets the flag by an xchgl. ld-prime's saves
// the general-purpose ones alone, which lets dlopen() clobber the
// floating-point arguments of the call that first reaches the
// dylib (addf(1.0, 2.0, 3.0, 4.0) returns 0.5): this one saves
// xmm0-xmm7 too.
fn write_delay_helper(ctx: &Context<Self>, addr: u64, buf: &mut [u8]) {
let delay = &ctx.delay_init;
for h in &delay.helpers {
let size = Self::delay_helper_size(h.kind) as usize;
let ent = &mut buf[h.offset as usize..h.offset as usize + size];
let base = addr + h.offset as u64;
write_delay_check(ctx, ent, base, h.dlopen);
let slot = ctx.symbols[h.sym].got_addr(ctx);
match h.kind {
DelayUse::Load { reg, .. } => {
ent[19..22].copy_from_slice(&movq_rip(reg));
write32(&mut ent[22..], slot.wrapping_sub(base + 26) as u32);
ent[26] = 0xc3;
}
DelayUse::Cmp => {
ent[19..22].copy_from_slice(&[0x48, 0x83, 0x3d]);
write32(&mut ent[22..], slot.wrapping_sub(base + 27) as u32);
ent[26..28].copy_from_slice(&[0, 0xc3]);
}
}
}
let dlopen = ctx.symbols[delay.dlopen_sym.unwrap()].stub_addr(ctx);
for d in &delay.dlopens {
let size = Self::DLOPEN_HELPER_SIZE as usize;
let ent = &mut buf[d.offset as usize..d.offset as usize + size];
let base = addr + d.offset as u64;
ent.copy_from_slice(&DLOPEN_HELPER);
let (name, flag) =
(ctx.isecs[d.string as usize].addr(ctx), ctx.isecs[d.flag as usize].addr(ctx));
write32(&mut ent[68..], name.wrapping_sub(base + 72) as u32);
write32(&mut ent[75..], dlopen.wrapping_sub(base + 79) as u32);
write32(&mut ent[86..], flag.wrapping_sub(base + 90) as u32);
}
}
fn delay_helper_size(kind: DelayUse) -> u32 {
if kind == DelayUse::Cmp { 28 } else { 27 }
}
// Each a 32-bit displacement.
fn delay_refs(code: DelayCode) -> Vec<(u32, u8, DelayTarget)> {
use DelayTarget::*;
let refs = match code {
DelayCode::Stub => vec![(2, Flag), (14, DlopenHelper), (21, Slot)],
DelayCode::Helper(_) => vec![(2, Flag), (14, DlopenHelper), (22, Slot)],
DelayCode::Dlopen => vec![(68, Name), (75, Dlopen), (86, Flag)],
};
refs.into_iter().map(|(off, to)| (off, DYLD_CACHE_ADJ_V2_DELTA_32, to)).collect()
}
// Each a 32-bit displacement: the flag's in the cmpl, the lea's of
// the call's arguments and the call's; and the jmp's or the final
// movq's or cmpq's of the slot.
fn lazy_helper_refs(kind: LazyUse) -> Vec<(u32, u8, LazyTarget)> {
use LazyTarget::*;
let call = |k: u32| [(k + 9, Flag), (k + 16, Header), (k + 21, LazyLoad)];
let mut refs = vec![(2, Flag)];
match kind {
LazyUse::Call => {
refs.push((11, Slot));
refs.extend(call(15));
}
_ => {
refs.extend(call(9));
refs.push((40, Slot));
}
}
refs.into_iter().map(|(off, to)| (off, DYLD_CACHE_ADJ_V2_DELTA_32, to)).collect()
}
fn lazy_ref(r: &Reloc, data: &[u8]) -> crate::arch::LazyRef {
use crate::arch::LazyRef;
let off = r.offset as usize;
match r.ty {
X86_64_RELOC_BRANCH if r.size == 4 => LazyRef::Call,
X86_64_RELOC_GOT_LOAD => LazyRef::Load,
// cmpq $0, sym@GOTPCREL(%rip)
X86_64_RELOC_GOT
if off >= 3
&& data[off - 3..off] == [0x48, 0x83, 0x3d]
&& data.get(off + 4) == Some(&0) =>
{
LazyRef::Cmp
}
_ => LazyRef::Unsupported,
}
}
// The movq's destination: ModRM's reg field, extended by REX.R.
// The helper returns (by ret), so any code may call it.
fn lazy_load_site(data: &[u8], offset: u32) -> (u8, bool) {
let off = offset as usize;
let reg = (data[off - 3] >> 2 & 1) << 3 | (data[off - 1] >> 3 & 7);
(reg, false)
}
// (ld-prime names both %rsp and %rbp "xxx".)
fn lazy_register_name(reg: u8) -> String {
const NAMES: [&str; 16] = [
"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11",
"r12", "r13", "r14", "r15",
];
NAMES[reg as usize & 15].to_string()
}
fn write_thunk(
_ctx: &Context<Self>,
_addr: u64,
_syms: &[crate::symbol::SymbolId],
_buf: &mut [u8],
) {
unreachable!("x86-64 branches never need thunks");
}
fn read_relocs(
file_name: &Path,
sections: &[MachSection],
hdr: &MachSection,
contents: &[u8],
rels: &[MachRel],
) -> Vec<Reloc> {
let mut vec = Vec::with_capacity(rels.len());
for (i, r) in rels.iter().enumerate() {
if !is_supported(r) {
crate::arch::bad_reloc(file_name, hdr, r, "unsupported relocation");
}
// On x86-64 every relocation's addend is embedded in the
// relocated field.
let loc = &contents[r.offset as usize..];
let embedded = match r.p2size() {
0 => loc[0] as i8 as i64,
2 => i32::from_le_bytes(loc[..4].try_into().unwrap()) as i64,
_ => i64::from_le_bytes(loc[..8].try_into().unwrap()),
};
let addend = embedded + reloc_bias(r.ty());
let is_subtracted = i > 0 && rels[i - 1].ty() == X86_64_RELOC_SUBTRACTOR;
// A non-extern record's field holds the address it points
// at, a pcrel one as a displacement from the field's end.
let (target, addend) = if r.is_extern() {
(RelocTarget::Sym(r.idx()), addend)
} else if r.is_pcrel() {
let addr = (hdr.addr + r.offset as u64 + 4).wrapping_add_signed(addend);
section_target(file_name, sections, r, addr)
} else {
section_target(file_name, sections, r, addend as u64)
};
vec.push(Reloc {
offset: r.offset,
ty: r.ty(),
size: 1 << r.p2size(),
is_pcrel: r.is_pcrel(),
is_subtracted,
target: target.pack(),
addend,
});
}
vec
}
fn scan_relocations(ctx: &Context<Self>, isec: &InputSection) {
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
let Some(id) = rel.sym(file) 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 sym.is_lazy_import(ctx) {
continue;
}
// A TLV load must load a thread-local, and only a TLV load
// may, as on arm64.
if (rel.ty == X86_64_RELOC_TLV) != sym.is_tlv(ctx) {
error!("illegal thread local variable reference to regular symbol `{sym}`");
}
match rel.ty {
// A one-byte branch (jmp rel8) reaches only code near it,
// so it takes no stub: one to an import is a fixup error,
// as in ld-prime.
X86_64_RELOC_BRANCH if rel.size == 1 => {}
// A kext calls an import directly, unless
// -kexts_use_stubs: kmutil fills in the call by an
// external relocation, or the stub's GOT slot. What dyld
// resolves by weak lookup is called through a stub all
// the same.
X86_64_RELOC_BRANCH
if ctx.args.is_kext()
&& !ctx.args.kexts_use_stubs
&& !sym.binds_weak_lookup(ctx) => {}
// Otherwise, as on arm64.
X86_64_RELOC_BRANCH => {
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 !sym.can_relax_got(ctx) {
sym.add_flags(NEEDS_GOT);
}
}
X86_64_RELOC_GOT => sym.add_flags(NEEDS_GOT),
_ => {}
}
}
}
fn apply_reloc_alloc(
ctx: &Context<Self>,
rels: &[Reloc],
isec_id: usize,
base: u64,
buf: &mut [u8],
) {
let isec = &ctx.isecs[isec_id];
let file = &ctx.objs[isec.file as usize];
let mut i = 0;
while i < rels.len() {
let r = &rels[i];
// A GOT load of a local symbol relaxes: the movq that
// reads the slot (opcode 0x8b) becomes a leaq (0x8d) of
// the target itself, and a leaq is taken as one already.
// ld-prime refuses any other instruction. The opcode sits
// 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)
&& r.sym(file).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,
Some(op) if *op == 0x8d => {}
_ => {
let msg =
format_args!("GOT load fixup does not point to a movq instruction");
isec.fixup_error(ctx, r.offset, msg);
}
}
}
// A GOT load or compare of a lazy or delay-init dylib's
// symbol becomes a call of its helper, nops filling the rest
// of the movq or cmpq (see LazyUse and DelayUse).
if matches!(r.ty, X86_64_RELOC_GOT_LOAD | X86_64_RELOC_GOT)
&& let Some((helper, _)) = lazy_helpers::load_helper(ctx, isec_id, r)
.or_else(|| delay_init::load_helper(ctx, isec_id, r))
{
let at = r.offset as usize - 3;
let disp = helper.wrapping_sub(base + at as u64 + 5);
buf[at] = 0xe8;
write32(&mut buf[at + 1..], disp as u32);
let end = r.offset as usize + if r.ty == X86_64_RELOC_GOT { 5 } else { 4 };
buf[at + 5..end].fill(0x90);
i += 1;
continue;
}
// A DTrace probe site does nothing (see dtrace).
if r.ty == X86_64_RELOC_BRANCH
&& r.size == 4
&& let Some(code) = dtrace_site_code(ctx, file, r)
{
let at = r.offset as usize - 1;
buf[at..at + 5].copy_from_slice(code);
i += 1;
continue;
}
let loc = &mut buf[r.offset as usize..];
let s = r.addr(ctx, file);
let a = r.addend;
let p = base + r.offset as u64;
match r.ty {
X86_64_RELOC_UNSIGNED if r.size == 4 => {
// A 32-bit pointer (.long sym) can be neither slid
// nor bound, so ld-prime takes one only in an image
// no dyld or kmutil loads, where it must fit
// ("oveflow" sic). Elsewhere one where a pointer
// would be a text relocation is one, and any other
// an error (see passes::report_text_relocs).
let val = s.wrapping_add_signed(a);
if ctx.args.static_link {
if val > u32::MAX as u64 {
isec.fixup_error(
ctx,
r.offset,
format_args!("32-bit pointer overflow"),
);
}
} else if ctx.text_reloc_ranges.iter().any(|range| range.contains(&p)) {
isec.check_text_reloc(ctx, isec_id, rels, i, p);
} else {
ctx.pointers32.lock().unwrap().push((isec_id as u32, r.offset));
}
write32(loc, val as u32);
}
X86_64_RELOC_UNSIGNED => {
isec.check_text_reloc(ctx, isec_id, rels, i, p);
let imported = r.sym(file).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
// relocation has dyld add the symbol's address.
} else if r.refers_to_tls(ctx, file) {
write64(loc, s.wrapping_add_signed(a).wrapping_sub(ctx.tls_begin));
} else {
write64(loc, s.wrapping_add_signed(a));
}
}
// The assembler pairs it with an UNSIGNED of its size.
X86_64_RELOC_SUBTRACTOR => {
i += 1;
let val =
rels[i].addr(ctx, file).wrapping_add_signed(rels[i].addend).wrapping_sub(s);
if r.size == 4 {
write32(loc, val as u32);
} else {
write64(loc, val);
}
}
X86_64_RELOC_BRANCH if r.size == 1 => {
let sym = r.sym(file).unwrap();
write_branch8(ctx, isec, r, sym, s.wrapping_add_signed(a), p, loc);
}
// A kext's call to an import, without a stub, keeps
// its addend for kmutil's external relocation.
X86_64_RELOC_BRANCH
if r.sym(file).is_some_and(|id| {
let sym = &ctx.symbols[id];
sym.is_imported() && !sym.has_stub(&ctx.symbols)
}) =>
{
write32(loc, a as u32);
}
// A pc-relative fixup that can't reach is an error.
X86_64_RELOC_BRANCH => {
let s = match r.sym(file) {
Some(id) => ctx.symbols[id].branch_target_addr(ctx),
None => s,
};
let t = s.wrapping_add_signed(a);
write32(loc, rip32_displacement(ctx, isec, r, p, t));
}
X86_64_RELOC_SIGNED
| X86_64_RELOC_SIGNED_1
| X86_64_RELOC_SIGNED_2
| X86_64_RELOC_SIGNED_4 => {
if isec.target_has_address(ctx, r) {
let t = s.wrapping_add_signed(a);
write32(loc, rip32_displacement(ctx, isec, r, p, t));
}
}
// A local thread-local's TLV load relaxes just like a
// GOT load: the movq of the descriptor's GOT slot
// becomes a leaq of the __thread_vars descriptor itself.
X86_64_RELOC_GOT_LOAD | X86_64_RELOC_TLV if relaxed_got_load => {
let t = s.wrapping_add_signed(a);
write32(loc, rip32_displacement(ctx, isec, r, p, t));
}
X86_64_RELOC_GOT_LOAD | X86_64_RELOC_GOT | X86_64_RELOC_TLV => {
let g = ctx.symbols[r.sym(file).unwrap()].got_addr(ctx);
let t = g.wrapping_add_signed(a);
write32(loc, rip32_displacement(ctx, isec, r, p, t));
}
_ => fatal!("unsupported relocation type: {}", r.ty),
}
i += 1;
}
}
}
+145
View File
@@ -0,0 +1,145 @@
//! This file contains functions to read an archive file (.a file).
//! An archive file is just a bundle of object files. It's similar to
//! tar or zip, but the contents are not compressed.
//!
//! If an archive file is given to the linker, the linker pulls out
//! object files that are needed to resolve undefined symbols. So,
//! bunding object files as an archive and giving that archive to the
//! linker has a different meaning than directly giving the same set of
//! object files to the linker. The former links only needed object
//! files, while the latter links all the given object files.
//!
//! A Mach-O archive is the common ar format. Apple's ar uses the BSD
//! long-name convention - a member name of "#1/<len>" means the real
//! name is the first <len> bytes of the member body - and prepends a
//! __.SYMDEF index member, which a linker that parses every member
//! eagerly can simply skip. GNU ar's SysV long names are accepted too.
use std::ffi::OsString;
use std::path::PathBuf;
use crate::mapped_file::MappedFile;
use crate::util::os_str;
const HEADER_SIZE: usize = 60;
/// A parsed archive member header.
struct ArHeader<'a> {
name: &'a [u8],
date: u64,
size: usize,
}
impl<'a> ArHeader<'a> {
fn parse(bytes: &'a [u8]) -> Option<Self> {
let bytes = bytes.get(..HEADER_SIZE)?;
let date = parse_decimal(&bytes[16..28]) as u64;
let size = parse_decimal(&bytes[48..58]);
Some(ArHeader { name: &bytes[..16], date, size })
}
fn is_strtab(&self) -> bool {
self.name.starts_with(b"// ")
}
fn is_symtab(&self) -> bool {
self.name.starts_with(b"/ ") || self.name.starts_with(b"/SYM64/ ")
}
/// Returns the member's file name. A BSD-style long name is stored
/// right after the header, so `body` is advanced past it.
fn read_name(&self, strtab: &'a [u8], body: &mut &'a [u8]) -> &'a [u8] {
// BSD-style long filename
if let Some(rest) = self.name.strip_prefix(b"#1/") {
let len = parse_decimal(rest);
let (name, remaining) = body.split_at(len.min(body.len()));
*body = remaining;
return name.split(|&b| b == 0).next().unwrap_or(&[]);
}
// SysV-style long filename
if let Some(rest) = self.name.strip_prefix(b"/") {
let offset = parse_decimal(rest);
let start = strtab.get(offset..).unwrap_or(&[]);
let end = memchr::memmem::find(start, b"/\n").unwrap_or(start.len());
return &start[..end];
}
// Short filename, space-padded and (in the SysV form) slash-terminated.
let end = self.name.iter().position(|&b| b == b'/').unwrap_or(self.name.len());
self.name[..end].trim_ascii_end()
}
}
/// Parses the leading decimal digits of a field, like `atoi`.
fn parse_decimal(bytes: &[u8]) -> usize {
bytes
.iter()
.skip_while(|b| b.is_ascii_whitespace())
.take_while(|b| b.is_ascii_digit())
.fold(0, |acc, &b| acc * 10 + (b - b'0') as usize)
}
/// Iterates over the members of an archive as (name, body, date) triples, skipping
/// the symbol table and string table.
fn archive_members(
mf: &'static MappedFile,
) -> impl Iterator<Item = (&'static [u8], &'static [u8], u64)> {
let data = mf.data();
let mut pos = 8;
let mut strtab: &'static [u8] = &[];
std::iter::from_fn(move || {
loop {
if data.len() - pos < 2 {
return None;
}
// Each header is aligned to a 2 byte boundary.
if pos % 2 != 0 {
pos += 1;
}
let hdr = ArHeader::parse(&data[pos..])?;
let body_start = pos + HEADER_SIZE;
let body_end = (body_start + hdr.size).min(data.len());
let mut body = &data[body_start..body_end];
pos = body_end;
// Read a string table.
if hdr.is_strtab() {
strtab = body;
continue;
}
// Skip a symbol table.
if hdr.is_symtab() {
continue;
}
// Read the name field
let name = hdr.read_name(strtab, &mut body);
// Skip BSD archive symbol tables (__.SYMDEF, __.SYMDEF SORTED,
// __.SYMDEF_64 ...).
if name.starts_with(b"__.SYMDEF") {
continue;
}
return Some((name, body, hdr.date));
}
})
}
/// Opens members as they are consumed. A member is named
/// "archive(member)", as ld64 reports it.
pub fn read_archive_members(mf: &'static MappedFile) -> impl Iterator<Item = &'static MappedFile> {
debug_assert!(mf.data().starts_with(b"!<arch>\n"));
let base = mf.data().as_ptr() as usize;
archive_members(mf).map(move |(name, body, date)| {
let mut full_name = OsString::from(&mf.name);
full_name.push("(");
full_name.push(os_str(name));
full_name.push(")");
mf.member(PathBuf::from(full_name), body.as_ptr() as usize - base, body.len(), date)
})
}
+272
View File
@@ -0,0 +1,272 @@
//! The hook for the classes of mergeable libraries. Xcode keeps a
//! mergeable framework's bundle in the app, with its resources, where
//! an image merges the framework's code (-merge_*) or re-exports it
//! from elsewhere (-no_merge_*), and so +[NSBundle bundleForClass:]
//! would find the image's bundle for the framework's classes. ld-prime
//! links a hook into such an image, unless -no_merged_libraries_hook:
//! an initializer that, in an app, has the Objective-C runtime name the
//! framework's binary in the app as each class's image
//! (objc_setHook_getImageName), from which NSBundle finds the bundle.
//! A debug build of a mergeable dylib gets one for the classes it
//! doesn't export with -add_mergeable_debug_hook.
//!
//! mold's hook is a C file of its own, c/bundle-hook.c, built ahead of
//! time and embedded. It looks a class up in a table mold makes: a
//! count, then a record per class of the class (a rebase or a bind),
//! its library's name and the path the hook makes from it. ld-prime's
//! hook has tables of another layout, and links Foundation,
//! CoreFoundation, libc++ and libswiftCore; mold's needs libSystem.
//! The hook's object goes first, as ld-prime's does, which so runs its
//! initializer first from __mod_init_func, but last from __init_offsets
//! (see passes::convert_init_offsets).
use crate::arch::Target;
use crate::cmdline::Args;
use crate::context::Context;
use crate::filetype::{fat_slice, foreign_arch, is_subtype_mismatch};
use crate::input_files::{DataField, FileId, add_data_blob, read_dylib_binary, read_tbd};
use crate::macho::*;
use crate::mapped_file::MappedFile;
use crate::objc::ObjcRef;
use crate::symbol::SymbolId;
use crate::tapi::LdSymbols;
/// The hook, built by c/build-bundle-hook.sh.
static ARM64_OBJECT: &[u8] = include_bytes!("c/bundle-hook-arm64.o");
static X86_64_OBJECT: &[u8] = include_bytes!("c/bundle-hook-x86_64.o");
/// The table the hook reads, which mold defines.
const TABLE_SYMBOL: &[u8] = b"___mold_bundle_hook_table";
/// The classes the hook is for, by library.
#[derive(Default)]
pub struct BundleHook {
/// The hook's object, if the link may need it.
pub obj: Option<usize>,
/// Each merged library that defines classes: its name, and the
/// object its code makes (see reader::merge_dylib).
merged: Vec<(Vec<u8>, &'static MappedFile)>,
/// Each re-exported library that exports classes: its name, and
/// the classes, which the table binds to.
reexported: Vec<(Vec<u8>, Vec<SymbolId>)>,
}
impl BundleHook {
/// The classes the table binds to, which resolution looks up as if
/// something referred to them.
pub fn imports(&self) -> impl Iterator<Item = SymbolId> + '_ {
self.reexported.iter().flat_map(|(_, ids)| ids.iter().copied())
}
}
/// Whether the output may get the hook: an image dyld loads, but none
/// -make_mergeable makes. (ld-prime adds the hook to that too, which a
/// later link then merges as any other code, its table symbols clashing
/// with those of the merging image's hook.)
fn may_hook(args: &Args) -> bool {
args.merged_libraries_hook
&& !args.relocatable
&& !args.make_mergeable
&& matches!(args.output_type, MH_EXECUTE | MH_DYLIB | MH_BUNDLE)
&& args.links_dylibs()
}
/// A library's name in the hook: the last component of its install
/// name, which names its framework and the framework's binary.
fn leaf(install_name: &[u8]) -> Vec<u8> {
install_name.rsplit(|&b| b == b'/').next().unwrap().to_vec()
}
/// Notes a merged library that defines classes, whose code makes the
/// object `obj`, for the hook.
pub fn note_merged_library<E: Target>(
ctx: &mut Context<E>,
install_name: &[u8],
obj: &'static MappedFile,
) {
if may_hook(&ctx.args) {
ctx.bundle_hook.merged.push((leaf(install_name), obj));
}
}
/// Notes the classes a library a -no_merge_* option names exports
/// itself (see exported_classes), which the hook binds to in whichever
/// library loads them.
pub fn note_reexported_library<E: Target>(ctx: &mut Context<E>, mf: &'static MappedFile) {
if !may_hook(&ctx.args) {
return;
}
let Some((install_name, classes)) = exported_classes(ctx, mf) else {
return;
};
if !classes.is_empty() {
let ids = classes.into_iter().map(|name| ctx.symbols.intern(name)).collect();
ctx.bundle_hook.reexported.push((leaf(&install_name), ids));
}
}
/// A dylib's or stub's install name, and the Objective-C and Swift
/// classes (see is_class_export) it exports itself for the link's
/// target, after its $ld$hide and $ld$add directives: the classes of
/// the libraries it re-exports don't count, those it re-exports one by
/// one (an alias, a -reexported_symbols_list entry) do. None for a file
/// the link ignores, or one that is no library. (ld-prime adds its hook
/// for such classes to an image that re-exports the library with
/// -no_merge_*.)
fn exported_classes<E: Target>(
ctx: &Context<E>,
mf: &'static MappedFile,
) -> Option<(Vec<u8>, Vec<&'static [u8]>)> {
use crate::filetype::{FileType, get_file_type};
let mf = match get_file_type(mf) {
FileType::Fat => fat_slice::<E>(&ctx.args, mf)?,
_ => mf,
};
let (install_name, ld, exports) = match get_file_type(mf) {
FileType::Tapi => {
let tbd = read_tbd(ctx, mf)?;
let ld = LdSymbols::read(ctx, &tbd.ld_symbols);
let exports = [tbd.exports, tbd.weak_exports, tbd.tlv_exports].concat();
(tbd.install_name.to_vec(), ld, exports)
}
FileType::Dylib
if foreign_arch::<E>(mf).is_none()
|| (ctx.args.allow_sub_type_mismatches && is_subtype_mismatch::<E>(mf)) =>
{
let dylib = read_dylib_binary(mf);
(dylib.install_name, LdSymbols::read(ctx, &dylib.ld_symbols), dylib.exports)
}
_ => return None,
};
let own = exports.into_iter().filter(|name| ld.keeps(name)).chain(ld.added.iter().copied());
// A binary names its exports in its symbol table and export trie.
let mut classes: Vec<&[u8]> = own.filter(|name| is_class_export(name)).collect();
classes.sort_unstable();
classes.dedup();
Some((install_name, classes))
}
/// Whether an export is that of a class, as ld-prime's hook for the
/// classes of mergeable libraries goes by its name: an Objective-C
/// class or metaclass object (_OBJC_CLASS_$_Foo, _OBJC_METACLASS_$_Foo),
/// or a Swift class's type metadata (_$s...CN, of any class, Objective-C
/// or not). A Swift class's other symbols (its nominal type descriptor,
/// metaclass or accessor), and an Objective-C class's exception type or
/// instance variables, don't count.
fn is_class_export(name: &[u8]) -> bool {
name.starts_with(b"_OBJC_CLASS_$_")
|| name.starts_with(b"_OBJC_METACLASS_$_")
|| (name.starts_with(b"_$s") && name.ends_with(b"CN"))
}
/// The hook's object, which goes ahead of the inputs as ld-prime's does,
/// if the link may need it: for a debug build's own classes, that only
/// resolving the symbols tells (see create_class_table).
pub fn hook_object<E: Target>(ctx: &Context<E>) -> Option<&'static MappedFile> {
let hook = &ctx.bundle_hook;
let debug = ctx.args.add_mergeable_debug_hook && may_hook(&ctx.args);
if hook.merged.is_empty() && hook.reexported.is_empty() && !debug {
return None;
}
let data = if E::CPUTYPE == CPU_TYPE_ARM64 { ARM64_OBJECT } else { X86_64_OBJECT };
Some(MappedFile::synthesized("bundleForClassHook.o".into(), data.to_vec()))
}
/// Makes the hook's table, once the symbols are resolved, or drops the
/// hook if there is no class for it.
pub fn create_class_table<E: Target>(ctx: &mut Context<E>) {
let Some(obj) = ctx.bundle_hook.obj else { return };
let libraries = hooked_classes(ctx);
if libraries.iter().all(|(_, classes)| classes.is_empty()) {
// Resolving the symbols again unbinds what only the hook named.
ctx.objs[obj].is_reachable = false;
crate::passes::resolve_symbols(ctx);
return;
}
let table = add_table(ctx, &libraries);
let internal = ctx.internal_obj.expect("internal object not created yet") as u32;
let id = ctx.symbols.intern(TABLE_SYMBOL);
let sym = &mut ctx.symbols[id];
sym.set_file(FileId::Obj(internal));
sym.set_input_section(Some(table));
sym.value = 0;
sym.set_extern(true);
sym.set_private_extern(true);
}
/// Adds the table to __DATA,__data, after the libraries' names: the
/// count of classes, then each one's record of c/bundle-hook.c's
/// struct entry. Returns its subsection.
fn add_table<E: Target>(ctx: &mut Context<E>, libraries: &[(Vec<u8>, Vec<ObjcRef>)]) -> u32 {
let names = libraries.iter().flat_map(|(name, _)| [&name[..], b"\0"].concat()).collect();
let names = add_data_blob(ctx, b"__data", S_REGULAR, vec![DataField::Bytes(names)]);
let count: usize = libraries.iter().map(|(_, classes)| classes.len()).sum();
let mut fields = vec![DataField::Bytes((count as u64).to_le_bytes().to_vec())];
let mut name_off = 0;
for (name, classes) in libraries {
let name_ptr = DataField::Ptr(ObjcRef::Isec(names, name_off));
for &cls in classes {
fields.extend([DataField::Ptr(cls), name_ptr.clone(), DataField::Bytes(vec![0; 8])]);
}
name_off += name.len() as u64 + 1;
}
add_data_blob(ctx, b"__data", S_REGULAR, fields)
}
/// The classes the hook is for, by library, as the table lists them: a
/// merged library's, those its __objc_classlist lists (with no generic
/// Swift class, which has no metadata until made); a re-exported one's,
/// those it exports itself, whether classes or metaclasses, as dyld
/// binds them; with -add_mergeable_debug_hook, those of the image's
/// own class lists it doesn't export, under the image's name. (Where a
/// class comes twice, the hook takes the first.)
fn hooked_classes<E: Target>(ctx: &Context<E>) -> Vec<(Vec<u8>, Vec<ObjcRef>)> {
let hook = &ctx.bundle_hook;
let mut libraries = Vec::new();
let mut merged = Vec::new();
for (name, mf) in &hook.merged {
let Some(obj) = ctx.objs.iter().position(|obj| std::ptr::eq(obj.mf, *mf)) else {
continue;
};
merged.push(obj);
libraries.push((name.clone(), listed_classes(ctx, |i| i == obj)));
}
for (name, ids) in &hook.reexported {
let defined = ids.iter().filter(|&&id| ctx.symbols[id].is_defined());
libraries.push((name.clone(), defined.map(|&id| ObjcRef::Sym(id, 0)).collect()));
}
if ctx.args.add_mergeable_debug_hook {
let mut classes = listed_classes(ctx, |i| !merged.contains(&i));
classes.retain(|&cls| !exports_class(ctx, cls));
libraries.push((leaf(ctx.args.output_install_name()), classes));
}
libraries
}
/// The classes the __objc_classlist sections of the live objects `of`
/// picks list.
fn listed_classes<E: Target>(ctx: &Context<E>, of: impl Fn(usize) -> bool) -> Vec<ObjcRef> {
let lists = (0..ctx.isecs.len() as u32).filter(|&i| {
let isec = &ctx.isecs[i];
let file = isec.file as usize;
of(file)
&& ctx.objs[file].is_reachable
&& isec.is_alive()
&& isec.hdr(&ctx.objs[file]).sectname() == b"__objc_classlist"
});
lists.flat_map(|i| crate::objc::list_entries(ctx, i).flatten()).collect()
}
/// Whether the image exports a class of its own, by the symbol its
/// class list names it by, under the export options the export passes
/// are yet to apply (see passes::hide_all_exports and the like).
fn exports_class<E: Target>(ctx: &Context<E>, cls: ObjcRef) -> bool {
let ObjcRef::Sym(id, 0) = cls else { return false };
let sym = &ctx.symbols[id];
let name = sym.name();
sym.is_extern()
&& !sym.is_private_extern()
&& !ctx.args.no_exported_symbols
&& ctx.args.exported_symbols.as_ref().is_none_or(|list| list.find(name) != -1)
&& ctx.args.unexported_symbols.find(name) == -1
}
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# Builds the objects of the hook for the classes of mergeable libraries
# that mold embeds (see bundle-hook.c and ../bundle_hook.rs), so that
# building mold needs no C compiler. Run it on macOS after changing
# bundle-hook.c, and commit what it writes.
#
# The objects are for the oldest macOS releases clang builds for: an
# object for a newer one than an image would make ld-prime warn (mold
# doesn't for its own, either way).
set -e
cd "$(dirname "$0")"
for target in arm64-apple-macos11.0 x86_64-apple-macos10.13; do
xcrun clang -target $target -O2 -Wall -Werror -fno-stack-protector \
-c bundle-hook.c -o bundle-hook-${target%%-*}.o
done
Binary file not shown.
Binary file not shown.
+94
View File
@@ -0,0 +1,94 @@
// The hook mold links into an image for the classes of the mergeable
// libraries it merges (-merge_*) or re-exports (-no_merge_*), and into
// a debug build of a mergeable dylib for the classes it doesn't export
// (-add_mergeable_debug_hook), as ld-prime does with a hook of its own.
// Such a class's code is no longer in its framework's binary, but the
// framework's resources still are in the framework's bundle in the
// app. The hook has the Objective-C runtime name the framework's
// binary in the app as the class's image, so that NSBundle's
// bundleForClass: (Swift's Bundle(for:)) finds that bundle.
//
// mold fills in the table of the classes (see src/bundle_hook.rs) and
// embeds the objects build-bundle-hook.sh makes of this file.
#include <dlfcn.h>
#include <limits.h>
#include <mach-o/dyld.h>
#include <objc/runtime.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// A class, the leaf name of its library's install name, and the path
// the hook gives for it, made on first use.
struct entry {
Class cls;
const char *name;
char *path;
};
struct table {
unsigned long count;
struct entry entries[];
};
extern struct table __mold_bundle_hook_table;
static char app_bundle[PATH_MAX];
static objc_hook_getImageName next_hook;
// The framework binary of a class's library in the app (as macOS lays
// an app out): <app>/Contents/Frameworks/<name>.framework/<name>. Two
// threads may make the path at once; then one leaks.
static BOOL get_image_name(Class cls, const char **out) {
struct table *t = &__mold_bundle_hook_table;
for (unsigned long i = 0; i < t->count; i++) {
struct entry *e = &t->entries[i];
if (e->cls != cls)
continue;
char *path = e->path;
if (!path && asprintf(&path, "%s/Contents/Frameworks/%s.framework/%s",
app_bundle, e->name, e->name) < 0)
break;
e->path = path;
*out = path;
return YES;
}
return next_hook(cls, out);
}
// Finds the main bundle as NSBundle does, from the executable's path:
// <bundle>/Contents/MacOS/<executable>, else the executable's
// directory. The hook is for an app's bundle only.
static int find_app_bundle(void) {
char exe[PATH_MAX];
uint32_t size = sizeof(exe);
if (_NSGetExecutablePath(exe, &size) || !realpath(exe, app_bundle))
return 0;
*strrchr(app_bundle, '/') = '\0';
size_t len = strlen(app_bundle);
const char *macos = "/Contents/MacOS";
if (len >= strlen(macos) && !strcmp(app_bundle + len - strlen(macos), macos))
app_bundle[len -= strlen(macos)] = '\0';
return len >= 4 && !strcmp(app_bundle + len - 4, ".app");
}
// Installs the hook as the image is initialized. Swift has a hook of
// its own, which answers for every Swift class and calls no other, and
// installs it on its first lookup of a type by name; the runtime asks
// the hook installed last first. So Swift's goes in first, if Swift is
// loaded. The objects are built for macOS releases that may lack
// objc_setHook_getImageName (10.14 has it), which is looked up too.
__attribute__((constructor)) static void install(void) {
if (!find_app_bundle())
return;
const void *(*lookup_type)(const char *, size_t, const void *,
const void *) =
dlsym(RTLD_DEFAULT, "swift_getTypeByMangledNameInContext");
if (lookup_type)
lookup_type("s14PartialKeyPathCyytG", 22, NULL, NULL);
void (*set_hook)(objc_hook_getImageName, objc_hook_getImageName *) =
dlsym(RTLD_DEFAULT, "objc_setHook_getImageName");
if (set_hook)
set_hook(get_image_name, &next_hook);
}
+8
View File
@@ -0,0 +1,8 @@
# Output chunks
A chunk is a contiguous region of the output file: the Mach header with
its load commands, an output section built from input sections, or a
table the linker synthesizes in `__LINKEDIT`. Each kind of section has
its own file in this directory. [`mod.rs`](mod.rs) defines `ChunkId` and
`ChunkHeader`, which every chunk has, and handles the Mach header and
load commands.
+192
View File
@@ -0,0 +1,192 @@
//! The LC_DYLD_INFO bind opcode stream: every slot dyld fills with an
//! import.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, output_section, segment_and_offset};
use crate::context::Context;
use crate::input_files::data_blob_binds;
use crate::macho::*;
use crate::util::encode_uleb;
/// The bind opcode stream: every slot dyld fills with an import.
#[derive(Debug)]
pub struct BindInfoSection {
pub hdr: ChunkHeader,
/// The stream, built during layout.
pub contents: Vec<u8>,
}
impl BindInfoSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), contents: Vec::new() }
}
}
impl Default for BindInfoSection {
fn default() -> Self {
Self::new()
}
}
/// Builds the bind opcode stream: it tells dyld which imported symbol to
/// write into each GOT slot. Runs during layout, once every segment
/// before __LINKEDIT has an address.
pub fn construct<E: Target>(ctx: &Context<E>) -> Vec<u8> {
let mut binds: Vec<(u64, crate::symbol::SymbolId, i64)> = Vec::new();
// GOT slots for imported symbols.
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if ctx.symbols[id].binds_as_import(ctx) {
binds.push((ctx.got.slot_addr(i), id, 0));
}
}
// Pointers in data sections initialized with an imported symbol's
// address.
for isec in ctx.isecs.iter() {
if !isec.is_emitted() {
continue;
}
let file = &ctx.objs[isec.file as usize];
for (addr, rel) in output_section::pointer_relocs(ctx, isec) {
if let Some(id) = rel.sym(file)
&& (ctx.symbols[id].binds_as_import(ctx)
|| ctx.symbols[id].is_dtrace_pointer_target())
{
binds.push((addr, id, rel.addend));
}
}
}
for (addr, id) in data_blob_binds(ctx) {
binds.push((addr, id, 0));
}
if binds.is_empty() {
return Vec::new();
}
// ld64 lists the binds by library, symbol, addend, then address, so
// each library and symbol is set once.
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),
ctx.symbols[b.1].name(),
b.2,
b.0,
))
});
let flags = |id: crate::symbol::SymbolId| {
if ctx.symbols[id].is_weak_ref() { BIND_SYMBOL_FLAGS_WEAK_IMPORT } else { 0 }
};
encode(bind_ops(ctx, &binds, |id| Some(ordinal(id)), flags))
}
/// Encodes a stream of bind opcodes, ending it.
pub(crate) fn encode(ops: Vec<Op>) -> Vec<u8> {
let mut buf = Vec::new();
for op in ops {
encode_op(&mut buf, op);
}
buf.push(BIND_OPCODE_DONE);
while !buf.len().is_multiple_of(8) {
buf.push(0);
}
buf
}
/// Appends a bind opcode.
pub(crate) fn encode_op(buf: &mut Vec<u8>, op: Op) {
match op {
Op::Dylib(ord) if ord <= 0 => {
buf.push(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | (ord & 0xf) as u8)
}
Op::Dylib(ord) if ord < 16 => buf.push(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | ord as u8),
Op::Dylib(ord) => {
buf.push(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB);
encode_uleb(buf, ord as u64);
}
Op::Symbol(name, flags) => {
buf.push(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM | flags);
buf.extend_from_slice(name);
buf.push(0);
}
Op::Type => buf.push(BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER),
Op::SegOffset(seg, off) => {
buf.push(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | seg as u8);
encode_uleb(buf, off);
}
Op::AddAddr(delta) => {
buf.push(BIND_OPCODE_ADD_ADDR_ULEB);
encode_uleb(buf, delta);
}
Op::Addend(addend) => {
buf.push(BIND_OPCODE_SET_ADDEND_SLEB);
crate::util::encode_sleb(buf, addend);
}
Op::Bind => buf.push(BIND_OPCODE_DO_BIND),
}
}
/// A bind opcode before encoding.
pub(crate) enum Op {
Dylib(i32),
Symbol(&'static [u8], u8),
Type,
SegOffset(usize, u64),
AddAddr(u64),
Addend(i64),
Bind,
}
/// The opcodes binding the sorted `binds`, each piece of the bind
/// machine's state set only when it changes:
/// the address moves by ADD_ADDR_ULEB within a segment, backwards too,
/// and by SET_SEGMENT_AND_OFFSET_ULEB into another one. `ordinal` is the
/// library each symbol binds to, if the stream names one, and `flags`
/// the flags its name goes with.
pub(crate) fn bind_ops<E: Target>(
ctx: &Context<E>,
binds: &[(u64, crate::symbol::SymbolId, i64)],
ordinal: impl Fn(crate::symbol::SymbolId) -> Option<i32>,
flags: impl Fn(crate::symbol::SymbolId) -> u8,
) -> Vec<Op> {
let mut ops = Vec::new();
let mut cur_ordinal = None;
let mut cur_symbol = None;
let mut cur_seg = None;
let mut cur_addr = 0;
let mut cur_addend = 0;
for &(addr, id, addend) in binds {
let sym = &ctx.symbols[id];
if let Some(ord) = ordinal(id)
&& cur_ordinal != Some(ord)
{
ops.push(Op::Dylib(ord));
cur_ordinal = Some(ord);
}
let flags = flags(id);
if cur_symbol != Some((id, flags)) {
ops.push(Op::Symbol(sym.name(), flags));
if cur_symbol.is_none() {
ops.push(Op::Type);
}
cur_symbol = Some((id, flags));
}
if cur_seg.is_none() || addr != cur_addr {
let (seg, off) = segment_and_offset(ctx, addr);
if cur_seg == Some(seg) {
ops.push(Op::AddAddr(addr.wrapping_sub(cur_addr)));
} else {
ops.push(Op::SegOffset(seg, off));
cur_seg = Some(seg);
}
}
if addend != cur_addend {
ops.push(Op::Addend(addend));
cur_addend = addend;
}
ops.push(Op::Bind);
cur_addr = addr + 8;
}
ops
}
+64
View File
@@ -0,0 +1,64 @@
//! __TEXT,__chain_starts: where the fixup chains of an image no dyld
//! loads start, for its own loader to walk (-fixup_chains_section), in
//! place of the LC_DYLD_CHAINED_FIXUPS payload.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
/// __TEXT,__chain_starts: a dyld_chained_starts_offsets - the chains'
/// pointer format, their count, then each chain's first fixup as an
/// offset from the image's address (see
/// chained_fixups::section_chain_starts).
#[derive(Debug)]
pub struct ChainStartsSection {
pub hdr: ChunkHeader,
/// The offsets of the chains' first fixups.
pub starts: Vec<u32>,
}
impl ChainStartsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__chain_starts");
hdr.p2align = 2;
hdr.size = Self::size(0);
Self { hdr, starts: Vec::new() }
}
/// The section's size for `n` chains.
pub fn size(n: usize) -> u64 {
8 + n as u64 * 4
}
}
impl Default for ChainStartsSection {
fn default() -> Self {
Self::new()
}
}
/// Finds where the chains __TEXT,__chain_starts lists start, which
/// follows from where the fixups in the other segments are. Returns
/// false if their number changes the section's size; the layout is
/// then done again (which moves later segments whole, and so no chain).
pub fn finish_chain_starts<E: Target>(ctx: &mut Context<E>) -> bool {
if !ctx.args.fixup_chains_section {
return true;
}
let starts = crate::chunks::chained_fixups::section_chain_starts(ctx);
let size = ChainStartsSection::size(starts.len());
let fits = size == ctx.chain_starts.hdr.size;
ctx.chain_starts.hdr.size = size;
ctx.chain_starts.starts = starts;
fits
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let starts = &ctx.chain_starts.starts;
let format = crate::chunks::chained_fixups::pointer_format(ctx) as u32;
buf[0..4].copy_from_slice(&format.to_le_bytes());
buf[4..8].copy_from_slice(&(starts.len() as u32).to_le_bytes());
for (i, &start) in starts.iter().enumerate() {
buf[8 + i * 4..12 + i * 4].copy_from_slice(&start.to_le_bytes());
}
}
+547
View File
@@ -0,0 +1,547 @@
//! The LC_DYLD_CHAINED_FIXUPS payload in __LINKEDIT: the modern
//! replacement for the rebase and bind opcode streams, with the fixup
//! chains it describes threaded through the data sections.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::{ChunkHeader, OutputSegment, output_section, rebase_info};
use crate::cmdline::Treatment;
use crate::context::Context;
use crate::fatal;
use crate::input_files::{FileId, data_blob_binds, data_blob_pointers};
use crate::macho::*;
use crate::symbol::{Symbol, SymbolId};
use crate::util::{push16, push32, push64};
#[derive(Debug)]
pub struct ChainedFixupsSection {
pub hdr: ChunkHeader,
/// The encoded payload, built during layout.
pub contents: Vec<u8>,
/// Every dynamic fixup, sorted by address.
pub fixups: Vec<Fixup>,
/// The import table: (symbol, table addend), in the order the binds
/// first name them; and each entry's index.
pub imports: Vec<(SymbolId, u64)>,
pub ordinals: ImportOrdinals,
/// Set when an x86-64 image laid out for chained fixups gets classic
/// dyld info instead, for an unaligned pointer.
pub disabled: bool,
}
/// Each import's index in the table, by (symbol, table addend).
pub type ImportOrdinals = std::collections::HashMap<(SymbolId, u64), usize>;
impl ChainedFixupsSection {
pub fn new() -> Self {
Self {
hdr: ChunkHeader::linkedit(),
contents: Vec::new(),
fixups: Vec::new(),
imports: Vec::new(),
ordinals: std::collections::HashMap::new(),
disabled: false,
}
}
}
impl Default for ChainedFixupsSection {
fn default() -> Self {
Self::new()
}
}
/// What build_chained_fixups makes, for the caller to store on the
/// context: the encoded payload, the fixups, the import table and each
/// import's index.
pub type ChainedFixups = (Vec<u8>, Vec<Fixup>, Vec<(SymbolId, u64)>, ImportOrdinals);
/// A fixup: its address, the symbol it binds (None for a rebase), and
/// the addend.
pub type Fixup = (u64, Option<SymbolId>, u64);
/// The import-table addend of a bind: addends up to 255 are carried
/// inline in the fixup word and share the symbol's addend-0 entry.
fn table_addend(addend: u64) -> u64 {
if addend <= MAX_INLINE_ADDEND { 0 } else { addend }
}
/// The import table: an entry per distinct (symbol, table addend), in
/// the order the binds first name them.
fn import_table(fixups: &[Fixup]) -> (Vec<(SymbolId, u64)>, ImportOrdinals) {
let mut imports = Vec::new();
let mut ordinals = ImportOrdinals::new();
for &(_, sym, addend) in fixups {
let Some(sym) = sym else { continue };
ordinals.entry((sym, table_addend(addend))).or_insert_with(|| {
imports.push((sym, table_addend(addend)));
imports.len() - 1
});
}
(imports, ordinals)
}
/// The pointer format of the chains. A rebase target is a VM address
/// under DYLD_CHAINED_PTR_64 and an offset from the image's load
/// address under DYLD_CHAINED_PTR_64_OFFSET, which dyld reads from
/// macOS 12 and iOS 15 on. ld-prime writes the latter for such a (or a
/// firmware) target on every architecture and output kind, and for a
/// -static image, which no dyld reads, whatever its target; the former
/// where chains came earlier (iOS 13.4, see cmdline::is_new_os) or when
/// -fixup_chains forces them on an older OS.
pub(crate) fn pointer_format<E: Target>(ctx: &Context<E>) -> u16 {
if ctx.args.static_link || ctx.args.targets(&crate::cmdline::VERSION_2021_FALL) {
DYLD_CHAINED_PTR_64_OFFSET
} else {
DYLD_CHAINED_PTR_64
}
}
fn pad(buf: &mut Vec<u8>, align: usize) {
buf.resize(buf.len().next_multiple_of(align), 0);
}
/// Builds the LC_DYLD_CHAINED_FIXUPS payload. Instead of opcode
/// streams, chained fixups store, per page of each segment, the offset
/// of the first fixup; each 64-bit fixup word in the data itself then
/// encodes its target (a rebase value or an import ordinal) plus the
/// distance to the next fixup in the page, forming a chain dyld walks.
/// The payload is a header, the starts of the chains, the import table
/// and the imports' names. Returns None if the image must have classic
/// dyld info instead (see check_pointer_alignment).
pub fn build_chained_fixups<E: Target>(ctx: &Context<E>) -> Option<ChainedFixups> {
// An image with nothing to fix up still gets the payload (a
// header and a starts table with no pages), as ld64 writes it:
// dyld reads the format from the load command, and its absence
// would mean classic dyld info.
let (fixups, unaligned) = collect_fixups(ctx);
if !check_pointer_alignment(ctx, &fixups, unaligned, true) {
return None;
}
let (imports, ordinals) = import_table(&fixups);
let format = import_format(&imports);
let mut buf = Vec::new();
// dyld_chained_fixups_header; the offsets are backpatched.
push32(&mut buf, 0); // fixups_version
push32(&mut buf, 0); // starts_offset
push32(&mut buf, 0); // imports_offset
push32(&mut buf, 0); // symbols_offset
push32(&mut buf, imports.len() as u32);
push32(&mut buf, format);
push32(&mut buf, 0); // symbols_format: uncompressed
pad(&mut buf, 8);
let starts_offset = buf.len();
buf[4..8].copy_from_slice(&(starts_offset as u32).to_le_bytes());
write_starts_in_image(ctx, &mut buf, &fixups);
// The import table, aligned only as its entries need: 4 bytes, or 8
// for 64-bit addends.
pad(&mut buf, if format == DYLD_CHAINED_IMPORT_ADDEND64 { 8 } else { 4 });
let imports_offset = buf.len();
buf[8..12].copy_from_slice(&(imports_offset as u32).to_le_bytes());
write_imports(ctx, &mut buf, &imports, format);
// The imports' names, after a leading NUL.
let symbols_offset = buf.len();
buf[12..16].copy_from_slice(&(symbols_offset as u32).to_le_bytes());
buf.push(0);
for &(sym, _) in &imports {
buf.extend_from_slice(ctx.symbols[sym].name());
buf.push(0);
}
pad(&mut buf, 8);
Some((buf, fixups, imports, ordinals))
}
/// The import table's format: the narrowest whose entries hold every
/// import's table addend.
fn import_format(imports: &[(SymbolId, u64)]) -> u32 {
let max_addend = imports.iter().map(|&(_, a)| a).max().unwrap_or(0);
if max_addend == 0 {
DYLD_CHAINED_IMPORT
} else if imports.iter().all(|&(_, a)| i32::try_from(a as i64).is_ok()) {
DYLD_CHAINED_IMPORT_ADDEND
} else {
DYLD_CHAINED_IMPORT_ADDEND64
}
}
/// Appends dyld_chained_starts_in_image: an entry per segment command,
/// the offset of the segment's starts table, which follow, each
/// 8-aligned, with the offset of each page's first fixup.
fn write_starts_in_image<E: Target>(ctx: &Context<E>, buf: &mut Vec<u8>, fixups: &[Fixup]) {
let starts_offset = buf.len();
// One per segment command: a -preload image's __LINKEDIT has none.
let seg_count = ctx.segments.len() - usize::from(ctx.args.preload);
push32(buf, seg_count as u32);
let seg_info_table = buf.len();
for _ in 0..seg_count {
push32(buf, 0);
}
// A segment's offset counts from the image's own address, which
// -image_base may move.
let image_base = ctx.mach_header.hdr.addr;
for (seg_idx, seg) in ctx.segments.iter().enumerate() {
let fx = segment_fixups(fixups, seg);
if fx.is_empty() {
continue;
}
pad(buf, 8);
let off = buf.len() - starts_offset;
let ent = seg_info_table + seg_idx * 4;
buf[ent..ent + 4].copy_from_slice(&(off as u32).to_le_bytes());
// dyld reads chains only in 4 KiB or 16 KiB pages, which an
// arm64 image's -segalign sets.
let page_size = chain_page_size(ctx);
if !matches!(page_size, 0x1000 | 0x4000) {
crate::error!(
"chained fixups need a -segalign of 0x1000 or 0x4000, not {page_size:#x}"
);
break;
}
let npages = ((fx.last().unwrap().0 + 1 - seg.cmd.vmaddr).div_ceil(page_size)) as usize;
// The record is 22 bytes of fields plus one u16 per page; its
// size counts just those, without padding.
push32(buf, 22 + npages as u32 * 2);
push16(buf, page_size as u16);
push16(buf, pointer_format(ctx));
// A layout in error may put a segment below the image base; the
// table is never written then.
push64(buf, seg.cmd.vmaddr.wrapping_sub(image_base));
push32(buf, 0); // max_valid_pointer
push16(buf, npages as u16);
let mut j = 0;
for i in 0..npages {
let page_addr = seg.cmd.vmaddr + i as u64 * page_size;
while j < fx.len() && fx[j].0 < page_addr {
j += 1;
}
if j < fx.len() && fx[j].0 < page_addr + page_size {
push16(buf, (fx[j].0 - page_addr) as u16);
} else {
push16(buf, DYLD_CHAINED_PTR_START_NONE);
}
}
}
}
/// Appends the import table's entries in `format`. Each import names a
/// string of its own, repeated for a symbol imported with several
/// addends.
fn write_imports<E: Target>(
ctx: &Context<E>,
buf: &mut Vec<u8>,
imports: &[(SymbolId, u64)],
format: u32,
) {
// The names follow a leading NUL.
let mut name_off: u32 = 1;
for &(sym, addend) in imports {
let weak = ctx.symbols[sym].is_weak_ref() as u32;
match format {
DYLD_CHAINED_IMPORT => {
let ordinal = import_ordinal(ctx, sym, 8) as u32;
push32(buf, ordinal | (weak << 8) | (name_off << 9));
}
DYLD_CHAINED_IMPORT_ADDEND => {
let ordinal = import_ordinal(ctx, sym, 8) as u32;
push32(buf, ordinal | (weak << 8) | (name_off << 9));
push32(buf, addend as u32);
}
_ => {
let ordinal = import_ordinal(ctx, sym, 16);
push64(buf, ordinal | ((weak as u64) << 16) | ((name_off as u64) << 32));
push64(buf, addend);
}
}
name_off += ctx.symbols[sym].name().len() as u32 + 1;
}
}
/// The library ordinal an import's entry holds, in its `bits`. An
/// import names its dylib; one of this image's own weak definitions is
/// bound by weak lookup (ordinal -3), which makes dyld search every
/// loaded image for the coalesced winner, a class bound to the image
/// itself names it (0), and an interposable export is a flat lookup (-2)
/// 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);
let sym = &ctx.symbols[sym];
match sym.file() {
Some(FileId::Dylib(_)) if !sym.binds_weak_lookup(ctx) => {
chained_import_ordinal(ctx, sym, bits)
}
_ if sym.binds_to_self(ctx) => BIND_SPECIAL_DYLIB_SELF as u64,
_ if sym.is_interposable_export(ctx) && !sym.binds_weak_lookup(ctx) => {
special(sym.export_bind_ordinal(ctx))
}
_ if sym.is_dtrace_pointer_target() => special(BIND_SPECIAL_DYLIB_FLAT_LOOKUP),
_ => special(BIND_SPECIAL_DYLIB_WEAK_LOOKUP),
}
}
/// The library ordinal as the chained-fixups import formats encode
/// it in a `bits`-wide field: dylib ordinals as they are, the
/// special ones as negative values in the field's two's complement,
/// and, unlike the bind opcodes, the main executable as -1 (0 is
/// the image itself there).
fn chained_import_ordinal<E: Target>(ctx: &Context<E>, sym: &Symbol, bits: u32) -> u64 {
let ordinal = match sym.bind_ordinal(ctx) {
BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE => -1i64,
n => n as i64,
};
(ordinal as u64) & ((1u64 << bits) - 1)
}
/// The pages the fixup chains of a segment are cut into, from its start:
/// a chain stays in one page, which dyld takes to be 4 KiB or 16 KiB
/// long. An arm64 image's are its segment alignment (no other is
/// accepted with fixups), an x86-64 image's 4 KiB whatever its segment
/// alignment.
fn chain_page_size<E: Target>(ctx: &Context<E>) -> u64 {
if E::CPUTYPE == CPU_TYPE_X86_64 { 0x1000 } else { ctx.args.segment_align }
}
/// The farthest a fixup can be from the next one of its chain: the
/// 12-bit `next` field counts 4-byte strides.
const MAX_CHAIN_STRIDE: u64 = 0xfff * 4;
/// The chains of an image whose starts __TEXT,__chain_starts lists
/// (-fixup_chains_section): each segment's fixups chain on from the
/// first, a chain ending where the next fixup is out of its stride's
/// reach - none is cut at a page, as dyld would need. Returns each
/// chain's start, an offset from the image's address.
pub fn section_chain_starts<E: Target>(ctx: &Context<E>) -> Vec<u32> {
// The first fixups of the chains of those in [start, end).
fn chains(addrs: &[u64], start: u64, end: u64) -> impl Iterator<Item = u64> + '_ {
let lo = addrs.partition_point(|&a| a < start);
let hi = addrs.partition_point(|&a| a < end);
(lo..hi)
.filter(move |&i| i == lo || addrs[i] - addrs[i - 1] > MAX_CHAIN_STRIDE)
.map(|i| addrs[i])
}
let (fixups, _) = collect_fixups(ctx);
let addrs: Vec<u64> = fixups.iter().map(|&(addr, ..)| addr).collect();
let base = ctx.mach_header.hdr.addr;
(ctx.segments.iter())
.flat_map(|seg| chains(&addrs, seg.cmd.vmaddr, seg.cmd.vmaddr + seg.cmd.vmsize))
.map(|addr| addr.wrapping_sub(base) as u32)
.collect()
}
/// Writes the fixup chains into the copied output: every fixup word is
/// rewritten to encode its payload plus the 4-byte-stride distance to
/// the next fixup of its chain, in the same page (or, under
/// -fixup_chains_section, in its stride's reach; see
/// section_chain_starts).
pub fn write_fixup_chains<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let page_shift = chain_page_size(ctx).trailing_zeros();
// What a rebase target counts from: zero for a VM address, the
// image's own address for an offset.
let target_base = match pointer_format(ctx) {
DYLD_CHAINED_PTR_64_OFFSET => ctx.mach_header.hdr.addr,
_ => 0,
};
for seg in &ctx.segments {
let fx = segment_fixups(&ctx.chained_fixups.fixups, seg);
// Pages count from the segment's start.
let page = |addr: u64| (addr - seg.cmd.vmaddr) >> page_shift;
let chains_to = |addr: u64, next: u64| {
if ctx.args.fixup_chains_section {
next - addr <= MAX_CHAIN_STRIDE
} else {
page(next) == page(addr)
}
};
for (i, &(addr, sym, addend)) in fx.iter().enumerate() {
let next = match fx.get(i + 1) {
Some(&(next_addr, _, _)) if chains_to(addr, next_addr) => (next_addr - addr) / 4,
_ => 0,
};
if addr % 4 != 0 {
fatal!("unaligned fixup; re-link with -no_fixup_chains");
}
let off = (seg.cmd.fileoff + (addr - seg.cmd.vmaddr)) as usize;
let word = match sym {
Some(sym) => {
// dyld_chained_ptr_64_bind
let ordinal = ctx.chained_fixups.ordinals[&(sym, table_addend(addend))] as u64;
let inline_addend = if addend <= MAX_INLINE_ADDEND { addend } else { 0 };
ordinal | (inline_addend << 24) | (next << 51) | (1 << 63)
}
None => {
let val = u64::from_le_bytes(buf[off..off + 8].try_into().unwrap());
rebase_word(ctx, addr, val, target_base) | (next << 51)
}
};
buf[off..off + 8].copy_from_slice(&word.to_le_bytes());
}
}
}
/// The fixups, sorted by address, that lie in a segment.
fn segment_fixups<'a>(fixups: &'a [Fixup], seg: &OutputSegment) -> &'a [Fixup] {
let lo = fixups.partition_point(|&(a, _, _)| a < seg.cmd.vmaddr);
let hi = fixups.partition_point(|&(a, _, _)| a < seg.cmd.vmaddr + seg.cmd.vmsize);
&fixups[lo..hi]
}
/// A rebase's dyld_chained_ptr_64_rebase word, but for the link to the
/// next fixup: the pointer at `addr` holds `val`, the absolute target
/// address with its top byte (high8), which the word carries apart;
/// the target counts from `target_base`, and must fit in 36 bits.
fn rebase_word<E: Target>(ctx: &Context<E>, addr: u64, val: u64, target_base: u64) -> u64 {
let high8 = val >> 56;
let target = (val & 0x00ff_ffff_ffff_ffff).wrapping_sub(target_base);
if target >> 36 != 0 {
let sect = ctx
.chunks
.iter()
.map(|&id| ctx.chunk_header(id))
.find(|hdr| hdr.addr <= addr && addr < hdr.addr + hdr.size)
.map(|hdr| [hdr.segname, b",", hdr.sectname].concat())
.unwrap_or_default();
fatal!(
"rebase target unencodable at {addr:#x} in {} (value {val:#x}); re-link with -no_fixup_chains",
crate::error::raw(&sect)
);
}
target | (high8 << 36)
}
/// Collects the fixups, sorted by address, and those of subsections at
/// an address no multiple of 8, as (subsection, address) pairs, for
/// check_pointer_alignment.
fn collect_fixups<E: Target>(ctx: &Context<E>) -> (Vec<Fixup>, Vec<(u32, u64)>) {
// Every subsection's fixups are independent; collect them on all
// cores and sort the union in parallel, as mold does.
let unaligned = std::sync::Mutex::new(Vec::new());
let mut fixups: Vec<Fixup> = ctx
.isecs
.par_iter()
.enumerate()
.filter(|(_, isec)| isec.is_emitted())
.flat_map_iter(|(id, isec)| {
let unaligned = &unaligned;
let file = &ctx.objs[isec.file as usize];
output_section::pointer_relocs(ctx, isec).filter_map(move |(addr, rel)| {
let fixup = match rel.sym(file) {
Some(id)
if ctx.symbols[id].is_absolute(ctx)
&& !ctx.symbols[id].binds_at_runtime(ctx) =>
{
None
}
Some(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))
}
_ if rel.refers_to_tls(ctx, file) => None,
_ => Some((addr, None, 0)),
};
if fixup.is_some() && !addr.is_multiple_of(8) {
unaligned.lock().unwrap().push((id as u32, addr));
}
fixup
})
})
.collect();
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if ctx.symbols[id].is_absolute(ctx) && !ctx.symbols[id].binds_at_runtime(ctx) {
continue;
}
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));
}
for i in 0..ctx.objc_stubs.symbols.len() + ctx.objc_stubs.extra_selrefs.len() {
let slot = ctx.objc_stubs.selref_addr(ctx, i);
fixups.push((slot, None, 0));
}
for (addr, _) in data_blob_pointers(ctx) {
fixups.push((addr, None, 0));
}
for (addr, id) in data_blob_binds(ctx) {
fixups.push((addr, Some(id), 0));
}
// The pointers of an image nothing slides keep their addresses:
// its chains hold only binds.
if rebase_info::is_never_slid(ctx) {
fixups.retain(|&(_, sym, _)| sym.is_some());
}
fixups.par_sort_unstable_by_key(|&(addr, _, _)| addr);
(fixups, unaligned.into_inner().unwrap())
}
/// The largest addend a chained bind can carry inline; anything bigger
/// goes into the import table.
const MAX_INLINE_ADDEND: u64 = 255;
/// Checks the pointers of an image with classic dyld info as
/// check_pointer_alignment does.
pub fn check_classic_pointers<E: Target>(ctx: &Context<E>) {
if ctx.args.unaligned_pointers != Treatment::Suppress {
let (fixups, unaligned) = collect_fixups(ctx);
check_pointer_alignment(ctx, &fixups, unaligned, false);
}
}
/// Each pointer dyld fixes up should be 8-aligned, as the links of a
/// fixup chain are words. Reports the `unaligned` ones of subsections
/// (but text relocations, which report_text_relocs reports) as
/// -unaligned_pointers says: arm64 refuses them in chains. An x86-64
/// image laid out for chains gets classic dyld info instead, on any
/// unaligned fixup, saying so; its load commands grow by 16 bytes then,
/// which the header padding holds (see header_pad). Returns false for
/// that fallback.
fn check_pointer_alignment<E: Target>(
ctx: &Context<E>,
fixups: &[Fixup],
mut unaligned: Vec<(u32, u64)>,
chained: bool,
) -> bool {
unaligned.retain(|&(_, addr)| !ctx.text_reloc_ranges.iter().any(|r| r.contains(&addr)));
unaligned.sort_unstable_by_key(|&(_, addr)| addr);
let fallback = chained
&& E::CPUTYPE == CPU_TYPE_X86_64
&& !ctx.args.without_dyld()
&& (!unaligned.is_empty() || fixups.iter().any(|&(addr, ..)| !addr.is_multiple_of(8)));
if fallback {
crate::warn!("disabling chained fixups because of unaligned pointers");
}
for (id, addr) in unaligned {
let isec = &ctx.isecs[id as usize];
let place = isec.location(ctx, (addr - isec.addr(ctx)) as u32);
let place = crate::error::raw(&place);
match ctx.args.unaligned_pointers {
Treatment::Error => {
crate::error!("pointer not aligned at {addr:#x} in {place}");
break;
}
Treatment::Warning => crate::warn!("pointer not aligned at {addr:#x} in {place}"),
Treatment::Suppress => break,
}
}
!fallback
}
+231
View File
@@ -0,0 +1,231 @@
//! The ad-hoc code signature: SHA-256 page hashes in a code directory, the
//! last chunk of the file.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::macho::*;
use crate::util::align_to;
/// The ad-hoc code signature. Must be the last chunk in the file.
#[derive(Debug)]
pub struct CodeSignatureSection {
pub hdr: ChunkHeader,
}
impl CodeSignatureSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 4;
Self { hdr }
}
}
impl Default for CodeSignatureSection {
fn default() -> Self {
Self::new()
}
}
/// Whether the signature has a SHA-1 code directory too, for a loader
/// that reads no other: macOS before 10.11.4 and iOS and tvOS before 11
/// check SHA-1 page hashes only. ld-prime adds one, ahead of the
/// SHA-256 directory, for an image targeting such a release (an arm64
/// macOS one too, though no such release runs it), for an x86-64 one for
/// firmware, whose loader it can't tell, and for a -static image, which
/// no dyld loads, on either architecture. A simulator's image never has
/// one: the Mac's own kernel checks its pages.
fn has_sha1_directory<E: Target>(ctx: &Context<E>) -> bool {
let minos = ctx.args.platform_minos;
ctx.args.static_link
|| match ctx.args.platform {
PLATFORM_MACOS => minos < encode_version(10, 11, 4),
PLATFORM_IOS | PLATFORM_TVOS => minos < encode_version(11, 0, 0),
PLATFORM_FIRMWARE | 0 => E::CPUTYPE == CPU_TYPE_X86_64,
_ => false,
}
}
/// The length of a code directory: its fixed part, the NUL-terminated
/// identifier and the page hashes right after it, unpadded as ld-prime
/// writes them.
fn directory_size(ident: &[u8], nblocks: u64, hash_size: usize) -> u64 {
88 + ident.len() as u64 + 1 + nblocks * hash_size as u64
}
/// The length of the signature's superblob, for a signature placed at
/// `fileoff`: its header and blob index, then the code directories, one
/// right after another.
fn superblob_size<E: Target>(ctx: &Context<E>, fileoff: u64) -> u64 {
let ident = identifier(&ctx.args);
let nblocks = fileoff.div_ceil(CS_PAGE_SIZE);
let size = 12 + 8 + directory_size(ident, nblocks, SHA256_SIZE);
if has_sha1_directory(ctx) {
size + 8 + directory_size(ident, nblocks, SHA1_SIZE)
} else {
size
}
}
/// Returns the size of the code signature given the file offset it will
/// be placed at: the superblob, zero-padded to 8 bytes.
pub fn size<E: Target>(ctx: &Context<E>, fileoff: u64) -> u64 {
align_to(superblob_size(ctx, fileoff), 8)
}
/// The signature's identifier, as ld-prime takes it: the leaf name of
/// the image's install name, which -install_name gives (an executable's
/// too), else -final_output, else the output path - what follows the
/// last slash, so nothing for a name that ends in one.
fn identifier(args: &crate::cmdline::Args) -> &[u8] {
args.output_install_name().rsplit(|&b| b == b'/').next().unwrap()
}
fn push_be32(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn push_be64(buf: &mut Vec<u8>, val: u64) {
buf.extend_from_slice(&val.to_be_bytes());
}
/// SHA256 of every code-signature page (4KiB) of `data`, computed in
/// parallel: the hashes are independent. The last page may be short.
/// These are the code directory's page hashes, and the UUID is derived
/// from them too.
pub fn page_hashes(data: &[u8]) -> Vec<[u8; SHA256_SIZE]> {
let page = CS_PAGE_SIZE as usize;
data.par_chunks(page)
.map(|chunk| {
let mut hash = [0; SHA256_SIZE];
crate::util::sha256(chunk, &mut hash);
hash
})
.collect()
}
/// Recomputes the hashes of the pages that overlap `data[range]`, after
/// those bytes changed.
pub fn rehash_pages(data: &[u8], hashes: &mut [[u8; SHA256_SIZE]], range: std::ops::Range<usize>) {
let page = CS_PAGE_SIZE as usize;
let first = range.start / page;
let last = range.end.div_ceil(page).min(hashes.len());
for (i, hash) in hashes[first..last].iter_mut().enumerate() {
let start = (first + i) * page;
let end = (start + page).min(data.len());
crate::util::sha256(&data[start..end], hash);
}
}
/// Writes the ad-hoc code signature at the code signature chunk's
/// offset, from the page hashes of the file contents before it
/// (page_hashes, brought up to date after the header's last change).
///
/// On ARM64 macOS a code signature is mandatory: the kernel refuses to
/// run an executable without one. The signature we create is just SHA256
/// hashes of every page, marked ad-hoc and linker-signed; no signing
/// identity is involved. One with a SHA-1 directory too (see
/// has_sha1_directory) indexes that as the code directory and the
/// SHA-256 one as the first alternate.
pub fn write<E: Target>(ctx: &Context<E>, buf: &mut [u8], hashes: &[[u8; SHA256_SIZE]]) {
let cs_off = ctx.code_signature.hdr.fileoff;
let sha1_hashes: Option<Vec<[u8; SHA1_SIZE]>> = has_sha1_directory(ctx).then(|| {
buf[..cs_off as usize]
.par_chunks(CS_PAGE_SIZE as usize)
.map(|chunk| {
let mut hash = [0; SHA1_SIZE];
crate::util::sha1(chunk, &mut hash);
hash
})
.collect()
});
// All code signature fields are big-endian.
let mut sig = Vec::with_capacity(ctx.code_signature.hdr.size as usize);
// The superblob header and the index of its blobs, the code
// directories.
push_be32(&mut sig, CSMAGIC_EMBEDDED_SIGNATURE);
push_be32(&mut sig, superblob_size(ctx, cs_off) as u32);
match &sha1_hashes {
Some(sha1) => {
push_be32(&mut sig, 2);
push_be32(&mut sig, CSSLOT_CODEDIRECTORY);
push_be32(&mut sig, 28);
push_be32(&mut sig, CSSLOT_ALTERNATE_CODEDIRECTORIES);
let ident = identifier(&ctx.args);
push_be32(&mut sig, 28 + directory_size(ident, sha1.len() as u64, SHA1_SIZE) as u32);
push_code_directory(ctx, &mut sig, CS_HASHTYPE_SHA1, sha1.as_flattened(), SHA1_SIZE);
}
None => {
push_be32(&mut sig, 1);
push_be32(&mut sig, CSSLOT_CODEDIRECTORY);
push_be32(&mut sig, 20);
}
}
push_code_directory(ctx, &mut sig, CS_HASHTYPE_SHA256, hashes.as_flattened(), SHA256_SIZE);
// The chunk is 8-byte aligned in size; the superblob's length
// above leaves the padding out.
sig.resize(ctx.code_signature.hdr.size as usize, 0);
debug_assert_eq!(sig.len() as u64, ctx.code_signature.hdr.size);
buf[cs_off as usize..cs_off as usize + sig.len()].copy_from_slice(&sig);
}
/// Appends to `sig` a code directory of the page hashes `hashes`, each
/// `hash_size` bytes of hash type `hash_type`.
fn push_code_directory<E: Target>(
ctx: &Context<E>,
sig: &mut Vec<u8>,
hash_type: u8,
hashes: &[u8],
hash_size: usize,
) {
let cs_off = ctx.code_signature.hdr.fileoff;
let ident = identifier(&ctx.args);
let ident_size = ident.len() as u64 + 1;
let nblocks = cs_off.div_ceil(CS_PAGE_SIZE);
debug_assert_eq!(hashes.len() as u64, nblocks * hash_size as u64);
// (__TEXT, but for a -static image's renamed by -rename_segment.)
let text = ctx.segments.iter().find(|s| s.name == ctx.mach_header.hdr.segname).unwrap();
// The executable segment's limit, as ld-prime sets it: the size of
// __TEXT,__text (0 without one), not of the whole segment.
let text_size = ctx
.chunks
.iter()
.map(|&id| ctx.chunk_header(id))
.find(|hdr| hdr.segname == b"__TEXT" && hdr.sectname == b"__text")
.map_or(0, |hdr| hdr.size);
push_be32(sig, CSMAGIC_CODEDIRECTORY);
push_be32(sig, directory_size(ident, nblocks, hash_size) as u32);
push_be32(sig, CS_SUPPORTSEXECSEG); // version
push_be32(sig, CS_ADHOC | CS_LINKER_SIGNED); // flags
push_be32(sig, (88 + ident_size) as u32); // hash offset
push_be32(sig, 88); // identifier offset
push_be32(sig, 0); // special slots
push_be32(sig, nblocks as u32); // code slots
push_be32(sig, cs_off as u32); // code limit
sig.push(hash_size as u8);
sig.push(hash_type);
sig.push(0); // platform
sig.push(CS_PAGE_SIZE.trailing_zeros() as u8);
push_be32(sig, 0); // spare2
push_be32(sig, 0); // scatter offset
push_be32(sig, 0); // team offset
push_be32(sig, 0); // spare3
push_be64(sig, 0); // code limit 64
push_be64(sig, text.cmd.fileoff); // exec segment base
push_be64(sig, text_size); // exec segment limit
let exec_seg_flags =
if ctx.args.output_type == MH_EXECUTE { CS_EXECSEG_MAIN_BINARY } else { 0 };
push_be64(sig, exec_seg_flags); // exec segment flags
sig.extend_from_slice(ident);
sig.push(0);
sig.extend_from_slice(hashes);
}
+90
View File
@@ -0,0 +1,90 @@
//! LC_DATA_IN_CODE: ranges inside __text that hold data (jump tables, inline
//! constants), so disassemblers and the signature verifier can treat them
//! as bytes.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::input_sections::InputSection;
/// LC_DATA_IN_CODE: ranges inside __text that hold data (jump tables,
/// inline constants), so disassemblers and the signature verifier can
/// treat them as bytes.
#[derive(Debug)]
pub struct DataInCodeSection {
pub hdr: ChunkHeader,
/// The entries (fileoff, length, kind), built once when layout
/// reaches __LINKEDIT.
pub entries: Vec<(u32, u16, u16)>,
}
impl DataInCodeSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, entries: Vec::new() }
}
}
impl Default for DataInCodeSection {
fn default() -> Self {
Self::new()
}
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
write_entries(&ctx.data_in_code.entries, buf);
}
/// Writes data-in-code entries (offset, length, kind) at the start of
/// `buf`, 8 bytes each.
pub fn write_entries(entries: &[(u32, u16, u16)], buf: &mut [u8]) {
let mut p = 0;
for &(off, len, kind) in entries {
buf[p..p + 4].copy_from_slice(&off.to_le_bytes());
buf[p + 4..p + 6].copy_from_slice(&len.to_le_bytes());
buf[p + 6..p + 8].copy_from_slice(&kind.to_le_bytes());
p += 8;
}
}
/// Live data-in-code entries as (subsection, offset within it,
/// length, kind). In an object, an entry's offset is an address in
/// the object's own address space (sections there are laid out from
/// zero), which find_subsec maps to the owning subsection. Entries go
/// with their subsection: a dead-stripped one's vanish, and so do those
/// of a coalesced-away weak copy or a folded function, since the copy
/// that stays brings its own (ld64 writes each range once).
fn live_entries<E: Target>(
ctx: &Context<E>,
) -> impl Iterator<Item = (&InputSection, u64, u16, u16)> {
ctx.objs.iter().filter(|obj| obj.is_reachable).flat_map(move |obj| {
obj.dice.iter().filter_map(move |&(off, len, kind)| {
let (isec, off_in) = obj.find_subsec(&ctx.isecs, off as u64)?;
let isec = &ctx.isecs[isec];
isec.is_emitted().then_some((isec, off_in, len, kind))
})
})
}
/// Builds the LC_DATA_IN_CODE entries, sorted: each where `pos` puts
/// its output section - at its file offset in a final image, its
/// address in an object (a -r output) - plus the entry's offset there.
/// A final link runs it when layout reaches __LINKEDIT: the __text file
/// offsets the entries record are final by then, so the table is built
/// exactly once (sold builds its contents in compute_size the same way)
/// and copied out verbatim.
pub fn construct<E: Target>(
ctx: &Context<E>,
pos: impl Fn(&ChunkHeader) -> u64,
) -> Vec<(u32, u16, u16)> {
let mut out: Vec<(u32, u16, u16)> = live_entries(ctx)
.map(|(isec, off_in, len, kind)| {
let off =
pos(ctx.chunk_header(isec.output_section().unwrap())) + isec.offset as u64 + off_in;
(off as u32, len, kind)
})
.collect();
out.sort_unstable();
out
}
+280
View File
@@ -0,0 +1,280 @@
//! __TEXT,__delay_stubs and __TEXT,__delay_helper: the code through
//! which an image reaches the symbols of the dylibs it initializes at
//! the first use of one (-delay-l and the like). dyld loads and binds
//! such a dylib at launch as any other, but runs its initializers only
//! when the image dlopen()s it: each stub and helper checks the flag
//! word of the dylib's dlopen helper, has the helper dlopen the dylib
//! (which sets the flag) the first time, and then goes on through the
//! symbol's __got slot.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Place, Places, push};
use crate::chunks::symtab::{NamedEntry, local_msym};
use crate::chunks::{ChunkHeader, ChunkId, stubs};
use crate::context::Context;
use crate::input_sections::Reloc;
use crate::macho::*;
use crate::symbol::SymbolId;
/// A __delay_stubs entry, which the calls of a symbol branch to:
/// `_foo$delayInitStub`. It jumps through a __got slot of its own when
/// GOT loads of the symbol read another one.
#[derive(Debug)]
pub struct DelayStub {
pub sym: SymbolId,
/// The stub's local symbol.
pub name: &'static [u8],
/// The dlopen helper of the symbol's dylib (an index into
/// DelayInit::dlopens), and the stub's __got slot.
pub dlopen: u32,
pub got: u32,
}
/// What a __delay_helper entry before the dlopen helpers stands in for.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum DelayUse {
/// A load of the symbol's address from the GOT into register
/// `reg`: arm64's adrp of the slot's page becomes a call of
/// `_foo$loadHelper_<reg>`, which returns with the slot's page in
/// the register, for the ldr under the adrp to load from it;
/// x86-64's movq becomes a call of one that loads the slot. arm64
/// code that may not have saved its link register branches instead
/// to a helper of its own, which branches back past the adrp:
/// `site` names that adrp (subsection, offset).
Load { reg: u8, site: Option<(u32, u32)> },
/// x86-64's test of a weak import, cmpq $0 of its GOT slot: it
/// becomes a call of `_foo$cmpHelper`, which compares the slot
/// instead, leaving the flags for the code after the call.
Cmp,
}
/// A delay-init stub or helper, as LC_SEGMENT_SPLIT_INFO tells them
/// apart (see Target::delay_refs).
#[derive(Clone, Copy, Debug)]
pub enum DelayCode {
Stub,
Helper(DelayUse),
Dlopen,
}
/// What a delay-init stub or helper refers to: the flag word, the GOT
/// slot it goes through, the dlopen helper it calls, the code past the
/// site a frameless load helper returns to, and a dlopen helper's
/// install name and _dlopen's stub.
#[derive(Clone, Copy, Debug)]
pub enum DelayTarget {
Flag,
Slot,
DlopenHelper,
Site,
Name,
Dlopen,
}
/// A __delay_helper entry for GOT loads or compares of a symbol.
#[derive(Debug)]
pub struct DelayHelper {
pub sym: SymbolId,
pub kind: DelayUse,
/// The helper's local symbol, as ld-prime names it.
pub name: &'static [u8],
pub dlopen: u32,
/// Where the helper lies in __delay_helper.
pub offset: u32,
}
/// The dlopen helper of a dylib, last in __delay_helper:
/// `_dlopenHelper$libfoo.dylib`. It calls dlopen(install name, 0) with
/// the argument registers saved and then sets the flag word,
/// `_dlopenHelperFlag$libfoo.dylib` in __data.
#[derive(Debug)]
pub struct DlopenHelper {
pub install_name: Vec<u8>,
/// The helper's local symbol and its flag's.
pub name: &'static [u8],
pub flag_name: &'static [u8],
/// The subsections of the flag and of the install name's C string
/// in __cstring.
pub flag: u32,
pub string: u32,
pub offset: u32,
}
#[derive(Debug)]
pub struct DelayInit {
pub stubs_hdr: ChunkHeader,
pub helper_hdr: ChunkHeader,
/// The stubs by name, the helpers for GOT loads by name, and the
/// dlopen helpers by install name, as ld-prime lays them out.
pub stubs: Vec<DelayStub>,
pub helpers: Vec<DelayHelper>,
pub dlopens: Vec<DlopenHelper>,
/// The helper each rewritten GOT load goes to, by the subsection
/// and offset of the instruction it replaces.
pub sites: hashbrown::HashMap<(u32, u32), u32>,
/// _dlopen, which the dlopen helpers call through its stub.
pub dlopen_sym: Option<SymbolId>,
}
impl DelayInit {
pub fn new() -> Self {
let flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
let mut stubs_hdr = ChunkHeader::new(b"__TEXT", b"__delay_stubs");
stubs_hdr.flags = flags;
let mut helper_hdr = ChunkHeader::new(b"__TEXT", b"__delay_helper");
helper_hdr.flags = flags;
Self {
stubs_hdr,
helper_hdr,
stubs: Vec::new(),
helpers: Vec::new(),
dlopens: Vec::new(),
sites: Default::default(),
dlopen_sym: None,
}
}
/// Returns the address of __delay_stubs entry `i`.
pub fn stub_addr<E: Target>(&self, i: usize) -> u64 {
self.stubs_hdr.addr + stub_offset::<E>(i as u32)
}
/// Returns the address of __delay_helper's load helper `i`.
pub fn helper_addr(&self, i: usize) -> u64 {
self.helper_hdr.addr + self.helpers[i].offset as u64
}
/// Returns the address of __delay_helper's dlopen helper `i`.
pub fn dlopen_helper_addr(&self, i: usize) -> u64 {
self.helper_hdr.addr + self.dlopens[i].offset as u64
}
}
impl Default for DelayInit {
fn default() -> Self {
Self::new()
}
}
/// The helper that relocation `r` of subsection `isec` calls in place
/// of a GOT load (or x86-64's compare), if it reaches a symbol of a
/// delay-init dylib (see DelayUse): its address, and whether it is the
/// site's own, which branches back to the site rather than returning.
pub fn load_helper<E: Target>(ctx: &Context<E>, isec: usize, r: &Reloc) -> Option<(u64, bool)> {
let delay = &ctx.delay_init;
if delay.sites.is_empty() {
return None;
}
let &i = delay.sites.get(&(isec as u32, r.offset))?;
let own = matches!(delay.helpers[i as usize].kind, DelayUse::Load { site: Some(_), .. });
Some((delay.helper_addr(i as usize), own))
}
/// The offset of stub `idx` in __delay_stubs.
pub fn stub_offset<E: Target>(idx: u32) -> u64 {
idx as u64 * E::DELAY_STUB_SIZE
}
/// Sizes the stubs, which go where the other stubs do.
pub fn update_stubs_shdr<E: Target>(ctx: &mut Context<E>) {
let delay = &mut ctx.delay_init;
delay.stubs_hdr.segname = ctx.stubs.hdr.segname;
delay.stubs_hdr.p2align = E::DELAY_P2ALIGN;
delay.stubs_hdr.size = delay.stubs.len() as u64 * E::DELAY_STUB_SIZE;
}
/// Sizes the helpers, which go where the stubs do: the dlopen helpers
/// come last.
pub fn update_helper_shdr<E: Target>(ctx: &mut Context<E>) {
let delay = &mut ctx.delay_init;
let last = delay.dlopens.last().unwrap();
delay.helper_hdr.segname = ctx.stubs.hdr.segname;
delay.helper_hdr.p2align = E::DELAY_P2ALIGN;
delay.helper_hdr.size = (last.offset + E::DLOPEN_HELPER_SIZE) as u64;
}
/// The stubs' local symbols, like selector stubs' with N_PEXT set.
pub fn populate_stubs_symtab<E: Target>(ctx: &Context<E>, out: &mut Vec<NamedEntry>) {
let delay = &ctx.delay_init;
for (i, stub) in delay.stubs.iter().enumerate() {
let addr = delay.stub_addr::<E>(i);
let ent = MachSym { n_type: N_PEXT | N_SECT, ..local_msym(delay.stubs_hdr.sect_idx, addr) };
out.push((stub.name, ent, None));
}
}
/// The helpers' local symbols, the dlopen helpers' last. (Their flags'
/// are Context::extra_local_syms.)
pub fn populate_helper_symtab<E: Target>(ctx: &Context<E>, out: &mut Vec<NamedEntry>) {
let delay = &ctx.delay_init;
let sect = delay.helper_hdr.sect_idx;
for (i, h) in delay.helpers.iter().enumerate() {
let addr = delay.helper_addr(i);
out.push((h.name, local_msym(sect, addr), None));
}
for (i, d) in delay.dlopens.iter().enumerate() {
let addr = delay.dlopen_helper_addr(i);
out.push((d.name, local_msym(sect, addr), None));
}
}
pub fn copy_stubs<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_delay_stubs(ctx, ctx.delay_init.stubs_hdr.addr, buf);
}
pub fn copy_helper<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_delay_helper(ctx, ctx.delay_init.helper_hdr.addr, buf);
}
/// The stubs' and helpers' references to other sections, for
/// LC_SEGMENT_SPLIT_INFO (see DelayTarget).
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
let delay = &ctx.delay_init;
let Some(dlopen) = delay.dlopen_sym else { return };
let dlopen_stub = stubs::entry_offset::<E>(ctx.symbols[dlopen].stub_idx(&ctx.symbols).unwrap());
let dlopen_helper =
|i: u32| p.chunk_addr(ChunkId::DelayHelper, delay.dlopen_helper_addr(i as usize));
let mut push_refs = |from: Place, code: DelayCode, resolve: &dyn Fn(DelayTarget) -> _| {
for (off, kind, to) in E::delay_refs(code) {
let from = (from.0, from.1 + off as u64);
let to: Option<Place> = resolve(to);
if to.is_some_and(|to| to.0 != from.0) {
push(out, from, kind, to);
}
}
};
for (i, stub) in delay.stubs.iter().enumerate() {
let from = p.chunk(ChunkId::DelayStubs, stub_offset::<E>(i as u32));
let flag = delay.dlopens[stub.dlopen as usize].flag;
push_refs(from, DelayCode::Stub, &|to| match to {
DelayTarget::Flag => p.isec(flag as usize),
DelayTarget::Slot => Some(p.got_index(stub.got as usize)),
DelayTarget::DlopenHelper => Some(dlopen_helper(stub.dlopen)),
_ => None,
});
}
for (i, h) in delay.helpers.iter().enumerate() {
let flag = delay.dlopens[h.dlopen as usize].flag;
push_refs(p.delay_helper(i), DelayCode::Helper(h.kind), &|to| match to {
DelayTarget::Flag => p.isec(flag as usize),
DelayTarget::Slot => Some(p.got_slot(h.sym)),
DelayTarget::DlopenHelper => Some(dlopen_helper(h.dlopen)),
DelayTarget::Site => match h.kind {
DelayUse::Load { site: Some((isec, off)), .. } => {
p.isec(isec as usize).map(|(n, o)| (n, o + off as u64 + 4))
}
_ => None,
},
_ => None,
});
}
for (i, d) in delay.dlopens.iter().enumerate() {
push_refs(dlopen_helper(i as u32), DelayCode::Dlopen, &|to| match to {
DelayTarget::Flag => p.isec(d.flag as usize),
DelayTarget::Name => p.isec(d.string as usize),
DelayTarget::Dlopen => Some(p.chunk(ChunkId::Stubs, dlopen_stub)),
_ => None,
});
}
}
+176
View File
@@ -0,0 +1,176 @@
//! __TEXT,__eh_frame: the re-synthesized DWARF unwind records that
//! compact unwind can't express.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_sections::FdeRecord;
#[derive(Debug)]
pub struct EhFrameSection {
pub hdr: ChunkHeader,
}
impl EhFrameSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__eh_frame");
hdr.p2align = 3;
Self { hdr }
}
}
impl Default for EhFrameSection {
fn default() -> Self {
Self::new()
}
}
/// Lays out __eh_frame, the surviving DWARF unwind records: the CIEs
/// the kept FDEs use, then the FDEs, as mold's EhFrameSection does (an
/// FDE's CIE pointer is a backward offset). Their offsets are needed
/// before layout, because the __unwind_info encoding embeds each FDE's
/// offset; an FDE it can't reach draws a warning (see
/// warn_eh_frame_too_large).
pub fn construct<E: Target>(ctx: &mut Context<E>) {
// FDEs of folded copies duplicate their leader's; drop them, and
// remap the unwind records' FDE indices around the removals as the
// dead-strip pass does, so that none points past the shortened
// table.
let mut fde_map = vec![usize::MAX; ctx.fdes.len()];
let mut kept_fdes = Vec::new();
for (i, fde) in std::mem::take(&mut ctx.fdes).into_iter().enumerate() {
if ctx.isecs[fde.isec as usize].replacement == crate::input_sections::NO_REPLACEMENT {
fde_map[i] = kept_fdes.len();
kept_fdes.push(fde);
}
}
ctx.fdes = kept_fdes;
let num_records = ctx.unwind_records.len();
ctx.unwind_records.retain_mut(|rec| {
if rec.fde_idx == crate::input_sections::UNWIND_NONE {
return true;
}
let mapped = fde_map[rec.fde_idx as usize];
if mapped == usize::MAX {
// A folded copy's record; its leader has its own.
return false;
}
rec.fde_idx = mapped as u32;
true
});
// The compaction moved the surviving records; refresh the
// subsections' ranges, which the __unwind_info encoding reads.
if ctx.unwind_records.len() < num_records {
crate::input_files::refresh_unwind_ranges(ctx);
}
if ctx.fdes.is_empty() {
return;
}
for fde in &ctx.fdes {
ctx.cies[fde.cie as usize].is_alive = true;
}
let mut off = 0;
for cie in ctx.cies.iter_mut().filter(|cie| cie.is_alive) {
cie.output_offset = off;
off += cie.data.len() as u32;
}
for fde in &mut ctx.fdes {
fde.output_offset = off;
off += fde.data.len() as u32;
}
ctx.eh_frame.hdr.size = off as u64;
ctx.chunks.push(ChunkId::EhFrame);
warn_eh_frame_too_large(ctx);
}
/// Warns, as ld-prime does, if __unwind_info points a function at an
/// FDE beyond the reach of the 24 bits an entry has for its offset
/// (which it leaves 0 then, see encode_unwind_info).
fn warn_eh_frame_too_large<E: Target>(ctx: &Context<E>) {
use crate::chunks::unwind_info::MAX_FDE_OFFSET;
if !ctx.args.warn_eh_frame_too_large
|| ctx.eh_frame.hdr.size <= MAX_FDE_OFFSET as u64
|| !ctx.chunks.contains(&ChunkId::UnwindInfo)
{
return;
}
let out_of_reach = ctx.unwind_records.iter().any(|rec| {
let isec = &ctx.isecs[rec.isec as usize];
isec.is_emitted()
&& rec.fde().is_some_and(|fde| ctx.fdes[fde].output_offset > MAX_FDE_OFFSET)
});
if out_of_reach {
crate::warn!(
"__eh_frame section too large (max 16MB) to encode dwarf unwind offsets in compact unwind table, performance of exception handling might be affected"
);
}
}
/// Writes the re-synthesized __eh_frame section: live CIEs with their
/// personality cells rewritten to be GOT-relative, then live FDEs with
/// their CIE pointer, function pointer and LSDA pointer re-targeted.
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
// `buf` is the chunk's own slice of the output.
let base_off = 0;
let base_addr = ctx.eh_frame.hdr.addr;
for cie in &ctx.cies {
if !cie.is_alive {
continue;
}
let off = base_off + cie.output_offset as usize;
buf[off..off + cie.data.len()].copy_from_slice(cie.data);
if let Some(personality) = cie.personality {
let cell_addr = base_addr + cie.output_offset as u64 + cie.personality_offset as u64;
let val = ctx.symbols[personality].got_addr(ctx).wrapping_sub(cell_addr) as u32;
let cell = off + cie.personality_offset as usize;
buf[cell..cell + 4].copy_from_slice(&val.to_le_bytes());
}
}
for fde in &ctx.fdes {
let off = base_off + fde.output_offset as usize;
let cie = &ctx.cies[fde.cie as usize];
buf[off..off + fde.data.len()].copy_from_slice(fde.data);
// The CIE pointer is the distance back to the owning CIE.
let cie_ptr = fde.output_offset + 4 - cie.output_offset;
let fde_addr = base_addr + fde.output_offset as u64;
relocate_fde(ctx, fde, &mut buf[off..], fde_addr, cie_ptr);
}
}
/// Rewrites the self-relative fields of an FDE copied to the start of
/// `buf` for its address in the output: the CIE pointer, to `cie_ptr`,
/// then pc_begin and the LSDA pointer, each of the size the CIE's
/// encoding gives. A final image and a -r output both write their FDEs
/// so.
pub fn relocate_fde<E: Target>(
ctx: &Context<E>,
fde: &FdeRecord,
buf: &mut [u8],
fde_addr: u64,
cie_ptr: u32,
) {
let cie = &ctx.cies[fde.cie as usize];
buf[4..8].copy_from_slice(&cie_ptr.to_le_bytes());
let func_addr = ctx.isecs[fde.isec as usize].addr(ctx) + fde.func_offset as u64;
let pc_begin = func_addr.wrapping_sub(fde_addr + 8);
match cie.pc_size() {
4 => buf[8..12].copy_from_slice(&(pc_begin as u32).to_le_bytes()),
_ => buf[8..16].copy_from_slice(&pc_begin.to_le_bytes()),
}
if let Some((lsda_isec, lsda_off)) = fde.lsda {
let pos = fde.lsda_pos(cie.pc_size());
let cell_addr = fde_addr + pos as u64;
let val =
(ctx.isecs[lsda_isec as usize].addr(ctx) + lsda_off as u64).wrapping_sub(cell_addr);
match cie.lsda_size() {
4 => buf[pos..pos + 4].copy_from_slice(&(val as u32).to_le_bytes()),
_ => buf[pos..pos + 8].copy_from_slice(&val.to_le_bytes()),
}
}
}
+363
View File
@@ -0,0 +1,363 @@
//! The export trie in __LINKEDIT: dyld's index of exported symbols.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::input_files::FileId;
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::{uleb_size, write_uleb};
#[derive(Debug)]
pub struct ExportTrieSection {
pub hdr: ChunkHeader,
/// The trie, encoded once when its chunk is sized (every address is
/// final by then) and reused when copied out.
pub contents: Vec<u8>,
}
impl ExportTrieSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, contents: Vec::new() }
}
}
impl Default for ExportTrieSection {
fn default() -> Self {
Self::new()
}
}
/// What an export trie terminal says about a symbol.
#[derive(Clone, Copy)]
enum Export {
/// A symbol defined in this image: flags and image-relative address.
Addr { flags: u32, addr: u64 },
/// A symbol re-exported from a dylib under this name (an -alias of
/// an imported symbol): the dylib's ordinal and the name it has
/// there.
Reexport { ordinal: u32, name: &'static [u8] },
}
impl Export {
/// The terminal's payload after its size: flags, then either the
/// address or the ordinal and the re-exported name (an empty name
/// means the same name).
fn terminal_size(self) -> usize {
match self {
Self::Addr { flags, addr } => uleb_size(flags as u64) + uleb_size(addr),
Self::Reexport { ordinal, name } => {
uleb_size(EXPORT_SYMBOL_FLAGS_REEXPORT as u64)
+ uleb_size(ordinal as u64)
+ name.len()
+ 1
}
}
}
}
/// A node of the export trie under construction.
#[derive(Default)]
struct TrieNode {
/// The edges, labeled with slices of the symbol names.
children: Vec<(&'static [u8], Self)>,
/// The exported symbol ending here, if any.
export: Option<Export>,
/// Pre-order index, assigned by number_nodes; lets the later passes
/// name a child by index without a pointer hash map.
index: u32,
/// Node count of this subtree (including this node), so that
/// number_nodes can hand every subtree a disjoint slot range.
size: u32,
}
/// Builds the subtrie for a sorted run of names that all share their
/// first `depth` bytes. The run splits into children by the byte at
/// `depth`, and each child's edge label is its group's remaining
/// common prefix (for a sorted group, the common prefix of its first
/// and last name). Sibling subtries build in parallel, so
/// construction parallelizes at every branching level - splitting on
/// leading bytes alone is useless when every Mach-O symbol starts
/// with '_'. Construction stays linear in the total name length.
fn build_trie(names: &[(&'static [u8], Export)], depth: usize) -> TrieNode {
let mut node = TrieNode::default();
let mut rest = names;
if let Some(&(name, export)) = rest.first()
&& name.len() == depth
{
node.export = Some(export);
rest = &rest[1..];
}
let mut groups: Vec<&[(&'static [u8], Export)]> = Vec::new();
while let Some(&(first, _)) = rest.first() {
let b = first[depth];
let n = rest.iter().take_while(|(n, _)| n[depth] == b).count();
groups.push(&rest[..n]);
rest = &rest[n..];
}
let build_child = |group: &&[(&'static [u8], Export)]| {
let first = group[0].0;
let last = group[group.len() - 1].0;
let common =
depth + first[depth..].iter().zip(&last[depth..]).take_while(|(a, b)| a == b).count();
(&first[depth..common], build_trie(group, common))
};
node.children = if names.len() >= 1024 {
groups.par_iter().map(build_child).collect()
} else {
groups.iter().map(build_child).collect()
};
node
}
/// Encodes the export trie: dyld's index of the image's exported
/// symbols. It is a radix tree; each node holds an optional terminal
/// payload (flags and the symbol's image-relative address, both ULEB128)
/// and edges labeled with NUL-terminated string fragments pointing at
/// child nodes by ULEB128 offset within the trie. The nodes are laid out
/// in ld-prime's order (see place_nodes), then the trie is padded to 8
/// bytes.
pub fn encode_export_trie<E: Target>(ctx: &Context<E>, sorted_globals: &[SymbolId]) -> Vec<u8> {
// An image no dyld loads has no one to look its symbols up:
// ld-prime writes no trie at all, even with the dyld info
// -no_fixup_chains gives a -static one.
if ctx.args.without_dyld() {
return Vec::new();
}
let exports = exports(ctx, sorted_globals);
// Nothing exported: an empty root node (terminal size 0, no
// children), padded to 8 bytes as ld-prime writes it.
if exports.is_empty() {
return vec![0; 8];
}
let mut root = build_trie(&exports, 0);
let nodes = number_nodes(&mut root);
let placement = place_nodes(&nodes);
let mut buf = write_nodes(&nodes, &placement);
buf.resize(buf.len().next_multiple_of(8), 0);
buf
}
/// The trie's entries, in name order. The caller hands over the
/// defined globals already sorted by name - the same list the symbol
/// table emits, with what the export lists leave out already made
/// private extern - so the trie never sorts.
fn exports<E: Target>(
ctx: &Context<E>,
sorted_globals: &[SymbolId],
) -> Vec<(&'static [u8], Export)> {
let base = ctx.mach_header.hdr.addr;
sorted_globals
.par_iter()
.filter_map(|&id| {
let sym = &ctx.symbols[id];
let target = ctx.indirect_aliases.iter().find_map(|&(a, t)| (a == id).then_some(t));
if let Some(target) = target {
let same_name = ctx.symbols[target].name() == sym.name();
let Some(FileId::Dylib(_)) = ctx.symbols[target].file() else {
return None;
};
let ordinal = ctx.symbols[target].bind_ordinal(ctx) as u32;
let name = if same_name { b"" } else { ctx.symbols[target].name() };
return Some((sym.name(), Export::Reexport { ordinal, name }));
}
// The kind bits tell a client linker (and dyld) that the
// export is a TLV descriptor; ld64 sets them, and a
// linker reading a stripped dylib's trie has nothing else
// to go by.
let mut flags = 0;
if sym.is_weak_def() {
flags |= EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION;
}
if sym.is_tlv(ctx) {
flags |= EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL;
}
let addr = if sym.is_absolute(ctx) {
flags |= EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE;
sym.addr(ctx)
} else {
sym.addr(ctx) - base
};
Some((sym.name(), Export::Addr { flags, addr }))
})
.collect()
}
/// Numbers the trie's nodes in pre-order (TrieNode::index), sorting each
/// node's children by label, and returns them in that order. Two
/// parallel passes in mold's prefix-sum shape: count every subtree, then
/// each subtree stores its pre-order run into its own disjoint slot
/// range of one preallocated array - no appending or copying, and the
/// pre-order index is simply the slot number. Fan-out happens at nodes
/// with many children (the second level: every Mach-O name starts with
/// '_', so the root has one child).
fn number_nodes(root: &mut TrieNode) -> Vec<&TrieNode> {
const FANOUT: usize = 8;
fn count(node: &mut TrieNode) -> u32 {
node.children.sort_by(|a, b| a.0.cmp(b.0));
let below: u32 = if node.children.len() >= FANOUT {
node.children.par_iter_mut().map(|(_, c)| count(c)).sum()
} else {
node.children.iter_mut().map(|(_, c)| count(c)).sum()
};
node.size = 1 + below;
node.size
}
// The nodes as raw pointers until every index is stamped, which
// borrows them mutably.
struct Slots(*mut *const TrieNode);
unsafe impl Sync for Slots {}
fn fill(node: &mut TrieNode, base: u32, slots: &Slots) {
node.index = base;
// SAFETY: every subtree owns [base, base+size), the ranges are
// disjoint by construction of the prefix sums below, and the
// array holds exactly root.size slots.
unsafe { *slots.0.add(base as usize) = node };
if node.children.len() >= FANOUT {
let mut b = base + 1;
let bases: Vec<u32> = node
.children
.iter()
.map(|(_, c)| {
let x = b;
b += c.size;
x
})
.collect();
node.children.par_iter_mut().zip(bases).for_each(|((_, c), cb)| fill(c, cb, slots));
} else {
let mut b = base + 1;
for (_, c) in &mut node.children {
fill(c, b, slots);
b += c.size;
}
}
}
let total = count(root) as usize;
let mut nodes: Vec<*const TrieNode> = vec![std::ptr::null(); total];
fill(root, 0, &Slots(nodes.as_mut_ptr()));
// SAFETY: fill stored a pointer to every node of `root`, which
// stays borrowed, and unchanged, as long as the references live.
nodes.into_iter().map(|p| unsafe { &*p }).collect()
}
/// Where place_nodes puts the nodes: each one's offset and size, by
/// pre-order index, the nodes in file order, and the trie's size.
struct Placement {
offs: Vec<u32>,
sizes: Vec<u32>,
order: Vec<u32>,
total: u32,
}
/// Places the trie's nodes, given in pre-order, as ld-prime lays them
/// out. The root comes first, with room for each child offset at its
/// widest (5 bytes, a u32's ULEB128) since it is written before its
/// children are placed; the unused bytes stay zero after its last edge.
/// The other nodes follow in post-order, a node after its subtrees, so
/// its children's offsets and thus its own size are known when it is
/// placed.
fn place_nodes(nodes: &[&TrieNode]) -> Placement {
// Each node's size apart from its child-offset ULEBs, and the
// pre-order indices of its children: a pure per-node map.
let (fixed, kids): (Vec<usize>, Vec<Vec<u32>>) = nodes
.par_iter()
.map(|node| {
let terminal_size = node.export.map_or(0, Export::terminal_size);
let mut f = uleb_size(terminal_size as u64) + terminal_size + 1;
let mut k = Vec::with_capacity(node.children.len());
for (label, child) in &node.children {
f += label.len() + 1;
k.push(child.index);
}
(f, k)
})
.unzip();
let mut offs = vec![0u32; nodes.len()];
let mut sizes = vec![0u32; nodes.len()];
let mut order = Vec::with_capacity(nodes.len());
sizes[0] = (fixed[0] + 5 * kids[0].len()) as u32;
order.push(0);
let mut off = sizes[0];
// An explicit stack of (node, next child to visit): tries of long
// mangled names nest deeply.
let mut stack: Vec<(usize, usize)> = Vec::new();
for &top in &kids[0] {
stack.push((top as usize, 0));
while let Some(&(node, next)) = stack.last() {
if let Some(&child) = kids[node].get(next) {
stack.last_mut().unwrap().1 += 1;
stack.push((child as usize, 0));
continue;
}
stack.pop();
let edges: usize = kids[node].iter().map(|&c| uleb_size(offs[c as usize] as u64)).sum();
offs[node] = off;
sizes[node] = (fixed[node] + edges) as u32;
order.push(node as u32);
off += sizes[node];
}
}
Placement { offs, sizes, order, total: off }
}
/// Writes the placed nodes, each on a core of its own into its slice of
/// the trie: on a big Rust debug link the trie is tens of MB.
fn write_nodes(nodes: &[&TrieNode], placement: &Placement) -> Vec<u8> {
let mut buf = vec![0u8; placement.total as usize];
// The nodes' slices, cut off one after another in file order.
let mut rest = buf.as_mut_slice();
let slices: Vec<(&TrieNode, &mut [u8])> = (placement.order.iter())
.map(|&i| {
let size = placement.sizes[i as usize] as usize;
(nodes[i as usize], rest.split_off_mut(..size).unwrap())
})
.collect();
slices.into_par_iter().for_each(|(node, dst)| write_node(node, &placement.offs, dst));
buf
}
/// Writes a node: its terminal - its size, then the export's flags and
/// address, or a re-export's flags, ordinal and name - or a 0, then the
/// count of its edges, each a NUL-terminated label and the child's
/// offset in `offs`.
fn write_node(node: &TrieNode, offs: &[u32], dst: &mut [u8]) {
let mut p = 0;
match node.export {
Some(export @ Export::Addr { flags, addr }) => {
p += write_uleb(&mut dst[p..], export.terminal_size() as u64);
p += write_uleb(&mut dst[p..], flags as u64);
p += write_uleb(&mut dst[p..], addr);
}
Some(export @ Export::Reexport { ordinal, name }) => {
p += write_uleb(&mut dst[p..], export.terminal_size() as u64);
p += write_uleb(&mut dst[p..], EXPORT_SYMBOL_FLAGS_REEXPORT as u64);
p += write_uleb(&mut dst[p..], ordinal as u64);
dst[p..p + name.len()].copy_from_slice(name);
p += name.len();
dst[p] = 0;
p += 1;
}
None => {
dst[p] = 0;
p += 1;
}
}
dst[p] = node.children.len() as u8;
p += 1;
for (label, child) in &node.children {
dst[p..p + label.len()].copy_from_slice(label);
p += label.len();
dst[p] = 0;
p += 1;
p += write_uleb(&mut dst[p..], offs[child.index as usize] as u64);
}
// The root's unused reserved offset bytes stay zero.
debug_assert!(if node.index == 0 { p <= dst.len() } else { p == dst.len() });
}
+108
View File
@@ -0,0 +1,108 @@
//! The external relocations of a kext (LC_DYSYMTAB's extreloff): each
//! place kmutil fills with the address of a symbol the kext imports
//! from the kernel or another kext, as dyld's binds would for a dylib.
//! Its local relocations (local_relocs.rs) slide the rest. Legacy
//! LINKEDIT (Args::legacy_linkedit) has dyld bind an image's pointers
//! in data by them too, its GOT slots and lazy pointers being bound by
//! the indirect symbol table.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::macho::*;
use crate::symbol::SymbolId;
#[derive(Debug)]
pub struct ExternRelocsSection {
pub hdr: ChunkHeader,
/// The places, each with its symbol and whether it is an x86-64
/// call, which calls an import directly.
pub relocs: Vec<(u64, SymbolId, bool)>,
}
impl ExternRelocsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, relocs: Vec::new() }
}
}
impl Default for ExternRelocsSection {
fn default() -> Self {
Self::new()
}
}
/// 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 Symbol::binds_pointer).
pub fn construct<E: Target>(ctx: &mut Context<E>) {
let got = &ctx.got;
let mut vec: Vec<(u64, SymbolId, bool)> = Vec::new();
if !ctx.args.legacy_linkedit {
vec.extend(
(got.got_syms.iter().enumerate())
.filter(|&(_, &id)| ctx.symbols[id].is_imported())
.map(|(i, &id)| (got.slot_addr(i), id, false)),
);
}
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() {
if !isec.is_emitted() {
continue;
}
let Some(chunk) = isec.output_section() else {
continue;
};
let base = ctx.chunk_header(chunk).addr + isec.offset as u64;
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
let Some(id) = rel.sym(file) else {
continue;
};
if !binds(id) || rel.is_subtracted {
continue;
}
let pointer = E::is_absrel(rel);
let call = rel.is_func_call::<E>() && ctx.symbols[id].stub_idx(&ctx.symbols).is_none();
if pointer || call {
vec.push((base + rel.offset as u64, id, call));
}
}
}
ctx.extern_relocs.hdr.size = (vec.len() * size_of::<MachRel>()) as u64;
ctx.extern_relocs.relocs = vec;
}
/// Writes the records once the symbol table is numbered, in ld-prime's
/// order: the pointers, then the calls, each by symbol and address. An
/// address counts from the start of a kext, and in legacy LINKEDIT as
/// a local relocation's does.
pub fn write<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let index = |id: SymbolId| ctx.symtab.output_sym_indices[id as usize];
let mut relocs: Vec<(bool, u32, u64)> =
ctx.extern_relocs.relocs.iter().map(|&(addr, id, call)| (call, index(id), addr)).collect();
relocs.sort_unstable();
let base = match ctx.args.legacy_linkedit {
true => crate::chunks::local_relocs::relocation_base(ctx),
false => ctx.mach_header.hdr.addr,
};
let mut off = ctx.extern_relocs.hdr.fileoff as usize;
for (call, sym, addr) in relocs {
// A pointer: 8-byte UNSIGNED. A call: 4-byte pc-relative
// BRANCH (x86-64's).
let bits = if call {
sym | (1 << 24) | (2 << 25) | (1 << 27) | (u32::from(X86_64_RELOC_BRANCH) << 28)
} else {
sym | (3 << 25) | (1 << 27) | (u32::from(E::RELOC_UNSIGNED) << 28)
};
let rel = MachRel { offset: addr.wrapping_sub(base) as u32, bits };
rel.write_to(&mut buf[off..]);
off += size_of::<MachRel>();
}
}
+92
View File
@@ -0,0 +1,92 @@
//! LC_FUNCTION_STARTS data: delta-encoded function addresses, used by
//! debuggers and crash reporters.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::input_files::FileId;
use crate::macho::S_ATTR_PURE_INSTRUCTIONS;
use crate::util::encode_uleb;
/// LC_FUNCTION_STARTS data: delta-encoded function addresses, used by
/// debuggers and crash reporters.
#[derive(Debug)]
pub struct FunctionStartsSection {
pub hdr: ChunkHeader,
/// The encoded table, built during layout.
pub contents: Vec<u8>,
}
impl FunctionStartsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, contents: Vec::new() }
}
}
impl Default for FunctionStartsSection {
fn default() -> Self {
Self::new()
}
}
/// Lists, as ld-prime does, the start of each piece it splits the
/// image's code into: each non-empty subsection of an output section
/// of pure instructions (__text with the __StaticInit static
/// initializers merged into it, or any other), each symbol inside one -
/// a local, an alt entry or an l-prefixed label too, but not a label at
/// its end - and each thunk entry, ld-prime's branch islands. The stubs
/// of every kind, sections of their own, are left out.
pub fn construct<E: Target>(ctx: &Context<E>) -> Vec<u8> {
if !ctx.args.function_starts {
return Vec::new();
}
let is_code = |hdr: &ChunkHeader| hdr.flags & S_ATTR_PURE_INSTRUCTIONS != 0;
let starts =
ctx.output_sections.par_iter().filter(|osec| is_code(&osec.hdr)).flat_map(|osec| {
let base = osec.hdr.addr;
let isecs = osec.members.par_iter().filter_map(move |&id| {
let isec = &ctx.isecs[id as usize];
(isec.size != 0 && isec.is_emitted()).then_some(base + isec.offset as u64)
});
let thunks = osec.thunks.par_iter().flat_map_iter(move |thunk| {
(0..thunk.syms.len() as u64).map(move |i| base + thunk.offset + i * E::THUNK_SIZE)
});
isecs.chain(thunks)
});
// The labels inside the subsections. One at a subsection's start
// (the usual case: subsections split at symbols) repeats its entry.
let syms = ctx.symbols.syms.par_iter().filter_map(|sym| {
if sym.value == 0 || !matches!(sym.file(), Some(FileId::Obj(_))) {
return None;
}
let isec = &ctx.isecs[ctx.isecs.resolve(sym.input_section()? as usize)];
let osec = ctx.chunk_header(isec.output_section()?);
(isec.is_alive() && is_code(osec) && sym.value < isec.size as u64)
.then(|| osec.addr + isec.offset as u64 + sym.value)
});
let mut addrs: Vec<u64> = starts.chain(syms).collect();
// No functions: the terminator alone, padded like any table.
if addrs.is_empty() {
return vec![0; 8];
}
addrs.par_sort_unstable();
addrs.dedup();
let mut buf = Vec::new();
// Offsets count from the image's start, the mach header, which
// -image_base (a kernel's) moves.
let mut last = ctx.mach_header.hdr.addr;
for addr in addrs {
encode_uleb(&mut buf, addr - last);
last = addr;
}
buf.push(0);
while buf.len() % 8 != 0 {
buf.push(0);
}
buf
}
+140
View File
@@ -0,0 +1,140 @@
//! The global offset table: pointers to symbols, bound by dyld for imported
//! ones. mold's got.rs holds the ELF counterpart.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_files::add_synthetic_section;
use crate::input_sections::InputSection;
use crate::macho::*;
use crate::symbol::SymbolId;
/// The global offset table: pointers to symbols, bound by dyld for
/// imported ones.
#[derive(Debug)]
pub struct GotSection {
pub hdr: ChunkHeader,
/// Symbols with a __got slot, in slot order.
pub got_syms: Vec<SymbolId>,
/// The subsections standing for the GOT entries of the classes whose
/// __objc_classrefs slots stay when the slots fold into __got (see
/// objc::fold_objc_classrefs), with the classes.
pub stand_ins: Vec<(u32, SymbolId)>,
}
impl GotSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__DATA", b"__got");
hdr.flags = S_NON_LAZY_SYMBOL_POINTERS;
hdr.p2align = 3;
Self { hdr, got_syms: Vec::new(), stand_ins: Vec::new() }
}
pub fn slot_addr(&self, i: usize) -> u64 {
self.hdr.addr + i as u64 * 8
}
}
impl Default for GotSection {
fn default() -> Self {
Self::new()
}
}
/// Gives a symbol a __got slot, unless it has one.
pub fn add_got_symbol<E: Target>(ctx: &mut Context<E>, id: SymbolId) {
if !ctx.symbols[id].has_got(&ctx.symbols) {
ctx.symbols.aux_mut(id).got_idx = ctx.got.got_syms.len() as u32;
ctx.got.got_syms.push(id);
}
}
/// Replaces the class reference slots that stay (see
/// objc::fold_objc_classrefs), each with its class, by stand-ins for
/// the classes' GOT entries: subsections of a synthetic __got section,
/// placed at the entries once passes::scan_relocations has made them
/// (see update_shdr), so that what refers to a slot reads its class's
/// entry. A stand-in is not alive: the GOT chunk writes the entry, and
/// the slot's local symbol is not emitted.
pub(crate) fn add_classref_stand_ins<E: Target>(ctx: &mut Context<E>, kept: Vec<(u32, SymbolId)>) {
if kept.is_empty() {
return;
}
let hdr = MachSection {
sectname: bytes_to_name(b"__got"),
segname: bytes_to_name(b"__DATA"),
p2align: 3,
flags: S_NON_LAZY_SYMBOL_POINTERS,
..Default::default()
};
let (file, shndx) = add_synthetic_section(ctx, hdr);
for (slot, class) in kept {
ctx.isecs.push(InputSection {
output_section: ChunkId::Got.pack(),
flags: InputSection::flags_dead(),
..InputSection::new(file, shndx, 3, 8, &[])
});
let stand_in = (ctx.isecs.len() - 1) as u32;
ctx.isecs[slot as usize].replacement = stand_in;
ctx.got.stand_ins.push((stand_in, class));
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
let seg = crate::chunks::data_seg(ctx);
// A kext's are plain data to ld-prime (indexed into the indirect
// symbol table all the same), and so are a -static image's, but for
// a PIE's - one that has an indirect symbol table.
let plain = ctx.args.is_kext() || (ctx.args.static_link && !ctx.args.pie);
let flags = if plain { S_REGULAR } else { S_NON_LAZY_SYMBOL_POINTERS };
let hdr = &mut ctx.got.hdr;
hdr.size = ctx.got.got_syms.len() as u64 * 8;
hdr.segname = seg;
hdr.flags = flags;
// The stand-ins for the __objc_classrefs slots that stay go at their
// classes' entries (see objc::fold_objc_classrefs).
for i in 0..ctx.got.stand_ins.len() {
let (stand_in, class) = ctx.got.stand_ins[i];
ctx.isecs[stand_in as usize].offset = ctx.symbols[class].got_idx(&ctx.symbols).unwrap() * 8;
}
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
// Slots for imported symbols stay zero; dyld fills them via
// the bind stream. Legacy LINKEDIT's slot of an interposable
// export, which dyld binds by name too, starts out with its
// 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.symbols[id].binds_as_import(ctx),
};
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
if !binds(id) {
buf[i * 8..i * 8 + 8].copy_from_slice(&ctx.symbols[id].addr(ctx).to_le_bytes());
}
}
}
/// The GOT slots that hold local addresses, for the loader to slide
/// (see rebase_info::rebase_locations). (Legacy LINKEDIT's dyld slides
/// those the indirect symbol table marks local itself, and binds the
/// others by name.)
pub fn rebase_locations<E: Target>(ctx: &Context<E>, locs: &mut Vec<u64>) {
if !ctx.args.legacy_linkedit {
for (i, &id) in ctx.got.got_syms.iter().enumerate() {
let sym = &ctx.symbols[id];
if !sym.binds_as_import(ctx) && !sym.is_absolute(ctx) {
locs.push(ctx.got.slot_addr(i));
}
}
}
}
/// The GOT slots' references to the symbols of this image they hold,
/// for LC_SEGMENT_SPLIT_INFO.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
for (i, &id) in p.ctx.got.got_syms.iter().enumerate() {
push(out, p.got_index(i), DYLD_CACHE_ADJ_V2_POINTER_64, p.own_sym(id));
}
}
+119
View File
@@ -0,0 +1,119 @@
//! The indirect symbol table: for each __stubs, __got and
//! __la_symbol_ptr slot, and each slot of the inputs' other non-lazy
//! symbol pointer sections, the output symbol it holds.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_sections::RelocTarget;
use crate::macho::*;
use crate::symbol::SymbolId;
/// The indirect symbol table: for each __stubs, __got and
/// __la_symbol_ptr slot, and each slot of the inputs' other non-lazy
/// symbol pointer sections, the output symbol it holds.
#[derive(Debug)]
pub struct IndirectSymtabSection {
pub hdr: ChunkHeader,
}
impl IndirectSymtabSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 2;
Self { hdr }
}
}
impl Default for IndirectSymtabSection {
fn default() -> Self {
Self::new()
}
}
/// The sections the table covers, in the image's order, as ld-prime
/// lists them: __stubs, then __got before __la_symbol_ptr, or after it
/// where both are in __DATA (-no_data_const) - and the output sections
/// of non-lazy symbol pointers (see output_section_flags) where they
/// are.
pub fn sections<E: Target>(ctx: &Context<E>) -> impl Iterator<Item = ChunkId> + '_ {
ctx.chunks.iter().copied().filter(|&id| match id {
ChunkId::Stubs | ChunkId::LazyPtrs | ChunkId::Got => true,
ChunkId::Output(osec) => {
!ctx.args.relocatable
&& ctx.output_section(osec).hdr.flags & SECTION_TYPE == S_NON_LAZY_SYMBOL_POINTERS
}
_ => false,
})
}
/// A section's slots: the symbol each holds, or None for
/// INDIRECT_SYMBOL_LOCAL (a GOT slot filled in here).
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.symbols[id].binds_at_runtime(ctx)))
.collect()
};
match id {
ChunkId::Stubs => ctx.stubs.symbols.iter().map(|&id| Some(id)).collect(),
ChunkId::LazyPtrs => {
ctx.stubs.lazy.iter().map(|&i| Some(ctx.stubs.symbols[i as usize])).collect()
}
ChunkId::Got => got_slots(&got.got_syms),
ChunkId::Output(osec) => {
let members = &ctx.output_section(osec).members;
members.iter().flat_map(|&isec| input_slots(ctx, isec as usize)).collect()
}
_ => unreachable!(),
}
}
/// The slots of input subsection `isec` of non-lazy symbol pointers:
/// the symbol each relocation points one at, if dyld binds it - a
/// pointer to anything else is one filled in here.
fn input_slots<E: Target>(ctx: &Context<E>, isec: usize) -> Vec<Option<SymbolId>> {
let isec = &ctx.isecs[isec];
let obj = isec.file as usize;
let size = isec.size;
// Each slot's relocation, the first at its offset, found in one pass.
let mut slots = vec![None; size.div_ceil(8) as usize];
for rel in isec.rels(&ctx.objs[obj]).iter().rev() {
if rel.offset % 8 == 0 && rel.offset < size && rel.ty != E::RELOC_SUBTRACTOR {
slots[(rel.offset / 8) as usize] = Some(rel);
}
}
(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.symbols[id].binds_at_runtime(ctx))
})
.collect()
}
/// Gives each covered section its first entry's index (reserved1),
/// once the chunks are in order, and sizes the table.
pub fn assign_indices<E: Target>(ctx: &mut Context<E>) {
let ids: Vec<ChunkId> = sections(ctx).collect();
let mut n = 0;
for id in ids {
let len = entries(ctx, id).len() as u32;
ctx.chunk_header_mut(id).reserved1 = n;
n += len;
}
ctx.indirect_symtab.hdr.size = n as u64 * 4;
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let mut off = 0;
for entry in sections(ctx).flat_map(|id| entries(ctx, id)) {
let val = match entry.map(|id| ctx.symtab.output_sym_indices[id as usize]) {
None | Some(u32::MAX) => INDIRECT_SYMBOL_LOCAL,
Some(idx) => idx,
};
buf[off..off + 4].copy_from_slice(&val.to_le_bytes());
off += 4;
}
}
+78
View File
@@ -0,0 +1,78 @@
//! __TEXT,__init_offsets: 32-bit image-relative initializer offsets,
//! replacing __mod_init_func's absolute pointers.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::macho::*;
use crate::symbol::SymbolId;
/// __TEXT,__init_offsets: 32-bit image-relative initializer offsets,
/// replacing __mod_init_func's absolute pointers.
#[derive(Debug)]
pub struct InitOffsetsSection {
pub hdr: ChunkHeader,
/// Initializer targets in run order.
pub init_funcs: Vec<InitFunc>,
}
/// An initializer function: a subsection and the function's offset in
/// it, or a symbol with no offset in the image (one dyld binds, or an
/// absolute one).
#[derive(Clone, Copy, Debug)]
pub enum InitFunc {
Local(usize, u64),
Imported(SymbolId),
}
impl InitFunc {
/// The initializer symbol `id` is. One dyld binds, or an absolute
/// one, has no offset in the image: the link fails as it is written
/// (see copy_buf).
pub fn new<E: Target>(ctx: &Context<E>, id: SymbolId) -> Self {
let sym = &ctx.symbols[id];
match sym.input_section() {
Some(isec) => InitFunc::Local(ctx.isecs.resolve(isec as usize), sym.value),
None => InitFunc::Imported(id),
}
}
}
impl InitOffsetsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__init_offsets");
hdr.flags = S_INIT_FUNC_OFFSETS;
hdr.p2align = 2;
Self { hdr, init_funcs: Vec::new() }
}
}
impl Default for InitOffsetsSection {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
ctx.init_offsets.hdr.size = ctx.init_offsets.init_funcs.len() as u64 * 4;
}
/// Writes the offsets. An initializer dyld binds has no offset in the
/// image: that is an error.
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
for (i, &func) in ctx.init_offsets.init_funcs.iter().enumerate() {
let val = match func {
InitFunc::Local(isec, off) => {
ctx.isecs[isec].addr(ctx) + off - ctx.mach_header.hdr.addr
}
InitFunc::Imported(id) => {
crate::error!(
"__init_offsets entry {i}: target '{}' does not have address",
ctx.symbols[id]
);
continue;
}
};
buf[i * 4..i * 4 + 4].copy_from_slice(&(val as u32).to_le_bytes());
}
}
+63
View File
@@ -0,0 +1,63 @@
//! The LC_DYLD_INFO lazy-bind opcode stream: one record per lazy pointer,
//! entered by its stub helper on first call.
use crate::arch::Target;
use crate::chunks::bind_info::{self, Op};
use crate::chunks::{ChunkHeader, segment_and_offset};
use crate::context::Context;
use crate::macho::*;
/// The lazy-bind opcode stream: one record per lazy pointer, entered
/// by its stub helper on first call.
#[derive(Debug)]
pub struct LazyBindInfoSection {
pub hdr: ChunkHeader,
/// The stream, built during layout, and each stub's record offset
/// in it (what its stub helper entry pushes for dyld_stub_binder).
pub contents: Vec<u8>,
pub offsets: Vec<u32>,
}
impl LazyBindInfoSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), contents: Vec::new(), offsets: Vec::new() }
}
}
impl Default for LazyBindInfoSection {
fn default() -> Self {
Self::new()
}
}
/// The lazy-bind opcode stream: one self-contained record per lazily
/// bound stub (segment/offset of its lazy pointer, dylib ordinal,
/// symbol, bind, done), and each record's offset, which the stub helper
/// entry pushes for dyld_stub_binder. ld64's layout, byte for byte.
pub fn construct<E: Target>(ctx: &Context<E>) -> (Vec<u8>, Vec<u32>) {
if ctx.stubs.lazy.is_empty() {
return (Vec::new(), Vec::new());
}
let mut buf = Vec::new();
let mut offsets = Vec::with_capacity(ctx.stubs.lazy.len());
for &i in &ctx.stubs.lazy {
let sym = &ctx.symbols[ctx.stubs.symbols[i as usize]];
offsets.push(buf.len() as u32);
let (seg, off) = segment_and_offset(ctx, sym.stub_ptr_addr(ctx, i as usize));
let flags = if sym.is_weak_ref() { BIND_SYMBOL_FLAGS_WEAK_IMPORT } else { 0 };
let ops = [
Op::SegOffset(seg, off),
Op::Dylib(sym.bind_ordinal(ctx)),
Op::Symbol(sym.name(), flags),
Op::Bind,
];
for op in ops {
bind_info::encode_op(&mut buf, op);
}
buf.push(BIND_OPCODE_DONE);
}
while buf.len() % 8 != 0 {
buf.push(0);
}
(buf, offsets)
}
+174
View File
@@ -0,0 +1,174 @@
//! __TEXT,__lazy_helpers: the code through which an image reaches the
//! symbols of a dylib dyld loads lazily (-lazy-l and the like, macOS 27
//! on). Each helper checks the dylib's flag word; once dyld has loaded
//! the dylib and bound its __lazy_load_got slots, it goes on through
//! the symbol's slot, and before that it first has __dyld_lazy_load
//! load the dylib.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::symtab::{NamedEntry, local_msym};
use crate::chunks::{ChunkHeader, ChunkId, stubs};
use crate::context::Context;
use crate::input_sections::Reloc;
use crate::macho::*;
use crate::symbol::SymbolId;
/// The reference a helper stands in for.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum LazyUse {
/// Calls, which branch to `_foo$lazyLoadStub` instead: it jumps
/// through the symbol's slot.
Call,
/// A load of the symbol's address from the GOT into register `reg`:
/// arm64's adrp of the GOT slot's page becomes a call of
/// `_foo$lazyGOT$loadHelper_<reg>`, which returns with the page of
/// the symbol's __lazy_load_got slot in the register, for the ldr
/// under the adrp to load from the slot; x86-64's movq becomes a
/// call of one that loads the slot. arm64 code that may not have
/// saved its link register branches instead to a helper of its
/// own, which branches back past the adrp: `site` names that adrp
/// (subsection, offset).
Load { reg: u8, site: Option<(u32, u32)> },
/// x86-64's test of a weak import, cmpq $0 of its GOT slot: it
/// becomes a call of `_foo$lazyGOT$cmpHelper`, which compares the
/// slot instead, leaving the flags for the code after the call.
Cmp,
}
/// What a helper's code refers to, for LC_SEGMENT_SPLIT_INFO: the flag
/// word, the helper's slot, the mach header, __dyld_lazy_load's stub,
/// or the code to return to past the site.
#[derive(Clone, Copy, Debug)]
pub enum LazyTarget {
Flag,
Slot,
Header,
LazyLoad,
Site,
}
#[derive(Debug)]
pub struct LazyHelper {
pub sym: SymbolId,
pub kind: LazyUse,
/// The helper's local symbol, as ld-prime names it.
pub name: &'static [u8],
/// The subsection of the flag word of the symbol's dylib, and the
/// symbol's __lazy_load_got slot the helper goes through.
pub flag: u32,
pub slot: u32,
/// Where the helper lies in the section.
pub offset: u32,
}
/// __TEXT,__lazy_helpers: the helpers, in the order of the image's
/// first uses.
#[derive(Debug)]
pub struct LazyHelpersSection {
pub hdr: ChunkHeader,
pub helpers: Vec<LazyHelper>,
/// The helper each rewritten GOT load goes to, by the subsection
/// and offset of the instruction it replaces.
pub sites: hashbrown::HashMap<(u32, u32), u32>,
/// __dyld_lazy_load, which the helpers call through its stub.
pub dyld_lazy_load: Option<SymbolId>,
}
impl LazyHelpersSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__lazy_helpers");
hdr.flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
Self { hdr, helpers: Vec::new(), sites: Default::default(), dyld_lazy_load: None }
}
/// Returns the address of helper `i`.
pub fn helper_addr(&self, i: usize) -> u64 {
self.hdr.addr + self.helpers[i].offset as u64
}
}
impl Default for LazyHelpersSection {
fn default() -> Self {
Self::new()
}
}
/// The helper that relocation `r` of subsection `isec` calls in place
/// of a GOT load (or x86-64's compare), if it reaches a symbol of a
/// dylib dyld loads lazily (see LazyUse): its address, and whether it
/// is the site's own, which branches back to the site rather than
/// returning.
pub fn load_helper<E: Target>(ctx: &Context<E>, isec: usize, r: &Reloc) -> Option<(u64, bool)> {
let lazy = &ctx.lazy_helpers;
if lazy.sites.is_empty() {
return None;
}
let &i = lazy.sites.get(&(isec as u32, r.offset))?;
let own = matches!(lazy.helpers[i as usize].kind, LazyUse::Load { site: Some(_), .. });
Some((lazy.helper_addr(i as usize), own))
}
/// Sizes the helpers, which go where the stubs do.
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
let last = ctx.lazy_helpers.helpers.last().unwrap();
let size = last.offset + E::lazy_helper_size(last.kind);
let hdr = &mut ctx.lazy_helpers.hdr;
hdr.segname = ctx.stubs.hdr.segname;
hdr.p2align = E::LAZY_HELPERS_P2ALIGN;
hdr.size = size as u64;
}
/// The helpers' local symbols: a call helper's, like a selector stub's,
/// with N_PEXT set.
pub fn populate_symtab<E: Target>(ctx: &Context<E>, out: &mut Vec<NamedEntry>) {
let hdr = &ctx.lazy_helpers.hdr;
for (i, h) in ctx.lazy_helpers.helpers.iter().enumerate() {
let addr = ctx.lazy_helpers.helper_addr(i);
let n_type = match h.kind {
LazyUse::Call => N_PEXT | N_SECT,
_ => N_SECT,
};
let ent = MachSym { n_type, ..local_msym(hdr.sect_idx, addr) };
out.push((h.name, ent, None));
}
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_lazy_helpers(ctx, ctx.lazy_helpers.hdr.addr, buf);
}
/// The helpers' references, for LC_SEGMENT_SPLIT_INFO: to the flag word
/// and slot they check and load, to the arguments and the stub of the
/// call of __dyld_lazy_load, and to the code after the site they return
/// to (see LazyTarget).
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
let lazy = &ctx.lazy_helpers;
let Some(lazy_load) = lazy.dyld_lazy_load else { return };
let stub = p.chunk(
ChunkId::Stubs,
stubs::entry_offset::<E>(ctx.symbols[lazy_load].stub_idx(&ctx.symbols).unwrap()),
);
for (i, h) in lazy.helpers.iter().enumerate() {
let (n, at) = p.lazy_helper(i as u32);
for (off, kind, to) in E::lazy_helper_refs(h.kind) {
let to = match to {
LazyTarget::Flag => p.isec(h.flag as usize),
LazyTarget::Slot => {
let slot = ctx.lazy_load_got.slot_addr(h.slot);
Some(p.chunk_addr(ChunkId::LazyLoadGot, slot))
}
LazyTarget::Header => Some((0, 0)),
LazyTarget::LazyLoad => Some(stub),
LazyTarget::Site => match h.kind {
LazyUse::Load { site: Some((isec, off)), .. } => {
p.isec(isec as usize).map(|(n, o)| (n, o + off as u64 + 4))
}
_ => None,
},
};
push(out, (n, at + off as u64), kind, to);
}
}
}
+75
View File
@@ -0,0 +1,75 @@
//! __DATA,__lazy_load_got: the pointers through which the lazy-load
//! helpers reach the symbols of the dylibs dyld loads lazily.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::chunks::symtab::{NamedEntry, local_msym};
use crate::context::Context;
use crate::symbol::SymbolId;
/// __DATA,__lazy_load_got: a slot per lazily loaded symbol (on x86-64,
/// a second one for its call helper: see Target::LAZY_CALL_OWN_SLOT),
/// each dylib's together, in the order of the image's first uses (see
/// lazy_load::create_lazy_load_slots). Each dylib's slots
/// are a chain of DYLD_CHAINED_PTR_64 binds, which
/// LC_DYLD_CHAINED_FIXUPS does not list: the dylib's
/// LC_LAZY_LOAD_DYLIB_INFO record points dyld at the chain's first
/// slot, and dyld binds the chain when __dyld_lazy_load loads the
/// dylib.
#[derive(Debug)]
pub struct LazyLoadGotSection {
pub hdr: ChunkHeader,
/// Each slot's symbol, and the slot's local symbol
/// (_foo$lazyGOT).
pub slots: Vec<(SymbolId, &'static [u8])>,
}
impl LazyLoadGotSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__DATA", b"__lazy_load_got");
hdr.p2align = 3;
Self { hdr, slots: Vec::new() }
}
pub fn slot_addr(&self, i: u32) -> u64 {
self.hdr.addr + i as u64 * 8
}
}
impl Default for LazyLoadGotSection {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
// Read-only data in the shared region, as its lazy pointers are.
if ctx.args.shared_region {
ctx.lazy_load_got.hdr.segname = crate::chunks::data_seg(ctx);
}
ctx.lazy_load_got.hdr.size = ctx.lazy_load_got.slots.len() as u64 * 8;
}
/// The slots' local symbols.
pub fn populate_symtab<E: Target>(ctx: &Context<E>, out: &mut Vec<NamedEntry>) {
let hdr = &ctx.lazy_load_got.hdr;
for (i, &(_, name)) in ctx.lazy_load_got.slots.iter().enumerate() {
let addr = ctx.lazy_load_got.slot_addr(i as u32);
out.push((name, local_msym(hdr.sect_idx, addr), None));
}
}
/// Writes each dylib's chain: a bind of the symbol at its index in the
/// dylib's record (ordinal:24 addend:8 reserved:19 next:12 bind:1),
/// `next` counting 4-byte strides to the next slot, 0 at the last.
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
for dylib in &ctx.lazy_load_info.dylibs {
let n = dylib.syms.len() as u64;
for i in 0..n {
let next = if i + 1 < n { 2 } else { 0 };
let word = (1u64 << 63) | (next << 51) | i;
let off = (dylib.got_start as usize + i as usize) * 8;
buf[off..off + 8].copy_from_slice(&word.to_le_bytes());
}
}
}
+97
View File
@@ -0,0 +1,97 @@
//! The LC_LAZY_LOAD_DYLIB_INFO records in __LINKEDIT: for each dylib
//! dyld loads lazily, what __dyld_lazy_load needs to load it and bind
//! the image's slots for its symbols.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::macho::DYLD_CHAINED_PTR_64_OFFSET;
use crate::symbol::SymbolId;
/// A dylib dyld loads lazily, one of whose symbols the image uses.
#[derive(Debug)]
pub struct LazyDylib {
/// The dylib, an index into ctx.dylibs.
pub dylib: u32,
/// The subsection of its flag word in __DATA,__data
/// (_lazyLoadFlag$<leaf name>), which dyld sets once it has loaded
/// the dylib.
pub flag: u32,
/// The symbol of each of its __lazy_load_got slots, which follow
/// one another from `got_start`.
pub syms: Vec<SymbolId>,
pub got_start: u32,
/// Where its record lies in this chunk, and its size.
pub offset: u32,
pub size: u32,
}
/// Each lazy dylib's record, in the order of the image's first uses of
/// the dylibs, as their load commands are; dyld finds each record by
/// its load command and reads it on its own. A record is 32-bit
/// words - the offset of the dylib's install name
/// (from the record's start), the image offset of its flag word, the
/// pointer format of its __lazy_load_got chain in the high half with
/// flags in the low half (1: a weak dylib, which may be missing), the
/// image offset of its chain's first slot, the number of symbols, and
/// the offset of an array holding each symbol's name's offset - then
/// that array, the install name and the symbol names, padded to 8
/// bytes.
#[derive(Debug)]
pub struct LazyLoadInfoSection {
pub hdr: ChunkHeader,
/// The dylibs, in the order of the image's first uses.
pub dylibs: Vec<LazyDylib>,
}
impl LazyLoadInfoSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), dylibs: Vec::new() }
}
}
impl Default for LazyLoadInfoSection {
fn default() -> Self {
Self::new()
}
}
/// The size of a record naming `install_name` and `syms`.
pub fn record_size<E: Target>(ctx: &Context<E>, install_name: &[u8], syms: &[SymbolId]) -> u32 {
let names: usize = syms.iter().map(|&id| ctx.symbols[id].name().len() + 1).sum();
(24 + syms.len() * 4 + install_name.len() + 1 + names).next_multiple_of(8) as u32
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let base = ctx.mach_header.hdr.addr;
for d in &ctx.lazy_load_info.dylibs {
let dylib = &ctx.dylibs[d.dylib as usize];
let rec = &mut buf[d.offset as usize..(d.offset + d.size) as usize];
let nsyms = d.syms.len();
let mut strs = 24 + nsyms * 4;
let format = (DYLD_CHAINED_PTR_64_OFFSET as u32) << 16 | dylib.is_weak as u32;
let got = ctx.lazy_load_got.slot_addr(d.got_start);
let header = [
strs as u32,
(ctx.isecs[d.flag as usize].addr(ctx) - base) as u32,
format,
(got - base) as u32,
nsyms as u32,
24,
];
for (i, word) in header.iter().enumerate() {
rec[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes());
}
let mut put = |rec: &mut [u8], s: &[u8]| {
let at = strs;
rec[at..at + s.len()].copy_from_slice(s);
strs += s.len() + 1;
at as u32
};
put(rec, &dylib.install_name);
for (i, &id) in d.syms.iter().enumerate() {
let at = put(rec, ctx.symbols[id].name());
rec[24 + i * 4..28 + i * 4].copy_from_slice(&at.to_le_bytes());
}
}
}
+81
View File
@@ -0,0 +1,81 @@
//! __DATA,__la_symbol_ptr: the lazy pointers the stubs jump through, bound
//! by dyld on first call.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::{ChunkHeader, ChunkId, stub_helper};
use crate::context::Context;
use crate::macho::*;
/// __DATA,__la_symbol_ptr: the lazy pointers the stubs jump through,
/// bound by dyld on first call.
#[derive(Debug)]
pub struct LazyPtrsSection {
pub hdr: ChunkHeader,
}
impl LazyPtrsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__DATA", b"__la_symbol_ptr");
hdr.flags = S_LAZY_SYMBOL_POINTERS;
hdr.p2align = 3;
Self { hdr }
}
pub fn slot_addr(&self, i: usize) -> u64 {
self.hdr.addr + i as u64 * 8
}
}
impl Default for LazyPtrsSection {
fn default() -> Self {
Self::new()
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
// In the shared region, dyld binds them all at load, and the
// section joins the read-only data.
if ctx.args.shared_region {
ctx.lazy_ptrs.hdr.segname = crate::chunks::data_seg(ctx);
}
ctx.lazy_ptrs.hdr.size = ctx.stubs.lazy.len() as u64 * 8;
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
// Each lazy pointer starts at its stub helper entry.
let helper = ctx.stub_helper.hdr.addr;
for i in 0..ctx.stubs.lazy.len() {
let val = helper + crate::chunks::stub_helper::entry_offset(ctx, i as u32);
buf[i * 8..i * 8 + 8].copy_from_slice(&val.to_le_bytes());
}
}
/// The lazy pointers, for the loader to slide (see
/// rebase_info::rebase_locations): they start out pointing at their
/// stub helper entries. (A weak-lookup stub's GOT slot is rebased with
/// the GOT.)
pub fn rebase_locations<E: Target>(ctx: &Context<E>, locs: &mut Vec<u64>) {
for &i in &ctx.stubs.lazy {
let i = i as usize;
locs.push(ctx.symbols[ctx.stubs.symbols[i]].stub_ptr_addr(ctx, i));
}
}
/// The lazy pointers' references to their stub helper entries, for
/// LC_SEGMENT_SPLIT_INFO.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
if !ctx.chunks.contains(&ChunkId::LazyPtrs) {
return;
}
for i in 0..ctx.stubs.lazy.len() {
let to = p.chunk(ChunkId::StubHelper, stub_helper::entry_offset(ctx, i as u32));
push(
out,
p.chunk_addr(ChunkId::LazyPtrs, ctx.lazy_ptrs.slot_addr(i)),
DYLD_CACHE_ADJ_V2_POINTER_64,
Some(to),
);
}
}
+94
View File
@@ -0,0 +1,94 @@
//! The local relocations of a -static -pie image or a kext
//! (LC_DYSYMTAB's locreloff): one per pointer a loader must slide, the
//! job dyld's rebase info does for a dynamic image. A kernel slides
//! itself by them, kmutil a kext, and dyld an image with legacy
//! LINKEDIT (Args::legacy_linkedit). mold's reldyn.rs holds the ELF
//! relative relocations they stand in for.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, segment_and_offset, segment_prot};
use crate::context::Context;
use crate::macho::*;
#[derive(Debug)]
pub struct LocalRelocsSection {
pub hdr: ChunkHeader,
/// The addresses of the pointers, in the order the records list
/// them.
pub locs: Vec<u64>,
}
impl LocalRelocsSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), locs: Vec::new() }
}
}
impl Default for LocalRelocsSection {
fn default() -> Self {
Self::new()
}
}
/// Lists the pointers in address order, and sizes the table. A
/// record's offset is a signed 32-bit distance from relocation_base,
/// which must reach each.
pub fn construct<E: Target>(ctx: &mut Context<E>) {
let mut locs = crate::chunks::rebase_info::rebase_locations(ctx);
locs.sort_unstable();
let base = relocation_base(ctx);
let reaches = |addr: u64| i32::try_from(addr.wrapping_sub(base) as i64).is_ok();
if let Some(&addr) = locs.iter().find(|&&addr| !reaches(addr)) {
crate::error!("a local relocation can't reach the pointer at {addr:#x} from {base:#x}");
}
ctx.local_relocs.hdr.size = (locs.len() * size_of::<MachRel>()) as u64;
ctx.local_relocs.locs = locs;
}
/// Where the relocation addresses count from: the first segment, or on
/// x86-64 the first writable one, as ld64 bases x86-64 relocations, but
/// for a kext's. A pointer below it (in a segment -segaddr pins lower,
/// like XNU's __HIB) gets a negative address.
pub(crate) fn relocation_base<E: Target>(ctx: &Context<E>) -> u64 {
ctx.segments
.iter()
.filter(|seg| seg.name != b"__PAGEZERO")
.find(|seg| {
E::CPUTYPE != CPU_TYPE_X86_64
|| ctx.args.is_kext()
|| segment_prot(seg.name) & VM_PROT_WRITE != 0
})
.map_or(0, |seg| seg.cmd.vmaddr)
}
/// Writes the records once the sections are in the buffer. Each is a
/// non-extern 8-byte UNSIGNED relocation whose idx is the
/// ordinal of the section the pointer points into, and whose address
/// counts from relocation_base.
pub fn write<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let mut sects: Vec<(u64, u8)> = ctx
.chunks
.iter()
.map(|&id| ctx.chunk_header(id))
.filter(|hdr| hdr.is_sect)
.map(|hdr| (hdr.addr, hdr.sect_idx))
.collect();
sects.sort_unstable();
let base = relocation_base(ctx);
let mut off = ctx.local_relocs.hdr.fileoff as usize;
for &addr in &ctx.local_relocs.locs {
let (seg, seg_off) = segment_and_offset(ctx, addr);
let pos = (ctx.segments[seg].cmd.fileoff + seg_off) as usize;
let value = u64::from_le_bytes(buf[pos..pos + 8].try_into().unwrap());
// The last section starting at or below the target.
let i = sects.partition_point(|&(start, _)| start <= value);
let sect = sects[i.saturating_sub(1)].1;
let rel = MachRel {
offset: addr.wrapping_sub(base) as u32,
bits: u32::from(sect) | (3 << 25) | (u32::from(E::RELOC_UNSIGNED) << 28),
};
rel.write_to(&mut buf[off..]);
off += size_of::<MachRel>();
}
}
+52
View File
@@ -0,0 +1,52 @@
//! LC_ATOM_INFO: the mergeable record of a -make_mergeable dylib, which
//! make_mergeable builds (see there).
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
use crate::mergeable::header;
/// LC_ATOM_INFO's data in __LINKEDIT: the record, but for what depends
/// on where it lands in the file, filled in as it is copied out.
#[derive(Debug)]
pub struct MergeableRecordSection {
pub hdr: ChunkHeader,
/// The record, with the content offsets of the entries whose bytes
/// are the image's left to fill.
pub contents: Vec<u8>,
/// Where in the record each such offset goes, and the file offset
/// of the bytes it points at.
pub image_contents: Vec<(u32, u64)>,
/// The content pool's offset in the record.
pub pool_offset: u32,
}
impl MergeableRecordSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, contents: Vec::new(), image_contents: Vec::new(), pool_offset: 0 }
}
}
impl Default for MergeableRecordSection {
fn default() -> Self {
Self::new()
}
}
/// Copies the record out, pointing its entries at their bytes now that
/// the record has its place: by offsets back from the content pool, and
/// the whole file by one back from the record.
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = &ctx.mergeable_record;
buf[..sec.contents.len()].copy_from_slice(&sec.contents);
let pool = sec.hdr.fileoff as i64 + sec.pool_offset as i64;
for &(at, fileoff) in &sec.image_contents {
let off = (fileoff as i64 - pool) as i32;
buf[at as usize..at as usize + 4].copy_from_slice(&off.to_le_bytes());
}
let image = header::IMAGE;
buf[image..image + 4].copy_from_slice(&(-(sec.hdr.fileoff as i32)).to_le_bytes());
buf[image + 4..image + 8].copy_from_slice(&(ctx.output_size as u32).to_le_bytes());
}
+1125
View File
File diff suppressed because it is too large. Load diff
+104
View File
@@ -0,0 +1,104 @@
//! The merged __objc_imageinfo section: the Objective-C runtime reads
//! exactly one 8-byte record per image.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_files::ObjcImageInfo;
use crate::macho::*;
/// The merged __objc_imageinfo section: the Objective-C runtime reads
/// exactly one 8-byte record per image.
#[derive(Debug)]
pub struct ObjcImageInfoSection {
pub hdr: ChunkHeader,
/// The merged flags word.
pub flags: u32,
}
impl ObjcImageInfoSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__DATA", b"__objc_imageinfo");
hdr.p2align = 2;
Self { hdr, flags: 0 }
}
}
impl Default for ObjcImageInfoSection {
fn default() -> Self {
Self::new()
}
}
/// Merges the objects' __objc_imageinfo records into the image's, cut
/// to the flags an image keeps, a lone record's too (see
/// objc_image_flags and merge_objc_info), in the order ld-prime checks
/// the objects in (see passes::check_objc_flags, which gave the
/// diagnostics). An image no dyld loads (-static, -preload, a kext),
/// whose Objective-C no runtime sets up, gets none from ld-prime.
pub fn create<E: Target>(ctx: &mut Context<E>) {
let mut objs: Vec<&crate::input_files::ObjectFile> =
ctx.objs.iter().filter(|o| o.is_reachable && o.objc_image_info.is_some()).collect();
objs.sort_by_key(|o| o.priority);
let info = objs
.iter()
.filter_map(|o| o.objc_image_info)
.map(|info| ObjcImageInfo { flags: objc_image_flags(info.flags), ..info })
.reduce(merge_objc_info);
let Some(ObjcImageInfo { flags, .. }) = info else { return };
if ctx.args.without_dyld() {
return;
}
ctx.objc_imageinfo.flags = flags;
ctx.objc_imageinfo.hdr.segname = crate::chunks::data_seg(ctx);
ctx.objc_imageinfo.hdr.size = 8;
ctx.chunks.push(ChunkId::ObjcImageInfo);
}
/// The Objective-C image info of objects whose info so far is `merged`,
/// and of an object with `info`, as ld-prime merges them: the first
/// Swift ABI version given stays, the Swift language version is the
/// oldest given, and the image's categories may have class properties
/// if every object's may (see objc_image_flags). Its class_ro_t
/// pointers are signed if those of every object with classes are; an
/// object without classes has none to sign, and its flag counts only
/// until one with classes comes, and only if it is set.
pub fn merge_objc_info(merged: ObjcImageInfo, info: ObjcImageInfo) -> ObjcImageInfo {
let (a, b) = (merged.flags, info.flags);
let abi = if a & 0xff00 != 0 { a & 0xff00 } else { b & 0xff00 };
let lang = match (a >> 16, b >> 16) {
(0, lang) | (lang, 0) => lang,
(a, b) => a.min(b),
};
let signed = match (merged.classes, info.classes) {
(false, false) => a | b,
(false, true) => b,
(true, false) => a,
(true, true) => a & b,
};
ObjcImageInfo {
flags: (lang << 16)
| abi
| (a & b & OBJC_HAS_CATEGORY_CLASS_PROPERTIES)
| (signed & OBJC_SIGNED_CLASS_RO),
classes: merged.classes || info.classes,
}
}
/// The Objective-C image info flags of an object as the image keeps
/// them, alone or to merge with others' (see merge_objc_info): the
/// Swift versions, category class properties and signed class_ro_t
/// pointers, which describe the code. ld-prime drops the rest: the
/// simulator bit (0x20) clang sets in a simulator's objects, the bits
/// of the garbage collector the runtime no longer has, and those of
/// dyld's optimizations (0x08 and 0x80), which dyld sets itself.
fn objc_image_flags(flags: u32) -> u32 {
flags & (0xffff_ff00 | OBJC_HAS_CATEGORY_CLASS_PROPERTIES | OBJC_SIGNED_CLASS_RO)
}
/// Writes the record: its version, 0, and the merged flags. A
/// relocatable output's __objc_imageinfo is written so too.
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
buf[..4].copy_from_slice(&0u32.to_le_bytes());
buf[4..8].copy_from_slice(&ctx.objc_imageinfo.flags.to_le_bytes());
}
+162
View File
@@ -0,0 +1,162 @@
//! __TEXT,__objc_methlist: the Objective-C method lists rewritten in the
//! relative (12-byte entry) form, which needs no fixups.
use crate::arch::Target;
use crate::chunks::output_section::append_tail;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::{ChunkHeader, ChunkId, OutputSectionId, Tail};
use crate::context::Context;
use crate::input_files::add_placed_isec;
use crate::macho::DYLD_CACHE_ADJ_V2_DELTA_32;
use crate::objc::{ObjcMethList, ObjcMethod};
use crate::passes::{SectionName, record_section};
use crate::symbol_moves::{Move, MoveOption};
use crate::util::align_to;
/// __TEXT,__objc_methlist: the Objective-C method lists rewritten in
/// the relative (12-byte entry) form, which needs no fixups.
#[derive(Debug)]
pub struct ObjcMethlistSection {
pub hdr: ChunkHeader,
/// The rewritten lists, each with its synthetic subsection here, in
/// the order of their subsections (each list is added with the
/// subsection made for it).
pub lists: Vec<ObjcMethList>,
}
impl ObjcMethlistSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__objc_methlist");
hdr.p2align = 3;
Self { hdr, lists: Vec::new() }
}
}
impl Default for ObjcMethlistSection {
fn default() -> Self {
Self::new()
}
}
/// Appends a method list in the relative form, at `*offset` in `sect`
/// (an __objc_methlist section of the internal object), and returns
/// its subsection.
pub(crate) fn add_relative_method_list<E: Target>(
ctx: &mut Context<E>,
sect: (u32, u32),
offset: &mut u64,
methods: Vec<ObjcMethod>,
) -> u32 {
let size = 8 + 12 * methods.len() as u64;
*offset = align_to(*offset, 4);
let isec = add_placed_isec(ctx, sect, 2, size, *offset);
*offset += size;
ctx.objc_methlist.lists.push(ObjcMethList { isec, methods });
isec
}
/// Lays out __objc_methlist, the method lists rewritten in the relative
/// form (see convert_objc_method_lists), in the order they were made,
/// each 8-byte aligned (the class records point at them); category
/// merging also retires some after their first placement. The lists
/// -move_to_ro_segment takes to another segment (see symbol_moves) go
/// alike to an __objc_methlist there.
pub fn lay_out_objc_method_lists<E: Target>(
ctx: &mut Context<E>,
text: SectionName,
moves: &hashbrown::HashMap<u32, Move>,
) {
if ctx.objc_methlist.lists.is_empty() {
return;
}
let order: Vec<u32> = ctx.objc_methlist.lists.iter().map(|l| l.isec).collect();
// The lists of each section, by the section: None for
// __TEXT,__objc_methlist. (Of the symbol moves, only
// -move_to_ro_segment's takes code.)
let mut groups: Vec<(Option<OutputSectionId>, Vec<u32>)> = Vec::new();
for isec in order {
let sec = &ctx.isecs[isec as usize];
let hdr = *sec.hdr(&ctx.objs[sec.file as usize]);
let m = moves.get(&isec).filter(|m| m.option == MoveOption::Ro);
let dest = m.and_then(|&m| record_section(ctx, &hdr, text, Some(m)));
match groups.iter_mut().find(|(d, _)| *d == dest) {
Some((_, lists)) => lists.push(isec),
None => groups.push((dest, vec![isec])),
}
}
for (dest, lists) in groups {
let mut off = 0u64;
for &isec in &lists {
off = align_to(off, 8);
ctx.isecs[isec as usize].offset = off as u32;
off += ctx.isecs[isec as usize].size as u64;
}
let (chunk, base) = match dest {
None => {
ctx.objc_methlist.hdr.size = off;
ctx.chunks.push(ChunkId::ObjcMethlist);
(ChunkId::ObjcMethlist, 0)
}
Some(id) => {
let osec = ctx.output_section_mut(id);
append_tail(osec, 3, Tail::ObjcMethlists, off);
(ChunkId::Output(id), osec.tail_off)
}
};
for isec in lists {
let isec = &mut ctx.isecs[isec as usize];
isec.offset += base as u32;
isec.set_output_section(chunk);
}
}
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
write_lists(ctx, ChunkId::ObjcMethlist, ctx.objc_methlist.hdr.addr, buf);
}
/// Writes the lists laid out in the chunk `chunk` at `chunk_addr` - this
/// section, or another segment's __objc_methlist a symbol move took
/// some to (see lay_out_objc_method_lists) - each at
/// its offset in `buf`, the chunk's contents.
pub fn write_lists<E: Target>(ctx: &Context<E>, chunk: ChunkId, chunk_addr: u64, buf: &mut [u8]) {
let lists = ctx.objc_methlist.lists.iter();
for list in lists.filter(|l| ctx.isecs[l.isec as usize].output_section() == Some(chunk)) {
let isec = &ctx.isecs[list.isec as usize];
let base = isec.offset as usize;
let addr = chunk_addr + base as u64;
let count = list.methods.len() as u32;
buf[base..base + 4].copy_from_slice(&(12u32 | 0x8000_0000).to_le_bytes());
buf[base + 4..base + 8].copy_from_slice(&count.to_le_bytes());
for (i, m) in list.methods.iter().enumerate() {
let at = base + 8 + 12 * i;
let field = addr + 8 + 12 * i as u64;
for (k, r) in [m.name, m.types, m.imp].into_iter().enumerate() {
let target = r.addr(ctx);
let rel =
if target == 0 { 0 } else { target.wrapping_sub(field + 4 * k as u64) as i64 };
if rel != rel as i32 as i64 {
crate::fatal!("relative method list entry out of range");
}
buf[at + 4 * k..at + 4 * k + 4].copy_from_slice(&(rel as i32).to_le_bytes());
}
}
}
}
/// The lists' entries' references, self-relative offsets, wherever the
/// lists went, for LC_SEGMENT_SPLIT_INFO.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
for list in &p.ctx.objc_methlist.lists {
let Some((n, base)) = p.isec(list.isec as usize) else {
continue;
};
for (i, m) in list.methods.iter().enumerate() {
for (k, r) in [m.name, m.types, m.imp].into_iter().enumerate() {
let from = (n, base + 8 + 12 * i as u64 + 4 * k as u64);
push(out, from, DYLD_CACHE_ADJ_V2_DELTA_32, p.objc_ref(r));
}
}
}
}
+170
View File
@@ -0,0 +1,170 @@
//! __TEXT,__objc_stubs: the linker-synthesized _objc_msgSend$<selector>
//! stubs, with the selector strings and references they load.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::symtab::{NamedEntry, local_msym};
use crate::chunks::{ChunkHeader, ChunkId, OutputSectionId};
use crate::context::Context;
use crate::macho::*;
use crate::symbol::SymbolId;
/// __TEXT,__objc_stubs: linker-synthesized _objc_msgSend$<selector>
/// stubs, with the selector strings and references they load (laid
/// out as the tails of the __objc_methname and __objc_selrefs output
/// sections, see Tail).
#[derive(Debug)]
pub struct ObjcStubsSection {
pub hdr: ChunkHeader,
/// _objc_msgSend$<selector> symbols, in entry order, with their
/// selector names. Stub `i` loads slot `i` of the __objc_selrefs
/// tail.
pub symbols: Vec<(SymbolId, &'static [u8])>,
/// Selector references synthesized for method lists whose selector
/// no input references: the __objc_methname subsection each points
/// at. They follow the stubs' slots in the __objc_selrefs tail.
pub extra_selrefs: Vec<u32>,
/// Contents of the synthesized __objc_methname tail, and each
/// selector's offset in it.
pub methname_data: Vec<u8>,
pub methname_offs: Vec<u64>,
/// The output sections carrying the synthesized selector strings
/// and selector references as their tail.
pub methname: Option<OutputSectionId>,
pub selrefs: Option<OutputSectionId>,
/// The _objc_msgSend symbol, once objc stubs exist.
pub msgsend_sym: Option<SymbolId>,
}
impl ObjcStubsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__objc_stubs");
hdr.flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
hdr.p2align = 5;
Self {
hdr,
symbols: Vec::new(),
extra_selrefs: Vec::new(),
methname_data: Vec::new(),
methname_offs: Vec::new(),
methname: None,
selrefs: None,
msgsend_sym: None,
}
}
/// Address of the selector reference slot `i` in the tail of the
/// __objc_selrefs output section: objc stub `i`'s, or past the
/// stubs, extra selector reference `i - stubs`.
pub fn selref_addr<E: Target>(&self, ctx: &Context<E>, i: usize) -> u64 {
let osec = ctx.output_section(self.selrefs.unwrap());
osec.hdr.addr + osec.tail_off + i as u64 * 8
}
}
impl Default for ObjcStubsSection {
fn default() -> Self {
Self::new()
}
}
/// The size of one __objc_stubs entry.
pub fn entry_size<E: Target>(ctx: &Context<E>) -> u64 {
if ctx.args.objc_stubs_small { E::OBJC_SMALL_STUB_SIZE } else { E::OBJC_STUB_SIZE }
}
/// The offset of objc stub `idx` in __objc_stubs.
pub fn entry_offset<E: Target>(ctx: &Context<E>, idx: u32) -> u64 {
idx as u64 * entry_size(ctx)
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
ctx.objc_stubs.hdr.size = ctx.objc_stubs.symbols.len() as u64 * entry_size(ctx);
// Code, which -text_exec moves as it does __stubs.
if ctx.args.text_exec {
ctx.objc_stubs.hdr.segname = b"__TEXT_EXEC";
}
// 32-byte aligned, but arm64's small stubs word-aligned.
if ctx.args.objc_stubs_small {
ctx.objc_stubs.hdr.p2align = 2;
}
}
/// The selector stubs' local symbols, each a non-external symbol with
/// N_PEXT set (nm: "was a private external"), as ld64 lists them -
/// NetNewsWire's debug dylib has 851 _objc_msgSend$... entries.
pub fn populate_symtab<E: Target>(ctx: &Context<E>, out: &mut Vec<NamedEntry>) {
let hdr = &ctx.objc_stubs.hdr;
for (i, &(sym, _)) in ctx.objc_stubs.symbols.iter().enumerate() {
let addr = hdr.addr + entry_offset(ctx, i as u32);
let ent = MachSym { n_type: N_PEXT | N_SECT, ..local_msym(hdr.sect_idx, addr) };
out.push((ctx.symbols[sym].name(), ent, None));
}
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_objc_stubs(ctx, ctx.objc_stubs.hdr.addr, buf);
}
/// Writes the selector names of the stubs into `buf`, the tail of
/// __objc_methname.
pub fn write_methnames<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let data = &ctx.objc_stubs.methname_data;
buf[..data.len()].copy_from_slice(data);
}
/// Writes the synthesized selector references into `buf`, the tail of
/// __objc_selrefs: the stubs', each to its selector's name in the tail
/// of __objc_methname, then the method lists' extra ones.
pub fn write_selrefs<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let stubs = &ctx.objc_stubs;
for i in 0..stubs.symbols.len() {
// Its selector's name, in the tail of __objc_methname.
let methname = ctx.output_section(stubs.methname.unwrap());
let val = methname.hdr.addr + methname.tail_off + stubs.methname_offs[i];
buf[i * 8..i * 8 + 8].copy_from_slice(&val.to_le_bytes());
}
let n = stubs.symbols.len();
for (j, &name) in stubs.extra_selrefs.iter().enumerate() {
let val = ctx.isecs[name as usize].addr(ctx);
buf[(n + j) * 8..(n + j) * 8 + 8].copy_from_slice(&val.to_le_bytes());
}
}
/// The synthesized selector reference slots, which hold pointers into
/// __objc_methname, for the loader to slide (see
/// rebase_info::rebase_locations).
pub fn rebase_locations<E: Target>(ctx: &Context<E>, locs: &mut Vec<u64>) {
for i in 0..ctx.objc_stubs.symbols.len() + ctx.objc_stubs.extra_selrefs.len() {
locs.push(ctx.objc_stubs.selref_addr(ctx, i));
}
}
/// The references of the stubs, to their selector references and
/// _objc_msgSend's GOT slot, and of the synthesized selector
/// references, to their selectors' names, for LC_SEGMENT_SPLIT_INFO.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
let stubs = &ctx.objc_stubs;
if ctx.chunks.contains(&ChunkId::ObjcStubs) {
let [sel, msgsend] = E::OBJC_STUB_REF_OFFS;
let msgsend_slot = p.got_slot(stubs.msgsend_sym.unwrap());
for i in 0..stubs.symbols.len() {
let at = entry_offset(ctx, i as u32);
p.pcrel(out, p.chunk(ChunkId::ObjcStubs, at + sel), Some(p.selref(i)));
p.pcrel(out, p.chunk(ChunkId::ObjcStubs, at + msgsend), Some(msgsend_slot));
}
}
if stubs.selrefs.is_some() {
let n = stubs.symbols.len();
for (i, &off) in stubs.methname_offs.iter().enumerate() {
let methname = ctx.output_section(stubs.methname.unwrap());
let name = (methname.hdr.sect_idx, methname.tail_off + off);
push(out, p.selref(i), DYLD_CACHE_ADJ_V2_POINTER_64, Some(name));
}
for (j, &name) in stubs.extra_selrefs.iter().enumerate() {
let to = p.isec(name as usize);
push(out, p.selref(n + j), DYLD_CACHE_ADJ_V2_POINTER_64, to);
}
}
}
+335
View File
@@ -0,0 +1,335 @@
//! An output section of the image: the concatenation of the input
//! subsections assigned to it, the range-extension thunks placed among
//! them, and the linker-synthesized tail after them.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places, push};
use crate::chunks::symtab::{NamedEntry, local_msym};
use crate::chunks::{ChunkHeader, ChunkId, OutputSectionId};
use crate::context::Context;
use crate::input_files::{DataField, data_blob_pointers, is_listed_out};
use crate::input_sections::{InputSection, InputSectionId, Reloc};
use crate::macho::DYLD_CACHE_ADJ_V2_POINTER_64;
use crate::symbol_moves::MoveOption;
use crate::thunks::Thunk;
use crate::util::align_to;
/// Linker-synthesized data appended to an output section after its
/// input subsections. The Objective-C runtime reads exactly one
/// __objc_selrefs section per image (the selectors it uniques at load
/// time) and one __objc_methname, so the selector references and name
/// strings the _objc_msgSend$<selector> stubs need cannot form
/// sections of their own next to the compilers'; they are laid out as
/// the tail of the section of that name, which is created empty when
/// no input provides one.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tail {
None,
/// Selector name strings for the synthesized objc stubs.
ObjcMethname,
/// Selector references (pointers into __objc_methname) loaded by the
/// synthesized objc stubs.
ObjcSelrefs,
/// Objective-C data the linker synthesized (merged class data:
/// class_ro_t records, protocol and property lists, classic method
/// lists) in the section of that name.
DataBlobs,
/// Relative method lists -move_to_ro_segment took out of
/// __TEXT,__objc_methlist (see chunks::objc_methlist).
ObjcMethlists,
}
#[derive(Debug)]
pub struct OutputSection {
pub hdr: ChunkHeader,
/// The input subsections, in output order.
pub members: Vec<InputSectionId>,
pub thunks: Vec<Thunk>,
pub tail: Tail,
/// Offset of the tail within the section, set at layout.
pub tail_off: u64,
/// Whether synthesized records (data blobs) stand among the members
/// in place of the input records they replace.
pub has_blobs: bool,
/// Whether thread-local data (input sections so typed) went here,
/// which a rename may have put in a section of another type (see
/// passes::set_osec_offsets).
pub has_tlv_data: bool,
/// For a section a symbol move made (see symbol_moves), the option
/// that moved its first member.
pub moved: Option<MoveOption>,
}
impl OutputSection {
pub fn new(segname: &'static [u8], sectname: &'static [u8]) -> Self {
Self {
hdr: ChunkHeader::new(segname, sectname),
members: Vec::new(),
thunks: Vec::new(),
tail: Tail::None,
tail_off: 0,
has_blobs: false,
has_tlv_data: false,
moved: None,
}
}
}
/// Appends a synthesized `tail` of `tail_size` bytes aligned to
/// 2^`p2align` to an output section, after its input subsections.
pub(crate) fn append_tail(osec: &mut OutputSection, p2align: u32, tail: Tail, tail_size: u64) {
osec.hdr.p2align = osec.hdr.p2align.max(p2align);
osec.tail = tail;
osec.tail_off = align_to(osec.hdr.size, 1 << p2align);
osec.hdr.size = osec.tail_off + tail_size;
}
/// Lays out an output section's members, each at its alignment after
/// the one before, and returns their offsets, in member order, and the
/// section's size, for compute_section_sizes to record. mold's layout,
/// which records the offsets itself.
pub fn layout<E: Target>(ctx: &Context<E>, osec: &OutputSection) -> (Vec<u64>, u64) {
let mut offs = Vec::with_capacity(osec.members.len());
let mut off = 0;
for &id in &osec.members {
let isec = &ctx.isecs[id];
off = isec.align_offset(off);
offs.push(off);
off += isec.size as u64;
}
(offs, off)
}
/// Runs `f` in parallel on each member's bytes in `buf`, the output
/// section's contents, along with the bytes up to the next member, as
/// mold's for_each_member does: splitting `buf` at member offsets,
/// rather than indexing it by them, gives each member its own exclusive
/// slice.
fn for_each_member<E: Target>(
ctx: &Context<E>,
osec: &OutputSection,
buf: &mut [u8],
f: impl Fn(InputSectionId, &mut [u8]) + Sync,
) {
let members = &osec.members;
let offset = |i: usize| match members.get(i) {
Some(&m) => ctx.isecs[m].offset as usize,
None => osec.hdr.size as usize,
};
// The first member starts at its offset modulo its alignment (see
// InputSection::align_offset), not necessarily at 0.
rayon::iter::split((0..members.len(), &mut buf[offset(0)..]), |(range, buf)| {
if range.len() <= 1 {
return ((range, buf), None);
}
let mid = range.start + range.len() / 2;
let (left, right) = buf.split_at_mut(offset(mid) - offset(range.start));
((range.start..mid, left), Some((mid..range.end, right)))
})
.for_each(|(range, mut buf)| {
let mut pos = offset(range.start);
for i in range {
let end = offset(i + 1);
let slice = buf.split_off_mut(..end - pos).unwrap();
pos = end;
f(members[i], slice);
}
});
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, id: OutputSectionId, buf: &mut [u8]) {
let osec = ctx.output_section(id);
// Subsections copy and relocate in parallel, as in mold: relocations
// only ever write within their own subsection.
for_each_member(ctx, osec, buf, |m, slice| {
let isec = &ctx.isecs[m];
let data = isec.contents();
if data.is_empty() {
return;
}
let own = &mut slice[..data.len()];
own.copy_from_slice(data);
let base = osec.hdr.addr + isec.offset as u64;
let rels = isec.rels(&ctx.objs[isec.file as usize]);
E::apply_reloc_alloc(ctx, rels, m as usize, base, own);
});
// The range-extension thunks, between the members.
crate::thunks::copy_buf(ctx, osec, buf);
// Synthesized Objective-C records, in the tail or among the members.
if osec.tail == Tail::DataBlobs || osec.has_blobs {
write_data_blobs(ctx, id, buf);
}
if osec.tail == Tail::ObjcMethlists {
let chunk = ChunkId::Output(id);
crate::chunks::objc_methlist::write_lists(ctx, chunk, osec.hdr.addr, buf);
}
// The linker-synthesized tail after the inputs.
let tail = &mut buf[osec.tail_off as usize..];
match osec.tail {
Tail::None => {}
Tail::ObjcMethname => crate::chunks::objc_stubs::write_methnames(ctx, tail),
// Written above, each record at its offset in the section.
Tail::DataBlobs | Tail::ObjcMethlists => {}
Tail::ObjcSelrefs => crate::chunks::objc_stubs::write_selrefs(ctx, tail),
}
}
/// Writes the synthesized records (data blobs) placed in an output
/// section, each at its offset in it.
fn write_data_blobs<E: Target>(ctx: &Context<E>, id: OutputSectionId, buf: &mut [u8]) {
for b in ctx
.data_blobs
.iter()
.filter(|b| ctx.isecs[b.isec as usize].output_section() == Some(ChunkId::Output(id)))
{
let mut at = ctx.isecs[b.isec as usize].offset as usize;
for f in &b.fields {
match f {
DataField::Bytes(bytes) => {
buf[at..at + bytes.len()].copy_from_slice(bytes);
at += bytes.len();
}
DataField::Ptr(r) => {
// dyld fills in a pointer to an import.
let addr = if r.import(ctx).is_some() { 0 } else { r.addr(ctx) };
buf[at..at + 8].copy_from_slice(&addr.to_le_bytes());
at += 8;
}
}
}
}
}
/// The pointers (see Target::is_absrel) the relocations of a live
/// subsection write, each with its address.
pub(crate) fn pointer_relocs<'a, E: Target>(
ctx: &'a Context<E>,
isec: &'a InputSection,
) -> impl Iterator<Item = (u64, &'a Reloc)> + 'a {
let base = ctx.chunk_header(isec.output_section().unwrap()).addr + isec.offset as u64;
let rels = isec.rels(&ctx.objs[isec.file as usize]).iter();
rels.filter(|rel| E::is_absrel(rel)).map(move |rel| (base + rel.offset as u64, rel))
}
/// The pointers of the output sections a loader must slide (see
/// rebase_info::rebase_locations): those written for absolute
/// relocations to local targets, and the synthesized records' pointer
/// fields into the image.
pub fn rebase_locations<E: Target>(ctx: &Context<E>, locs: &mut Vec<u64>) {
// Pointers written for UNSIGNED relocations to local targets.
for isec in ctx.isecs.iter() {
if !isec.is_emitted() {
continue;
}
let file = &ctx.objs[isec.file as usize];
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 = rel.sym(file);
let imported = target.is_some_and(|id| {
ctx.symbols[id].binds_pointer(ctx) || ctx.symbols[id].is_dtrace_pointer_target()
});
let absolute = target.is_some_and(|id| ctx.symbols[id].is_absolute(ctx));
if !imported && !absolute && !rel.refers_to_tls(ctx, file) {
locs.push(addr);
}
}
}
// Pointer fields of the synthesized Objective-C records.
for (addr, _) in data_blob_pointers(ctx) {
locs.push(addr);
}
}
/// The output sections' references for LC_SEGMENT_SPLIT_INFO but their
/// inputs' (see split_info::construct): the range-extension thunks' to
/// their targets, and the synthesized records' pointer fields.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
for osec in &ctx.output_sections {
for thunk in &osec.thunks {
for (i, &id) in thunk.syms.iter().enumerate() {
let from = (osec.hdr.sect_idx, thunk.offset + i as u64 * E::THUNK_SIZE);
p.pcrel(out, from, p.sym(id));
}
}
}
for blob in &ctx.data_blobs {
let Some((n, mut at)) = p.isec(blob.isec as usize) else {
continue;
};
for field in &blob.fields {
match field {
DataField::Bytes(bytes) => at += bytes.len() as u64,
DataField::Ptr(r) => {
push(out, (n, at), DYLD_CACHE_ADJ_V2_POINTER_64, p.objc_ref(*r));
at += 8;
}
}
}
}
}
/// The local symbols of an output section's range-extension thunks'
/// entries, named as ld-prime names its branch islands (see
/// island_symbols).
pub fn populate_symtab<E: Target>(
ctx: &Context<E>,
id: OutputSectionId,
out: &mut Vec<NamedEntry>,
) {
let osec = ctx.output_section(id);
for (addr, name) in island_symbols(ctx, osec) {
if !is_listed_out(ctx, name) {
out.push((name, local_msym(osec.hdr.sect_idx, addr), None));
}
}
}
/// The local symbols naming an output section's thunk entries, as
/// (address, name). ld-prime lists each branch island among the locals,
/// named after its target: "<target>.island" for the target's first
/// and "<target>.island<n>" for its n-th, in address order, which is
/// the order gather_thunk_addresses recorded them in.
pub fn island_symbols<E: Target>(
ctx: &Context<E>,
osec: &OutputSection,
) -> Vec<(u64, &'static [u8])> {
use std::io::Write;
let hdr = &osec.hdr;
osec.thunks
.par_iter()
.flat_map_iter(|thunk| {
let addr = |i: usize| hdr.addr + thunk.offset + i as u64 * E::THUNK_SIZE;
// A thunk can have many thousands of entries; their names
// share one allocation.
let mut buf = Vec::new();
let mut ends = Vec::with_capacity(thunk.syms.len());
for (i, &sym) in thunk.syms.iter().enumerate() {
let addrs = &ctx.symbols[sym].aux(&ctx.symbols).unwrap().thunk_addrs;
let n = addrs.iter().position(|&a| a == addr(i)).unwrap() + 1;
buf.extend_from_slice(ctx.symbols[sym].name());
buf.extend_from_slice(b".island");
if n > 1 {
write!(buf, "{n}").unwrap();
}
ends.push(buf.len());
}
let buf = crate::util::leak_bytes(buf);
let mut start = 0;
ends.into_iter().enumerate().map(move |(i, end)| {
let name = &buf[start..end];
start = end;
(addr(i), name)
})
})
.collect()
}
+138
View File
@@ -0,0 +1,138 @@
//! The LC_DYLD_INFO rebase opcode stream: every pointer dyld slides.
//! mold's reldyn.rs holds the ELF relative relocations it stands in for.
use crate::arch::Target;
use crate::chunks::{ChunkHeader, got, lazy_ptrs, objc_stubs, output_section, segment_and_offset};
use crate::context::Context;
use crate::macho::*;
use crate::util::encode_uleb;
/// The rebase opcode stream: every pointer dyld slides.
#[derive(Debug)]
pub struct RebaseInfoSection {
pub hdr: ChunkHeader,
/// The stream, built during layout.
pub contents: Vec<u8>,
}
impl RebaseInfoSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), contents: Vec::new() }
}
}
impl Default for RebaseInfoSection {
fn default() -> Self {
Self::new()
}
}
/// Every pointer a loader must slide when the image lands at another
/// address than its own: those each chunk writes - the output sections'
/// (for absolute relocations to local targets, and in the synthesized
/// records), the synthesized selector references, the lazy pointers
/// and the GOT slots. Unsorted. The rebase stream describes them to
/// dyld, a -static -pie image's local relocations to whatever loads it,
/// and legacy LINKEDIT's to dyld.
pub fn rebase_locations<E: Target>(ctx: &Context<E>) -> Vec<u64> {
let mut locs: Vec<u64> = Vec::new();
output_section::rebase_locations(ctx, &mut locs);
objc_stubs::rebase_locations(ctx, &mut locs);
lazy_ptrs::rebase_locations(ctx, &mut locs);
got::rebase_locations(ctx, &mut locs);
locs
}
/// Whether nothing ever slides the image: a non-PIE executable, which
/// the kernel maps where its segments say and dyld leaves there.
/// ld-prime records no rebases for one, in opcodes or in chains. (A
/// -static image keeps them for whatever loads it.)
pub fn is_never_slid<E: Target>(ctx: &Context<E>) -> bool {
ctx.args.output_type == MH_EXECUTE && !ctx.args.pie && !ctx.args.static_link
}
/// Builds the rebase opcode stream: it tells dyld which pointers in the
/// image it must slide when the image is loaded at a non-default address.
/// Every absolute address the linker writes into a data section gets a
/// record. Runs during layout, once every segment before __LINKEDIT has
/// an address.
pub fn construct<E: Target>(ctx: &Context<E>) -> Vec<u8> {
if is_never_slid(ctx) {
return Vec::new();
}
let mut locs = rebase_locations(ctx);
if locs.is_empty() {
return Vec::new();
}
locs.sort_unstable();
let mut buf = vec![REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER];
for op in rebase_ops(ctx, &locs) {
match op {
Op::SegOffset(seg, off) => {
buf.push(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | seg as u8);
encode_uleb(&mut buf, off);
}
Op::AddAddr(delta) if delta <= 15 * 8 && delta % 8 == 0 => {
buf.push(REBASE_OPCODE_ADD_ADDR_IMM_SCALED | (delta / 8) as u8)
}
Op::AddAddr(delta) => {
buf.push(REBASE_OPCODE_ADD_ADDR_ULEB);
encode_uleb(&mut buf, delta);
}
Op::Rebase(count) if count <= 15 => {
buf.push(REBASE_OPCODE_DO_REBASE_IMM_TIMES | count as u8)
}
Op::Rebase(count) => {
buf.push(REBASE_OPCODE_DO_REBASE_ULEB_TIMES);
encode_uleb(&mut buf, count);
}
}
}
buf.push(REBASE_OPCODE_DONE);
while !buf.len().is_multiple_of(8) {
buf.push(REBASE_OPCODE_DONE);
}
buf
}
/// A rebase opcode before encoding.
#[derive(Clone, Copy)]
enum Op {
SegOffset(usize, u64),
AddAddr(u64),
Rebase(u64),
}
/// The opcodes rebasing the sorted `locs`: the address moves by
/// ADD_ADDR_ULEB within a segment and by SET_SEGMENT_AND_OFFSET_ULEB
/// into another one, and a run of adjacent pointers is one
/// DO_REBASE_ULEB_TIMES, since each rebase advances the address past
/// its slot. A run ends at its segment's end, where dyld's bounds
/// check would reject a rebase past it.
fn rebase_ops<E: Target>(ctx: &Context<E>, locs: &[u64]) -> Vec<Op> {
let mut ops = Vec::new();
let mut seg_start = 0;
let mut seg_end = 0;
let mut cur_addr = 0;
let mut i = 0;
while i < locs.len() {
let addr = locs[i];
if addr < seg_start || seg_end <= addr {
let (seg, off) = segment_and_offset(ctx, addr);
seg_start = addr - off;
seg_end = seg_start + ctx.segments[seg].cmd.vmsize;
ops.push(Op::SegOffset(seg, off));
} else if addr != cur_addr {
ops.push(Op::AddAddr(addr.wrapping_sub(cur_addr)));
}
let mut n = 1;
while i + n < locs.len() && locs[i + n] == addr + n as u64 * 8 && locs[i + n] < seg_end {
n += 1;
}
ops.push(Op::Rebase(n as u64));
cur_addr = addr + n as u64 * 8;
i += n;
}
ops
}
+61
View File
@@ -0,0 +1,61 @@
//! The sections -sectcreate makes from files and -add_empty_section
//! makes empty, and an empty one for a section only a boundary symbol
//! names.
use crate::arch::Target;
use crate::chunks::ChunkHeader;
use crate::context::Context;
/// A section the -sectcreate and -add_empty_section options make (see
/// SectCreateInput) of a name no input section has, or an empty one
/// for a section only a boundary symbol names.
#[derive(Debug)]
pub struct SectCreateSection {
pub hdr: ChunkHeader,
/// The options' contents, in file order.
pub contents: &'static [u8],
}
impl SectCreateSection {
pub fn new(segname: &'static [u8], sectname: &'static [u8], contents: &'static [u8]) -> Self {
let mut hdr = ChunkHeader::new(segname, sectname);
hdr.size = contents.len() as u64;
Self { hdr, contents }
}
}
/// The input section of a -sectcreate or -add_empty_section option. It
/// joins the output section of its name of the input sections, after
/// them, or a section of the options' own (see
/// passes::place_sectcreate_inputs).
#[derive(Debug)]
pub struct SectCreateInput {
pub size: u64,
pub place: InputPlace,
}
/// Where a -sectcreate option's input section is laid out.
#[derive(Clone, Copy, Debug)]
pub enum InputPlace {
/// In an input section's output section, as this subsection of the
/// internal object.
Isec(u32),
/// In `Context::sectcreate_sections[section]`, at `offset`.
Section { section: u32, offset: u64 },
}
impl SectCreateInput {
/// The input section's address, and its output section's ordinal.
pub fn place<E: Target>(&self, ctx: &Context<E>) -> (u64, u8) {
match self.place {
InputPlace::Isec(id) => {
let isec = &ctx.isecs[id as usize];
(isec.addr(ctx), isec.sect_idx(ctx))
}
InputPlace::Section { section, offset } => {
let hdr = &ctx.sectcreate_sections[section as usize].hdr;
(hdr.addr + offset, hdr.sect_idx)
}
}
}
}
+498
View File
@@ -0,0 +1,498 @@
//! LC_SEGMENT_SPLIT_INFO: every reference the image makes from one
//! section into another, or into the mach header, so that the dyld
//! shared cache builder, or kmutil building a kernel collection, can
//! slide the segments apart and fix the references up. ld-prime 27037
//! writes it in the V2 format on both targets.
//!
//! The entries are ld-prime's. An absolute pointer or a distance (a
//! SUBTRACTOR pair) in data counts wherever it points, its own section
//! included; arm64's adrp, the ldr or add under it and its branches
//! count when they reach another section, and so does an x86-64 32-bit
//! displacement, which outside code counts anywhere. A reference dyld
//! binds (into another image) or to an absolute symbol needs no entry.
//! Besides the inputs' relocations, the linker's own references count:
//! __stubs and __stub_helper to their pointers, the lazy pointers to
//! __stub_helper, a GOT slot to a symbol of this image, __objc_stubs,
//! the synthesized selector references and Objective-C records, method
//! lists, __init_offsets and __unwind_info (image offsets), and
//! __eh_frame's records.
use rayon::prelude::*;
use crate::arch::{SplitRef, Target};
use crate::chunks::init_offsets::InitFunc;
use crate::chunks::{
ChunkHeader, ChunkId, delay_init, got, lazy_helpers, lazy_ptrs, objc_methlist, objc_stubs,
output_section, stub_helper, stubs,
};
use crate::context::Context;
use crate::input_files::FileId;
use crate::input_sections::{InputSection, Reloc, RelocTarget};
use crate::macho_consts::*;
use crate::objc::ObjcRef;
use crate::symbol::SymbolId;
use crate::util::encode_uleb;
#[derive(Debug)]
pub struct SplitInfoSection {
pub hdr: ChunkHeader,
pub contents: Vec<u8>,
}
impl SplitInfoSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr, contents: Vec::new() }
}
}
impl Default for SplitInfoSection {
fn default() -> Self {
Self::new()
}
}
/// One reference, in the V2 format's terms. The fields are in the
/// order the format groups references by: (from, to) section, then
/// target offset, then kind.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) struct Entry {
from_sect: u8,
to_sect: u8,
to_off: u64,
kind: u8,
from_off: u64,
}
/// A place in the image: a section's ordinal (0 for the mach header)
/// and an offset in it.
pub(crate) type Place = (u8, u64);
pub(crate) fn push(out: &mut Vec<Entry>, from: Place, kind: u8, to: Option<Place>) {
if let Some((to_sect, to_off)) = to {
out.push(Entry { from_sect: from.0, to_sect, to_off, kind, from_off: from.1 });
}
}
pub fn construct<E: Target>(ctx: &Context<E>) -> Vec<u8> {
if !ctx.args.shared_region {
return Vec::new();
}
let places = Places::new(ctx);
let mut entries: Vec<Entry> = ctx
.output_sections
.par_iter()
.flat_map(|osec| {
osec.members.par_iter().flat_map_iter(|&id| {
let mut v = Vec::new();
places.isec_entries(id as usize, &mut v);
v
})
})
.collect();
// The references of the linker's own chunks, each of which lists
// its own.
stubs::split_info_entries(&places, &mut entries);
lazy_ptrs::split_info_entries(&places, &mut entries);
stub_helper::split_info_entries(&places, &mut entries);
got::split_info_entries(&places, &mut entries);
output_section::split_info_entries(&places, &mut entries);
lazy_helpers::split_info_entries(&places, &mut entries);
delay_init::split_info_entries(&places, &mut entries);
objc_stubs::split_info_entries(&places, &mut entries);
objc_methlist::split_info_entries(&places, &mut entries);
places.table_entries(&mut entries);
places.unwind_entries(&mut entries);
places.eh_frame_entries(&mut entries);
entries.par_sort_unstable();
encode(&entries)
}
/// Encodes the V2 format:
/// 0x7f <count> FromToSection+ 0, padded to 8 bytes
/// FromToSection :== <from-sect> <to-sect> <count> ToOffset+
/// ToOffset :== <to-offset-delta> <count> FromOffset+
/// FromOffset :== <kind> <count> <from-offset-delta>+
fn encode(entries: &[Entry]) -> Vec<u8> {
let mut buf = vec![DYLD_CACHE_ADJ_V2_FORMAT];
let sects: Vec<&[Entry]> =
entries.chunk_by(|a, b| (a.from_sect, a.to_sect) == (b.from_sect, b.to_sect)).collect();
encode_uleb(&mut buf, sects.len() as u64);
for sect in sects {
encode_uleb(&mut buf, sect[0].from_sect as u64);
encode_uleb(&mut buf, sect[0].to_sect as u64);
let targets: Vec<&[Entry]> = sect.chunk_by(|a, b| a.to_off == b.to_off).collect();
encode_uleb(&mut buf, targets.len() as u64);
let mut last_to = 0u64;
for target in targets {
encode_uleb(&mut buf, target[0].to_off.wrapping_sub(last_to));
last_to = target[0].to_off;
let kinds: Vec<&[Entry]> = target.chunk_by(|a, b| a.kind == b.kind).collect();
encode_uleb(&mut buf, kinds.len() as u64);
for kind in kinds {
encode_uleb(&mut buf, kind[0].kind as u64);
encode_uleb(&mut buf, kind.len() as u64);
let mut last_from = 0u64;
for e in kind {
encode_uleb(&mut buf, e.from_off - last_from);
last_from = e.from_off;
}
}
}
}
buf.push(0);
buf.resize(buf.len().next_multiple_of(8), 0);
buf
}
/// Resolves what the image refers to into places, as the output's
/// writers resolve them into addresses.
pub(crate) struct Places<'a, E: Target> {
pub(crate) ctx: &'a Context<E>,
header_addr: u64,
/// The sections with contents, by address: (start, end, ordinal).
sects: Vec<(u64, u64, u8)>,
/// Each section's address, by ordinal.
starts: Vec<u64>,
/// The layout-boundary symbols (section$start$..., segment$end$...),
/// each at the section it bounds.
boundaries: hashbrown::HashMap<SymbolId, Place>,
}
impl<'a, E: Target> Places<'a, E> {
fn new(ctx: &'a Context<E>) -> Self {
let mut sects: Vec<(u64, u64, u8)> = ctx
.chunks
.iter()
.map(|&id| ctx.chunk_header(id))
.filter(|h| h.is_sect && h.size > 0)
.map(|h| (h.addr, h.addr + h.size, h.sect_idx))
.collect();
sects.sort_unstable();
let mut starts = vec![0; 256];
for hdr in ctx.chunks.iter().map(|&id| ctx.chunk_header(id)).filter(|h| h.is_sect) {
starts[hdr.sect_idx as usize] = hdr.addr;
}
let mut places = Self {
ctx,
header_addr: ctx.mach_header.hdr.addr,
sects,
starts,
boundaries: Default::default(),
};
places.boundaries = ctx
.boundary_syms
.iter()
.filter_map(|(id, is_start, seg, sect)| {
Some((*id, places.boundary(*is_start, seg, *sect)?))
})
.collect();
places
}
/// A section$ symbol's section, at its start or end; a segment$
/// symbol's first section at its start, or last at its end.
fn boundary(&self, is_start: bool, seg: &[u8], sect: Option<&[u8]>) -> Option<Place> {
let ctx = self.ctx;
let mut hdrs = ctx.chunks.iter().map(|&id| ctx.chunk_header(id)).filter(|h| {
h.is_sect && h.segname == seg && sect.is_none_or(|sect| h.sectname == sect)
});
let hdr = if is_start { hdrs.next()? } else { hdrs.last()? };
let end = match sect {
Some(_) => hdr.addr + hdr.size,
None => {
let seg = ctx.segments.iter().find(|s| s.name == seg)?;
seg.cmd.vmaddr + seg.cmd.vmsize
}
};
Some((hdr.sect_idx, if is_start { 0 } else { end - hdr.addr }))
}
/// Where offset `off` of chunk `id` lies.
pub(crate) fn chunk(&self, id: ChunkId, off: u64) -> Place {
let hdr = self.ctx.chunk_header(id);
(hdr.sect_idx, hdr.addr - self.starts[hdr.sect_idx as usize] + off)
}
/// Where address `addr`, in chunk `id`, lies.
pub(crate) fn chunk_addr(&self, id: ChunkId, addr: u64) -> Place {
let hdr = self.ctx.chunk_header(id);
(hdr.sect_idx, addr - self.starts[hdr.sect_idx as usize])
}
/// Where a subsection lies, if it is laid out.
pub(crate) fn isec(&self, id: usize) -> Option<Place> {
let isec = &self.ctx.isecs[self.ctx.isecs.resolve(id)];
let chunk = isec.output_section()?;
(isec.offset != u32::MAX).then(|| self.chunk(chunk, isec.offset as u64))
}
/// Where a symbol's address lies, as Symbol::addr resolves it
/// (a dylib symbol at its stub, see Symbol::stub_entry); None for
/// an absolute symbol. The linker's own sectionless symbols are the
/// layout boundaries and the mach header's names (___dso_handle,
/// __mh_*_header).
pub(crate) fn sym(&self, id: SymbolId) -> Option<Place> {
let ctx = self.ctx;
let sym = &ctx.symbols[id];
match sym.file()? {
FileId::Dylib(_) => {
let (chunk, addr) = sym.stub_entry(ctx)?;
Some(self.chunk_addr(chunk, addr))
}
FileId::Obj(obj) => {
if let Some(isec) = sym.input_section() {
let (n, off) = self.isec(isec as usize)?;
Some((n, off + sym.value))
} else if let Some((chunk, addr)) = sym.stub_entry(ctx) {
Some(self.chunk_addr(chunk, addr))
} else if ctx.is_internal(obj as usize) {
let header = (0, sym.value.wrapping_sub(self.header_addr));
Some(self.boundaries.get(&id).copied().unwrap_or(header))
} else {
None
}
}
}
}
/// Where a symbol dyld doesn't bind lies; None for an import.
pub(crate) fn own_sym(&self, id: SymbolId) -> Option<Place> {
if self.ctx.symbols[id].is_imported() { None } else { self.sym(id) }
}
/// A symbol's GOT slot, or a lazy dylib's symbol's __lazy_load_got
/// slot.
pub(crate) fn got_slot(&self, id: SymbolId) -> Place {
let (chunk, addr) = self.ctx.symbols[id].got_slot(self.ctx);
self.chunk_addr(chunk, addr)
}
/// Where __lazy_helpers entry `i` lies.
pub(crate) fn lazy_helper(&self, i: u32) -> Place {
self.chunk_addr(ChunkId::LazyHelpers, self.ctx.lazy_helpers.helper_addr(i as usize))
}
pub(crate) fn got_index(&self, i: usize) -> Place {
self.chunk_addr(ChunkId::Got, self.ctx.got.slot_addr(i))
}
/// The place of an image offset (in __unwind_info): the section it
/// lies in, or for the index's sentinel, one past the end of the
/// last function, the section that function ends.
fn image_offset(&self, off: u32, is_sentinel: bool) -> Option<Place> {
let addr = self.header_addr + off as u64;
let key = addr - if is_sentinel { 2 } else { 0 };
let i = self.sects.partition_point(|&(start, _, _)| start <= key).checked_sub(1)?;
let (start, end, n) = self.sects[i];
(key < end).then_some((n, addr - start))
}
/// Where relocation `r` of a subsection points, as apply_reloc_alloc
/// resolves it: at a stub or GOT slot, or at the target plus the
/// addend; for a distance's positive term, at the target itself,
/// as ld64 takes it. None for a reference dyld binds.
fn reloc_target(&self, isec: &InputSection, r: &Reloc, with_addend: bool) -> Option<Place> {
let ctx = self.ctx;
let addend = if with_addend { r.addend } else { 0 };
let id = match r.target() {
RelocTarget::Section(idx) => {
let (n, off) = self.isec(idx as usize)?;
return Some((n, off.wrapping_add_signed(addend)));
}
RelocTarget::Sym(idx) => ctx.objs[isec.file as usize].symbols[idx as usize],
};
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 sym.is_interposable(ctx)
&& let Some(stub) = sym.stub_idx(&ctx.symbols)
{
return Some(self.chunk(ChunkId::Stubs, stubs::entry_offset::<E>(stub)));
}
let (n, off) = self.sym(id)?;
return Some((n, off.wrapping_add_signed(addend)));
}
let (n, off) = self.own_sym(id)?;
Some((n, off.wrapping_add_signed(addend)))
}
/// The references of one input subsection's relocations.
fn isec_entries(&self, id: usize, out: &mut Vec<Entry>) {
let ctx = self.ctx;
let isec = &ctx.isecs[id];
let Some(chunk) = isec.output_section().filter(|_| isec.is_alive()) else {
return;
};
let hdr = ctx.chunk_header(chunk);
let file = &ctx.objs[isec.file as usize];
let rels = isec.rels(file);
let mut i = 0;
while i < rels.len() {
let r = &rels[i];
let from = (hdr.sect_idx, isec.offset as u64 + r.offset as u64);
let pointer = if r.size == 8 {
DYLD_CACHE_ADJ_V2_POINTER_64
} else {
DYLD_CACHE_ADJ_V2_POINTER_32
};
match E::split_ref(r.ty) {
// The UNSIGNED record that follows names the target.
SplitRef::Subtractor => {
i += 1;
let kind = if r.size == 8 {
DYLD_CACHE_ADJ_V2_DELTA_64
} else {
DYLD_CACHE_ADJ_V2_DELTA_32
};
push(out, from, kind, self.reloc_target(isec, &rels[i], false));
}
// A thread-local's descriptor holds an offset into the
// thread-local template, which doesn't move.
SplitRef::Pointer if r.refers_to_tls(ctx, file) => {}
SplitRef::Pointer => push(out, from, pointer, self.reloc_target(isec, r, true)),
split => {
// A GOT load of a lazy or delay-init dylib's symbol
// that calls a helper instead: an arm64 site is a
// branch now, and ld-prime keeps an x86-64 one at
// its old displacement's place.
let site = (id as u32, r.offset);
let lazy = ctx.lazy_helpers.sites.get(&site).map(|&i| self.lazy_helper(i));
let delay =
ctx.delay_init.sites.get(&site).map(|&i| self.delay_helper(i as usize));
let (split, to) = match lazy.or(delay) {
Some(helper) if split == SplitRef::Page => {
(SplitRef::Branch26, Some(helper))
}
Some(helper) => (split, Some(helper)),
None => (split, self.reloc_target(isec, r, true)),
};
let kind = match split {
SplitRef::Page => DYLD_CACHE_ADJ_V2_ARM64_ADRP,
SplitRef::PageOff => DYLD_CACHE_ADJ_V2_ARM64_OFF12,
SplitRef::Branch26 => DYLD_CACHE_ADJ_V2_ARM64_BR26,
_ => DYLD_CACHE_ADJ_V2_DELTA_32,
};
if to.is_some_and(|to| to.0 != from.0) {
push(out, from, kind, to);
}
}
}
i += 1;
}
}
/// An address the linker's own code materializes PC-relatively at
/// `from`, when it reaches another section.
pub(crate) fn pcrel(&self, out: &mut Vec<Entry>, from: Place, to: Option<Place>) {
if to.is_some_and(|to| to.0 != from.0) {
for (i, &kind) in E::SPLIT_PCREL_KINDS.iter().enumerate() {
push(out, (from.0, from.1 + 4 * i as u64), kind, to);
}
}
}
/// Where __delay_helper's load helper `i` lies.
pub(crate) fn delay_helper(&self, i: usize) -> Place {
self.chunk_addr(ChunkId::DelayHelper, self.ctx.delay_init.helper_addr(i))
}
pub(crate) fn objc_ref(&self, r: ObjcRef) -> Option<Place> {
match r {
ObjcRef::Isec(isec, off) => self.isec(isec as usize).map(|(n, o)| (n, o + off)),
ObjcRef::Sym(id, addend) => {
self.own_sym(id).map(|(n, o)| (n, o.wrapping_add_signed(addend)))
}
ObjcRef::TailSelref(i) => Some(self.selref(i)),
ObjcRef::Null => None,
}
}
/// Slot `i` of the synthesized selector references.
pub(crate) fn selref(&self, i: usize) -> Place {
let osec = self.ctx.output_section(self.ctx.objc_stubs.selrefs.unwrap());
(osec.hdr.sect_idx, osec.tail_off + i as u64 * 8)
}
/// __init_offsets: an image offset per initializer.
fn table_entries(&self, out: &mut Vec<Entry>) {
let ctx = self.ctx;
if !ctx.chunks.contains(&ChunkId::InitOffsets) {
return;
}
for (i, &func) in ctx.init_offsets.init_funcs.iter().enumerate() {
let InitFunc::Local(isec, off) = func else { continue };
let from = self.chunk(ChunkId::InitOffsets, i as u64 * 4);
let to = self.isec(isec).map(|(n, o)| (n, o + off));
push(out, from, DYLD_CACHE_ADJ_V2_IMAGE_OFF_32, to);
}
}
/// __unwind_info's image offsets: the personalities' GOT slots,
/// the first function of each second-level page and the end of
/// the last one, and the (function, LSDA) pairs.
fn unwind_entries(&self, out: &mut Vec<Entry>) {
let ctx = self.ctx;
if !ctx.chunks.contains(&ChunkId::UnwindInfo) {
return;
}
let buf = &ctx.unwind_info.contents;
let word = |off: usize| u32::from_le_bytes(buf[off..off + 4].try_into().unwrap());
let from = |off: usize| self.chunk(ChunkId::UnwindInfo, off as u64);
let kind = DYLD_CACHE_ADJ_V2_IMAGE_OFF_32;
let personalities = word(12) as usize;
for (i, &id) in ctx.unwind_info.personalities.iter().enumerate() {
push(out, from(personalities + 4 * i), kind, Some(self.got_slot(id)));
}
let (index, count) = (word(20) as usize, word(24) as usize);
for i in 0..count {
let off = index + 12 * i;
push(out, from(off), kind, self.image_offset(word(off), i + 1 == count));
}
if count > 0 {
let lsdas = word(index + 8) as usize..word(index + 12 * (count - 1) + 8) as usize;
for off in lsdas.step_by(4) {
push(out, from(off), kind, self.image_offset(word(off), false));
}
}
}
/// __eh_frame: each CIE's personality pointer (to its GOT slot),
/// and each FDE's function and LSDA. (An FDE's CIE pointer stays in
/// the section.)
fn eh_frame_entries(&self, out: &mut Vec<Entry>) {
let ctx = self.ctx;
if !ctx.chunks.contains(&ChunkId::EhFrame) {
return;
}
let at = |off: u64| self.chunk(ChunkId::EhFrame, off);
for cie in ctx.cies.iter().filter(|cie| cie.is_alive) {
if let Some(id) = cie.personality {
let from = at((cie.output_offset + cie.personality_offset) as u64);
push(out, from, DYLD_CACHE_ADJ_V2_DELTA_32, Some(self.got_slot(id)));
}
}
for fde in &ctx.fdes {
let off = fde.output_offset as u64;
let cie = &ctx.cies[fde.cie as usize];
let func = self.isec(fde.isec as usize).map(|(n, o)| (n, o + fde.func_offset as u64));
let kind = match cie.pc_size() {
4 => DYLD_CACHE_ADJ_V2_DELTA_32,
_ => DYLD_CACHE_ADJ_V2_DELTA_64,
};
push(out, at(off + 8), kind, func);
if let Some((isec, lsda_off)) = fde.lsda {
let pos = fde.lsda_pos(cie.pc_size()) as u64;
let kind = match cie.lsda_size() {
8 => DYLD_CACHE_ADJ_V2_DELTA_64,
_ => DYLD_CACHE_ADJ_V2_DELTA_32,
};
let to = self.isec(isec as usize).map(|(n, o)| (n, o + lsda_off as u64));
push(out, at(off + pos), kind, to);
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
//! The string table in __LINKEDIT.
use crate::chunks::ChunkHeader;
/// The string table.
#[derive(Debug)]
pub struct StrtabSection {
pub hdr: ChunkHeader,
}
impl StrtabSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::linkedit();
hdr.p2align = 3;
Self { hdr }
}
}
impl Default for StrtabSection {
fn default() -> Self {
Self::new()
}
}
+124
View File
@@ -0,0 +1,124 @@
//! __TEXT,__stub_helper: with classic dyld info, the code a lazy pointer
//! initially points at, which enters dyld_stub_binder with the pointer's
//! lazy-bind record - or, in legacy LINKEDIT, dyld_stub_binding_helper
//! with the pointer's address.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places};
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_files::add_data_word;
use crate::macho::*;
use crate::symbol::SymbolId;
/// __TEXT,__stub_helper: with classic dyld info, the code a lazy
/// pointer initially points at, which enters dyld_stub_binder with the
/// pointer's lazy-bind record - or, in legacy LINKEDIT,
/// dyld_stub_binding_helper with the pointer's address.
#[derive(Debug)]
pub struct StubHelperSection {
pub hdr: ChunkHeader,
/// dyld_stub_binder, resolved from the loaded dylibs when lazy
/// binding is in use, and the __dyld_private word the stub helper
/// hands it (a synthesized record in __DATA,__data).
pub dyld_stub_binder: Option<SymbolId>,
pub dyld_private_isec: u32,
/// In legacy LINKEDIT (Args::legacy_linkedit), what the entries
/// jump to instead: crt1.o's dyld_stub_binding_helper, if defined.
pub binding_helper: Option<SymbolId>,
}
impl StubHelperSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__stub_helper");
hdr.flags = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
hdr.p2align = 2;
Self { hdr, dyld_stub_binder: None, dyld_private_isec: u32::MAX, binding_helper: None }
}
}
impl Default for StubHelperSection {
fn default() -> Self {
Self::new()
}
}
/// The size of __stub_helper's header, the code its entries jump to
/// that enters dyld_stub_binder. Legacy LINKEDIT's entries go to
/// crt1.o's dyld_stub_binding_helper instead, and its helper has no
/// header.
pub fn header_size<E: Target>(ctx: &Context<E>) -> u64 {
if ctx.args.legacy_linkedit { 0 } else { E::STUB_HELPER_HEADER_SIZE }
}
/// The offset in __stub_helper of the entry of lazily bound stub `idx`
/// (an index into StubsSection::lazy), past the header.
pub fn entry_offset<E: Target>(ctx: &Context<E>, idx: u32) -> u64 {
header_size(ctx) + idx as u64 * E::STUB_HELPER_ENTRY_SIZE
}
/// With lazy binding, the stub helper enters dyld through
/// dyld_stub_binder (libSystem's): the symbol is bound from whichever
/// loaded dylib exports it - or, where the image may look it up
/// dynamically (-undefined dynamic_lookup, -U), from whatever image dyld
/// finds it in - given a GOT slot, and __dyld_private (the word
/// dyld_stub_binder is handed, ld64 puts it in __DATA,__data) is
/// synthesized. Once, on the first stub.
pub fn ensure_stub_binder<E: Target>(ctx: &mut Context<E>) {
// Legacy LINKEDIT's helper enters dyld through crt1.o's
// dyld_stub_binding_helper instead (see resolve_stub_binder).
if ctx.stub_helper.dyld_stub_binder.is_some() || ctx.args.legacy_linkedit {
return;
}
let Some(id) = ctx.bind_linker_import(b"dyld_stub_binder") else {
crate::fatal!("lazy binding needs dyld_stub_binder, which no loaded dylib exports");
};
ctx.symbols[id].set_used(true);
crate::chunks::got::add_got_symbol(ctx, id);
ctx.stub_helper.dyld_stub_binder = Some(id);
let isec = add_data_word(ctx, 8);
ctx.stub_helper.dyld_private_isec = isec;
ctx.extra_local_syms.push((b"__dyld_private", isec));
}
/// Finds the helper legacy LINKEDIT's stub helper entries jump to. It
/// binds no dyld_stub_binder: its entries go to dyld_stub_binding_helper,
/// which crt1.o, dylib1.o or bundle1.o defines; no dylib exports it.
/// (Otherwise dyld_stub_binder is bound once a stub needs it; see
/// ensure_stub_binder.)
pub fn resolve_stub_binder<E: Target>(ctx: &mut Context<E>) {
if ctx.args.legacy_linkedit {
let id = ctx.symbols.lookup(b"dyld_stub_binding_helper");
let id = id.filter(|&id| ctx.symbols[id].input_section().is_some());
ctx.stub_helper.binding_helper = id;
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
ctx.stub_helper.hdr.size =
header_size(ctx) + ctx.stubs.lazy.len() as u64 * E::STUB_HELPER_ENTRY_SIZE;
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let addr = ctx.stub_helper.hdr.addr;
if ctx.args.legacy_linkedit {
E::write_legacy_stub_helper(ctx, addr, buf);
} else {
E::write_stub_helper(ctx, addr, buf);
}
}
/// The header's references to __dyld_private and dyld_stub_binder's GOT
/// slot, for LC_SEGMENT_SPLIT_INFO.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
if !ctx.chunks.contains(&ChunkId::StubHelper) {
return;
}
let helper = &ctx.stub_helper;
let [private, binder] = E::STUB_HELPER_REF_OFFS;
let to = p.isec(helper.dyld_private_isec as usize);
p.pcrel(out, p.chunk(ChunkId::StubHelper, private), to);
let to = helper.dyld_stub_binder.map(|id| p.got_slot(id));
p.pcrel(out, p.chunk(ChunkId::StubHelper, binder), to);
}
+89
View File
@@ -0,0 +1,89 @@
//! __TEXT,__stubs: jump stubs for calls to imported functions.
use crate::arch::Target;
use crate::chunks::split_info::{Entry, Places};
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::macho::*;
use crate::symbol::SymbolId;
/// __TEXT,__stubs: jump stubs for calls to imported functions.
#[derive(Debug)]
pub struct StubsSection {
pub hdr: ChunkHeader,
/// Symbols with a __stubs entry, in stub order.
pub symbols: Vec<SymbolId>,
/// The stubs bound lazily, by index into `symbols`, in stub order:
/// only these have a lazy pointer and a stub helper entry (a
/// weak-lookup stub jumps through its GOT slot).
pub lazy: Vec<u32>,
}
impl StubsSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__stubs");
hdr.flags = S_SYMBOL_STUBS | S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
hdr.p2align = 2;
Self { hdr, symbols: Vec::new(), lazy: Vec::new() }
}
}
impl Default for StubsSection {
fn default() -> Self {
Self::new()
}
}
/// The offset of stub `idx` in __stubs.
pub fn entry_offset<E: Target>(idx: u32) -> u64 {
idx as u64 * E::STUB_SIZE
}
/// Gives a symbol a stub, unless it has one, and the stub what it jumps
/// through: the symbol's lazy pointer, which the stub helper fills in,
/// or, without lazy binding, its GOT slot. A symbol dyld resolves by
/// weak lookup goes through its GOT slot either way, never a lazy
/// pointer, as ld64 has it.
pub fn add_symbol<E: Target>(ctx: &mut Context<E>, id: SymbolId) {
if ctx.symbols[id].stub_idx(&ctx.symbols).is_some() {
return;
}
ctx.symbols.aux_mut(id).stub_idx = ctx.stubs.symbols.len() as u32;
ctx.stubs.symbols.push(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);
}
}
pub fn update_shdr<E: Target>(ctx: &mut Context<E>) {
if ctx.args.text_exec {
ctx.stubs.hdr.segname = b"__TEXT_EXEC";
}
ctx.stubs.hdr.reserved2 = E::STUB_SIZE as u32;
ctx.stubs.hdr.size = ctx.stubs.symbols.len() as u64 * E::STUB_SIZE;
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
E::write_stubs(ctx, ctx.stubs.hdr.addr, buf);
}
/// The stubs' references to the pointers they jump through, for
/// LC_SEGMENT_SPLIT_INFO: lazy pointers, or GOT slots.
pub(crate) fn split_info_entries<E: Target>(p: &Places<'_, E>, out: &mut Vec<Entry>) {
let ctx = p.ctx;
if !ctx.chunks.contains(&ChunkId::Stubs) {
return;
}
for (i, &id) in ctx.stubs.symbols.iter().enumerate() {
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();
p.chunk_addr(ChunkId::LazyPtrs, ctx.lazy_ptrs.slot_addr(lazy))
} else {
p.got_slot(id)
};
let off = entry_offset::<E>(i as u32) + E::STUB_REF_OFF;
p.pcrel(out, p.chunk(ChunkId::Stubs, off), Some(slot));
}
}
File diff suppressed because it is too large. Load diff
+435
View File
@@ -0,0 +1,435 @@
//! __TEXT,__unwind_info: the compact unwind table, generated from the
//! objects' __compact_unwind records.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::{ChunkHeader, ChunkId};
use crate::context::Context;
use crate::input_sections::{InputSection, UnwindRecord};
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::{push16, push32};
#[derive(Debug)]
pub struct UnwindInfoSection {
pub hdr: ChunkHeader,
/// The encoded table, except its personality cells (GOT addresses
/// unknown when __TEXT is sized): the symbols to patch into offsets
/// 28, 32, ... at copy time.
pub contents: Vec<u8>,
pub personalities: Vec<SymbolId>,
/// The room __TEXT keeps for the section when that is more than its
/// first encoding takes: what an encoding with every segment placed
/// took (see set_osec_offsets). The contents are padded with zeros.
pub min_size: u64,
}
/// The greatest offset into __eh_frame the low 24 bits of a DWARF-mode
/// encoding hold.
pub const MAX_FDE_OFFSET: u32 = 0xff_ffff;
impl UnwindInfoSection {
pub fn new() -> Self {
let mut hdr = ChunkHeader::new(b"__TEXT", b"__unwind_info");
hdr.p2align = 2;
Self { hdr, contents: Vec::new(), personalities: Vec::new(), min_size: 0 }
}
}
impl Default for UnwindInfoSection {
fn default() -> Self {
Self::new()
}
}
/// Encodes __unwind_info for the addresses its segment has, and returns
/// its size. The personality cells the encoding cannot know yet (GOT
/// addresses) come back as a patch list for the copy phase.
pub fn compute_size<E: Target>(ctx: &mut Context<E>) -> u64 {
let (data, personalities) = {
let _t = ctx.timer("unwind_encode");
encode_unwind_info(ctx)
};
let size = data.len() as u64;
ctx.unwind_info.contents = data;
ctx.unwind_info.personalities = personalities;
size
}
/// __unwind_info is encoded as __TEXT is laid out, when only __TEXT's
/// addresses are final. If it covers code or LSDAs in other segments
/// too, this encodes it again now every segment has its address.
/// Returns false if that encoding needs more room than __TEXT left the
/// section; the layout is then done again with that much room (a
/// smaller one leaves zeros after it).
pub fn finish_unwind_info<E: Target>(ctx: &mut Context<E>) -> bool {
if !ctx.chunks.contains(&ChunkId::UnwindInfo) || !covers_other_segments(ctx) {
return true;
}
let size = compute_size(ctx);
if size > ctx.unwind_info.hdr.size {
ctx.unwind_info.min_size = size;
return false;
}
true
}
pub fn copy_buf<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let sec = &ctx.unwind_info;
debug_assert!(sec.contents.len() as u64 <= sec.hdr.size);
buf[..sec.contents.len()].copy_from_slice(&sec.contents);
// Patch the personality cells now the GOT has addresses; the header
// says where the array is (after the common encodings).
let base = ctx.mach_header.hdr.addr;
let personality_off = u32::from_le_bytes(sec.contents[12..16].try_into().unwrap()) as usize;
for (i, &sym) in sec.personalities.iter().enumerate() {
let off = personality_off + i * 4;
let val = ctx.symbols[sym].got_addr(ctx).wrapping_sub(base) as u32;
buf[off..off + 4].copy_from_slice(&val.to_le_bytes());
}
}
/// Encodes the __unwind_info section from the compact unwind records.
///
/// __unwind_info stores unwind records in two-level tables: a first-level
/// table of page entries, each covering up to 2^24 bytes of code, and
/// second-level pages holding 32-bit entries with the function's low
/// address bits and an index into a per-page encoding table.
///
/// It runs as __TEXT is laid out, for the section's size, when the
/// addresses in __TEXT are final and those in other segments are not
/// yet (they are taken as they are, wrapping around the image base);
/// if the section covers any of those (covers_other_segments), it runs
/// again once every segment is placed. The personality entries are
/// image-relative pointers to GOT slots, whose addresses are not final
/// either - so they are returned as a patch list instead of written,
/// and the copy phase fills the cells at offsets 28, 32, ... once the
/// GOT has its address.
pub fn encode_unwind_info<E: Target>(ctx: &Context<E>) -> (Vec<u8>, Vec<SymbolId>) {
let mut records = table_records(ctx);
if records.is_empty() {
return (Vec::new(), Vec::new());
}
let personalities = assign_personalities(&mut records);
// The table ends where the last function does, as in ld64, however
// much of it its unwind record covers.
let last = &ctx.isecs[records.last().unwrap().isec as usize];
let end = last.addr(ctx) + last.size as u64;
merge_records::<E>(&mut records);
let pages = split_pages(ctx, &records);
(write_table(ctx, &records, &personalities, &pages, end), personalities)
}
/// The address of a record's function.
fn func_addr<E: Target>(ctx: &Context<E>, rec: &UnwindRecord) -> u64 {
ctx.isecs[rec.isec as usize].addr(ctx) + rec.input_offset as u64
}
/// The records the table lists, sorted by address: those of the live
/// functions and those of the code with no unwind information (see
/// bare_code_records).
fn table_records<E: Target>(ctx: &Context<E>) -> Vec<UnwindRecord> {
let mut records: Vec<UnwindRecord> = ctx
.unwind_records
.par_iter()
.filter(|rec| {
let isec = &ctx.isecs[rec.isec as usize];
isec.is_emitted()
})
.cloned()
.collect();
records.extend(bare_code_records(ctx));
// A DWARF-mode record's encoding holds its FDE's offset in
// __eh_frame in the low 24 bits, or 0 if they can't hold it, as in
// ld-prime (which warns, see eh_frame::construct): the unwinder
// then looks for the FDE through the whole section. It takes the
// personality and the LSDA from the FDE.
for rec in &mut records {
if let Some(fde) = rec.fde() {
let off = ctx.fdes[fde].output_offset;
rec.encoding = E::UNWIND_MODE_DWARF | if off <= MAX_FDE_OFFSET { off } else { 0 };
}
}
// Of the entries of one address, the unwinder finds the last: an
// empty subsection's of encoding 0 comes before the function
// sharing its address, and of two records for a function (or one
// at a section's end and the next section's first), the greater
// encoding comes last.
records.par_sort_by_key(|r| (func_addr(ctx, r), r.encoding));
records
}
/// Gives each record with a personality routine the routine's 1-based
/// index in the table's array of them, in bits 28-29 of its encoding,
/// the routines indexed in order of first use by address; returns the
/// array.
fn assign_personalities(records: &mut [UnwindRecord]) -> Vec<SymbolId> {
let mut personalities: Vec<SymbolId> = Vec::new();
for rec in records {
if let Some(p) = rec.personality() {
let idx = match personalities.iter().position(|&s| s == p) {
Some(idx) => idx,
None => {
personalities.push(p);
personalities.len() - 1
}
};
if idx >= 3 {
crate::fatal!("too many personality functions");
}
rec.encoding |= ((idx + 1) as u32) << UNWIND_PERSONALITY_MASK.trailing_zeros();
}
}
personalities
}
/// Merges consecutive records with identical contents. An entry has no
/// length - it covers the code up to the next one - so the padding
/// between two functions does not keep them apart. An x86-64 entry in
/// "stack immediate indirect" mode gives where the stack size is in the
/// function as an offset from the entry's start, so it can't cover a
/// second function (ld64's encodingCannotBeMerged).
fn merge_records<E: Target>(records: &mut Vec<UnwindRecord>) {
let stack_ind = |enc: u32| {
E::CPUTYPE == CPU_TYPE_X86_64 && enc & UNWIND_MODE_MASK == UNWIND_X86_64_MODE_STACK_IND
};
records.dedup_by(|rec, last| {
!stack_ind(rec.encoding)
&& last.encoding == rec.encoding
&& last.personality() == rec.personality()
&& last.lsda().is_none()
&& rec.lsda().is_none()
});
}
/// A second-level page's size limit, and the size of its header.
const PAGE_SIZE: usize = 4096;
const PAGE_HDR: usize = 12;
/// A compressed second-level page: a run of the table's records, and
/// their encodings in order of first use.
struct Page {
records: std::ops::Range<usize>,
encodings: Vec<u32>,
}
/// Splits the records into compressed second-level pages, of 32-bit
/// entries: a 24-bit offset from the page's first function and an 8-bit
/// index into the page's encodings, listed after the entries. A page
/// ends at 4096 bytes, at 2^24 bytes of code, or at 256 encodings.
fn split_pages<E: Target>(ctx: &Context<E>, records: &[UnwindRecord]) -> Vec<Page> {
let mut pages = Vec::new();
let mut start = 0;
while start < records.len() {
let first_addr = func_addr(ctx, &records[start]);
let mut encs: Vec<u32> = Vec::new();
let mut i = start;
while i < records.len() {
let enc = records[i].encoding;
let new_enc = !encs.contains(&enc) as usize;
if encs.len() + new_enc > 256
|| PAGE_HDR + (i - start + 1 + encs.len() + new_enc) * 4 > PAGE_SIZE
|| func_addr(ctx, &records[i]) - first_addr >= 1 << 24
{
break;
}
if new_enc == 1 {
encs.push(enc);
}
i += 1;
}
pages.push(Page { records: start..i, encodings: encs });
start = i;
}
pages
}
/// Writes the table: the header, no common encodings, the
/// personalities (zeros, which copy_buf patches), the first-level index
/// (a row per page and a terminator, at `end`), the LSDA index, then the
/// pages.
fn write_table<E: Target>(
ctx: &Context<E>,
records: &[UnwindRecord],
personalities: &[SymbolId],
pages: &[Page],
end: u64,
) -> Vec<u8> {
let base = ctx.mach_header.hdr.addr;
let num_lsda = records.iter().filter(|r| r.lsda().is_some()).count();
let personality_off = 28;
let page1_off = personality_off + personalities.len() * 4;
let lsda_off = page1_off + (pages.len() + 1) * 12;
let page2_off = lsda_off + num_lsda * 8;
let mut buf = Vec::new();
push32(&mut buf, UNWIND_SECTION_VERSION);
push32(&mut buf, personality_off as u32);
push32(&mut buf, 0);
push32(&mut buf, personality_off as u32);
push32(&mut buf, personalities.len() as u32);
push32(&mut buf, page1_off as u32);
push32(&mut buf, pages.len() as u32 + 1);
// Personalities are image-relative pointers to the functions' GOT
// slots, patched in by copy_buf.
for &_sym in personalities {
push32(&mut buf, 0);
}
// Each second-level page and its LSDA rows depend only on its own
// records, so the pages are encoded in parallel; the first-level
// index is then a serial walk over their lengths.
let outs: Vec<EncodedPage> =
pages.par_iter().map(|page| encode_page(ctx, records, page)).collect();
let mut page1 = Vec::new();
let mut lsda = Vec::new();
let mut page2 = Vec::new();
for out in &outs {
push32(&mut page1, out.first);
push32(&mut page1, (page2_off + page2.len()) as u32);
push32(&mut page1, (lsda_off + lsda.len()) as u32);
lsda.extend_from_slice(&out.lsda);
page2.extend_from_slice(&out.page2);
}
// The terminating first-level entry.
push32(&mut page1, (end + 1).wrapping_sub(base) as u32);
push32(&mut page1, 0);
push32(&mut page1, (lsda_off + lsda.len()) as u32);
buf.extend_from_slice(&page1);
buf.extend_from_slice(&lsda);
buf.extend_from_slice(&page2);
buf
}
/// A second-level page, encoded, and its rows of the LSDA index
/// (function, LSDA), each an image offset.
struct EncodedPage {
page2: Vec<u8>,
lsda: Vec<u8>,
/// The image offset of the page's first function.
first: u32,
}
/// Encodes a compressed second-level page of the table's `records`.
fn encode_page<E: Target>(ctx: &Context<E>, records: &[UnwindRecord], page: &Page) -> EncodedPage {
let base = ctx.mach_header.hdr.addr;
let span = &records[page.records.clone()];
let encs = &page.encodings;
let mut lsda = Vec::new();
for rec in span {
if let Some((isec, off)) = rec.lsda() {
push32(&mut lsda, func_addr(ctx, rec).wrapping_sub(base) as u32);
push32(&mut lsda, (ctx.isecs[isec].addr(ctx) + off as u64).wrapping_sub(base) as u32);
}
}
let mut page2 = Vec::new();
push32(&mut page2, UNWIND_SECOND_LEVEL_COMPRESSED);
push16(&mut page2, PAGE_HDR as u16); // entries offset
push16(&mut page2, span.len() as u16);
push16(&mut page2, (PAGE_HDR + span.len() * 4) as u16); // encodings offset
push16(&mut page2, encs.len() as u16);
let page_base = func_addr(ctx, &span[0]);
for rec in span {
let idx = encs.iter().position(|&e| e == rec.encoding).unwrap() as u32;
push32(&mut page2, (func_addr(ctx, rec) - page_base) as u32 | idx << 24);
}
for &enc in encs {
push32(&mut page2, enc);
}
EncodedPage { page2, lsda, first: page_base.wrapping_sub(base) as u32 }
}
/// Whether the image has __unwind_info: whether any of its functions
/// has unwind info.
pub fn is_needed<E: Target>(ctx: &Context<E>) -> bool {
!ctx.unwind_records.is_empty()
}
/// Whether __unwind_info covers addresses outside __TEXT, its own
/// segment: code in another segment (a -rename_section can move __text
/// out), a function with unwind info in a section of data (ld-prime
/// warns, but gives it its entry all the same), or an LSDA there.
/// Those are not final when the section is first encoded, with __TEXT.
pub fn covers_other_segments<E: Target>(ctx: &Context<E>) -> bool {
let segname = ctx.unwind_info.hdr.segname;
let code_elsewhere = ctx.chunks.iter().map(|&id| ctx.chunk_header(id)).any(|hdr| {
hdr.segname != segname
&& hdr.flags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS) != 0
});
let elsewhere = |isec: usize| {
ctx.isecs[isec].output_section().is_some_and(|id| ctx.chunk_header(id).segname != segname)
};
code_elsewhere
|| ctx.unwind_records.par_iter().any(|rec| {
elsewhere(rec.isec as usize)
|| rec.function_lsda(ctx).is_some_and(|(isec, _)| elsewhere(isec))
})
}
/// Records for the code that has no unwind information: every
/// subsection of a code section - an output section of pure
/// instructions, not one the assembler marked as holding some - gets an
/// entry, encoding 0 ("none") where no record of its own starts, so that
/// it does not fall under the unwind rules of the function before it -
/// an empty subsection too, such as the empty __text of an object with
/// only data. A section of an object without MH_SUBSECTIONS_VIA_SYMBOLS
/// is one subsection of several functions: the code past each record's
/// length up to the next record has no unwind information either.
///
/// Each subsection is looked at on its own, with its records (its range
/// of ctx.unwind_records), as sold reads a subsection's unwind records.
fn bare_code_records<E: Target>(ctx: &Context<E>) -> Vec<UnwindRecord> {
ctx.isecs
.par_iter()
.enumerate()
.filter(|&(_, isec)| is_code_subsec(ctx, isec))
.flat_map_iter(|(i, isec)| {
let start = isec.unwind_offset as usize;
let recs = &ctx.unwind_records[start..start + isec.nunwind as usize];
let unsplit =
if ctx.objs[isec.file as usize].subsections_via_symbols { &[][..] } else { recs };
let ends = (unsplit.iter())
.map(|rec| rec.input_offset + rec.code_len)
.filter(move |&off| off < isec.size);
std::iter::once(0)
.chain(ends)
.filter(move |&off| !recs.iter().any(|rec| rec.input_offset == off))
.map(move |off| bare_record(i as u32, off))
})
.collect()
}
/// The record of a piece of code with no unwind information.
fn bare_record(isec: u32, off: u32) -> UnwindRecord {
use crate::input_sections::UNWIND_NONE;
UnwindRecord {
isec,
input_offset: off,
code_len: 0,
encoding: 0,
personality_sym: UNWIND_NONE,
lsda_isec: UNWIND_NONE,
lsda_off: 0,
fde_idx: UNWIND_NONE,
}
}
/// Whether a subsection is one of a code section, which gets an entry
/// whatever its unwind info (see bare_code_records).
fn is_code_subsec<E: Target>(ctx: &Context<E>, isec: &InputSection) -> bool {
isec.is_emitted()
&& isec
.output_section()
.is_some_and(|id| ctx.chunk_header(id).flags & S_ATTR_PURE_INSTRUCTIONS != 0)
}
+87
View File
@@ -0,0 +1,87 @@
//! The LC_DYLD_INFO weak-bind opcode stream: the slots dyld redirects when
//! another image's copy of one of this image's weak definitions wins
//! coalescing.
use crate::arch::Target;
use crate::chunks::bind_info::{self, Op};
use crate::chunks::{ChunkHeader, output_section};
use crate::context::Context;
use crate::macho::*;
/// The weak-bind opcode stream: the slots dyld redirects when another
/// image's copy of one of this image's weak definitions wins
/// coalescing.
#[derive(Debug)]
pub struct WeakBindInfoSection {
pub hdr: ChunkHeader,
/// The stream, built during layout.
pub contents: Vec<u8>,
}
impl WeakBindInfoSection {
pub fn new() -> Self {
Self { hdr: ChunkHeader::linkedit(), contents: Vec::new() }
}
}
impl Default for WeakBindInfoSection {
fn default() -> Self {
Self::new()
}
}
/// Builds the classic weak_bind stream: for every slot that holds the
/// address of one of this image's coalescable weak definitions - GOT
/// entries and data pointers - a bind by name, which dyld applies
/// only if another image's copy of the symbol won coalescing (the
/// slot's rebase already holds this image's copy). Sorted by symbol
/// name, then address, and encoded as the bind stream is (see
/// bind_info::bind_ops), each piece of state set only when it changes,
/// as ld64 writes them.
pub fn construct<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: 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() {
if binds_weak(id) {
binds.push((id, ctx.got.slot_addr(i)));
}
}
for isec in ctx.isecs.iter() {
if !isec.is_emitted() {
continue;
}
let file = &ctx.objs[isec.file as usize];
for (addr, rel) in output_section::pointer_relocs(ctx, isec) {
if let Some(id) = rel.sym(file)
&& binds_weak(id)
{
binds.push((id, addr));
}
}
}
// 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.symbols[i].overrides_weak_export(ctx))
.collect();
if binds.is_empty() && overrides.is_empty() {
return Vec::new();
}
overrides.sort_by(|&a, &b| ctx.symbols[a].name().cmp(ctx.symbols[b].name()));
binds.sort_by(|a, b| ctx.symbols[a.0].name().cmp(ctx.symbols[b.0].name()).then(a.1.cmp(&b.1)));
let flags = BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION;
let mut ops: Vec<Op> =
overrides.iter().map(|&id| Op::Symbol(ctx.symbols[id].name(), flags)).collect();
let binds: Vec<_> = binds.into_iter().map(|(id, addr)| (addr, id, 0)).collect();
ops.extend(bind_info::bind_ops(ctx, &binds, |_| None, |_| 0));
bind_info::encode(ops)
}
+4099
View File
File diff suppressed because it is too large. Load diff
+420
View File
@@ -0,0 +1,420 @@
//! The linker context: everything about one link, from parsed arguments
//! to output chunks.
use std::marker::PhantomData;
use crate::arch::Target;
use crate::chunks::bind_info::BindInfoSection;
use crate::chunks::chain_starts::ChainStartsSection;
use crate::chunks::chained_fixups::ChainedFixupsSection;
use crate::chunks::code_signature::CodeSignatureSection;
use crate::chunks::data_in_code::DataInCodeSection;
use crate::chunks::delay_init::DelayInit;
use crate::chunks::eh_frame::EhFrameSection;
use crate::chunks::export_trie::ExportTrieSection;
use crate::chunks::extern_relocs::ExternRelocsSection;
use crate::chunks::function_starts::FunctionStartsSection;
use crate::chunks::got::GotSection;
use crate::chunks::indirect_symtab::IndirectSymtabSection;
use crate::chunks::init_offsets::InitOffsetsSection;
use crate::chunks::lazy_bind_info::LazyBindInfoSection;
use crate::chunks::lazy_helpers::LazyHelpersSection;
use crate::chunks::lazy_load_got::LazyLoadGotSection;
use crate::chunks::lazy_load_info::LazyLoadInfoSection;
use crate::chunks::lazy_ptrs::LazyPtrsSection;
use crate::chunks::local_relocs::LocalRelocsSection;
use crate::chunks::objc_imageinfo::ObjcImageInfoSection;
use crate::chunks::objc_methlist::ObjcMethlistSection;
use crate::chunks::objc_stubs::ObjcStubsSection;
use crate::chunks::rebase_info::RebaseInfoSection;
use crate::chunks::sectcreate::{SectCreateInput, SectCreateSection};
use crate::chunks::split_info::SplitInfoSection;
use crate::chunks::strtab::StrtabSection;
use crate::chunks::stub_helper::StubHelperSection;
use crate::chunks::stubs::StubsSection;
use crate::chunks::symtab::SymtabSection;
use crate::chunks::unwind_info::UnwindInfoSection;
use crate::chunks::weak_bind_info::WeakBindInfoSection;
use crate::chunks::{
ChunkHeader, ChunkId, OutputMachHeader, OutputSection, OutputSectionId, OutputSegment,
};
use crate::cmdline::Args;
use crate::input_files::{DylibFile, FileId, ObjectFile};
use crate::symbol::{SymbolId, SymbolTable};
use crate::util::perf::Timers;
// Keep immutable and mutable chunk lookup in the same static match.
macro_rules! chunk_header {
($ctx:ident, $id:ident $(, $mutable:tt)?) => {
match $id {
ChunkId::MachHeader => &$($mutable)? $ctx.mach_header.hdr,
ChunkId::Output(id) => &$($mutable)? $ctx.output_sections[id.index()].hdr,
ChunkId::Stubs => &$($mutable)? $ctx.stubs.hdr,
ChunkId::StubHelper => &$($mutable)? $ctx.stub_helper.hdr,
ChunkId::LazyPtrs => &$($mutable)? $ctx.lazy_ptrs.hdr,
ChunkId::Got => &$($mutable)? $ctx.got.hdr,
ChunkId::DelayStubs => &$($mutable)? $ctx.delay_init.stubs_hdr,
ChunkId::DelayHelper => &$($mutable)? $ctx.delay_init.helper_hdr,
ChunkId::LazyHelpers => &$($mutable)? $ctx.lazy_helpers.hdr,
ChunkId::LazyLoadGot => &$($mutable)? $ctx.lazy_load_got.hdr,
ChunkId::ObjcStubs => &$($mutable)? $ctx.objc_stubs.hdr,
ChunkId::ObjcMethlist => &$($mutable)? $ctx.objc_methlist.hdr,
ChunkId::ObjcImageInfo => &$($mutable)? $ctx.objc_imageinfo.hdr,
ChunkId::SectCreate(i) => &$($mutable)? $ctx.sectcreate_sections[i as usize].hdr,
ChunkId::InitOffsets => &$($mutable)? $ctx.init_offsets.hdr,
ChunkId::ChainStarts => &$($mutable)? $ctx.chain_starts.hdr,
ChunkId::UnwindInfo => &$($mutable)? $ctx.unwind_info.hdr,
ChunkId::EhFrame => &$($mutable)? $ctx.eh_frame.hdr,
ChunkId::RebaseInfo => &$($mutable)? $ctx.rebase_info.hdr,
ChunkId::BindInfo => &$($mutable)? $ctx.bind_info.hdr,
ChunkId::WeakBindInfo => &$($mutable)? $ctx.weak_bind_info.hdr,
ChunkId::LazyBindInfo => &$($mutable)? $ctx.lazy_bind_info.hdr,
ChunkId::ChainedFixups => &$($mutable)? $ctx.chained_fixups.hdr,
ChunkId::ExportTrie => &$($mutable)? $ctx.export_trie.hdr,
ChunkId::FunctionStarts => &$($mutable)? $ctx.function_starts.hdr,
ChunkId::DataInCode => &$($mutable)? $ctx.data_in_code.hdr,
ChunkId::MergeableRecord => &$($mutable)? $ctx.mergeable_record.hdr,
ChunkId::SplitInfo => &$($mutable)? $ctx.split_info.hdr,
ChunkId::LazyLoadInfo => &$($mutable)? $ctx.lazy_load_info.hdr,
ChunkId::LocalRelocs => &$($mutable)? $ctx.local_relocs.hdr,
ChunkId::ExternRelocs => &$($mutable)? $ctx.extern_relocs.hdr,
ChunkId::IndirectSymtab => &$($mutable)? $ctx.indirect_symtab.hdr,
ChunkId::Symtab => &$($mutable)? $ctx.symtab.hdr,
ChunkId::Strtab => &$($mutable)? $ctx.strtab.hdr,
ChunkId::CodeSignature => &$($mutable)? $ctx.code_signature.hdr,
}
};
}
pub struct Context<E: Target> {
pub args: Args,
pub objs: Vec<ObjectFile>,
pub dylibs: Vec<DylibFile>,
pub symbols: SymbolTable,
/// All input sections, in one arena.
pub isecs: crate::input_sections::InputSections,
/// The object that owns the linker-synthesized sections and
/// symbols, once created; mold's internal_obj.
pub internal_obj: Option<usize>,
/// Input-order counter for resolution tie-breaking.
pub priority_counter: u32,
/// The first priority of the files auto-link options brought in,
/// which come after the libraries the command line names and the
/// ones they re-export (see passes::dylib_ranks); u32::MAX before
/// auto-linking.
pub autolink_priority: u32,
/// Files already loaded, so a library named twice (command line
/// plus auto-link) is read once.
pub visited_files: std::collections::HashSet<std::path::PathBuf>,
/// Under -dependency_info, the files of the libraries loaded as
/// another's re-exports, as found (see load_reexports).
pub reexport_files: Vec<std::path::PathBuf>,
/// Under -dependency_info, the files a search for an input looked
/// for and did not find (see reader::Prober).
pub missing_files: std::sync::Mutex<Vec<std::path::PathBuf>>,
/// The loaded libLTO, once a bitcode input has been seen.
pub lto_plugin: Option<crate::lto::Plugin>,
/// Bitcode modules registered for LTO.
pub lto_modules: Vec<crate::lto::BitcodeModule>,
/// The objects LTO compiled the live bitcode modules to: one per
/// ThinLTO module, in input order, then the merged modules' one.
pub lto_objs: std::ops::Range<usize>,
/// The bitcode files LTO compiled, in input order.
pub lto_inputs: Vec<crate::lto::LtoInput>,
/// Auto-link options already acted on.
pub processed_linker_options: std::collections::HashSet<Vec<Vec<u8>>>,
/// -add_linker_option's auto-link options, once read as an
/// object's are (see reader::read_linker_options).
pub cmdline_linker_options: Option<Vec<Vec<Vec<u8>>>>,
/// The libraries and frameworks auto-link options named that were
/// not found, or that don't take this link as a client: reported if
/// symbols stay undefined.
pub autolink_misses: Vec<crate::error::Message>,
/// The files only -possible-l and the like name, which load with
/// the auto-linked libraries (see reader::load_autolink_deps).
pub possible_files: Vec<std::path::PathBuf>,
/// The files -dylib_file names for re-exported libraries that are
/// no libraries, to load as inputs (see collect_indirect_files).
pub indirect_files: Vec<&'static crate::mapped_file::MappedFile>,
/// The dylibs the mergeable dylibs merged into the image link, by
/// their recorded identities, to add after the command line's (see
/// reader::add_merged_dependencies).
pub merged_dependencies: Vec<crate::mergeable::Dependency>,
/// The mergeable dylibs merged into the image, which are none of
/// its dependencies, in input order.
pub merged_libraries: Vec<crate::mergeable::MergedLibrary>,
/// The hook for the classes of the mergeable libraries merged or
/// re-exported, and the classes it is for.
pub bundle_hook: crate::bundle_hook::BundleHook,
/// Unwind records from all objects' __compact_unwind sections.
pub unwind_records: Vec<crate::input_sections::UnwindRecord>,
/// DWARF CIEs and FDEs from all objects' __eh_frame sections.
pub cies: Vec<crate::input_sections::CieRecord>,
pub fdes: Vec<crate::input_sections::FdeRecord>,
/// The chunks of the output, in file order, and the segments they
/// are grouped into. Each chunk's header and data live in the
/// typed field for its kind below, as in mold.
pub chunks: Vec<ChunkId>,
pub segments: Vec<OutputSegment>,
pub mach_header: OutputMachHeader,
pub output_sections: Vec<OutputSection>,
pub stubs: StubsSection,
pub stub_helper: StubHelperSection,
pub lazy_ptrs: LazyPtrsSection,
pub got: GotSection,
pub delay_init: DelayInit,
pub lazy_helpers: LazyHelpersSection,
pub lazy_load_got: LazyLoadGotSection,
pub objc_stubs: ObjcStubsSection,
pub objc_methlist: ObjcMethlistSection,
pub objc_imageinfo: ObjcImageInfoSection,
pub sectcreate_sections: Vec<SectCreateSection>,
/// The input section of each -sectcreate or -add_empty_section
/// option.
pub sectcreate_inputs: Vec<SectCreateInput>,
pub init_offsets: InitOffsetsSection,
pub chain_starts: ChainStartsSection,
pub unwind_info: UnwindInfoSection,
pub eh_frame: EhFrameSection,
pub rebase_info: RebaseInfoSection,
pub bind_info: BindInfoSection,
pub weak_bind_info: WeakBindInfoSection,
pub lazy_bind_info: LazyBindInfoSection,
pub chained_fixups: ChainedFixupsSection,
pub export_trie: ExportTrieSection,
pub function_starts: FunctionStartsSection,
pub data_in_code: DataInCodeSection,
pub mergeable_record: crate::chunks::mergeable_record::MergeableRecordSection,
pub split_info: SplitInfoSection,
pub lazy_load_info: LazyLoadInfoSection,
pub local_relocs: LocalRelocsSection,
pub extern_relocs: ExternRelocsSection,
pub indirect_symtab: IndirectSymtabSection,
pub symtab: SymtabSection,
pub strtab: StrtabSection,
pub code_signature: CodeSignatureSection,
/// Objective-C data records the linker synthesized (see
/// merge_objc_categories) and the table of bundle_hook, each placed
/// as the tail of the output section it names.
pub data_blobs: Vec<crate::input_files::DataBlob>,
/// Local symbols the linker names itself, on synthesized data:
/// ld64's __OBJC_$_INSTANCE_METHODS_Foo(A|B) on a merged method
/// list, and the like. (name, subsection).
pub extra_local_syms: Vec<(&'static [u8], u32)>,
/// The symbols naming the subsections of the functions icf folded
/// (see icf::folded_subsec_names).
pub folded_subsec_names: hashbrown::HashSet<SymbolId>,
/// A DOF section for each provider of DTrace probes the image has
/// sites of (see dtrace::create_dof_sections).
pub dof_sections: Vec<crate::dtrace::DofSection>,
/// -alias and selective reexports: (alias, imported target).
/// Emitted as N_INDR symbols and re-export trie entries.
pub indirect_aliases: Vec<(SymbolId, SymbolId)>,
/// The symbols export lists would re-export that a library the
/// image re-exports whole exports already (see
/// passes::warn_redundant_reexports).
pub redundant_reexports: Vec<SymbolId>,
/// section$start/end and segment$start/end symbols to resolve
/// after layout.
pub boundary_syms: Vec<crate::passes::BoundarySym>,
/// For -why_load: the symbol that made each object live, refreshed
/// each resolution round.
pub why_load: std::collections::HashMap<usize, &'static [u8]>,
/// For -why_load: the archives -force_load or -force-l loads whole.
pub force_loaded: std::collections::HashSet<std::path::PathBuf>,
/// For -t: every input file as it is loaded, by the path it was
/// found at (a library inlined in a stub by its install name).
pub traced_files: Vec<Vec<u8>>,
/// The address of the TLS template image. A TLV descriptor's offset
/// is relative to it (see tls.rs).
pub tls_begin: u64,
/// The address ranges where a pointer that needs a fixup is a text
/// relocation the output can't have (passes::text_reloc_ranges).
pub text_reloc_ranges: Vec<std::ops::Range<u64>>,
/// The text relocations found applying relocations, as (subsection,
/// relocation index) pairs.
pub text_relocs: std::sync::Mutex<Vec<(u32, u32)>>,
/// The 32-bit pointers an image dyld loads can't have (see
/// passes::report_text_relocs), as (subsection, offset) pairs.
pub pointers32: std::sync::Mutex<Vec<(u32, u32)>>,
/// The output's UUID, computed from its contents.
pub uuid: std::sync::Mutex<[u8; 16]>,
/// The -init function, when LC_ROUTINES_64 names it (the image has
/// no __init_offsets to run it first from).
pub init_routine: Option<SymbolId>,
/// Total size of the output file.
pub output_size: u64,
/// -print_statistics timers; inactive otherwise.
pub timers: Timers,
_marker: PhantomData<E>,
}
impl<E: Target> Context<E> {
pub fn new(args: Args) -> Self {
let timers = if args.perf { Timers::new() } else { Timers::disabled() };
Self {
args,
objs: Vec::new(),
dylibs: Vec::new(),
symbols: SymbolTable::default(),
isecs: Default::default(),
internal_obj: None,
priority_counter: 0,
autolink_priority: u32::MAX,
lto_plugin: None,
lto_modules: Vec::new(),
lto_objs: 0..0,
lto_inputs: Vec::new(),
visited_files: std::collections::HashSet::new(),
reexport_files: Vec::new(),
missing_files: Default::default(),
processed_linker_options: std::collections::HashSet::new(),
cmdline_linker_options: None,
autolink_misses: Vec::new(),
possible_files: Vec::new(),
indirect_files: Vec::new(),
merged_dependencies: Vec::new(),
merged_libraries: Vec::new(),
bundle_hook: Default::default(),
unwind_records: Vec::new(),
cies: Vec::new(),
fdes: Vec::new(),
chunks: Vec::new(),
segments: Vec::new(),
mach_header: OutputMachHeader::new(),
output_sections: Vec::new(),
stubs: StubsSection::new(),
stub_helper: StubHelperSection::new(),
lazy_ptrs: LazyPtrsSection::new(),
got: GotSection::new(),
delay_init: DelayInit::new(),
lazy_helpers: LazyHelpersSection::new(),
lazy_load_got: LazyLoadGotSection::new(),
objc_stubs: ObjcStubsSection::new(),
objc_methlist: ObjcMethlistSection::new(),
objc_imageinfo: ObjcImageInfoSection::new(),
sectcreate_sections: Vec::new(),
sectcreate_inputs: Vec::new(),
init_offsets: InitOffsetsSection::new(),
chain_starts: ChainStartsSection::new(),
unwind_info: UnwindInfoSection::new(),
eh_frame: EhFrameSection::new(),
rebase_info: RebaseInfoSection::new(),
bind_info: BindInfoSection::new(),
weak_bind_info: WeakBindInfoSection::new(),
lazy_bind_info: LazyBindInfoSection::new(),
chained_fixups: ChainedFixupsSection::new(),
export_trie: ExportTrieSection::new(),
function_starts: FunctionStartsSection::new(),
data_in_code: DataInCodeSection::new(),
mergeable_record: crate::chunks::mergeable_record::MergeableRecordSection::new(),
split_info: SplitInfoSection::new(),
lazy_load_info: LazyLoadInfoSection::new(),
local_relocs: LocalRelocsSection::new(),
extern_relocs: ExternRelocsSection::new(),
indirect_symtab: IndirectSymtabSection::new(),
symtab: SymtabSection::new(),
strtab: StrtabSection::new(),
code_signature: CodeSignatureSection::new(),
data_blobs: Vec::new(),
extra_local_syms: Vec::new(),
folded_subsec_names: hashbrown::HashSet::new(),
dof_sections: Vec::new(),
indirect_aliases: Vec::new(),
redundant_reexports: Vec::new(),
boundary_syms: Vec::new(),
why_load: std::collections::HashMap::new(),
force_loaded: std::collections::HashSet::new(),
traced_files: Vec::new(),
tls_begin: 0,
text_reloc_ranges: Vec::new(),
text_relocs: std::sync::Mutex::new(Vec::new()),
pointers32: std::sync::Mutex::new(Vec::new()),
uuid: std::sync::Mutex::new([0; 16]),
init_routine: None,
output_size: 0,
timers,
_marker: PhantomData,
}
}
/// Starts a -print_statistics timer for a pass.
pub fn timer(&self, name: &str) -> crate::util::perf::Timer {
self.timers.start(name)
}
/// The header of any chunk.
pub fn chunk_header(&self, id: ChunkId) -> &ChunkHeader {
chunk_header!(self, id)
}
pub fn chunk_header_mut(&mut self, id: ChunkId) -> &mut ChunkHeader {
chunk_header!(self, id, mut)
}
pub fn output_section(&self, id: OutputSectionId) -> &OutputSection {
&self.output_sections[id.index()]
}
pub fn output_section_mut(&mut self, id: OutputSectionId) -> &mut OutputSection {
&mut self.output_sections[id.index()]
}
/// Returns the next input-order priority value.
pub fn next_priority(&mut self) -> u32 {
self.priority_counter += 1;
self.priority_counter
}
/// Returns true if the output uses chained fixups rather than
/// classic dyld rebase/bind opcodes: it is laid out for them, and
/// no unaligned pointer turned an x86-64 image's to classic dyld
/// info.
pub fn use_chained_fixups(&self) -> bool {
self.args.fixup_chains && !self.chained_fixups.disabled
}
/// Whether the file is the internal object holding synthesized
/// sections and symbols.
pub fn is_internal(&self, idx: usize) -> bool {
self.internal_obj == Some(idx)
}
/// Whether the file is the hook for the classes of mergeable
/// libraries, which ld-prime counts as linker synthesized.
pub fn is_bundle_hook(&self, idx: usize) -> bool {
self.bundle_hook.obj == Some(idx)
}
/// Binds a symbol the linker's own code calls (dyld_stub_binder,
/// __dyld_lazy_load), unless something in the link defines it, to
/// the first loaded dylib that exports it - or, if none does and
/// the image may look the symbol up dynamically (-undefined
/// dynamic_lookup, -U), to whatever image dyld finds it in. None if
/// neither.
pub fn bind_linker_import(&mut self, name: &'static [u8]) -> Option<SymbolId> {
let args = &self.args;
let looked_up =
args.undefined_dynamic_lookup || args.allowed_undefined.iter().any(|n| n == name);
let dylib = match self.dylibs.iter().position(|d| d.exports.contains(name)) {
Some(i) => i as u32,
None if looked_up => u32::MAX,
None => return None,
};
let id = self.symbols.intern(name);
let sym = &mut self.symbols[id];
if !sym.is_defined() {
sym.set_file(FileId::Dylib(dylib));
sym.set_imported(true);
sym.set_extern(true);
sym.set_input_section(None);
}
Some(id)
}
/// Returns the address of the __got slot the objc stubs load
/// _objc_msgSend from.
pub fn objc_msgsend_got_addr(&self) -> u64 {
self.symbols[self.objc_stubs.msgsend_sym.unwrap()].got_addr(self)
}
}
+600
View File
@@ -0,0 +1,600 @@
//! Dead-stripping (-dead_strip): mold's gc-sections for Mach-O.
//!
//! Subsections not reachable from the roots - the entry point,
//! exported symbols (for a dylib), initializers and everything the
//! format pins (no-dead-strip sections and symbols) - are removed.
//! Reachability follows relocations and unwind-info edges, so a live
//! function keeps its LSDA and personality. This is passes.rs's
//! liveness walk's section-level counterpart, and mirrors
//! gc_sections.rs in mold (dead-strip.cc in sold).
use std::collections::{HashSet, VecDeque};
use std::sync::atomic::{AtomicBool, Ordering};
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::init_offsets::InitFunc;
use crate::context::Context;
use crate::error::{RawPath, notice, raw};
use crate::input_files::{FileId, is_literal_section};
use crate::input_sections::{InputSection, RelocTarget};
use crate::macho::*;
use crate::symbol::{Symbol, SymbolId};
/// Whether the link strips dead code: under -dead_strip, or - as
/// ld-prime does unasked - in a final image of code LTO compiled,
/// executable or not, which it walks as dead stripping does to find
/// what LTO must preserve (see native_refs_before_lto). That strip
/// leaves the imports only stripped code used in the symbol table,
/// unbound, and the map lists nothing it removed.
pub fn strips_dead_code<E: Target>(ctx: &Context<E>) -> bool {
ctx.args.dead_strip || (!ctx.args.relocatable && !ctx.lto_inputs.is_empty())
}
/// Strips dead code (see strips_dead_code) and refreshes which
/// symbols live code uses.
pub fn strip_dead_code<E: Target>(ctx: &mut Context<E>) {
dead_strip(ctx);
mark_live_references(ctx);
}
/// Removes subsections that are not reachable from the roots: the entry
/// point, exported symbols (for a dylib), and everything the format
/// requires to stay (initializers, no-dead-strip sections and symbols).
/// Reachability follows relocations and unwind-info edges.
fn dead_strip<E: Target>(ctx: &mut Context<E>) {
// Merged literals and rewritten ObjC data stand in for the
// subsections they replaced; roots and edges are marked through to
// the replacement.
let redirects: Vec<usize> =
(0..ctx.isecs.len()).into_par_iter().map(|i| ctx.isecs.resolve(i)).collect();
let redirects = &redirects;
// The set of live sections is order-independent, which lets the
// walk run on all cores; -why_live, which wants to know what made
// each subsection live, walks serially instead.
let mut why = (!ctx.args.why_live.is_empty()).then(|| vec![Why::Dead; ctx.isecs.len()]);
let is_root = |_, sym: &Symbol| is_symbol_root(ctx, sym);
let roots = collect_root_set(ctx, redirects, is_root, why.as_deref_mut());
match why.as_deref_mut() {
None => mark(ctx, redirects, &roots),
Some(why) => walk(ctx, redirects, roots, Some(why)),
}
mark_live_support(ctx, redirects, why.as_deref_mut());
sweep(ctx);
if let Some(why) = &why {
print_why_live(ctx, redirects, why);
}
}
/// Why a subsection is a dead-strip root, as -why_live says.
#[derive(Clone, Copy)]
enum Root {
/// It defines the entry point, a -u symbol or an -alias base.
Undefined,
/// It defines an exported symbol.
Export,
/// The format keeps it (see should_keep), or a no-dead-strip
/// symbol it defines.
Kept,
/// An initializer __init_offsets runs.
Initializer,
/// A live-support subsection referring to live code (see
/// mark_live_support).
LiveSupport,
}
impl Root {
fn name(self) -> &'static str {
match self {
Root::Undefined => "root: the entry point, -u or -alias",
Root::Export => "root: exported",
Root::Kept => "root: never dead-stripped",
Root::Initializer => "root: an initializer",
Root::LiveSupport => "root: live support of live code",
}
}
}
/// What made a subsection live, for -why_live: nothing yet, a root's
/// reason, or the subsection whose reference first reached it.
#[derive(Clone, Copy)]
enum Why {
Dead,
Root(Root),
From(u32),
}
/// Whether a symbol makes its subsection a root: a no-dead-strip
/// symbol, or an export dead stripping keeps.
fn is_symbol_root<E: Target>(ctx: &Context<E>, sym: &Symbol) -> bool {
sym.no_dead_strip()
|| (sym.is_extern()
&& !sym.is_private_extern()
&& sym.is_defined()
&& keeps_export(ctx, sym.name()))
}
/// The symbols the command line names, which the link must define and
/// dead stripping keeps: the -u ones (and those the options that add to
/// them name), the entry point and the bases of -alias - ld-prime's
/// initial undefines.
pub(crate) fn initial_undefines<E: Target>(ctx: &Context<E>) -> impl Iterator<Item = SymbolId> {
let entry = ctx.args.has_entry_point().then_some(&ctx.args.entry);
let aliased = ctx.args.aliases.iter().map(|(base, _)| base);
ctx.args
.forced_undefined
.iter()
.chain(entry)
.chain(aliased)
.filter_map(|name| ctx.symbols.lookup(name))
}
/// Sections the format keeps regardless of references: initializers
/// and terminators, and no-dead-strip sections (the ObjC image info
/// has no subsections: the link makes its own, see
/// input_files::is_objc_image_info; an __objc_imageinfo of another
/// segment than __DATA is any section to ld-prime, which strips it). So
/// is every section of an object without MH_SUBSECTIONS_VIA_SYMBOLS
/// that ld64 splits at symbols: it cannot tell where such a subsection
/// ends, so it treats the object as one huge subsection. Sections it
/// splits by content (literals, CFStrings, class references,
/// thread-local variable descriptors) are stripped as usual. ld-prime
/// strips a class reference nothing uses although clang marks
/// __objc_classrefs no-dead-strip (it keeps unused selector
/// references).
fn should_keep<E: Target>(ctx: &Context<E>, isec: &InputSection) -> bool {
let hdr = isec.hdr(&ctx.objs[isec.file as usize]);
matches!(hdr.section_type(), S_MOD_INIT_FUNC_POINTERS | S_MOD_TERM_FUNC_POINTERS)
|| hdr.section_type() == S_INIT_FUNC_OFFSETS
|| (hdr.flags & S_ATTR_NO_DEAD_STRIP != 0
&& !(hdr.segname() == b"__DATA" && hdr.sectname() == b"__objc_classrefs"))
|| (!ctx.objs[isec.file as usize].subsections_via_symbols
&& !is_literal_section(hdr)
&& hdr.section_type() != S_THREAD_LOCAL_VARIABLES)
}
/// Marks the roots and returns them: the sections the format keeps,
/// those defining a symbol `is_root` takes (a no-dead-strip one or an
/// export), and those of the initializers and initial undefines. They
/// are found on all cores; marking stays serial (it is a handful of
/// sections). Under -why_live, `why` gets each root's reason.
fn collect_root_set<E: Target>(
ctx: &Context<E>,
redirects: &[usize],
is_root: impl Fn(SymbolId, &Symbol) -> bool + Sync,
mut why: Option<&mut [Why]>,
) -> Vec<usize> {
let mut roots = Vec::new();
// Liveness is marked in place, on the section's atomic visited bit
// (mold's IS_VISITED), rather than in side arrays copied back at
// the end.
let mut enqueue = |id: usize, root: Root| {
let id = redirects[id];
if ctx.isecs[id].visit() {
roots.push(id);
if let Some(why) = why.as_deref_mut() {
why[id] = Why::Root(root);
}
}
};
// Sections of dead archive members are not part of the link at all
// and must not be resurrected.
let sections: Vec<usize> = ctx
.isecs
.par_iter()
.enumerate()
.filter(|(_, isec)| isec.is_alive() && should_keep(ctx, isec))
.map(|(id, _)| id)
.collect();
for id in sections {
enqueue(id, Root::Kept);
}
// Initializers converted to __init_offsets are roots; their source
// sections are gone.
for &func in &ctx.init_offsets.init_funcs {
if let InitFunc::Local(isec, _) = func {
enqueue(isec, Root::Initializer);
}
}
// Sections defining a no-dead-strip or an exported symbol - in the
// link: an archive member left unloaded keeps its local symbols
// (`__attribute__((used))` ones are no-dead-strip), but none of its
// sections.
let syms: Vec<(usize, Root)> = ctx
.symbols
.syms
.par_iter()
.enumerate()
.filter(|&(id, sym)| is_root(id as SymbolId, sym))
.filter_map(|(_, sym)| {
let root = if sym.no_dead_strip() { Root::Kept } else { Root::Export };
let isec = sym.input_section()? as usize;
ctx.isecs[isec].is_alive().then_some((isec, root))
})
.collect();
for (id, root) in syms {
enqueue(id, root);
}
// -u retains the symbol's subsection as well as extracting its
// containing archive member. It may name a private external,
// unlike an export root.
for id in initial_undefines(ctx) {
if let Some(isec) = ctx.symbols[id].input_section() {
enqueue(isec as usize, Root::Undefined);
}
}
// Legacy LINKEDIT's stub helper entries jump to crt1.o's
// dyld_stub_binding_helper, which ld-prime keeps wherever imports
// bind lazily, whether any stub needs it or not.
if ctx.args.legacy_linkedit
&& ctx.args.lazy_binding
&& let Some(id) = ctx.symbols.lookup(b"dyld_stub_binding_helper")
&& let Some(isec) = ctx.symbols[id].input_section()
{
enqueue(isec as usize, Root::Kept);
}
roots
}
/// The symbols live Mach-O code refers to before LTO, as ld-prime finds
/// them to tell libLTO what to preserve: it walks the subsections as
/// dead stripping does - in any link with bitcode, -dead_strip or not -
/// from the same roots, each bitcode file's "internal" node among them,
/// which stands for its module's code and refers to every symbol the
/// module does. A bitcode definition is a node that refers to that
/// one. So native code that only bitcode, a root or another such
/// function reaches counts, and code nothing does (a hidden helper no
/// one calls) doesn't. The export lists have hidden nothing yet; the
/// roots go by them, as `exported` says (see lto::exported_before_lto).
pub fn native_refs_before_lto<E: Target>(
ctx: &Context<E>,
exported: impl Fn(SymbolId) -> bool + Sync,
) -> Vec<AtomicBool> {
let redirects: Vec<usize> =
(0..ctx.isecs.len()).into_par_iter().map(|i| ctx.isecs.resolve(i)).collect();
let is_root = |id: SymbolId, sym: &Symbol| sym.no_dead_strip() || exported(id);
let mut roots = collect_root_set(ctx, &redirects, is_root, None);
for module in &ctx.lto_modules {
let obj = &ctx.objs[module.obj];
if !obj.is_reachable {
continue;
}
for (msym, &id) in obj.mach_syms.iter().zip(&obj.symbols) {
if msym.ty() == N_UNDF
&& let Some(isec) = ctx.symbols[id].input_section()
&& ctx.isecs[redirects[isec as usize]].visit()
{
roots.push(redirects[isec as usize]);
}
}
}
mark(ctx, &redirects, &roots);
mark_live_support(ctx, &redirects, None);
let refs: Vec<AtomicBool> =
(0..ctx.symbols.syms.len()).map(|_| AtomicBool::new(false)).collect();
ctx.isecs.par_iter().enumerate().for_each(|(id, isec)| {
if !isec.is_visited() {
return;
}
isec.unmark_visited();
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
if let RelocTarget::Sym(idx) = rel.target() {
refs[file.symbols[idx as usize] as usize].store(true, Ordering::Relaxed);
}
}
for_each_unwind_edge(ctx, id, |_, sym| {
if let Some(sym) = sym {
refs[sym as usize].store(true, Ordering::Relaxed);
}
});
});
refs
}
/// Whether dead stripping keeps an export named `name`: every one of a
/// dylib or bundle, and of an executable those an export list names, or
/// all its globals with -export_dynamic (but not a -preload image's,
/// which ld-prime still strips).
pub fn keeps_export<E: Target>(ctx: &Context<E>, name: &[u8]) -> bool {
ctx.args.output_type != MH_EXECUTE
|| (ctx.args.export_dynamic && !ctx.args.preload)
|| (ctx.args.exported_symbols.as_ref()).is_some_and(|exported| exported.find(name) != -1)
}
/// Calls `f` with each subsection that subsection `id` references:
/// its relocation targets and, via its compact-unwind record range,
/// its LSDAs and personality routine, so a live function keeps them
/// alive.
#[inline]
fn for_each_edge<E: Target>(ctx: &Context<E>, id: usize, mut f: impl FnMut(usize)) {
let isec = &ctx.isecs[id];
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
match rel.target() {
RelocTarget::Sym(idx) => {
if let Some(target) = ctx.symbols[file.symbols[idx as usize]].input_section() {
f(target as usize);
}
}
RelocTarget::Section(target) => f(target as usize),
}
}
for_each_unwind_edge(ctx, id, |target, _| f(target));
}
/// Calls `f` with the LSDAs and the personality routine (and the symbol
/// naming it) of subsection `id`'s compact-unwind records.
#[inline]
fn for_each_unwind_edge<E: Target>(
ctx: &Context<E>,
id: usize,
mut f: impl FnMut(usize, Option<SymbolId>),
) {
let isec = &ctx.isecs[id];
let recs = isec.unwind_offset as usize..(isec.unwind_offset + isec.nunwind) as usize;
for rec in &ctx.unwind_records[recs] {
if let Some((lsda, _)) = rec.lsda() {
f(lsda, None);
}
let mut personality = rec.personality();
if let Some(fde) = rec.fde() {
if let Some((lsda, _)) = ctx.fdes[fde].lsda {
f(lsda as usize, None);
}
personality = personality.or(ctx.cies[ctx.fdes[fde].cie as usize].personality);
}
if let Some(p) = personality
&& let Some(target) = ctx.symbols[p].input_section()
{
f(target as usize, Some(p));
}
}
}
const GC_BATCH: usize = 16;
/// Marks what subsection `id` references. Recurses for a few levels
/// before queueing more work in `next` so that we do not create a
/// rayon task for every edge.
fn visit_section<E: Target>(
ctx: &Context<E>,
redirects: &[usize],
id: usize,
depth: usize,
next: &mut Vec<usize>,
) {
// Read all the edges before recursing into any: recursing midway
// through the relocations evicts them and costs a few percent more
// CPU on a large link.
let mut targets = Vec::new();
for_each_edge(ctx, id, |target| targets.push(target));
for target in targets {
let target = redirects[target];
if ctx.isecs[target].visit() {
if depth < 3 {
visit_section(ctx, redirects, target, depth + 1, next);
} else {
next.push(target);
}
}
}
}
/// Visits marked sections and publishes newly found work in batches.
/// Batching preserves dynamic load balancing while amortizing rayon
/// task allocation.
fn visit_batch<'s, E: Target>(
ctx: &'s Context<E>,
redirects: &'s [usize],
batch: &[usize],
scope: &rayon::Scope<'s>,
) {
let mut next = Vec::with_capacity(GC_BATCH);
for &id in batch {
visit_section(ctx, redirects, id, 0, &mut next);
if next.len() >= GC_BATCH {
let found = std::mem::replace(&mut next, Vec::with_capacity(GC_BATCH));
scope.spawn(move |scope| visit_batch(ctx, redirects, &found, scope));
}
}
if !next.is_empty() {
scope.spawn(move |scope| visit_batch(ctx, redirects, &next, scope));
}
}
/// Marks everything reachable from the roots. mold's gc-sections marks
/// with a work-stealing task pool, not synchronous frontier rounds:
/// each visit follows edges up to three levels inline and banks the
/// rest in small batches that spawn as tasks (rayon tasks are heavier
/// than TBB feeder items, so batches of 16 amortize them). Unoptimized
/// debug code has deep call chains, which starve round-based marking;
/// dynamic tasks keep every core fed regardless of graph depth.
fn mark<E: Target>(ctx: &Context<E>, redirects: &[usize], roots: &[usize]) {
rayon::scope(|scope| {
roots.par_chunks(GC_BATCH).for_each(|batch| visit_batch(ctx, redirects, batch, scope));
});
}
/// The serial walk from the marked sections in `queue`, breadth first:
/// for what a live-support subsection keeps, and under -why_live for
/// everything, noting in `why` the subsection that first reaches each
/// one.
fn walk<E: Target>(
ctx: &Context<E>,
redirects: &[usize],
queue: Vec<usize>,
mut why: Option<&mut [Why]>,
) {
let mut queue = VecDeque::from(queue);
while let Some(id) = queue.pop_front() {
for_each_edge(ctx, id, |target| {
let target = redirects[target];
if ctx.isecs[target].visit() {
if let Some(why) = why.as_deref_mut() {
why[target] = Why::From(id as u32);
}
queue.push_back(target);
}
});
}
}
/// A live-support subsection (S_ATTR_LIVE_SUPPORT) lives only if it
/// references a live subsection, and then keeps what it references.
/// ld64 checks them once, in input order, after what the roots reach is
/// marked, so one that only a later live-support subsection would make
/// live stays dead.
fn mark_live_support<E: Target>(
ctx: &Context<E>,
redirects: &[usize],
mut why: Option<&mut [Why]>,
) {
let live_support: Vec<usize> = ctx
.isecs
.par_iter()
.enumerate()
.filter(|(_, isec)| {
isec.is_alive()
&& isec.hdr(&ctx.objs[isec.file as usize]).flags & S_ATTR_LIVE_SUPPORT != 0
})
.map(|(id, _)| id)
.collect();
for id in live_support {
let mut references_live = false;
for_each_edge(ctx, id, |target| {
references_live |= ctx.isecs[redirects[target]].is_visited();
});
let id = redirects[id];
if references_live && ctx.isecs[id].visit() {
if let Some(why) = why.as_deref_mut() {
why[id] = Why::Root(Root::LiveSupport);
}
walk(ctx, redirects, vec![id], why.as_deref_mut());
}
}
}
/// Kills the sections the walk did not reach, consuming the visited
/// bit, and drops the unwind records and FDEs of dead functions.
fn sweep<E: Target>(ctx: &mut Context<E>) {
for isec in ctx.isecs.iter_mut() {
let visited = isec.take_visited();
let alive = isec.is_alive();
isec.set_alive(visited && alive);
}
crate::input_files::remove_dead_unwind_info(ctx);
}
/// Refresh symbol usage after subsection liveness is known. Undefined
/// references in removed subsections must neither cause errors nor
/// become dynamic imports.
fn mark_live_references<E: Target>(ctx: &mut Context<E>) {
ctx.symbols.syms.par_iter().for_each(|sym| sym.unmark());
ctx.isecs.par_iter().filter(|isec| isec.is_emitted()).for_each(|isec| {
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
if let Some(id) = rel.sym(file) {
ctx.symbols[id].mark();
}
}
});
for rec in &ctx.unwind_records {
if let Some(id) = rec.personality() {
ctx.symbols[id].mark();
}
}
for fde in &ctx.fdes {
if let Some(id) = ctx.cies[fde.cie as usize].personality {
ctx.symbols[id].mark();
}
}
crate::objc::drop_dead_objc_stubs(ctx);
if let Some(id) = ctx.objc_stubs.msgsend_sym {
ctx.symbols[id].mark();
}
for &func in &ctx.init_offsets.init_funcs {
if let InitFunc::Imported(id) = func {
ctx.symbols[id].mark();
}
}
for id in initial_undefines(ctx) {
ctx.symbols[id].mark();
}
ctx.symbols.syms.par_iter_mut().for_each(|sym| {
sym.set_used(sym.is_marked());
sym.unmark();
});
}
/// -why_live prints, on stderr, for each live symbol whose name matches
/// a -why_live pattern ("*" wildcards), what keeps it alive: the symbol
/// and its file, then the subsection whose reference made its subsection
/// live, and that one's, each a step further indented, up to a root and
/// why it is one. An import is kept by the first live subsection that
/// refers to it. Only meaningful under -dead_strip, like ld64's option
/// of the same name.
fn print_why_live<E: Target>(ctx: &Context<E>, redirects: &[usize], why: &[Why]) {
let matches = |sym: &Symbol| ctx.args.why_live.find(sym.name()) != -1;
let print_chain = |mut step: Why| {
for depth in 1.. {
let indent = depth * 2;
match step {
Why::From(id) => {
let isec = &ctx.isecs[id as usize];
let name = isec.name(ctx);
let file = ctx.objs[isec.file as usize].mf.name.raw();
notice(format_args!("{:indent$}{} from {file}", "", raw(&name)));
step = why[id as usize];
}
Why::Root(root) => {
notice(format_args!("{:indent$}{}", "", root.name()));
return;
}
Why::Dead => return,
}
}
};
for (i, obj) in ctx.objs.iter().enumerate().filter(|(_, obj)| obj.is_reachable) {
for &id in &obj.symbols {
let sym = &ctx.symbols[id];
if sym.file() != Some(FileId::Obj(i as u32)) || !matches(sym) {
continue;
}
let Some(isec) = sym.input_section().map(|isec| redirects[isec as usize]) else {
continue;
};
if ctx.isecs[isec].is_alive() {
notice(format_args!("{} from {}", raw(sym.name()), obj.mf.name.raw()));
print_chain(why[isec]);
}
}
}
let mut seen = HashSet::new();
for (id, isec) in ctx.isecs.iter().enumerate().filter(|(_, isec)| isec.is_alive()) {
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
let Some(sym_id) = rel.sym(file) else { continue };
let sym = &ctx.symbols[sym_id];
let Some(FileId::Dylib(dylib)) = sym.file() else { continue };
if dylib != u32::MAX && matches(sym) && seen.insert(sym_id) {
let file = ctx.dylibs[dylib as usize].path.raw();
notice(format_args!("{} from {file}", raw(sym.name())));
print_chain(Why::From(id as u32));
}
}
}
}
+176
View File
@@ -0,0 +1,176 @@
//! Delay-init dylibs (-delay-l, -delay_library, -delay_framework): a
//! dylib dyld loads and binds at launch as any other, but whose
//! initializers run only once the image dlopen()s it, before its first
//! use of one of the dylib's symbols. ld-prime gives the dylib a
//! dylib_use_command with DYLIB_USE_DELAYED_INIT and reaches its
//! symbols through code that dlopen()s it once (see chunks::delay_init);
//! the public libraries such a dylib re-exports are delayed with it,
//! by its dlopen helper.
use crate::arch::{LazyRef, Target};
use crate::chunks::delay_init::{DelayHelper, DelayStub, DelayUse, DlopenHelper};
use crate::context::Context;
use crate::input_files::{FileId, add_cstring, add_data_word};
use crate::lazy_load::{LazyUseSite, import_uses, load_helper_name};
use crate::symbol::SymbolId;
use crate::util::leak_bytes;
/// Makes the stubs and helpers through which the image reaches the
/// symbols of its delay-init dylibs, as ld-prime does: calls branch to
/// a stub per symbol, GOT loads (and x86-64's compares of a GOT slot)
/// call a helper, and each first has the dylib's dlopen helper dlopen()
/// it, once; then they go on through the symbol's __got slot. ld-prime
/// refuses any other reference, such as a pointer in data, which dyld
/// binds at launch and so before the dylib is initialized.
pub fn create_delay_init<E: Target>(ctx: &mut Context<E>) {
if !ctx.dylibs.iter().any(|d| d.delay_init.is_some()) {
return;
}
let uses = delay_uses(ctx);
// (A mergeable dylib can't keep them; see lazy_load::create_lazy_loads.)
if ctx.args.make_mergeable && !uses.is_empty() {
crate::fatal!(
"-delay-l/-delay_library/-delay_framework cannot be used with -make_mergeable"
);
}
let dlopen_of = create_dlopen_helpers(ctx, &uses);
if ctx.delay_init.dlopens.is_empty() {
return;
}
create_delay_stubs(ctx, &uses, &dlopen_of);
create_delay_helpers(ctx, &uses, &dlopen_of);
// The dlopen helpers call _dlopen through its stub.
if let Some(id) = ctx.symbols.lookup(b"_dlopen") {
crate::chunks::stubs::add_symbol(ctx, id);
ctx.delay_init.dlopen_sym = Some(id);
}
}
/// The references to delay-init dylibs' symbols from live subsections,
/// in input order, once the ones that can't be delayed are reported:
/// 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.symbols[id].is_delay_import(ctx));
for &(isec, _, id, how) in &uses {
if how == LazyRef::Unsupported {
let (sym, subsec) = (&ctx.symbols[id], ctx.isecs[isec as usize].name(ctx));
let subsec = crate::error::raw(&subsec);
crate::error!("use of '{sym}' in '{subsec}' cannot be delayed");
}
}
crate::error::checkpoint();
uses
}
/// The install name of the dylib a delay-init dylib's dlopen helper
/// dlopen()s: its own, or for a library it re-exports, its own.
fn dlopen_name<E: Target>(ctx: &Context<E>, id: SymbolId) -> &[u8] {
let Some(FileId::Dylib(d)) = ctx.symbols[id].file() else { unreachable!() };
ctx.dylibs[d as usize].delay_init.as_deref().unwrap()
}
/// Gives each dylib the stubs and helpers dlopen() its dlopen helper,
/// by install name: the helper's flag word in __data and the install
/// name's C string in __cstring, after the inputs'. Returns the helper
/// of each install name.
fn create_dlopen_helpers<E: Target>(
ctx: &mut Context<E>,
uses: &[LazyUseSite],
) -> hashbrown::HashMap<Vec<u8>, u32> {
let mut names: Vec<Vec<u8>> = uses
.iter()
.filter(|u| !matches!(u.3, LazyRef::Slot | LazyRef::Unsupported))
.map(|u| dlopen_name(ctx, u.2).to_vec())
.collect();
names.sort_unstable();
names.dedup();
if names.is_empty() {
return Default::default();
}
let mut dlopen_of = hashbrown::HashMap::new();
for (i, install_name) in names.into_iter().enumerate() {
let leaf = install_name.rsplit(|&c| c == b'/').next().unwrap_or(&install_name);
let name = leak_bytes([b"_dlopenHelper$", leaf].concat());
let flag_name = leak_bytes([b"_dlopenHelperFlag$", leaf].concat());
let flag = add_data_word(ctx, 4);
ctx.extra_local_syms.push((flag_name, flag));
let string = add_cstring(ctx, &install_name);
dlopen_of.insert(install_name.clone(), i as u32);
let offset = 0;
let helper = DlopenHelper { install_name, name, flag_name, flag, string, offset };
ctx.delay_init.dlopens.push(helper);
}
dlopen_of
}
/// Makes a stub for each symbol something calls, by name, which jumps
/// through the symbol's __got slot.
fn create_delay_stubs<E: Target>(
ctx: &mut Context<E>,
uses: &[LazyUseSite],
dlopen_of: &hashbrown::HashMap<Vec<u8>, u32>,
) {
let mut called: Vec<SymbolId> =
uses.iter().filter(|u| u.3 == LazyRef::Call).map(|u| u.2).collect();
called.sort_unstable_by_key(|&id| ctx.symbols[id].name());
called.dedup();
for (i, &id) in called.iter().enumerate() {
crate::chunks::got::add_got_symbol(ctx, id);
let got = ctx.symbols[id].got_idx(&ctx.symbols).unwrap();
ctx.symbols.aux_mut(id).delay_stub_idx = i as u32;
let dlopen = dlopen_of[dlopen_name(ctx, id)];
let name = leak_bytes([ctx.symbols[id].name(), b"$delayInitStub"].concat());
ctx.delay_init.stubs.push(DelayStub { sym: id, name, dlopen, got });
}
}
/// Makes the helpers for GOT loads and compares: one per symbol and
/// register, or per load in arm64 frameless code, in the order of their
/// first uses; then lays out __delay_helper, the dlopen helpers after
/// them.
fn create_delay_helpers<E: Target>(
ctx: &mut Context<E>,
uses: &[LazyUseSite],
dlopen_of: &hashbrown::HashMap<Vec<u8>, u32>,
) {
let mut helpers: Vec<DelayHelper> = Vec::new();
let mut index: hashbrown::HashMap<(SymbolId, DelayUse), usize> = hashbrown::HashMap::new();
let mut sites = hashbrown::HashMap::new();
for &(isec, offset, id, how) in uses {
let kind = match how {
LazyRef::Cmp => DelayUse::Cmp,
LazyRef::Load => {
let (reg, own) = E::lazy_load_site(ctx.isecs[isec as usize].contents(), offset);
DelayUse::Load { reg, site: own.then_some((isec, offset)) }
}
_ => continue,
};
let i = *index.entry((id, kind)).or_insert_with(|| {
let sym = ctx.symbols[id].name();
let name = match kind {
DelayUse::Cmp => [sym, b"$cmpHelper"].concat(),
DelayUse::Load { reg, site } => load_helper_name(ctx, sym, b"$", reg, site),
};
let name = leak_bytes(name);
let dlopen = dlopen_of[dlopen_name(ctx, id)];
helpers.push(DelayHelper { sym: id, kind, name, dlopen, offset: 0 });
helpers.len() - 1
});
sites.insert((isec, offset), i as u32);
}
let mut offset = 0;
for h in &mut helpers {
h.offset = offset;
offset += E::delay_helper_size(h.kind);
}
for d in &mut ctx.delay_init.dlopens {
d.offset = offset;
offset += E::DLOPEN_HELPER_SIZE;
}
ctx.delay_init.sites = sites;
ctx.delay_init.helpers = helpers;
}
+408
View File
@@ -0,0 +1,408 @@
//! The linker driver: runs the passes in order.
use std::borrow::Cow;
use std::ffi::{OsStr, OsString};
use std::ops::Range;
use std::sync::Arc;
use rayon::prelude::*;
use crate::arch::Target;
use crate::bundle_hook;
use crate::chunks::{self, ChunkId};
use crate::cmdline;
use crate::context::Context;
use crate::dead_strip;
use crate::objc;
use crate::output_file;
use crate::passes;
use crate::reader;
use crate::symbol_moves;
/// The fully expanded command line.
pub type Cmdline = Arc<[Cow<'static, OsStr>]>;
/// The exit status of a link, or the name of the target the inputs are
/// actually for.
pub type LinkResult = Result<i32, &'static str>;
/// Runs the linker with the given command line. Returns the exit status.
///
/// `initial_target` is an enabled target used for the initial argument
/// parsing. `link_for_target` links for a named target, or reports the
/// target the inputs are actually for; the executable provides both, as
/// the targets are instantiated in crates of their own.
pub fn main(
argv: Vec<OsString>,
initial_target: &str,
link_for_target: impl Fn(&str, Cmdline) -> LinkResult,
) -> i32 {
let cmdline: Cmdline = cmdline::expand_response_files(argv).into();
// Parse with an enabled target's defaults; if the target turns out to
// be different, start over with the right one.
let mut target = initial_target;
loop {
match link_for_target(target, Arc::clone(&cmdline)) {
Ok(status) => return status,
Err(actual) => target = actual,
}
}
}
fn target_traits<E: Target>() -> cmdline::TargetTraits {
cmdline::TargetTraits { name: E::NAME, page_size: E::PAGE_SIZE }
}
/// Links for the target `E`, or reports the target the inputs are
/// actually for.
pub fn link<E: Target>(cmdline: Cmdline) -> LinkResult {
let args = cmdline::parse_args(&target_traits::<E>(), &cmdline);
// Redo if the target does not match with our speculation.
if args.arch != Some(E::NAME) {
return Err(args.arch.unwrap());
}
// Fork so exit latency (unmapping every input) hides behind the
// parent's return, as in mold; MOLD_NO_FORK=1 keeps one process
// for debuggers and profilers.
if std::env::var_os("MOLD_NO_FORK").is_none() {
crate::subprocess::fork_child();
}
let mut ctx: Context<E> = Context::new(args);
crate::error::set_demangle(ctx.args.demangle);
cmdline::set_search_paths(&mut ctx);
let t_all = ctx.timer("all");
crate::subprocess::install_signal_handler();
crate::error::install_panic_hook();
// Runs a pass under a -print_statistics timer.
macro_rules! timed {
($name:literal, $e:expr) => {{
let t = ctx.timer($name);
$e;
drop(t);
}};
}
// Parse input files
timed!("read_input_files", reader::read_input_files(&mut ctx));
// Create a dummy file containing linker-synthesized symbols.
passes::create_internal_file(&mut ctx);
crate::error::checkpoint();
// Resolve symbols by choosing the most appropriate file for each
// symbol, loading the libraries the live objects' auto-link options
// name as they become live.
let mut t = ctx.timer("resolve_symbols");
passes::resolve_symbols(&mut ctx);
let mut checked = passes::CheckedInputs::default();
passes::check_input_versions(&ctx, &mut checked);
passes::check_bitcode_duplicates(&ctx);
// A link that has failed so far compiles no bitcode: say, one that
// has a bitcode file built for another platform.
crate::error::checkpoint();
// A -r link of bitcode alone writes the bitcode merged.
if ctx.args.relocatable && passes::links_only_bitcode(&ctx) {
t.stop();
passes::print_why_load(&ctx);
crate::lto::write_merged_bitcode(&ctx);
crate::error::checkpoint();
crate::mapfile::write_dependency_info(&ctx);
crate::error::checkpoint();
crate::subprocess::notify_parent();
drop(t_all);
return Ok(0);
}
// If there's a bitcode file, do link-time optimization.
if passes::has_lto_obj(&ctx) {
passes::do_lto(&mut ctx);
passes::check_input_versions(&ctx, &mut checked);
}
passes::print_why_load(&ctx);
passes::warn_newer_dylibs(&ctx);
crate::error::checkpoint();
t.stop();
bundle_hook::create_class_table(&mut ctx);
passes::check_common_conflicts(&ctx);
// Now that we know which object files are to be included to the
// final output, we can remove unnecessary files.
timed!("remove_unreachable_files", passes::remove_unreachable_files(&mut ctx));
passes::remove_swift_reflection_metadata(&mut ctx);
// Handle -r. Since the linker's behavior is quite different from the
// normal one when the option is given, the logic is implemented to a
// separate file.
if ctx.args.relocatable {
passes::print_implicit_trace(&ctx);
passes::check_duplicate_symbols(&ctx);
passes::check_removed_swift_metadata_refs(&ctx);
crate::error::checkpoint();
passes::check_poisoned_symbols(&ctx);
crate::error::checkpoint();
passes::hide_all_exports(&mut ctx);
passes::handle_exported_symbols_list(&mut ctx);
passes::handle_unexported_symbols_list(&mut ctx);
passes::check_weak_exports(&ctx);
// Literals are left for the final link to merge: every input
// label and relocation then carries through as it is.
// ld64 -r keeps one copy of each weak definition (the marker
// stays on it for the final link to auto-hide); Swift's
// per-object conformance and metadata records doubled
// NetNewsWire's RSCore prelink's __DATA,__const otherwise.
timed!("coalesce_weak_defs", passes::coalesce_weak_defs(&mut ctx));
let moves = symbol_moves::find_moves(&ctx);
timed!("create_output_sections", passes::create_output_sections(&mut ctx, &moves));
passes::set_section_alignments(&mut ctx);
passes::sort_section_members(&mut ctx);
timed!("compute_section_sizes", passes::compute_section_sizes(&mut ctx));
timed!("create_synthetic_sections", passes::create_synthetic_sections(&mut ctx, &moves));
passes::rename_synthetic_sections(&mut ctx);
passes::add_boundary_sections(&mut ctx);
timed!("sort_output_sections", passes::sort_output_sections(&mut ctx));
passes::create_segments(&mut ctx);
passes::add_boundary_segments(&mut ctx);
passes::add_stack_segment(&mut ctx);
passes::finish_section_alignments(&mut ctx);
chunks::indirect_symtab::assign_indices(&mut ctx);
passes::check_segment_order(&ctx);
passes::check_section_order(&ctx);
passes::check_interposing(&ctx);
passes::check_header_segment(&ctx);
timed!("relocatable", ctx.output_size = crate::relocatable::combine_objects(&mut ctx));
crate::error::checkpoint();
crate::subprocess::notify_parent();
drop(t_all);
passes::show_stats(&ctx);
return Ok(0);
}
// Merge the initializers, literals and Objective-C references, and
// add the symbols and sections the linker synthesizes.
passes::check_initializers(&ctx);
passes::convert_init_offsets(&mut ctx);
timed!("merge_literals", {
passes::merge_literals(&mut ctx);
objc::coalesce_objc_refs(&mut ctx);
});
timed!("add_synthetic_symbols", passes::add_synthetic_symbols(&mut ctx));
timed!("convert_common_symbols", passes::convert_common_symbols(&mut ctx));
timed!("create_objc_msgsend_stubs", objc::create_objc_msgsend_stubs(&mut ctx));
// Decide what the image exports.
timed!("auto_hide_weak_defs", passes::auto_hide_weak_defs(&mut ctx));
timed!("hide_all_exports", passes::hide_all_exports(&mut ctx));
timed!("handle_exported_symbols_list", passes::handle_exported_symbols_list(&mut ctx));
timed!("handle_unexported_symbols_list", passes::handle_unexported_symbols_list(&mut ctx));
passes::force_symbol_weakness(&mut ctx);
timed!("create_symbol_reexports", passes::create_symbol_reexports(&mut ctx));
timed!("coalesce_weak_defs", passes::coalesce_weak_defs(&mut ctx));
passes::print_dependencies(&ctx);
passes::print_trace(&ctx);
passes::print_implicit_trace(&ctx);
// Garbage-collect unreachable subsections.
if dead_strip::strips_dead_code(&ctx) {
timed!("dead_strip", dead_strip::strip_dead_code(&mut ctx));
}
timed!("create_dof_sections", crate::dtrace::create_dof_sections(&mut ctx));
// Report the errors resolution and dead stripping leave.
passes::check_removed_swift_metadata_refs(&ctx);
passes::claim_unresolved_symbols(&mut ctx);
timed!("report_undef_errors", passes::report_undef_errors(&mut ctx));
crate::error::checkpoint();
passes::check_weak_imports(&ctx);
crate::error::checkpoint();
passes::check_duplicate_symbols(&ctx);
crate::error::checkpoint();
passes::check_poisoned_symbols(&ctx);
crate::error::checkpoint();
passes::warn_unused_dylibs(&ctx);
passes::warn_redundant_reexports(&ctx);
passes::check_weak_exports(&ctx);
crate::error::checkpoint();
// Fold identical functions.
if ctx.args.deduplicate {
timed!("compute_address_significance", passes::compute_address_significance(&mut ctx));
crate::icf::icf_sections(&mut ctx);
}
// Scan relocations to find symbols that need stubs, GOT slots and
// the like. The passes before the scan flag what else needs them,
// and rewrite the class loads it is to see as GOT loads.
passes::add_entry_stub(&mut ctx);
objc::scan_objc_stubs(&mut ctx);
objc::fold_objc_classrefs(&mut ctx);
timed!("scan_relocations", passes::scan_relocations(&mut ctx));
objc::convert_objc_method_lists(&mut ctx);
objc::merge_objc_categories(&mut ctx);
// Synthetic stubs and unwind data can introduce library references
// (notably dyld_stub_binder). Establish them before pruning dylibs.
chunks::stub_helper::resolve_stub_binder(&mut ctx);
crate::lazy_load::bind_dyld_lazy_load(&mut ctx);
timed!("dead_strip_dylibs", passes::dead_strip_dylibs(&mut ctx));
passes::check_shared_cache_deps(&ctx);
passes::check_libsystem_linked(&ctx);
passes::bind_private_reexports_to_image(&mut ctx);
passes::check_weak_assertions(&ctx);
crate::error::checkpoint();
crate::lazy_load::create_lazy_loads(&mut ctx);
crate::delay_init::create_delay_init(&mut ctx);
passes::finish_stubs(&mut ctx);
// Bin input sections into output sections. -move_to_rw_segment and
// the like take the subsections they name to other segments.
let moves = symbol_moves::find_moves(&ctx);
timed!("create_output_sections", passes::create_output_sections(&mut ctx, &moves));
passes::set_section_alignments(&mut ctx);
// Order each output section's members: -order_file's first, cold
// code last.
passes::sort_section_members(&mut ctx);
// Compute sizes of output sections while assigning offsets within
// an output section to input sections.
timed!("compute_section_sizes", passes::compute_section_sizes(&mut ctx));
// Create linker-synthesized sections such as __stubs or __got, and
// give them their final names.
timed!("create_synthetic_sections", passes::create_synthetic_sections(&mut ctx, &moves));
passes::rename_synthetic_sections(&mut ctx);
// Create the sections section$start$ and section$end$ symbols name.
passes::add_boundary_sections(&mut ctx);
crate::mapfile::trace_symbol_layout(&ctx);
// Sort the sections into file order, and group them into segments.
timed!("sort_output_sections", passes::sort_output_sections(&mut ctx));
passes::create_segments(&mut ctx);
passes::add_boundary_segments(&mut ctx);
passes::add_stack_segment(&mut ctx);
passes::finish_section_alignments(&mut ctx);
chunks::indirect_symtab::assign_indices(&mut ctx);
passes::check_segment_order(&ctx);
passes::check_section_order(&ctx);
passes::check_interposing(&ctx);
passes::check_header_segment(&ctx);
// Handle -no_zero_fill_sections.
if ctx.args.no_zero_fill_sections {
passes::fill_zero_fill_sections(&mut ctx);
}
// The output symbol table builds inside set_osec_offsets, as part
// of the parallel __LINKEDIT task group.
timed!("set_osec_offsets", passes::set_osec_offsets(&mut ctx));
passes::fix_synthetic_symbols(&mut ctx);
passes::check_entry_point(&ctx);
crate::error::checkpoint();
crate::mapfile::write_dependency_info(&ctx);
crate::mapfile::print_map(&ctx);
crate::mapfile::write_sdk_imports(&ctx);
// Write the output. The file is created up front, executable, and
// each range of the buffer is queued to `out` the moment it is
// final, so the file is written from background threads while the
// rest is produced: everything between the header and the symbol
// table after the copy and its fix-ups, the symbol and string
// tables after copy_symtab, the header after the UUID, the
// signature last. finish() waits for the last one.
let t_copy = ctx.timer("copy");
let mut buf = vec![0; output_file::buffer_len(&ctx.args.output, ctx.output_size)];
let out = output_file::OutputFile::open(&ctx.args.output, 0o777, buf.as_ptr(), buf.len());
// Copy input sections to the output file and apply relocations.
copy_chunks(&ctx, &mut buf);
// Relocations that failed to apply fail the link before the fixups
// are written.
passes::report_text_relocs(&ctx);
crate::error::checkpoint();
// The fixups, the symbol table (which also fills the string table),
// the mach header, the UUID and the code signature follow serially,
// in that order, since each depends on the bytes before it.
if ctx.use_chained_fixups() {
timed!("write_fixup_chains", chunks::chained_fixups::write_fixup_chains(&ctx, &mut buf));
}
if ctx.chunks.contains(&ChunkId::LocalRelocs) {
chunks::local_relocs::write(&ctx, &mut buf);
}
if ctx.chunks.contains(&ChunkId::ExternRelocs) {
chunks::extern_relocs::write(&ctx, &mut buf);
}
timed!("apply_optimization_hints", E::apply_optimization_hints(&ctx, &mut buf));
let hdr_end = ctx.mach_header.hdr.size as usize;
let sig_start = if ctx.chunks.contains(&ChunkId::CodeSignature) {
ctx.code_signature.hdr.fileoff as usize
} else {
buf.len()
};
let symtab_start = (ctx.symtab.hdr.fileoff as usize).min(ctx.strtab.hdr.fileoff as usize);
// Nothing below writes between the header and the symbol table.
out.queue(hdr_end, symtab_start - hdr_end);
timed!("copy_symtab", chunks::symtab::copy_symtab(&ctx, &mut buf));
out.queue(symtab_start, sig_start - symtab_start);
chunks::write_mach_header(&ctx, &mut buf);
let hashes = passes::compute_uuid(&ctx, &mut buf, sig_start);
out.queue(0, hdr_end);
if ctx.args.adhoc_codesign {
timed!("write_code_signature", chunks::code_signature::write(&ctx, &mut buf, &hashes));
}
out.queue(sig_start, buf.len() - sig_start);
crate::error::checkpoint();
// The traces name the output by its UUID; one that can't be written
// fails the link, which leaves no output.
crate::mapfile::write_trace_files(&ctx);
crate::error::checkpoint();
timed!("close_file", out.close());
drop(t_copy);
let _ = std::io::Write::flush(&mut std::io::stdout());
let _ = std::io::Write::flush(&mut std::io::stderr());
crate::subprocess::notify_parent();
drop(t_all);
passes::show_stats(&ctx);
Ok(0)
}
/// Copies all chunks to the output buffer and applies relocations, in
/// parallel: the buffer is carved into disjoint per-chunk slices, and
/// every chunk writes only within its own.
pub(crate) fn copy_chunks<E: Target>(ctx: &Context<E>, buf: &mut [u8]) {
let jobs: Vec<(ChunkId, Range<u64>)> = ctx
.chunks
.iter()
.map(|&id| (id, ctx.chunk_header(id)))
.filter(|(id, hdr)| {
!matches!(
id,
ChunkId::MachHeader | ChunkId::Symtab | ChunkId::Strtab | ChunkId::CodeSignature
) && !hdr.is_zerofill()
// An empty section (every subsection of a coverage
// section dead, say) shares its file offset with its
// neighbor; it has nothing to copy, and its range would
// start inside the neighbor's.
&& hdr.size != 0
})
.map(|(id, hdr)| (id, hdr.fileoff..hdr.fileoff + hdr.size))
.collect();
let ranges: Vec<Range<u64>> = jobs.iter().map(|(_, range)| range.clone()).collect();
let slices = output_file::split_ranges(buf, &ranges);
let t = ctx.timer("copy_chunks");
jobs.par_iter().zip(slices).for_each(|(&(id, _), slice)| chunks::copy_buf(ctx, id, slice));
drop(t);
}
+796
View File
@@ -0,0 +1,796 @@
//! DTrace USDT probes (statically defined tracing).
//!
//! A header `dtrace -h` makes from a D script has the code call an
//! undefined function for each probe site, `___dtrace_probe$<provider>$
//! <probe>$v1$<argument types>`, and for each is-enabled test,
//! `___dtrace_isenabled$<provider>$<probe>$v1`, and refer to two more
//! undefined symbols per provider that no relocation uses: its
//! stability attributes and its argument typedefs. None of these is
//! ever defined.
//!
//! A final link turns each site into code that does nothing - a probe's
//! call into a nop, an is-enabled test's into setting its result to 0 -
//! and describes the sites, one provider at a time, in a DOF ("DTrace
//! Object Format") section of __TEXT, __dof_<provider>, which dyld hands
//! to the kernel as it loads the image; enabling a probe has the kernel
//! patch its sites. ld-prime has the OS's libdtrace make the DOF from
//! the symbol names (dtrace_ld_create_dof): it rebuilds the provider's
//! D script from them, compiles it, and registers each site under its
//! probe and the function it is in. We make the DOF from the symbols
//! ourselves, in libdtrace's layout (see build_dof). The DOF leaves a
//! slot for each site's distance from it, which a pair of relocations
//! fills in once the output is laid out.
//!
//! ld-prime makes the DOF sections in the order of a hash table of the
//! providers; we make them in the order of their first sites.
use hashbrown::HashMap;
use rayon::prelude::*;
use crate::arch::Target;
use crate::context::Context;
use crate::error::raw;
use crate::input_files::add_synthetic_section;
use crate::input_sections::{InputSection, Reloc, RelocTarget};
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::{push16, push32, push64, split_once};
/// The prefix of the names of the symbols a `dtrace -h` header makes
/// code refer to. ld-prime takes every undefined symbol whose name
/// starts with it for one of them, whatever follows.
const PREFIX: &[u8] = b"___dtrace_";
const PROBE_PREFIX: &[u8] = b"___dtrace_probe$";
const IS_ENABLED_PREFIX: &[u8] = b"___dtrace_isenabled$";
/// Whether an undefined symbol of this name is a DTrace symbol.
pub fn is_dtrace_symbol(name: &[u8]) -> bool {
name.starts_with(PREFIX)
}
/// What a call of a DTrace symbol is: ld-prime's dtraceKind.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SiteKind {
/// A probe's site, which fires it.
Probe,
/// An is-enabled test, which returns whether the probe is on.
IsEnabled,
}
/// The kind of site a call of DTrace symbol `name` makes, or None for a
/// provider's other symbols (its stability and typedefs, or any other
/// name with the prefix), a call of which is no site.
fn site_kind_of(name: &[u8]) -> Option<SiteKind> {
if name.starts_with(PROBE_PREFIX) {
Some(SiteKind::Probe)
} else if name.starts_with(IS_ENABLED_PREFIX) {
Some(SiteKind::IsEnabled)
} else {
None
}
}
/// The provider a DTrace symbol belongs to: for a probe or an is-enabled
/// test the field after the prefix, for another symbol the one after
/// its first `$`; empty if the `$` that ends it is missing.
fn provider_of(name: &[u8]) -> &[u8] {
let rest = match site_kind_of(name) {
Some(SiteKind::Probe) => &name[PROBE_PREFIX.len()..],
Some(SiteKind::IsEnabled) => &name[IS_ENABLED_PREFIX.len()..],
None => split_once(name, b'$').map_or(&[][..], |(_, rest)| rest),
};
split_once(rest, b'$').map_or(&[][..], |(provider, _)| provider)
}
/// A provider's DOF section, as the link makes it: the subsections of
/// the sites it refers to (in site order).
#[derive(Debug)]
pub struct DofSection {
pub sites: Vec<u32>,
}
/// Whether a branch to symbol `sym` is a probe site, and of which kind:
/// a branch to a probe or an is-enabled test, in a link that makes DOF.
/// Another branch to a DTrace symbol goes to address 0, as ld-prime has
/// it.
#[inline]
pub fn site_kind<E: Target>(ctx: &Context<E>, sym: SymbolId) -> Option<SiteKind> {
let sym = &ctx.symbols[sym];
if sym.is_defined() || !ctx.args.dtrace_dof {
return None;
}
site_kind_of(sym.name())
}
/// A probe site: a branch to a probe or an is-enabled test, at offset
/// `offset` of subsection `isec` (the relocated field's).
struct Site {
isec: u32,
offset: u32,
sym: SymbolId,
}
/// Makes a DOF section for each provider some live code has a probe site
/// of, once dead stripping is done. (ld-prime makes them before, so that
/// a function nothing calls stays for its probe sites.)
pub fn create_dof_sections<E: Target>(ctx: &mut Context<E>) {
if !ctx.args.dtrace_dof || ctx.args.relocatable {
return;
}
let syms = dtrace_symbols(ctx);
if syms.is_empty() {
return;
}
let sites = collect_sites(ctx);
let names: Vec<&[u8]> = syms.iter().map(|&id| ctx.symbols[id].name()).collect();
let mut taken: Vec<Vec<u8>> = Vec::new();
for (provider, sites) in sites_by_provider(ctx, &sites) {
// The provider's stability and typedefs symbols, the first of
// each if there are more.
let info = |prefix: &[u8]| {
(names.iter().copied())
.find(|&name| name.starts_with(prefix) && provider_of(name) == provider)
};
let (stability, typedefs) = (info(b"___dtrace_stability$"), info(b"___dtrace_typedefs$"));
let probes: Vec<&[u8]> = sites.iter().map(|s| ctx.symbols[s.sym].name()).collect();
// A site goes by the function it is in, its subsection's label.
let functions: Vec<&[u8]> = sites
.iter()
.map(|s| ctx.isecs[s.isec as usize].label(ctx).unwrap_or_default())
.collect();
let dof = match build_dof(provider, stability, typedefs, &probes, &functions) {
Ok(dof) => dof,
Err(name) => {
crate::error!("{}: unsupported DTrace probe encoding", raw(name));
return;
}
};
let name = section_name(provider, &taken);
add_dof_section(ctx, &name, dof, &sites);
taken.push(name);
}
}
/// The undefined symbols with the DTrace prefix that live objects refer
/// to; most links have none.
fn dtrace_symbols<E: Target>(ctx: &Context<E>) -> Vec<SymbolId> {
(0..ctx.symbols.syms.len() as SymbolId)
.into_par_iter()
.filter(|&id| {
let sym = &ctx.symbols[id];
sym.is_used() && !sym.is_defined() && is_dtrace_symbol(sym.name())
})
.collect()
}
/// The probe sites: by file in input order, by section and address in a
/// file (the subsections' order), and by offset in a subsection. A copy
/// of a function another one replaced (a weak definition another file's
/// won) has none.
fn collect_sites<E: Target>(ctx: &Context<E>) -> Vec<Site> {
(0..ctx.isecs.len())
.into_par_iter()
.filter(|&i| {
let isec = &ctx.isecs[i];
isec.is_emitted()
})
.flat_map_iter(|i| {
let file = &ctx.objs[ctx.isecs[i].file as usize];
ctx.isecs[i].rels(file).iter().filter_map(move |r| {
if !r.is_func_call::<E>() || r.size != 4 {
return None;
}
let sym = r.sym(file)?;
let (defined, name) = (ctx.symbols[sym].is_defined(), ctx.symbols[sym].name());
(!defined && site_kind_of(name).is_some()).then_some(Site {
isec: i as u32,
offset: r.offset,
sym,
})
})
})
.collect()
}
/// Each provider's sites, in site order, the providers in the order of
/// their first sites; a site of no provider (a name the prefix ends)
/// belongs to none.
fn sites_by_provider<'a, E: Target>(
ctx: &Context<E>,
sites: &'a [Site],
) -> Vec<(&'static [u8], Vec<&'a Site>)> {
let mut providers: Vec<(&'static [u8], Vec<&Site>)> = Vec::new();
let mut index: HashMap<&[u8], usize> = HashMap::new();
for site in sites {
let provider = provider_of(ctx.symbols[site.sym].name());
if provider.is_empty() {
continue;
}
let i = *index.entry(provider).or_insert_with(|| {
providers.push((provider, Vec::new()));
providers.len() - 1
});
providers[i].1.push(site);
}
providers
}
/// The name of a provider's DOF section: "__dof_" and the provider, cut
/// to 15 bytes. If an earlier provider's has that name, ld-prime
/// replaces its last byte with '0', then '1', and so on, until it
/// finds one free.
fn section_name(provider: &[u8], taken: &[Vec<u8>]) -> Vec<u8> {
let mut name = [b"__dof_", provider].concat();
name.truncate(15);
if taken.contains(&name) {
*name.last_mut().unwrap() = b'0';
while taken.contains(&name) {
let last = name.last_mut().unwrap();
*last = last.wrapping_add(1);
}
}
name
}
/// Adds a provider's DOF to the internal object, a section of its own
/// in __TEXT, with relocations that fill each site's slot with its
/// distance from the DOF: a 4-byte SUBTRACTOR pair from the DOF to the
/// site, as ld-prime's fixups are.
fn add_dof_section<E: Target>(ctx: &mut Context<E>, name: &[u8], dof: Dof, sites: &[&Site]) {
let mut sectname = [0; 16];
sectname[..name.len()].copy_from_slice(name);
let hdr = MachSection {
sectname,
segname: bytes_to_name(b"__TEXT"),
flags: S_DTRACE_DOF,
..Default::default()
};
let (file, shndx) = add_synthetic_section(ctx, hdr);
let id = ctx.isecs.len() as u32;
let relocs = &mut ctx.objs[file as usize].relocs;
let rel_offset = relocs.len() as u32;
for (&slot, site) in dof.slots.iter().zip(sites) {
let pair = |ty, target: RelocTarget, addend, is_subtracted| Reloc {
offset: slot,
ty,
size: 4,
is_pcrel: false,
is_subtracted,
target: target.pack(),
addend,
};
relocs.push(pair(E::RELOC_SUBTRACTOR, RelocTarget::Section(id), 0, false));
let to = RelocTarget::Section(site.isec);
relocs.push(pair(E::RELOC_UNSIGNED, to, site.offset as i64, true));
}
let nrels = relocs.len() as u32 - rel_offset;
let bytes: &'static [u8] = Vec::leak(dof.bytes);
ctx.isecs.push(InputSection {
rel_offset,
nrels,
..InputSection::new(file, shndx, 0, bytes.len() as u32, bytes)
});
ctx.dof_sections.push(DofSection { sites: sites.iter().map(|s| s.isec).collect() });
}
/// What libdtrace's dtrace_ld_create_dof makes of a provider (see
/// build_dof): the DOF, and the offset in it of each site's slot.
#[derive(Debug)]
pub struct Dof {
pub bytes: Vec<u8>,
pub slots: Vec<u32>,
}
/// Makes a provider's DOF from its symbols as libdtrace would make it:
/// `stability` and `typedefs` are its symbols of those, if it has them,
/// and `probe_names` and `functions` the symbol and the name of the
/// subsection of each site, in the order of the site's slots. Fails
/// with the symbol of a probe of an encoding other than v1, whose fields
/// would be misread. The names are bytes, as any symbol's.
pub fn build_dof<'a>(
provider: &[u8],
stability: Option<&[u8]>,
typedefs: Option<&[u8]>,
probe_names: &[&'a [u8]],
functions: &[&[u8]],
) -> Result<Dof, &'a [u8]> {
let typedefs = typedefs
.map_or(Vec::new(), |name| fields(name).iter().skip(3).map(|hex| unhex(hex)).collect());
let probes = register(probe_names, functions, &typedefs)?;
Ok(write_dof(provider, attributes(stability), &probes, probe_names.len()))
}
/// The `$`-separated fields of a name as libdtrace reads them: a `$` at
/// the end starts no field.
fn fields(name: &[u8]) -> Vec<&[u8]> {
name.strip_suffix(b"$").unwrap_or(name).split(|&c| c == b'$').collect()
}
/// A string of bytes in hex, two digits each, as `dtrace -h` encodes
/// the argument types and typedef names in the symbol names.
fn unhex(hex: &[u8]) -> Vec<u8> {
let digit = |c: u8| (c as char).to_digit(16).unwrap_or(0) as u8;
hex.as_chunks::<2>().0.iter().map(|&[hi, lo]| digit(hi) << 4 | digit(lo)).collect()
}
/// A probe's name as DTrace spells it: each "__" a "-", from the left.
fn hyphenate(name: &[u8]) -> Vec<u8> {
let mut out = Vec::with_capacity(name.len());
let mut rest = name;
while let Some(i) = memchr::memmem::find(rest, b"__") {
out.extend_from_slice(&rest[..i]);
out.push(b'-');
rest = &rest[i + 2..];
}
out.extend_from_slice(rest);
out
}
/// The attributes a provider has when its stability symbol gives none:
/// D's default, Private/Private/Unknown, for each of the five.
const DEFAULT_ATTRIBUTES: [u32; 5] = [0x0101_0000; 5];
/// The attributes of a provider and of the module, function, name and
/// args of its probes, from its stability symbol: the last field has 15
/// digits, a stability level for the names, one for the data and a
/// dependency class each, which the DOF holds a byte each of.
fn attributes(stability: Option<&[u8]>) -> [u32; 5] {
let digits: Vec<u32> = stability.map_or(Vec::new(), |name| {
let f = fields(name);
let digits = f.get(3).copied().unwrap_or_default();
digits.iter().step_by(2).map(|&c| c.wrapping_sub(b'0') as u32).collect()
});
if digits.len() != 15 {
return DEFAULT_ATTRIBUTES;
}
std::array::from_fn(|k| digits[3 * k] << 24 | digits[3 * k + 1] << 16 | digits[3 * k + 2] << 8)
}
/// An argument's type as the DOF names it: as `dtrace -h` spelled it in
/// the symbol, but for each name of the provider's `typedefs`, which is
/// an int (the type it stood for is gone). D compiles the name when a
/// script uses the argument.
fn arg_type(spelled: &[u8], typedefs: &[Vec<u8>]) -> Vec<u8> {
let is_ident = |c: u8| c.is_ascii_alphanumeric() || c == b'_';
(spelled.chunk_by(|&a, &b| is_ident(a) && is_ident(b)))
.flat_map(|token| if typedefs.iter().any(|t| t == token) { b"int" } else { token })
.copied()
.collect()
}
/// A probe: its name, the types of its arguments, none if no probe site
/// declares them, and the functions it has sites in.
struct Probe {
name: Vec<u8>,
args: Option<Vec<Vec<u8>>>,
instances: Vec<Instance>,
}
/// A function a probe has sites in, by name, with the indices of the
/// sites: the probe's sites, and its is-enabled tests.
struct Instance {
function: Vec<u8>,
sites: Vec<u32>,
tests: Vec<u32>,
}
/// Registers each site with its probe, by the probe's hyphenated name,
/// in the instance of its function: the name of its subsection with one
/// leading underscore less, cut to the 127 bytes the kernel takes. The
/// probes and their instances come in the order of their first sites. A
/// probe's arguments are those the symbol of its first probe site gives
/// (see arg_type).
fn register<'a>(
probe_names: &[&'a [u8]],
functions: &[&[u8]],
typedefs: &[Vec<u8>],
) -> Result<Vec<Probe>, &'a [u8]> {
let mut probes: Vec<Probe> = Vec::new();
let mut index: HashMap<&[u8], usize> = HashMap::new();
for (i, (&symbol, &function)) in probe_names.iter().zip(functions).enumerate() {
let f = fields(symbol);
let field = f.get(2).copied().unwrap_or_default();
let k = *index.entry(field).or_insert_with(|| {
probes.push(Probe { name: hyphenate(field), args: None, instances: Vec::new() });
probes.len() - 1
});
let test = symbol.starts_with(IS_ENABLED_PREFIX);
if !test && probes[k].args.is_none() {
if f.get(3) != Some(&&b"v1"[..]) {
return Err(symbol);
}
probes[k].args =
Some(f[4..].iter().map(|hex| arg_type(&unhex(hex), typedefs)).collect());
}
let function = function.strip_prefix(b"_").unwrap_or(function);
let function = &function[..function.len().min(127)];
let instances = &mut probes[k].instances;
let j = match instances.iter().position(|inst| inst.function == function) {
Some(j) => j,
None => {
let function = function.to_vec();
instances.push(Instance { function, sites: Vec::new(), tests: Vec::new() });
instances.len() - 1
}
};
let inst = &mut instances[j];
if test {
inst.tests.push(i as u32);
} else {
inst.sites.push(i as u32);
}
}
Ok(probes)
}
/// DOF section types.
const DOF_SECT_STRTAB: u32 = 8;
const DOF_SECT_RELTAB: u32 = 10;
const DOF_SECT_URELHDR: u32 = 12;
const DOF_SECT_PROVIDER: u32 = 15;
const DOF_SECT_PROBES: u32 = 16;
const DOF_SECT_PRARGS: u32 = 17;
const DOF_SECT_PROFFS: u32 = 18;
const DOF_SECT_PRENOFFS: u32 = 26;
/// The sizes of a DOF's header (dof_hdr_t), a section header
/// (dof_sec_t), a probe (dof_probe_t) and a relocation
/// (dof_relodesc_t).
const DOF_HDR_SIZE: usize = 64;
const DOF_SEC_SIZE: usize = 32;
const DOF_PROBE_SIZE: usize = 48;
const DOF_RELO_SIZE: usize = 24;
/// A DOF's string table: a NUL, then each string added, whether or not
/// it is there already. An empty one is not added, and is at 0.
struct StringTable(Vec<u8>);
impl StringTable {
fn add(&mut self, s: &[u8]) -> u32 {
if s.is_empty() {
return 0;
}
let off = self.0.len() as u32;
self.0.extend_from_slice(s);
self.0.push(0);
off
}
}
/// Writes a provider's DOF as libdtrace lays it out: the header, the
/// section headers, then the sections' data - the probes (see
/// ProbeTables), the slots of the probe sites and of the is-enabled
/// tests, the provider, the relocations and their header - and the
/// string table last. A slot holds its site's distance from the DOF,
/// which the link fills in (see add_dof_section).
fn write_dof(provider: &[u8], attributes: [u32; 5], probes: &[Probe], nsites: usize) -> Dof {
let mut strtab = StringTable(vec![0]);
let tables = ProbeTables::new(probes, &mut strtab);
let name = strtab.add(provider);
let (sites, tests) = (tables.sites.clone(), tables.tests.clone());
let (bytes, offsets) = lay_out_dof(tables.sections(&attributes, name), &strtab.0);
let mut slots = vec![0; nsites];
for (list, ty) in [(&sites, DOF_SECT_PROFFS), (&tests, DOF_SECT_PRENOFFS)] {
let base = offsets.iter().find(|&&(t, _)| t == ty).map_or(0, |&(_, off)| off);
for (k, &i) in list.iter().enumerate() {
slots[i as usize] = base + 4 * k as u32;
}
}
Dof { bytes, slots }
}
/// The data of a DOF's probe sections, as libdtrace fills them probe by
/// probe, an instance at a time: a dof_probe_t per instance, the
/// mapping of each probe's arguments (the identity: no translator
/// makes them), a relocation per instance (the kernel adds the address
/// the DOF is loaded at to its dofpr_addr), and the indices of the
/// probe sites and the is-enabled tests whose slots come in that order.
/// The strings go in the string table as they come.
struct ProbeTables {
records: Vec<u8>,
prargs: Vec<u8>,
relocs: Vec<u8>,
sites: Vec<u32>,
tests: Vec<u32>,
}
impl ProbeTables {
fn new(probes: &[Probe], strtab: &mut StringTable) -> Self {
let mut t = ProbeTables {
records: Vec::new(),
prargs: Vec::new(),
relocs: Vec::new(),
sites: Vec::new(),
tests: Vec::new(),
};
for probe in probes {
let name = strtab.add(&probe.name);
let args = probe.args.as_deref().unwrap_or_default();
let nargv = strtab.0.len() as u32;
for arg in args {
strtab.add(arg);
}
let xargv = strtab.0.len() as u32;
for arg in args {
strtab.add(arg);
}
let argidx = t.prargs.len() as u32;
let nargc = args.len() as u8;
t.prargs.extend(0..nargc);
for inst in &probe.instances {
let func = strtab.add(&inst.function);
put_u32s(&mut t.relocs, &[func, 1]);
put_u64s(&mut t.relocs, &[t.records.len() as u64, 0]);
put_u64s(&mut t.records, &[0]);
let offidx = t.sites.len() as u32;
put_u32s(&mut t.records, &[func, name, nargv, xargv, argidx, offidx]);
t.records.extend_from_slice(&[nargc, nargc]);
push16(&mut t.records, inst.sites.len() as u16);
put_u32s(&mut t.records, &[t.tests.len() as u32]);
push16(&mut t.records, inst.tests.len() as u16);
t.records.extend_from_slice(&[0; 6]);
t.sites.extend(&inst.sites);
t.tests.extend(&inst.tests);
}
}
t
}
/// The DOF's sections but the string table, in their order: (type,
/// alignment, entry size, data). Without is-enabled tests there is
/// no PRENOFFS, and the provider says so with 0 for its index.
fn sections(self, attributes: &[u32; 5], name: u32) -> Vec<(u32, u32, u32, Vec<u8>)> {
let mut sections = vec![
(DOF_SECT_PROBES, 8, DOF_PROBE_SIZE as u32, self.records),
(DOF_SECT_PRARGS, 1, 1, self.prargs),
(DOF_SECT_PROFFS, 4, 4, vec![0; 4 * self.sites.len()]),
];
let prenoffs = if self.tests.is_empty() { 0 } else { sections.len() as u32 + 1 };
if !self.tests.is_empty() {
sections.push((DOF_SECT_PRENOFFS, 4, 4, vec![0; 4 * self.tests.len()]));
}
let reltab = sections.len() as u32 + 2;
let mut record = Vec::new();
put_u32s(&mut record, &[0, 1, 2, 3, name]);
put_u32s(&mut record, attributes);
put_u32s(&mut record, &[prenoffs]);
sections.push((DOF_SECT_PROVIDER, 4, 0, record));
sections.push((DOF_SECT_RELTAB, 8, DOF_RELO_SIZE as u32, self.relocs));
let mut urelhdr = Vec::new();
put_u32s(&mut urelhdr, &[0, reltab, 1]);
sections.push((DOF_SECT_URELHDR, 4, 0, urelhdr));
sections
}
}
/// Lays a DOF out: its header, a header for the string table and each
/// of `sections`, their data each at its alignment, and `strtab` right
/// after. Returns the bytes and where each section's data is, by type.
fn lay_out_dof(
sections: Vec<(u32, u32, u32, Vec<u8>)>,
strtab: &[u8],
) -> (Vec<u8>, Vec<(u32, u32)>) {
let nsec = sections.len() + 1;
let start = DOF_HDR_SIZE + DOF_SEC_SIZE * nsec;
let mut data: Vec<u8> = Vec::new();
let mut headers = Vec::new();
for (ty, align, entsize, bytes) in sections {
data.resize(crate::util::align_to(data.len() as u64, align as u64) as usize, 0);
headers.push((ty, align, entsize, (start + data.len()) as u64, bytes.len() as u64));
data.extend_from_slice(&bytes);
}
let strtab_off = (start + data.len()) as u64;
headers.insert(0, (DOF_SECT_STRTAB, 1, 0, strtab_off, strtab.len() as u64));
let total = strtab_off + strtab.len() as u64;
let mut out = b"\x7fDOF".to_vec();
// LP64, little-endian, DOF version 3, DIF version 2, 8 integer and
// 8 tuple registers.
out.extend_from_slice(&[2, 1, 3, 2, 8, 8, 0, 0, 0, 0, 0, 0]);
put_u32s(&mut out, &[0, DOF_HDR_SIZE as u32, DOF_SEC_SIZE as u32, nsec as u32]);
put_u64s(&mut out, &[DOF_HDR_SIZE as u64, total, total, 0]);
for &(ty, align, entsize, off, size) in &headers {
put_u32s(&mut out, &[ty, align, 1, entsize]);
put_u64s(&mut out, &[off, size]);
}
out.extend_from_slice(&data);
out.extend_from_slice(strtab);
(out, headers.iter().map(|h| (h.0, h.3 as u32)).collect())
}
fn put_u32s(out: &mut Vec<u8>, vals: &[u32]) {
for &v in vals {
push32(out, v);
}
}
fn put_u64s(out: &mut Vec<u8>, vals: &[u64]) {
for &v in vals {
push64(out, v);
}
}
#[cfg(test)]
mod tests {
use super::*;
const STAB: &[u8] = b"___dtrace_stability$stab$v1$5_5_4_1_1_0_1_1_0_5_6_5_7_3_2";
const TYPEDEFS: &[u8] = b"___dtrace_typedefs$stab$v2";
/// Names as the symbols spell them.
fn names<'a>(names: &[&'a str]) -> Vec<&'a [u8]> {
names.iter().map(|name| name.as_bytes()).collect()
}
fn bytes(hex: &[&str]) -> Vec<u8> {
let hex = hex.concat();
(0..hex.len()).step_by(2).map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap()).collect()
}
#[test]
fn symbol_kinds_and_providers() {
assert_eq!(site_kind_of(b"___dtrace_probe$p$x$v1"), Some(SiteKind::Probe));
assert_eq!(site_kind_of(b"___dtrace_isenabled$p$x$v1"), Some(SiteKind::IsEnabled));
assert_eq!(site_kind_of(b"___dtrace_probe"), None);
assert_eq!(provider_of(b"___dtrace_probe$myapp$x$v1"), b"myapp");
assert_eq!(provider_of(b"___dtrace_isenabled$myapp$x$v1"), b"myapp");
assert_eq!(provider_of(STAB), b"stab");
assert_eq!(provider_of(b"___dtrace_probe$noprovider"), b"");
assert_eq!(provider_of(b"___dtrace_foo"), b"");
assert!(is_dtrace_symbol(b"___dtrace_"));
assert!(!is_dtrace_symbol(b"___dtrace"));
assert!(!is_dtrace_symbol(b"__dtrace_x"));
}
/// A probe with an argument, its provider's attributes set: 7
/// sections, its one site's slot at 0x154.
#[test]
fn dof_of_one_site() {
let probe = names(&["___dtrace_probe$stab$x$v1$696e74"]);
let dof = build_dof(b"stab", Some(STAB), Some(TYPEDEFS), &probe, &[b"_main"]);
let dof = dof.unwrap();
let expected = bytes(&[
"7f444f4602010302080800000000000000000000400000002000000007000000",
"4000000000000000c101000000000000c1010000000000000000000000000000",
"08000000010000000100000000000000ac010000000000001500000000000000",
"1000000008000000010000003000000020010000000000003000000000000000",
"1100000001000000010000000100000050010000000000000100000000000000",
"1200000004000000010000000400000054010000000000000400000000000000",
"0f00000004000000010000000000000058010000000000002c00000000000000",
"0a00000008000000010000001800000088010000000000001800000000000000",
"0c000000040000000100000000000000a0010000000000000c00000000000000",
"00000000000000000b0000000100000003000000070000000000000000000000",
"0101010000000000000000000000000000000000000000000000000001000000",
"0200000003000000100000000004050500000101000001010005060500020307",
"00000000000000000b0000000100000000000000000000000000000000000000",
"000000000500000001000000007800696e7400696e74006d61696e0073746162",
"00",
]);
assert_eq!(dof.bytes, expected);
assert_eq!(dof.slots, [0x154]);
}
/// Probes of a function, one with an is-enabled test too: 8
/// sections, the probes in the order of their first sites, the tests'
/// slots after the sites'.
#[test]
fn dof_with_is_enabled_tests() {
let stability = b"___dtrace_stability$myapp$v1$1_1_0_1_1_0_1_1_0_1_1_0_1_1_0";
let probes = [
"___dtrace_probe$myapp$request__start$v1$696e74$63686172202a",
"___dtrace_probe$myapp$request__done$v1$696e74",
"___dtrace_probe$myapp$noargs$v1",
"___dtrace_isenabled$myapp$request__done$v1",
];
let dof = build_dof(b"myapp", Some(stability), None, &names(&probes), &[&b"_main"[..]; 4]);
let dof = dof.unwrap();
let expected = bytes(&[
"7f444f4602010302080800000000000000000000400000002000000008000000",
"4000000000000000ba02000000000000ba020000000000000000000000000000",
"0800000001000000010000000000000064020000000000005600000000000000",
"1000000008000000010000003000000040010000000000009000000000000000",
"11000000010000000100000001000000d0010000000000000300000000000000",
"12000000040000000100000004000000d4010000000000000c00000000000000",
"1a000000040000000100000004000000e0010000000000000400000000000000",
"0f000000040000000100000000000000e4010000000000002c00000000000000",
"0a00000008000000010000001800000010020000000000004800000000000000",
"0c00000004000000010000000000000058020000000000000c00000000000000",
"000000000000000025000000010000000f0000001a0000000000000000000000",
"0202010000000000000000000000000000000000000000003f0000002a000000",
"370000003b000000020000000100000001010100000000000100000000000000",
"00000000000000004b000000440000004b0000004b0000000300000002000000",
"0000010001000000000000000000000000010000000000000000000000000000",
"0000000000000000010000000200000003000000500000000000010100000101",
"0000010100000101000001010400000025000000010000000000000000000000",
"00000000000000003f0000000100000030000000000000000000000000000000",
"4b00000001000000600000000000000000000000000000000000000006000000",
"0100000000726571756573742d737461727400696e740063686172202a00696e",
"740063686172202a006d61696e00726571756573742d646f6e6500696e740069",
"6e74006d61696e006e6f61726773006d61696e006d7961707000",
]);
assert_eq!(dof.bytes, expected);
assert_eq!(dof.slots, [0x1d4, 0x1d8, 0x1dc, 0x1e0]);
}
/// A probe's arguments as dtrace -h spells them, but the provider's
/// typedefs, which are ints.
#[test]
fn arg_types() {
let user = [b"myint_t".to_vec(), b"foo_t".to_vec()];
let cases = [
("unsigned long int", "unsigned long int"),
("const char *", "const char *"),
("myint_t", "int"),
("const myint_t **", "const int **"),
("myint_t2 *", "myint_t2 *"),
("void (*)(foo_t)", "void (*)(int)"),
];
for (spelled, name) in cases {
assert_eq!(arg_type(spelled.as_bytes(), &user), name.as_bytes(), "{spelled}");
}
}
/// A probe's instances come in the order of their first sites, one
/// per function, a name longer than 127 bytes cut.
#[test]
fn instances() {
let long = format!("_{}", "x".repeat(130));
let probes = [&b"___dtrace_probe$stab$x$v1"[..]; 5];
let functions = names(&["_fa", "_fb", "_fa", &long, &long]);
let probes = register(&probes, &functions, &[]).unwrap();
let names: Vec<&[u8]> = probes[0].instances.iter().map(|i| &i.function[..]).collect();
let cut = &long.as_bytes()[1..128];
assert_eq!(names, [b"fa".as_slice(), b"fb".as_slice(), cut]);
assert_eq!(probes[0].instances[0].sites, [0, 2]);
assert_eq!(probes[0].instances[2].sites, [3, 4]);
}
#[test]
fn section_names() {
let providers = [
"longprovi",
"longprovidera",
"longprov",
"longprovid",
"longproviderc",
"longproviderb",
];
let mut taken: Vec<Vec<u8>> = Vec::new();
for p in providers {
let name = section_name(p.as_bytes(), &taken);
taken.push(name);
}
let names: Vec<&str> = taken.iter().map(|n| std::str::from_utf8(n).unwrap()).collect();
let expected = [
"__dof_longprovi",
"__dof_longprov0",
"__dof_longprov",
"__dof_longprov1",
"__dof_longprov2",
"__dof_longprov3",
];
assert_eq!(names, expected);
}
/// A provider without a stability symbol has D's default attributes;
/// a probe that only is-enabled tests name has no arguments; a
/// probe of another encoding than v1 can't be read.
#[test]
fn defaults_and_refusals() {
assert_eq!(attributes(None), DEFAULT_ATTRIBUTES);
assert_eq!(attributes(Some(b"___dtrace_stability$stab$v1$1_1_0")), DEFAULT_ATTRIBUTES);
let stab = [0x0505_0400, 0x0101_0000, 0x0101_0000, 0x0506_0500, 0x0703_0200];
assert_eq!(attributes(Some(STAB)), stab);
let test: [&[u8]; 1] = [b"___dtrace_isenabled$stab$a__b$v1"];
let probes = register(&test, &[b"_main"], &[]).unwrap();
assert_eq!(probes[0].name, b"a-b");
assert!(probes[0].args.is_none());
assert_eq!(probes[0].instances[0].tests, [0]);
assert!(build_dof(b"stab", None, None, &test, &[b"_main"]).is_ok());
let v2: [&[u8]; 1] = [b"___dtrace_probe$stab$x$v2$696e74"];
let err = build_dof(b"stab", Some(STAB), Some(TYPEDEFS), &v2, &[b"_main"]).unwrap_err();
assert_eq!(err, v2[0]);
}
}
+322
View File
@@ -0,0 +1,322 @@
//! The little DWARF reading the linker needs: the name and compilation
//! directory of an object's compile unit, for the N_SO debug-note stabs
//! ld64 writes ahead of the N_OSO that points debuggers at the object.
use crate::macho::MachSection;
const DW_TAG_COMPILE_UNIT: u64 = 0x11;
const DW_AT_NAME: u64 = 0x03;
const DW_AT_COMP_DIR: u64 = 0x1b;
const DW_AT_STR_OFFSETS_BASE: u64 = 0x72;
const DW_FORM_ADDR: u64 = 0x01;
const DW_FORM_BLOCK2: u64 = 0x03;
const DW_FORM_BLOCK4: u64 = 0x04;
const DW_FORM_DATA2: u64 = 0x05;
const DW_FORM_DATA4: u64 = 0x06;
const DW_FORM_DATA8: u64 = 0x07;
const DW_FORM_STRING: u64 = 0x08;
const DW_FORM_BLOCK: u64 = 0x09;
const DW_FORM_BLOCK1: u64 = 0x0a;
const DW_FORM_DATA1: u64 = 0x0b;
const DW_FORM_FLAG: u64 = 0x0c;
const DW_FORM_SDATA: u64 = 0x0d;
const DW_FORM_STRP: u64 = 0x0e;
const DW_FORM_UDATA: u64 = 0x0f;
const DW_FORM_REF_ADDR: u64 = 0x10;
const DW_FORM_REF1: u64 = 0x11;
const DW_FORM_REF2: u64 = 0x12;
const DW_FORM_REF4: u64 = 0x13;
const DW_FORM_REF8: u64 = 0x14;
const DW_FORM_REF_UDATA: u64 = 0x15;
const DW_FORM_INDIRECT: u64 = 0x16;
const DW_FORM_SEC_OFFSET: u64 = 0x17;
const DW_FORM_EXPRLOC: u64 = 0x18;
const DW_FORM_FLAG_PRESENT: u64 = 0x19;
const DW_FORM_STRX: u64 = 0x1a;
const DW_FORM_ADDRX: u64 = 0x1b;
const DW_FORM_REF_SUP4: u64 = 0x1c;
const DW_FORM_STRP_SUP: u64 = 0x1d;
const DW_FORM_DATA16: u64 = 0x1e;
const DW_FORM_LINE_STRP: u64 = 0x1f;
const DW_FORM_REF_SIG8: u64 = 0x20;
const DW_FORM_IMPLICIT_CONST: u64 = 0x21;
const DW_FORM_LOCLISTX: u64 = 0x22;
const DW_FORM_RNGLISTX: u64 = 0x23;
const DW_FORM_REF_SUP8: u64 = 0x24;
const DW_FORM_STRX1: u64 = 0x25;
const DW_FORM_STRX2: u64 = 0x26;
const DW_FORM_STRX3: u64 = 0x27;
const DW_FORM_STRX4: u64 = 0x28;
const DW_FORM_ADDRX1: u64 = 0x29;
const DW_FORM_ADDRX2: u64 = 0x2a;
const DW_FORM_ADDRX3: u64 = 0x2b;
const DW_FORM_ADDRX4: u64 = 0x2c;
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
/// The next `n` bytes, which the reader moves past.
fn take(&mut self, n: usize) -> Option<&'a [u8]> {
let bytes = self.data.get(self.pos..self.pos.checked_add(n)?)?;
self.pos += n;
Some(bytes)
}
fn u8(&mut self) -> Option<u8> {
Some(self.take(1)?[0])
}
fn u16(&mut self) -> Option<u16> {
Some(u16::from_le_bytes(self.take(2)?.try_into().unwrap()))
}
fn u32(&mut self) -> Option<u32> {
Some(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
fn u64(&mut self) -> Option<u64> {
Some(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
}
fn uleb(&mut self) -> Option<u64> {
let mut val = 0u64;
let mut shift = 0;
loop {
let b = self.u8()?;
if shift < 64 {
val |= ((b & 0x7f) as u64) << shift;
}
shift += 7;
if b & 0x80 == 0 {
return Some(val);
}
}
}
fn sleb(&mut self) -> Option<i64> {
let mut val = 0i64;
let mut shift = 0;
loop {
let b = self.u8()?;
if shift < 64 {
val |= ((b & 0x7f) as i64) << shift;
}
shift += 7;
if b & 0x80 == 0 {
if shift < 64 && b & 0x40 != 0 {
val |= -1i64 << shift;
}
return Some(val);
}
}
}
fn skip(&mut self, n: usize) -> Option<()> {
self.take(n).map(|_| ())
}
fn cstr(&mut self) -> Option<&'a [u8]> {
let rest = self.data.get(self.pos..)?;
let n = rest.iter().position(|&b| b == 0)?;
self.pos += n + 1;
Some(&rest[..n])
}
}
/// The NUL-terminated string at `off` in a string section.
fn cstr_at(data: &[u8], off: usize) -> Option<&[u8]> {
Reader { data, pos: off }.cstr()
}
/// What an attribute's value came as: a string, a string-section
/// offset, or a string-offsets-table index.
enum Value<'a> {
Inline(&'a [u8]),
Strp(u64),
LineStrp(u64),
Strx(u64),
Other(u64),
}
/// Reads one attribute value of the given form, returning it when it
/// can be a string.
fn read_form<'a>(
r: &mut Reader<'a>,
form: u64,
addr_size: usize,
implicit: i64,
) -> Option<Value<'a>> {
Some(match form {
DW_FORM_ADDR => Value::Other(match addr_size {
4 => r.u32()? as u64,
_ => r.u64()?,
}),
DW_FORM_BLOCK2 => {
let n = r.u16()? as usize;
r.skip(n)?;
Value::Other(0)
}
DW_FORM_BLOCK4 => {
let n = r.u32()? as usize;
r.skip(n)?;
Value::Other(0)
}
DW_FORM_DATA2 | DW_FORM_REF2 => Value::Other(r.u16()? as u64),
DW_FORM_DATA4 | DW_FORM_REF4 | DW_FORM_REF_ADDR | DW_FORM_SEC_OFFSET | DW_FORM_REF_SUP4
| DW_FORM_STRP_SUP => Value::Other(r.u32()? as u64),
DW_FORM_DATA8 | DW_FORM_REF8 | DW_FORM_REF_SIG8 | DW_FORM_REF_SUP8 => {
Value::Other(r.u64()?)
}
DW_FORM_STRING => Value::Inline(r.cstr()?),
DW_FORM_BLOCK | DW_FORM_EXPRLOC => {
let n = r.uleb()? as usize;
r.skip(n)?;
Value::Other(0)
}
DW_FORM_BLOCK1 => {
let n = r.u8()? as usize;
r.skip(n)?;
Value::Other(0)
}
DW_FORM_DATA1 | DW_FORM_REF1 | DW_FORM_FLAG => Value::Other(r.u8()? as u64),
DW_FORM_SDATA => Value::Other(r.sleb()? as u64),
DW_FORM_STRP => Value::Strp(r.u32()? as u64),
DW_FORM_LINE_STRP => Value::LineStrp(r.u32()? as u64),
DW_FORM_UDATA | DW_FORM_REF_UDATA | DW_FORM_ADDRX | DW_FORM_LOCLISTX | DW_FORM_RNGLISTX => {
Value::Other(r.uleb()?)
}
DW_FORM_INDIRECT => {
let actual = r.uleb()?;
return read_form(r, actual, addr_size, implicit);
}
DW_FORM_FLAG_PRESENT => Value::Other(1),
DW_FORM_STRX => Value::Strx(r.uleb()?),
DW_FORM_STRX1 => Value::Strx(r.u8()? as u64),
DW_FORM_STRX2 => Value::Strx(r.u16()? as u64),
DW_FORM_STRX3 => {
let b = [r.u8()?, r.u8()?, r.u8()?, 0];
Value::Strx(u32::from_le_bytes(b) as u64)
}
DW_FORM_STRX4 => Value::Strx(r.u32()? as u64),
DW_FORM_ADDRX1 => Value::Other(r.u8()? as u64),
DW_FORM_ADDRX2 => Value::Other(r.u16()? as u64),
DW_FORM_ADDRX3 => {
r.skip(3)?;
Value::Other(0)
}
DW_FORM_ADDRX4 => Value::Other(r.u32()? as u64),
DW_FORM_DATA16 => {
r.skip(16)?;
Value::Other(0)
}
DW_FORM_IMPLICIT_CONST => Value::Other(implicit as u64),
_ => return None,
})
}
/// Finds the abbreviation numbered `code` in an abbreviation table and
/// returns its tag and a reader at its attribute specifications (mold's
/// find_abbrev).
fn find_abbrev(table: &[u8], code: u64) -> Option<(u64, Reader<'_>)> {
let mut r = Reader { data: table, pos: 0 };
loop {
let c = r.uleb()?;
if c == 0 {
return None;
}
let tag = r.uleb()?;
let _children = r.u8()?;
if c == code {
return Some((tag, r));
}
// Skip the attribute specifications of another abbreviation.
loop {
let attr = r.uleb()?;
let form = r.uleb()?;
if attr == 0 && form == 0 {
break;
}
if form == DW_FORM_IMPLICIT_CONST {
r.sleb()?;
}
}
}
}
/// The compilation directory and source file name of an object's first
/// compile unit (DW_AT_comp_dir and DW_AT_name of the DW_TAG_compile_unit
/// DIE), read from its __DWARF sections. DWARF versions 2 through 5;
/// strings inline, in __debug_str or __debug_line_str, or indexed
/// through __debug_str_offsets.
pub fn compile_unit_name(file: &[u8], sects: &[MachSection]) -> Option<(Vec<u8>, Vec<u8>)> {
let section = |name: &[u8]| -> Option<&[u8]> {
let s = sects.iter().find(|s| s.segname() == b"__DWARF" && s.sectname() == name)?;
file.get(s.offset as usize..(s.offset as u64 + s.size) as usize)
};
let info = section(b"__debug_info")?;
let abbrev_sect = section(b"__debug_abbrev")?;
let str_sect = section(b"__debug_str").unwrap_or(&[]);
let line_str_sect = section(b"__debug_line_str").unwrap_or(&[]);
// Mach-O section names are 16 bytes: "__debug_str_offsets" is truncated.
let str_offsets_sect = section(b"__debug_str_offs").unwrap_or(&[]);
let mut r = Reader { data: info, pos: 0 };
let length = r.u32()?;
if length == 0xffff_ffff {
return None; // 64-bit DWARF: not produced by Apple's compilers
}
let version = r.u16()?;
let (abbrev_off, addr_size) = if version >= 5 {
let _unit_type = r.u8()?;
let addr_size = r.u8()?;
(r.u32()? as usize, addr_size as usize)
} else {
let abbrev_off = r.u32()? as usize;
(abbrev_off, r.u8()? as usize)
};
// The first DIE, read by its abbreviation's attribute specifications.
let (tag, mut specs) = find_abbrev(abbrev_sect.get(abbrev_off..)?, r.uleb()?)?;
if tag != DW_TAG_COMPILE_UNIT {
return None;
}
let mut name = None;
let mut comp_dir = None;
let mut str_offsets_base = None;
loop {
let attr = specs.uleb()?;
let form = specs.uleb()?;
if attr == 0 && form == 0 {
break;
}
let implicit = if form == DW_FORM_IMPLICIT_CONST { specs.sleb()? } else { 0 };
let val = read_form(&mut r, form, addr_size, implicit)?;
match attr {
DW_AT_NAME => name = Some(val),
DW_AT_COMP_DIR => comp_dir = Some(val),
DW_AT_STR_OFFSETS_BASE => {
if let Value::Other(v) = val {
str_offsets_base = Some(v as usize);
}
}
_ => {}
}
}
let resolve = |v: Value| -> Option<Vec<u8>> {
let bytes = match v {
Value::Inline(b) => b,
Value::Strp(off) => cstr_at(str_sect, off as usize)?,
Value::LineStrp(off) => cstr_at(line_str_sect, off as usize)?,
Value::Strx(idx) => {
// The table's header is 8 bytes; a unit's base normally
// points just past it.
let base = str_offsets_base.unwrap_or(8);
let at = base + idx as usize * 4;
let off = u32::from_le_bytes(str_offsets_sect.get(at..at + 4)?.try_into().unwrap());
cstr_at(str_sect, off as usize)?
}
Value::Other(_) => return None,
};
Some(bytes.to_vec())
};
let name = name.and_then(resolve)?;
let comp_dir = comp_dir.and_then(resolve).unwrap_or_default();
Some((comp_dir, name))
}
+314
View File
@@ -0,0 +1,314 @@
//! Error, warning and fatal-error reporting.
//!
//! Errors don't abort the link immediately: the linker keeps going so that
//! all problems are reported in one run, then exits before writing the
//! output. Fatal errors are for conditions the linker can't continue past.
use std::fmt;
use std::io::{self, Write};
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
/// Whether to demangle symbol names in diagnostics. This is process-wide
/// state so that `Display` implementations, which have no access to the
/// linker context, can consult it.
static DEMANGLE: AtomicBool = AtomicBool::new(true);
pub fn set_demangle(enabled: bool) {
DEMANGLE.store(enabled, Ordering::Relaxed);
}
pub fn demangle_enabled() -> bool {
DEMANGLE.load(Ordering::Relaxed)
}
// Diagnostic settings, error state and output serialization are process-wide.
static COLOR: AtomicBool = AtomicBool::new(false);
static FATAL_WARNINGS: AtomicBool = AtomicBool::new(false);
static SUPPRESS_WARNINGS: AtomicBool = AtomicBool::new(false);
static HAS_ERROR: AtomicBool = AtomicBool::new(false);
static OUTPUT_LOCK: Mutex<()> = Mutex::new(());
pub fn set_color(on: bool) {
COLOR.store(on, Ordering::Relaxed);
}
pub fn set_fatal_warnings(on: bool) {
FATAL_WARNINGS.store(on, Ordering::Relaxed);
}
pub fn set_suppress_warnings(on: bool) {
SUPPRESS_WARNINGS.store(on, Ordering::Relaxed);
}
/// The messages given from the worker threads of a parallel pass, which
/// would come out in whatever order the threads happened to run. They
/// wait here and come out sorted, the same in every run, before the
/// next message the link's own thread gives, or as the link ends.
static PARALLEL: Mutex<Vec<Message>> = Mutex::new(Vec::new());
// Format each message before taking the lock so diagnostics from different
// threads cannot interleave.
fn emit(prefix_mono: &str, prefix_color: &str, msg: fmt::Arguments) {
let prefix = if COLOR.load(Ordering::Relaxed) { prefix_color } else { prefix_mono };
let text = render(format_args!("{prefix}{msg}\n"));
if rayon::current_thread_index().is_some() {
PARALLEL.lock().unwrap_or_else(|e| e.into_inner()).push(text);
return;
}
release_parallel();
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let _ = io::stderr().write_all(&text);
}
/// Gives the messages of parallel passes held back so far, sorted.
fn release_parallel() {
let mut msgs = std::mem::take(&mut *PARALLEL.lock().unwrap_or_else(|e| e.into_inner()));
if msgs.is_empty() {
return;
}
msgs.sort_unstable();
let _guard = OUTPUT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
for text in msgs {
let _ = io::stderr().write_all(&text);
}
}
/// Reports an unrecoverable error and exits.
pub fn fatal(msg: fmt::Arguments) -> ! {
emit("mold: fatal: ", "mold: \x1b[0;1;31mfatal:\x1b[0m ", msg);
exit_after_cleanup(1);
}
/// Reports an error.
pub fn error(msg: fmt::Arguments) {
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
HAS_ERROR.store(true, Ordering::Relaxed);
}
/// Reports a warning, or holds it back (see hold_warnings). With -w it
/// is dropped; with -fatal_warnings it is promoted to an error.
pub fn warn(msg: fmt::Arguments) {
if let Some(held) = HELD.lock().unwrap_or_else(|e| e.into_inner()).as_mut() {
held.push(render(msg));
return;
}
if SUPPRESS_WARNINGS.load(Ordering::Relaxed) {
return;
}
if FATAL_WARNINGS.load(Ordering::Relaxed) {
emit("mold: error: ", "mold: \x1b[0;1;31merror:\x1b[0m ", msg);
HAS_ERROR.store(true, Ordering::Relaxed);
} else {
emit("mold: warning: ", "mold: \x1b[0;1;35mwarning:\x1b[0m ", msg);
}
}
/// The warnings held back while the options are read (Some while they
/// are), until the parse is known to be for the target: the driver
/// parses once per speculated target, and a warning is given once.
static HELD: Mutex<Option<Vec<Message>>> = Mutex::new(None);
/// Holds back the warnings from here on (see HELD).
pub fn hold_warnings() {
*HELD.lock().unwrap_or_else(|e| e.into_inner()) = Some(Vec::new());
}
/// Gives the warnings held back, and holds back no more.
pub fn release_held() {
let held = HELD.lock().unwrap_or_else(|e| e.into_inner()).take();
for msg in held.into_iter().flatten() {
warn(format_args!("{}", raw(&msg)));
}
}
/// Forgets the warnings held back, of options read for another target
/// that are read again, and holds back no more.
pub fn drop_held() {
*HELD.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
/// Prints a message with no prefix: some reports of what the link did
/// (-why_live, -why_load, the text relocations) come in lines of their
/// own.
pub fn notice(msg: fmt::Arguments) {
emit("", "", msg);
}
/// Returns the text that C's strerror gives for an I/O error. Unlike
/// `io::Error`'s `Display`, it doesn't append " (os error N)". An error that
/// doesn't come from the OS is formatted as usual.
pub fn strerror(err: &io::Error) -> String {
if let Some(errno) = err.raw_os_error() {
let mut buf = [0u8; 256];
// SAFETY: strerror_r writes at most `buf.len()` bytes to `buf`.
let ret = unsafe { libc::strerror_r(errno, buf.as_mut_ptr().cast(), buf.len()) };
if ret == 0
&& let Ok(msg) = std::ffi::CStr::from_bytes_until_nul(&buf)
{
return msg.to_string_lossy().into_owned();
}
}
err.to_string()
}
/// Exits with a failure status if any error has been reported, giving
/// the messages of the parallel passes before it first.
pub fn checkpoint() {
release_parallel();
if HAS_ERROR.load(Ordering::Relaxed) {
exit_after_cleanup(1);
}
}
/// Removes a partially-written output file, then terminates the process
/// without running destructors. Input files are mapped for the process's
/// lifetime, so there is nothing else to release.
pub fn exit_after_cleanup(status: i32) -> ! {
release_parallel();
crate::output_file::cleanup();
let _ = io::stdout().flush();
let _ = io::stderr().flush();
// SAFETY: `_exit` only terminates the process.
unsafe { libc::_exit(status) }
}
/// Makes a panic remove a partially-written output file, as a fatal error
/// does, before the default hook reports the panic. Nothing recovers from
/// a panic, so it always terminates the process.
pub fn install_panic_hook() {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
crate::output_file::cleanup();
default_hook(info);
}));
}
/// A byte string that a diagnostic prints as it is (see raw).
pub struct Raw<'a>(&'a [u8]);
/// Prints a byte string in a diagnostic as it is, byte for byte: the
/// names the linker reads - a symbol's, a section's, a file's - need
/// not be UTF-8. Formatted anywhere but into a diagnostic (or
/// error::render), each byte that isn't UTF-8 comes out as U+FFFD.
pub fn raw(bytes: &[u8]) -> Raw<'_> {
Raw(bytes)
}
/// An owned byte string a diagnostic prints as it is (see raw): a
/// file's name made up for the diagnostic, say.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct RawBuf(pub Vec<u8>);
impl From<&str> for RawBuf {
fn from(s: &str) -> Self {
Self(s.as_bytes().to_vec())
}
}
impl From<&std::path::Path> for RawBuf {
fn from(path: &std::path::Path) -> Self {
Self(std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str()).to_vec())
}
}
impl fmt::Display for RawBuf {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
raw(&self.0).fmt(f)
}
}
/// Paths a diagnostic prints as they are, byte for byte (see raw):
/// path.raw() in place of Path::display, which writes a U+FFFD for each
/// byte that isn't UTF-8.
pub trait RawPath {
fn raw(&self) -> Raw<'_>;
}
impl RawPath for std::path::Path {
fn raw(&self) -> Raw<'_> {
raw(std::os::unix::ffi::OsStrExt::as_bytes(self.as_os_str()))
}
}
impl RawPath for std::ffi::OsStr {
fn raw(&self) -> Raw<'_> {
raw(std::os::unix::ffi::OsStrExt::as_bytes(self))
}
}
impl fmt::Display for Raw<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for chunk in self.0.utf8_chunks() {
f.write_str(chunk.valid())?;
for &byte in chunk.invalid() {
f.write_str(byte_mark(byte))?;
}
}
Ok(())
}
}
/// A U+FFFD for each byte from 0x80 up, the bytes that can be invalid
/// in UTF-8: a Raw writes such a byte as the copy for it, which render
/// knows by its address and writes the byte for. (No character could
/// mark the byte in the text: a name may hold any.)
static BYTE_MARKS: [u8; 3 * 128] = {
let mut marks = [0; 3 * 128];
let mut i = 0;
while i < marks.len() {
(marks[i], marks[i + 1], marks[i + 2]) = (0xef, 0xbf, 0xbd);
i += 3;
}
marks
};
fn byte_mark(byte: u8) -> &'static str {
let i = usize::from(byte - 0x80) * 3;
std::str::from_utf8(&BYTE_MARKS[i..i + 3]).unwrap()
}
/// A diagnostic as the bytes to print.
pub type Message = Vec<u8>;
/// Formats a message into the bytes to print, with the bytes a Raw
/// marked put back.
pub(crate) fn render(msg: fmt::Arguments) -> Message {
struct Sink(Message);
impl fmt::Write for Sink {
fn write_str(&mut self, s: &str) -> fmt::Result {
let marks = BYTE_MARKS.as_ptr_range();
if marks.contains(&s.as_ptr()) {
let offset = s.as_ptr() as usize - marks.start as usize;
self.0.push(0x80 + (offset / 3) as u8);
} else {
self.0.extend_from_slice(s.as_bytes());
}
Ok(())
}
}
let mut sink = Sink(Vec::new());
let _ = fmt::write(&mut sink, msg);
sink.0
}
#[macro_export]
macro_rules! fatal {
($($arg:tt)*) => {
$crate::error::fatal(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! error {
($($arg:tt)*) => {
$crate::error::error(format_args!($($arg)*))
};
}
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => {
$crate::error::warn(format_args!($($arg)*))
};
}
+203
View File
@@ -0,0 +1,203 @@
//! Input file type detection, and the architectures of thin and
//! universal (fat) files.
use crate::arch::Target;
use crate::macho::*;
use crate::mapped_file::MappedFile;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FileType {
Unknown,
Empty,
Object,
Dylib,
Archive,
/// A TAPI text-based dylib stub (.tbd), a YAML description of a dylib
/// that ships in SDKs in place of the binary.
Tapi,
Fat,
/// An LLVM bitcode file, produced by -flto; compiled by libLTO at
/// link time.
LlvmBitcode,
}
/// The mach header of a 64-bit Mach-O file, or `None` if `data` is not
/// one.
fn macho_header(data: &[u8]) -> Option<MachHeader> {
if data.len() < size_of::<MachHeader>() {
return None;
}
let hdr = MachHeader::read_from(data);
(hdr.magic == MH_MAGIC_64).then_some(hdr)
}
/// Returns the target name of a Mach-O file, or `None` if it is not a
/// 64-bit Mach-O file for a CPU type we recognize.
pub fn get_macho_target(data: &[u8]) -> Option<&'static str> {
crate::arch::cputype_name(macho_header(data)?.cputype)
}
/// Returns the file type (MH_EXECUTE, MH_BUNDLE, ...) of a 64-bit
/// Mach-O file, or `None` if it is not one.
pub fn get_macho_filetype(data: &[u8]) -> Option<u32> {
macho_header(data).map(|hdr| hdr.filetype)
}
pub fn get_file_type(mf: &MappedFile) -> FileType {
let data = mf.data();
if data.is_empty() {
return FileType::Empty;
}
if data.len() >= 8 && &data[..8] == b"!<arch>\n" {
return FileType::Archive;
}
if data.starts_with(b"--- !tapi-tbd") || data.starts_with(b"---\narchs:") {
return FileType::Tapi;
}
// TBD version 5 is JSON; the version key may come last in the file
// (Xcode's eager-linking stubs put it there), so a .tbd that
// starts with '{' is taken as one.
if data.starts_with(b"{")
&& (mf.name.extension().is_some_and(|ext| ext == "tbd")
|| data.windows(16).any(|w| w == b"tapi_tbd_version"))
{
return FileType::Tapi;
}
// LLVM bitcode in the wrapper Apple's compilers put it in. ld-prime
// knows no raw bitcode ("BC\xc0\xde"), whatever its target: a file
// of it is of an unknown type, an archive member none it loads.
if data.starts_with(&0x0b17_c0deu32.to_le_bytes()) {
return FileType::LlvmBitcode;
}
if let Some(hdr) = macho_header(data) {
return match hdr.filetype {
MH_OBJECT => FileType::Object,
MH_DYLIB => FileType::Dylib,
_ => FileType::Unknown,
};
}
// A universal file's header is big-endian.
if data.len() >= size_of::<MachHeader>() && data.starts_with(&FAT_MAGIC.to_be_bytes()) {
return FileType::Fat;
}
FileType::Unknown
}
/// An architecture's name, from a Mach-O CPU type and subtype: one of
/// the subtypes of the CPU types mold links for, or "unknown".
fn arch_name(cputype: u32, cpusubtype: u32) -> &'static str {
match (cputype, cpusubtype & !CPU_SUBTYPE_MASK) {
(CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H) => "x86_64h",
(CPU_TYPE_X86_64, _) => "x86_64",
(CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64E) => "arm64e",
(CPU_TYPE_ARM64, _) => "arm64",
_ => "unknown",
}
}
/// Whether a Mach-O file of `filetype` built for `cputype` and
/// `cpusubtype` is one the link takes: an object must be for exactly its
/// architecture, while a dylib serves every link of its CPU type (an
/// arm64e one an arm64 link too).
fn takes_arch<E: Target>(filetype: u32, cputype: u32, cpusubtype: u32) -> bool {
match filetype {
MH_DYLIB => cputype == E::CPUTYPE,
_ => arch_name(cputype, cpusubtype) == E::NAME,
}
}
/// The architecture of a thin object or dylib the link doesn't take
/// (see takes_arch), which ld-prime ignores with a warning.
pub fn foreign_arch<E: Target>(mf: &MappedFile) -> Option<&'static str> {
let hdr = MachHeader::read_from(mf.data());
let takes = takes_arch::<E>(hdr.filetype, hdr.cputype, hdr.cpusubtype);
(!takes).then(|| arch_name(hdr.cputype, hdr.cpusubtype))
}
/// Whether a thin file the link doesn't take is of its CPU type all the
/// same, an x86_64h object in an x86_64 link: -allow_sub_type_mismatches
/// has ld-prime take it, but for arm64e, whose pointers are signed.
pub fn is_subtype_mismatch<E: Target>(mf: &MappedFile) -> bool {
let hdr = MachHeader::read_from(mf.data());
hdr.cputype == E::CPUTYPE && arch_name(hdr.cputype, hdr.cpusubtype) != "arm64e"
}
/// A fat (universal) file's slices: each one's CPU type, subtype, file
/// offset and size. Fat headers are big-endian.
fn fat_arches(mf: &MappedFile) -> impl Iterator<Item = (u32, u32, usize, usize)> + '_ {
let data = mf.data();
let read_be32 = |off: usize| u32::from_be_bytes(data[off..off + 4].try_into().unwrap());
(0..read_be32(4) as usize).map(move |i| {
let off = 8 + i * 20;
(
read_be32(off),
read_be32(off + 4),
read_be32(off + 8) as usize,
read_be32(off + 12) as usize,
)
})
}
/// The architectures a fat file has slices for.
pub fn fat_arch_names(mf: &MappedFile) -> Vec<&'static str> {
fat_arches(mf).map(|(cputype, cpusubtype, _, _)| arch_name(cputype, cpusubtype)).collect()
}
/// The slice of a fat file the link takes (see takes_arch), if any: the
/// one for exactly its architecture first, but for a dylib whose subtype
/// must match (Args::dylib_subtypes_must_match). With
/// -allow_sub_type_mismatches, one of another subtype of its CPU type
/// will do too, as for a thin file (see is_subtype_mismatch).
pub fn fat_slice<E: Target>(
args: &crate::cmdline::Args,
mf: &'static MappedFile,
) -> Option<&'static MappedFile> {
let slices: Vec<_> = fat_arches(mf).collect();
let (_, _, off, size) = slices
.iter()
.find(|&&(cputype, cpusubtype, _, _)| arch_name(cputype, cpusubtype) == E::NAME)
.or_else(|| {
slices.iter().find(|&&(cputype, cpusubtype, off, _)| {
let filetype = MachHeader::read_from(&mf.data()[off..]).filetype;
!args.dylib_subtypes_must_match && takes_arch::<E>(filetype, cputype, cpusubtype)
})
})
.or_else(|| {
slices.iter().find(|&&(cputype, cpusubtype, _, _)| {
args.allow_sub_type_mismatches
&& cputype == E::CPUTYPE
&& arch_name(cputype, cpusubtype) != "arm64e"
})
})
.copied()?;
let mut name = std::ffi::OsString::from(&mf.name);
name.push(format!("(for architecture {})", E::NAME));
Some(mf.slice(name.into(), off, size))
}
/// Splits the name fat_slice gives a fat file's slice into the file's
/// path and the slice's architecture.
pub fn split_fat_arch(name: &[u8]) -> (&[u8], Option<&[u8]>) {
const TAG: &[u8] = b"(for architecture ";
match memchr::memmem::find(name, TAG) {
Some(i) if name.ends_with(b")") => (&name[..i], Some(&name[i + TAG.len()..name.len() - 1])),
_ => (name, None),
}
}
/// A file's name without the "(for architecture ...)" that fat_slice
/// gives a fat file's slice, which ld-prime never shows: it names the
/// slice, and the members of a fat archive, by the file's own path.
pub fn without_fat_arch(name: &[u8]) -> Vec<u8> {
let mut name = name.to_vec();
if let Some(i) = memchr::memmem::find(&name, b"(for architecture")
&& let Some(len) = name[i..].iter().position(|&c| c == b')')
{
name.drain(i..=i + len);
}
name
}
+706
View File
@@ -0,0 +1,706 @@
//! Identical code folding.
//!
//! ld64 deduplicates identical functions (ld-prime at -O1 and up or
//! with -deduplicate, this linker unless -no_deduplicate); mold's ICF
//! does the same for ELF. Two subsections can share one copy when their
//! bytes, relocations and unwind information are all identical, *and*
//! folding cannot be observed. ld-prime folds the functions of
//! __TEXT,__text that no one can compare the addresses of: those the
//! compiler marked .weak_def_can_be_hidden (C++ inline functions with
//! unnamed_addr) that the link hid and Swift functions, whether or not
//! their address is taken, and any other unexported one whose address
//! is never taken - mold's --icf=safe.
//!
//! The algorithm follows mold: every candidate gets a hash of its
//! literal content, and a few refinement rounds rehash each candidate
//! with the previous-round hashes of its relocation targets, so the
//! hash comes to describe the whole reachable shape. Groups with equal
//! final hashes are then verified structurally and folded onto their
//! first member. That folds functions that call each other in a cycle
//! (two instances of a mutually recursive sort) as a group, which
//! ld-prime, folding only callers of functions already found equal,
//! leaves apart; either is correct, and ours is the cheaper to find.
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use portable_atomic::AtomicU64;
use rayon::prelude::*;
use crate::arch::Target;
use crate::context::Context;
use crate::input_files::{FileId, ObjectFile};
use crate::input_sections::{FdeRecord, InputSection, Reloc, RelocTarget, subsec_name_rank};
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::siphash::SipHash13_128;
/// A stable identifier for what a relocation edge points at.
#[derive(Hash, PartialEq, Eq, Clone, Copy)]
enum Edge {
/// A candidate subsection, compared by its evolving hash.
Candidate(usize),
/// Anything else, compared by identity.
Isec(usize, u64),
Sym(usize),
}
/// A 128-bit digest. Ordered so equivalence classes group by sorting.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
struct Digest {
hi: u64,
lo: u64,
}
impl Digest {
#[inline]
fn update(self, hasher: &mut SipHash13_128) {
hasher.update_u64(self.hi);
hasher.update_u64(self.lo);
}
fn from_ne_bytes(bytes: [u8; 16]) -> Self {
Self {
hi: u64::from_ne_bytes(bytes[..8].try_into().unwrap()),
lo: u64::from_ne_bytes(bytes[8..].try_into().unwrap()),
}
}
}
#[inline(always)]
fn finish_digest(hasher: SipHash13_128) -> Digest {
let mut bytes = [0; 16];
hasher.finish(&mut bytes);
Digest::from_ne_bytes(bytes)
}
/// A lock-free digest -> leader map, ported from mold. It counts
/// the distinct digests in a propagation round and, as a side effect,
/// records the lowest-index candidate for each digest as its class
/// leader - so counting classes and electing leaders is one pass, no
/// sort. Slots are stamped with the round they were written in and the
/// table is reused across rounds without clearing (a slot from an
/// earlier round reads as vacant).
struct DigestMap {
round: u64,
mask: usize,
slots: Vec<DigestSlot>,
}
#[derive(Default)]
struct DigestSlot {
hi: AtomicU64,
lo: AtomicU64,
leader: AtomicU32,
}
impl DigestMap {
fn new(n: usize) -> Self {
let len = n.saturating_mul(2).next_power_of_two();
Self { round: 1, mask: len - 1, slots: (0..len).map(|_| DigestSlot::default()).collect() }
}
fn next_round(&mut self) {
self.round += 1;
}
/// Inserts (digest -> candidate), keeping the lowest candidate index
/// as the leader. Returns true if the digest was not already present
/// this round.
fn insert(&self, digest: Digest, cand: u32) -> bool {
const BUSY_BIT: u64 = 1 << 48;
let tag = digest.hi >> 16;
let value = (self.round << 49) | tag;
let mut i = digest.hi as usize & self.mask;
loop {
let slot = &self.slots[i];
let mut x = slot.hi.load(Ordering::Acquire);
while x >> 49 != self.round {
match slot.hi.compare_exchange_weak(
x,
value | BUSY_BIT,
Ordering::Acquire,
Ordering::Relaxed,
) {
Ok(_) => {
slot.lo.store(digest.lo, Ordering::Relaxed);
slot.leader.store(cand, Ordering::Relaxed);
slot.hi.store(value, Ordering::Release);
return true;
}
Err(actual) => x = actual,
}
}
if x & 0xffff_ffff_ffff != tag {
i = (i + 1) & self.mask;
continue;
}
while x & BUSY_BIT != 0 {
std::hint::spin_loop();
x = slot.hi.load(Ordering::Acquire);
}
if slot.lo.load(Ordering::Relaxed) != digest.lo {
i = (i + 1) & self.mask;
continue;
}
// Already present; keep the lowest candidate index as leader.
let mut cur = slot.leader.load(Ordering::Relaxed);
while cand < cur {
match slot.leader.compare_exchange_weak(
cur,
cand,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => break,
Err(actual) => cur = actual,
}
}
return false;
}
}
fn find(&self, digest: Digest) -> u32 {
let value = (self.round << 49) | (digest.hi >> 16);
let mut i = digest.hi as usize & self.mask;
loop {
let slot = &self.slots[i];
if slot.hi.load(Ordering::Relaxed) == value
&& slot.lo.load(Ordering::Relaxed) == digest.lo
{
return slot.leader.load(Ordering::Relaxed);
}
i = (i + 1) & self.mask;
}
}
}
/// Whether each subsection is a function whose address is insignificant
/// by declaration, which ld-prime folds even where the address is taken
/// or exported: one the link auto-hid or a Swift function (see below).
fn insignificant_sections<E: Target>(ctx: &Context<E>) -> Vec<bool> {
let flags: Vec<AtomicBool> = (0..ctx.isecs.len()).map(|_| AtomicBool::new(false)).collect();
ctx.objs.par_iter().enumerate().for_each(|(i, obj)| {
if obj.is_reachable {
mark_auto_hidden(ctx, i, obj, &flags);
mark_swift_functions(ctx, i, obj, &flags);
}
});
flags.into_iter().map(AtomicBool::into_inner).collect()
}
/// Marks the functions of object `i` the link auto-hid: a global, not
/// private extern, whose definition carries N_WEAK_DEF | N_WEAK_REF
/// (.weak_def_can_be_hidden), which clang gives an inline function
/// with unnamed_addr. A private extern one is hidden anyway and is
/// folded as any other hidden function.
fn mark_auto_hidden<E: Target>(ctx: &Context<E>, i: usize, obj: &ObjectFile, flags: &[AtomicBool]) {
let r = obj.global_range();
for (msym, &sym_id) in obj.mach_syms[r.clone()].iter().zip(&obj.symbols[r]) {
let sym = &ctx.symbols[sym_id];
if !msym.is_stab()
&& msym.n_type & N_PEXT == 0
&& msym.desc & (N_WEAK_DEF | N_WEAK_REF) == N_WEAK_DEF | N_WEAK_REF
&& sym.is_private_extern()
&& sym.file() == Some(FileId::Obj(i as u32))
&& let Some(isec) = sym.input_section()
{
flags[isec as usize].store(true, Ordering::Relaxed);
}
}
}
/// Marks the Swift functions of object `i`: those whose subsection
/// ld-prime names (see InputSection::label) by a symbol Swift mangled,
/// "_$s...". Swift promises no function an address of its own, so
/// ld-prime folds one even where its address is taken (a coroutine's
/// resume function, a value witness) or it is exported; it goes by the
/// subsection's name only, so another label at the function's start that
/// outranks the Swift one keeps it apart. An @objc thunk ("...To"), which
/// an Objective-C method list names, is no such function: ld-prime folds
/// one only as it folds a C function (SwiftyMarkdown's unimplemented
/// init()'s thunk stays apart from the identical init() it calls for).
fn mark_swift_functions<E: Target>(
ctx: &Context<E>,
i: usize,
obj: &ObjectFile,
flags: &[AtomicBool],
) {
// Only a function whose address is taken needs to be one; most
// subsections Swift names so are its metadata, of other sections.
let is_swift = |name: &[u8]| name.starts_with(b"_$s") && !name.ends_with(b"To");
let wanted =
|isec: usize| ctx.isecs[isec].is_address_taken() && is_text_function(ctx, &ctx.isecs[isec]);
for (isec, sym) in subsec_names(ctx, i, obj, wanted) {
if is_swift(ctx.symbols[sym].name()) {
flags[isec as usize].store(true, Ordering::Relaxed);
}
}
}
/// The labels at the start of object `i`'s subsections that `wanted`
/// takes, but for an exported one another object's definition won:
/// (subsection, rank of the label as ld-prime picks the one naming the
/// subsection, name, symbol). An alternate entry point (N_ALT_ENTRY)
/// names no subsection but where no other label does. (An object's
/// mach_syms are mostly of undefined symbols, which are passed over
/// before their symbols are looked at.)
fn start_labels<'a, E: Target>(
ctx: &'a Context<E>,
i: usize,
obj: &'a ObjectFile,
wanted: impl Fn(usize) -> bool + 'a,
) -> impl Iterator<Item = (u32, u8, &'static [u8], SymbolId)> + 'a {
obj.mach_syms.iter().zip(&obj.symbols).filter_map(move |(msym, &id)| {
if msym.is_stab() || msym.ty() != N_SECT {
return None;
}
let sym = &ctx.symbols[id];
let isec = sym.input_section()?;
if sym.value != 0 || sym.file() != Some(FileId::Obj(i as u32)) || !wanted(isec as usize) {
return None;
}
let entry = (msym.desc & N_ALT_ENTRY == 0) as u8;
Some((isec, entry << 4 | subsec_name_rank(msym, sym.name()), sym.name(), id))
})
}
/// The symbol naming each of object `i`'s subsections that a label
/// starts (see InputSection::label) and `wanted` takes, by
/// subsection.
fn subsec_names<E: Target>(
ctx: &Context<E>,
i: usize,
obj: &ObjectFile,
wanted: impl Fn(usize) -> bool,
) -> Vec<(u32, SymbolId)> {
let mut labels: Vec<_> = start_labels(ctx, i, obj, wanted).collect();
labels.sort_unstable();
labels.chunk_by(|a, b| a.0 == b.0).map(|run| (run[0].0, run[run.len() - 1].3)).collect()
}
/// The symbols naming the subsections of folded functions: a folded
/// function keeps its name, at the function it folded into. `folded`
/// pairs each folded subsection with the one it folded into.
fn folded_subsec_names<E: Target>(
ctx: &Context<E>,
folded: &[(usize, usize)],
) -> hashbrown::HashSet<SymbolId> {
let mut involved = vec![false; ctx.isecs.len()];
let mut objs = Vec::new();
for &(member, _) in folded {
involved[member] = true;
objs.push(ctx.isecs[member].file);
}
objs.sort_unstable();
objs.dedup();
objs.par_iter()
.flat_map_iter(|&i| subsec_names(ctx, i as usize, &ctx.objs[i as usize], |j| involved[j]))
.collect::<Vec<_>>()
.into_iter()
.map(|(_, id)| id)
.collect()
}
/// Whether a subsection is a function of __TEXT,__text (ld64 folds no
/// other section) in the output.
fn is_text_function<E: Target>(ctx: &Context<E>, isec: &InputSection) -> bool {
let hdr = isec.hdr(&ctx.objs[isec.file as usize]);
isec.is_emitted() && hdr.segname_is(b"__TEXT") && hdr.sectname_is(b"__text")
}
/// Whether each subsection is a function -keep_duplicate or
/// -keep_duplicates_list names, a local one too, or one with a DTrace
/// probe site, which is neither folded nor folded into.
fn kept_sections<E: Target>(ctx: &Context<E>) -> Vec<bool> {
let kept: Vec<AtomicBool> = (0..ctx.isecs.len()).map(|_| AtomicBool::new(false)).collect();
let keep = &ctx.args.keep_duplicates;
if !keep.is_empty() {
ctx.symbols.syms.par_iter().for_each(|sym| {
if matches!(sym.file(), Some(FileId::Obj(_)))
&& let Some(isec) = sym.input_section()
&& keep.find(sym.name()) != -1
{
kept[isec as usize].store(true, Ordering::Relaxed);
}
});
}
let mut kept: Vec<bool> = kept.into_iter().map(AtomicBool::into_inner).collect();
for &isec in ctx.dof_sections.iter().flat_map(|dof| &dof.sites) {
kept[isec as usize] = true;
}
kept
}
/// Candidates: the non-empty functions whose addresses no one can
/// compare, but those -keep_duplicate names.
fn is_eligible<E: Target>(
ctx: &Context<E>,
insignificant: &[bool],
kept: &[bool],
id: usize,
) -> bool {
let isec = &ctx.isecs[id];
is_text_function(ctx, isec)
&& isec.size != 0
&& (insignificant[id] || !isec.is_address_taken())
&& !kept[id]
}
/// -verbose_deduplicate: ld-prime's summary of the functions folded
/// away, out of every function of __TEXT,__text, given when it folds
/// any. `total` is the functions' number and size before folding.
fn report_folds<E: Target>(
ctx: &Context<E>,
candidates: &[usize],
leaders: &[u32],
total: (usize, u64),
) {
let folded = || (0..leaders.len()).filter(|&i| leaders[i] as usize != i);
let count = folded().count();
if count == 0 {
return;
}
let size: u64 = folded().map(|i| ctx.isecs[candidates[i]].size as u64).sum();
let percent = size as f64 * 100.0 / total.1 as f64;
crate::error::notice(format_args!(
"code deduplicated functions {count} (size: {size}) out of total {} (size: {}) ({percent:.2}% size reduction)",
total.0, total.1
));
}
/// What relocation `rel` of object `obj` points at, and its addend. A
/// symbol's offset into a candidate moves into the addend, so that
/// references to the same place in equal candidates compare equal.
fn edge_target<E: Target>(
ctx: &Context<E>,
cand_index: &[usize],
obj: usize,
rel: &Reloc,
) -> (Edge, i64) {
match rel.target() {
RelocTarget::Sym(idx) => {
let sym_id = ctx.objs[obj].symbols[idx as usize];
let sym = &ctx.symbols[sym_id];
if let (Some(FileId::Obj(_)), Some(isec)) = (sym.file(), sym.input_section()) {
let isec = ctx.isecs.resolve(isec as usize);
if cand_index[isec] != usize::MAX {
return (Edge::Candidate(cand_index[isec]), rel.addend + sym.value as i64);
}
return (Edge::Isec(isec, sym.value), rel.addend);
}
(Edge::Sym(sym_id as usize), rel.addend)
}
RelocTarget::Section(isec) => {
let isec = ctx.isecs.resolve(isec as usize);
if cand_index[isec] != usize::MAX {
return (Edge::Candidate(cand_index[isec]), rel.addend);
}
(Edge::Isec(isec, 0), rel.addend)
}
}
}
// A fixed key keeps the link reproducible; the digest is used only to
// group, never emitted.
const KEY: [u8; 16] = *b"mold-macho-icf!!";
/// The base digest of a candidate: its bytes and the non-candidate
/// parts of its edges, hashed with SipHash13-128 exactly as mold's
/// compute_digest does (candidate edges are mixed in during the rounds
/// instead). Every candidate is in __TEXT,__text, so unlike mold's the
/// section flags are left out: ld-prime folds functions whose sections
/// differ only in attributes such as S_ATTR_NO_DEAD_STRIP.
fn compute_digest<E: Target>(ctx: &Context<E>, cand_index: &[usize], id: usize) -> Digest {
let isec = &ctx.isecs[id];
let mut h = SipHash13_128::new(&KEY);
h.update(&isec.size.to_ne_bytes());
h.update(&isec.contents().len().to_ne_bytes());
h.update(isec.contents());
for rel in isec.rels(&ctx.objs[isec.file as usize]) {
h.update(&rel.offset.to_ne_bytes());
h.update(&rel.ty.to_ne_bytes());
h.update(&[rel.size, rel.is_pcrel as u8, rel.is_subtracted as u8]);
let (edge, addend) = edge_target(ctx, cand_index, isec.file as usize, rel);
h.update(&addend.to_ne_bytes());
// A candidate edge contributes nothing to the base; the
// rounds fold in the target's evolving digest.
match edge {
Edge::Candidate(_) => h.update(b"c"),
Edge::Isec(i, v) => {
h.update(b"i");
h.update(&i.to_ne_bytes());
h.update(&v.to_ne_bytes());
}
Edge::Sym(s) => {
h.update(b"s");
h.update(&s.to_ne_bytes());
}
}
}
// A function's unwind information is part of its identity, as mold
// hashes a section's CIEs and FDEs: two functions fold only if they
// are unwound alike, by records at the same offsets with the same
// length, compact encoding, personality, LSDA and DWARF CFI, so that
// the survivor's describes every caller's frames. (ld-prime compares
// only the personality and the LSDA, and folds a function that has
// unwind information into one that has none.) A record with an FDE
// is in DWARF mode whether its object said so or the linker made it.
let recs = isec.unwind_offset as usize..(isec.unwind_offset + isec.nunwind) as usize;
h.update(&isec.nunwind.to_ne_bytes());
for rec in &ctx.unwind_records[recs] {
let encoding = if rec.fde().is_some() { E::UNWIND_MODE_DWARF } else { rec.encoding };
h.update(&rec.input_offset.to_ne_bytes());
h.update(&rec.code_len.to_ne_bytes());
h.update(&encoding.to_ne_bytes());
let personality = rec.function_personality(ctx);
h.update(&personality.map_or(u64::MAX, |p| p as u64).to_ne_bytes());
let (lsda, off) = rec
.function_lsda(ctx)
.map_or((usize::MAX, 0), |(lsda, off)| (ctx.isecs.resolve(lsda), off));
h.update(&lsda.to_ne_bytes());
h.update(&off.to_ne_bytes());
if let Some(fde) = rec.fde() {
hash_dwarf_cfi(ctx, &mut h, &ctx.fdes[fde]);
}
}
finish_digest(h)
}
/// Hashes the CIE and the FDE that unwind a function in DWARF mode, with
/// the fields that depend on where the records or their targets are -
/// the CIE pointer, the function's address and the personality and LSDA
/// pointers, which compute_digest hashes as targets - zeroed. Like mold,
/// it leaves out the length and the trailing DW_CFA_nops, which pad a
/// record to the section's alignment without meaning anything.
fn hash_dwarf_cfi<E: Target>(ctx: &Context<E>, h: &mut SipHash13_128, fde: &FdeRecord) {
let hash_record = |h: &mut SipHash13_128, body: &[u8]| {
let len = body.iter().rposition(|&b| b != 0).map_or(0, |i| i + 1);
h.update(&len.to_ne_bytes());
h.update(&body[..len]);
};
let cie = &ctx.cies[fde.cie as usize];
let mut buf = cie.data.to_vec();
if cie.personality.is_some() {
let pos = cie.personality_offset as usize;
buf[pos..pos + 4].fill(0);
}
hash_record(h, &buf[4..]);
let pc_size = cie.pc_size();
let mut buf = fde.data.to_vec();
buf[4..8 + pc_size].fill(0);
if fde.lsda.is_some() {
let pos = fde.lsda_pos(pc_size);
buf[pos..pos + cie.lsda_size()].fill(0);
}
hash_record(h, &buf[4..]);
}
/// Calls `f` with the candidate index of each candidate that candidate
/// `id` references.
fn for_each_edge<E: Target>(
ctx: &Context<E>,
cand_index: &[usize],
id: usize,
mut f: impl FnMut(u32),
) {
let obj = ctx.isecs[id].file as usize;
for rel in ctx.isecs[id].rels(&ctx.objs[obj]) {
if let Edge::Candidate(c) = edge_target(ctx, cand_index, obj, rel).0 {
f(c as u32);
}
}
}
/// The candidate edges in CSR form: candidate i's edges are
/// values[indices[i]..indices[i + 1]]. The propagation loop is the hot
/// path; a single contiguous array keeps it cache-friendly, where a
/// Vec<Vec<u32>> would chase one heap allocation per candidate.
struct Edges {
values: Vec<u32>,
indices: Vec<u32>,
}
/// Builds the graph whose vertices are the candidates and whose edges
/// are their references to other candidates, as mold's gather_edges.
fn gather_edges<E: Target>(ctx: &Context<E>, cand_index: &[usize], candidates: &[usize]) -> Edges {
// Count the outgoing edges of each vertex and turn the counts into
// starting indices with a prefix sum. The extra entry at the end
// makes indices[i + 1] valid for every vertex.
let mut indices = vec![0u32; candidates.len() + 1];
indices[..candidates.len()]
.par_iter_mut()
.zip(candidates)
.for_each(|(count, &id)| for_each_edge(ctx, cand_index, id, |_| *count += 1));
let mut sum = 0u32;
for count in &mut indices {
let next = sum.checked_add(*count).expect("too many ICF edges");
*count = sum;
sum = next;
}
let mut values = vec![0; *indices.last().unwrap() as usize];
// Split at vertex boundaries so each task owns a disjoint slice of
// the edge array.
rayon::iter::split((0..candidates.len(), values.as_mut_slice()), |(range, out)| {
if range.len() <= 1 {
return ((range, out), None);
}
let mid = range.start + range.len() / 2;
let (left, right) = out.split_at_mut((indices[mid] - indices[range.start]) as usize);
((range.start..mid, left), Some((mid..range.end, right)))
})
.for_each(|(range, out)| {
let mut i = 0;
for vertex in range {
for_each_edge(ctx, cand_index, candidates[vertex], |edge| {
out[i] = edge;
i += 1;
});
}
});
Edges { values, indices }
}
/// Computes the next-round digest of each vertex by hashing its current
/// digest and the current digests of the vertices it refers to, so its
/// nth-round digest is a hash of its unfolding into a tree of depth n.
/// Content is hashed exactly once, in compute_digest; a round mixes
/// only fixed-size digests, through SipHash's aligned 8-byte
/// fast path, so a round is cheap however many rounds are needed.
fn propagate(cur: &mut Vec<Digest>, next: &mut Vec<Digest>, edges: &Edges) {
next.par_iter_mut().enumerate().for_each(|(i, out)| {
let mut h = SipHash13_128::new(&KEY);
cur[i].update(&mut h);
let begin = edges.indices[i] as usize;
let end = edges.indices[i + 1] as usize;
for &j in &edges.values[begin..end] {
cur[j as usize].update(&mut h);
}
*out = finish_digest(h);
});
std::mem::swap(cur, next);
}
/// Counts the distinct digests, electing each one's leader (its lowest
/// candidate index) as a side effect.
fn count_num_classes(digests: &[Digest], map: &mut DigestMap) -> usize {
map.next_round();
(0..digests.len()).into_par_iter().map(|i| map.insert(digests[i], i as u32) as usize).sum()
}
/// Debug builds re-verify that each candidate is byte-for-byte equal to
/// its leader.
#[cfg(debug_assertions)]
fn verify_leaders<E: Target>(
ctx: &Context<E>,
cand_index: &[usize],
candidates: &[usize],
leaders: &[u32],
) {
// Two members may reference different candidates of one class: those
// were folded together, so an edge names the class's leader.
let edge = |obj: usize, r: &Reloc| match edge_target(ctx, cand_index, obj, r) {
(Edge::Candidate(c), addend) => (Edge::Candidate(leaders[c] as usize), addend),
e => e,
};
let equal = |a: usize, b: usize| -> bool {
let (x, y) = (&ctx.isecs[a], &ctx.isecs[b]);
let (xr, yr) = (x.rels(&ctx.objs[x.file as usize]), y.rels(&ctx.objs[y.file as usize]));
x.contents() == y.contents()
&& xr.len() == yr.len()
&& xr.iter().zip(yr).all(|(r, s)| {
r.offset == s.offset
&& r.ty == s.ty
&& r.size == s.size
&& r.is_pcrel == s.is_pcrel
&& edge(x.file as usize, r) == edge(y.file as usize, s)
})
};
for (i, &l) in leaders.iter().enumerate() {
debug_assert!(equal(candidates[i], candidates[l as usize]));
}
}
pub fn icf_sections<E: Target>(ctx: &mut Context<E>) {
let _t_all = ctx.timer("icf");
let mut t = ctx.timer("icf-prep");
let insignificant = insignificant_sections(ctx);
let kept = kept_sections(ctx);
let candidates: Vec<usize> = (0..ctx.isecs.len())
.into_par_iter()
.filter(|&i| is_eligible(ctx, &insignificant, &kept, i))
.collect();
if candidates.len() < 2 {
return;
}
let mut cand_index = vec![usize::MAX; ctx.isecs.len()];
for (i, &id) in candidates.iter().enumerate() {
cand_index[id] = i;
}
let functions = ctx.args.verbose_deduplicate.then(|| {
let functions = ctx.isecs.iter().filter(|isec| is_text_function(ctx, isec));
functions.fold((0, 0), |(n, size), isec| (n + 1, size + isec.size as u64))
});
t.stop();
let mut t = ctx.timer("icf-rounds");
let mut digests: Vec<Digest> =
candidates.par_iter().map(|&id| compute_digest(ctx, &cand_index, id)).collect();
let edges = gather_edges(ctx, &cand_index, &candidates);
// Refine until the number of equivalence classes stops growing, as
// mold does. Counting the classes costs about as much as a
// propagation, so propagate three times per count (mold's ratio).
// count_num_classes inserts every digest into the reused DigestMap,
// which counts distinct digests and elects each class's leader in
// one pass, so no sort is needed here or afterward. Classes only
// ever split, so the count is monotone and the loop terminates.
let mut map = DigestMap::new(candidates.len());
let mut scratch = vec![Digest::default(); digests.len()];
let mut num_classes = usize::MAX;
loop {
propagate(&mut digests, &mut scratch, &edges);
propagate(&mut digests, &mut scratch, &edges);
propagate(&mut digests, &mut scratch, &edges);
let n = count_num_classes(&digests, &mut map);
if n == num_classes {
break;
}
num_classes = n;
}
t.stop();
let _t = ctx.timer("icf-fold");
// The final counting round elected a leader for every digest; look
// each candidate's leader up. The converged 128-bit digests are the
// equivalence classes, folded directly, as mold does.
let leaders: Vec<u32> = digests.par_iter().map(|&digest| map.find(digest)).collect();
#[cfg(debug_assertions)]
verify_leaders(ctx, &cand_index, &candidates, &leaders);
if let Some(total) = functions {
report_folds(ctx, &candidates, &leaders, total);
}
// Fold members onto leaders and give each leader the strongest
// alignment among its members (mold's update_alignment): the leader
// is laid out for every folded reference.
let mut folded = Vec::new();
for (i, &l) in leaders.iter().enumerate() {
let l = l as usize;
if l != i {
let member = candidates[i];
let leader = candidates[l];
ctx.isecs[member].replacement = leader as u32;
let a = ctx.isecs[member].p2align;
ctx.isecs[leader].p2align = ctx.isecs[leader].p2align.max(a);
folded.push((member, leader));
}
}
ctx.folded_subsec_names = folded_subsec_names(ctx, &folded);
}
+4329
View File
File diff suppressed because it is too large. Load diff
+830
View File
@@ -0,0 +1,830 @@
//! Input sections.
use crate::arch::Target;
use crate::chunks::ChunkId;
use crate::context::Context;
use crate::input_files::{DW_EH_PE_SDATA4, ObjectFile};
use crate::macho::{
MachSection, MachSym, N_PEXT, N_WEAK_DEF, S_THREAD_LOCAL_REGULAR, S_THREAD_LOCAL_ZEROFILL,
};
use crate::symbol::SymbolId;
/// A subsection index, u32 as in mold.
pub type InputSectionId = u32;
/// The subsection arena. Indexable by a u32 id or a usize (through
/// Deref to the Vec), so id vectors can be u32 - half the size - while
/// every arena access stays `isecs[id]`.
#[derive(Debug, Default)]
pub struct InputSections(pub Vec<InputSection>);
impl std::ops::Deref for InputSections {
type Target = Vec<InputSection>;
#[inline]
fn deref(&self) -> &Vec<InputSection> {
&self.0
}
}
impl std::ops::DerefMut for InputSections {
#[inline]
fn deref_mut(&mut self) -> &mut Vec<InputSection> {
&mut self.0
}
}
impl std::ops::Index<u32> for InputSections {
type Output = InputSection;
#[inline]
fn index(&self, id: u32) -> &InputSection {
&self.0[id as usize]
}
}
impl std::ops::IndexMut<u32> for InputSections {
#[inline]
fn index_mut(&mut self, id: u32) -> &mut InputSection {
&mut self.0[id as usize]
}
}
impl std::ops::Index<usize> for InputSections {
type Output = InputSection;
#[inline]
fn index(&self, id: usize) -> &InputSection {
&self.0[id]
}
}
impl std::ops::IndexMut<usize> for InputSections {
#[inline]
fn index_mut(&mut self, id: usize) -> &mut InputSection {
&mut self.0[id]
}
}
impl InputSections {
/// Follows literal-merge redirects to the surviving subsection.
pub fn resolve(&self, mut id: usize) -> usize {
while self[id].replacement != NO_REPLACEMENT {
id = self[id].replacement as usize;
}
id
}
}
/// What a relocation refers to.
#[derive(Clone, Copy, Debug)]
pub enum RelocTarget {
/// An index into the owning object's symbol list.
Sym(u32),
/// A subsection. During target-specific relocation reading this is
/// an index into the object's section header list; input file
/// parsing rewrites it to an index into the global subsection
/// arena.
Section(u32),
}
/// A relocation in a form independent of the raw Mach-O records: the
/// addend is explicit, and the target is a symbol or a section.
#[derive(Clone, Copy, Debug)]
pub struct Reloc {
/// Offset within the containing section.
pub offset: u32,
pub ty: u8,
/// Size in bytes of the relocated field.
pub size: u8,
pub is_pcrel: bool,
/// True if the previous record is a SUBTRACTOR paired with this one.
pub is_subtracted: bool,
/// The target, packed into one word: a symbol or subsection index
/// in the low 31 bits, with `TARGET_SECTION` set for a subsection.
/// Read through `target()`, write through `set_target()` or
/// `RelocTarget::pack()`; as a two-word enum it padded the struct
/// from 24 to 32 bytes, and a debug link holds ~12M of these.
pub target: u32,
pub addend: i64,
}
// A Reloc is the size of an ELF RELA entry, which mold reads from
// the mapping without materializing anything; Mach-O needs the record
// processed (ADDEND fusion, SUBTRACTOR pairing, subsection rebasing),
// so this is the form that processing produces. It can be smaller on
// 32-bit hosts, as i386 aligns i64 to 4 bytes.
const _: () = assert!(std::mem::size_of::<Reloc>() <= 24);
const TARGET_SECTION: u32 = 1 << 31;
impl RelocTarget {
#[inline]
pub fn pack(self) -> u32 {
match self {
Self::Sym(i) => {
debug_assert!(i & TARGET_SECTION == 0);
i
}
Self::Section(i) => {
debug_assert!(i & TARGET_SECTION == 0);
i | TARGET_SECTION
}
}
}
}
impl Reloc {
#[inline]
pub fn target(&self) -> RelocTarget {
if self.target & TARGET_SECTION != 0 {
RelocTarget::Section(self.target & !TARGET_SECTION)
} else {
RelocTarget::Sym(self.target)
}
}
#[inline]
pub fn set_target(&mut self, t: RelocTarget) {
self.target = t.pack();
}
/// Whether the relocation is a branch: a direct call or jump.
#[inline]
pub fn is_func_call<E: crate::arch::Target>(&self) -> bool {
self.ty == E::RELOC_BRANCH
}
/// The output address of the relocation's target, a symbol or a
/// subsection, as sold's Relocation::get_addr. `file` is the object
/// the relocation is of.
pub fn addr<E: Target>(&self, ctx: &Context<E>, file: &ObjectFile) -> u64 {
match self.target() {
RelocTarget::Sym(idx) => ctx.symbols[file.symbols[idx as usize]].addr(ctx),
RelocTarget::Section(idx) => ctx.isecs[idx as usize].addr(ctx),
}
}
/// The symbol the relocation refers to, if it refers to one.
#[inline]
pub fn sym(&self, file: &ObjectFile) -> Option<SymbolId> {
match self.target() {
RelocTarget::Sym(idx) => Some(file.symbols[idx as usize]),
RelocTarget::Section(_) => None,
}
}
/// The subsection the relocation's target lives in, if any: the
/// one it refers to, or its symbol's.
pub fn subsec<E: Target>(&self, ctx: &Context<E>, file: &ObjectFile) -> Option<usize> {
match self.target() {
RelocTarget::Sym(idx) => {
ctx.symbols[file.symbols[idx as usize]].input_section().map(|i| i as usize)
}
RelocTarget::Section(idx) => Some(idx as usize),
}
}
/// Whether the relocation's target is thread-local data.
pub fn refers_to_tls<E: Target>(&self, ctx: &Context<E>, file: &ObjectFile) -> bool {
self.subsec(ctx, file).is_some_and(|isec| {
let isec = &ctx.isecs[isec];
matches!(
isec.hdr(&ctx.objs[isec.file as usize]).section_type(),
S_THREAD_LOCAL_REGULAR | S_THREAD_LOCAL_ZEROFILL
)
})
}
/// How a diagnostic names the relocation's target: its symbol, or
/// the subsection it points to.
pub fn target_name<E: Target>(
&self,
ctx: &Context<E>,
file: &ObjectFile,
) -> std::borrow::Cow<'static, [u8]> {
match self.target() {
RelocTarget::Sym(idx) => ctx.symbols[file.symbols[idx as usize]].name().into(),
RelocTarget::Section(idx) => ctx.isecs[idx as usize].name(ctx),
}
}
}
/// Sentinel for `InputSection::replacement`: no surviving copy.
pub const NO_REPLACEMENT: u32 = u32::MAX;
/// A subsection of an input object file's section.
///
/// Mach-O linking granularity is the subsection: objects are built with
/// MH_SUBSECTIONS_VIA_SYMBOLS, and each section is split at its symbols,
/// so that unreferenced pieces can be dead-stripped. `shndx` names the
/// containing section; `input_addr` and `size` delimit this piece of it.
#[derive(Debug)]
pub struct InputSection {
/// Index of the object file this section came from (u32 to keep the
/// struct small; `usize::MAX` becomes `u32::MAX` for a synthetic
/// section with no object).
pub file: u32,
/// Index of the parent section's header in the owning object's
/// section list (the internal object's, for a synthesized one):
/// mold's shndx. Resolved through `hdr`; a u32 index
/// instead of an 8-byte header pointer. `p2align` is held inline
/// because it is the one header field the linker raises per
/// subsection.
pub shndx: u32,
pub p2align: u8,
/// This subsection's address in the object's address space. Object
/// files stay well under 4 GiB, so a u32 holds it.
pub input_addr: u32,
/// The subsection's size. One subsection is far under 4 GiB, so a
/// u32 holds it; arithmetic with 64-bit addresses casts up.
pub size: u32,
/// The subsection contents, as a bare pointer - the length is
/// `size` - or 0 when there are none (a zero-fill or empty
/// section). Stored as an integer, not a slice, to save 8 bytes and
/// keep the struct trivially Send/Sync; read through `contents()`.
/// mold likewise keeps `contents` as a bare address.
pub contents: usize,
/// This subsection's relocations: a range in the owning object's
/// `relocs` arena, offsets relative to the subsection. sold keeps
/// rel_offset/nrels per subsection the same way, rather than a Vec
/// per subsection - a debug link has millions of relocations.
pub rel_offset: u32,
pub nrels: u32,
/// The chunk this subsection is laid out in, as `ChunkId::pack`
/// encodes it, or `u32::MAX` until assigned: read through
/// `output_section()`. mold's field of this name holds an
/// Option<OutputSectionId> (a word, thanks to the id's niche); a
/// Mach-O subsection may also be placed in the GOT (a folded
/// __objc_classrefs entry) or in __objc_methlist (a rewritten
/// method list), so the packed ChunkId keeps the struct at 56 bytes.
pub output_section: u32,
/// Offset from the start of the output section (u32::MAX marks a
/// subsection not yet placed, during thunk layout). An output
/// section stays well under 4 GiB, so a u32 suffices.
pub offset: u32,
/// IS_ALIVE and the transient IS_VISITED bit, in one atomic byte so
/// the parallel dead-strip walk marks sections in place (mold's
/// InputSection flags). Read through is_alive(); the &mut setters
/// write without an atomic operation.
pub flags: std::sync::atomic::AtomicU8,
/// For a literal merged with an identical one, the surviving copy's
/// subsection index, or `NO_REPLACEMENT`. A u32 sentinel rather than
/// an `Option<usize>` (16 bytes) keeps the struct small.
pub replacement: u32,
/// This subsection's compact-unwind records: a range in
/// ctx.unwind_records, as sold keeps unwind_offset/nunwind on each
/// subsection (records arrive grouped by function).
pub unwind_offset: u32,
pub nunwind: u32,
}
// InputSection is the highest-count struct in a link (millions on a
// debug build), so it is kept compact: 56 bytes, under mold's 64
// (ours carries the unwind range but no section-name/flags word).
#[cfg(target_pointer_width = "64")]
const _: () = assert!(std::mem::size_of::<InputSection>() == 56);
const IS_ALIVE: u8 = 1 << 0;
const IS_VISITED: u8 = 1 << 1;
/// Placed by the pass that synthesized it, or that moved it into a
/// chunk of the linker's (its osec and output offset are set by hand),
/// so create_output_sections must not assign it to an output section by
/// name.
const IS_PLACED: u8 = 1 << 2;
/// The subsection starts at a multiple of its alignment regardless of
/// its input offset: a fixed-size record (a literal, an initializer
/// pointer, a CFString), which ld64 aligns with no modulus.
const NO_MODULUS: u8 = 1 << 3;
/// A literal record a symbol names: ld-prime keeps it a subsection of its
/// own, merged with no identical copy, and a -r output keeps its label.
const IS_LABELED: u8 = 1 << 4;
/// The image may observe the subsection's address (see
/// compute_address_significance), so ICF must keep it apart.
const IS_ADDRESS_TAKEN: u8 = 1 << 5;
impl InputSection {
/// A live subsection of section `shndx` of object `file`: `size`
/// bytes, `data` (empty for zero-fill), aligned to 2^`p2align`, at
/// input address 0, with no relocations and no place in the output
/// yet. The callers set what differs.
pub fn new(file: u32, shndx: u32, p2align: u8, size: u32, data: &'static [u8]) -> Self {
Self {
file,
shndx,
p2align,
input_addr: 0,
size,
contents: if data.is_empty() { 0 } else { data.as_ptr() as usize },
rel_offset: 0,
nrels: 0,
output_section: u32::MAX,
offset: 0,
flags: Self::flags_alive(),
replacement: NO_REPLACEMENT,
unwind_offset: 0,
nunwind: 0,
}
}
/// The initial flag word of a live section.
pub fn flags_alive() -> std::sync::atomic::AtomicU8 {
std::sync::atomic::AtomicU8::new(IS_ALIVE)
}
pub fn flags_alive_no_modulus() -> std::sync::atomic::AtomicU8 {
std::sync::atomic::AtomicU8::new(IS_ALIVE | NO_MODULUS)
}
/// The initial flag word of a section that never joins the link.
pub fn flags_dead() -> std::sync::atomic::AtomicU8 {
std::sync::atomic::AtomicU8::new(0)
}
/// The initial flag word of a live synthetic section placed by the
/// pass that made it.
pub fn flags_placed() -> std::sync::atomic::AtomicU8 {
std::sync::atomic::AtomicU8::new(IS_ALIVE | IS_PLACED)
}
/// The chunk this subsection is laid out in, once assigned.
#[inline]
pub fn output_section(&self) -> Option<ChunkId> {
if self.output_section == u32::MAX {
None
} else {
Some(ChunkId::unpack(self.output_section))
}
}
pub fn set_output_section(&mut self, id: ChunkId) {
self.output_section = id.pack();
}
#[inline]
pub fn is_placed(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & IS_PLACED != 0
}
/// The next output offset at or after `off` where this subsection
/// may start. ld64 keeps each subsection at the offset it had within
/// its input section modulo the section's alignment (an 8-byte
/// subsection at offset 8 of a 16-aligned section stays 8 mod 16),
/// rather than rounding every subsection up to the section's
/// alignment; the latter pads the output by an average of half the
/// alignment per subsection (NetNewsWire's __TEXT,__const was 11KB
/// larger than ld-prime's).
///
/// ld64 models this as a subsection alignment of (power of two,
/// modulus). A fixed-size literal is the exception: its alignment
/// is the literal size with modulus 0, so a 16-byte literal from a
/// p2align-3 __literal16 section, or one that survived merging
/// with a copy from a better-aligned section, still lands on a
/// 16-byte boundary. Its input offset is not a constraint the
/// compiler meant, and a 16-byte load through a scaled PAGEOFF12
/// immediate can only address a 16-aligned slot.
#[inline]
pub fn align_offset(&self, off: u64) -> u64 {
let align = 1u64 << self.p2align;
if self.flags.load(std::sync::atomic::Ordering::Relaxed) & NO_MODULUS != 0 {
return crate::util::align_to(off, align);
}
crate::util::align_to_mod(off, align, self.input_addr as u64 & (align - 1))
}
/// The alignment, as a power of two, that ld64 gives the part of
/// this subsection starting at object address `addr`: the
/// section's, unless that part sits at a nonzero offset modulo it,
/// whose trailing zeros count instead (a subsection at 8 mod 16 is
/// 8-aligned). ld64 keeps the most aligned of two copies of a weak
/// definition or a literal.
pub fn p2align_at(&self, addr: u64) -> u8 {
let modulus = addr & ((1 << self.p2align) - 1);
if self.is_record() || modulus == 0 { self.p2align } else { modulus.trailing_zeros() as u8 }
}
/// Whether this subsection is made of fixed-size records.
pub fn is_record(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & NO_MODULUS != 0
}
pub fn is_alive(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & IS_ALIVE != 0
}
/// Whether the subsection is in the output as itself: live, and not
/// replaced by an identical copy (see `replacement`).
#[inline]
pub fn is_emitted(&self) -> bool {
self.is_alive() && self.replacement == NO_REPLACEMENT
}
#[inline]
pub fn set_alive(&mut self, v: bool) {
let f = self.flags.get_mut();
if v {
*f |= IS_ALIVE;
} else {
*f &= !IS_ALIVE;
}
}
/// Marks the subsection dead.
#[inline]
pub fn kill(&mut self) {
*self.flags.get_mut() &= !IS_ALIVE;
}
/// Atomically sets the visited bit; true if this call set it.
#[inline]
pub fn visit(&self) -> bool {
self.flags.fetch_or(IS_VISITED, std::sync::atomic::Ordering::Relaxed) & IS_VISITED == 0
}
/// Clears the visited bit, through a shared reference.
#[inline]
pub fn unmark_visited(&self) {
self.flags.fetch_and(!IS_VISITED, std::sync::atomic::Ordering::Relaxed);
}
/// Whether the visited bit is set.
#[inline]
pub fn is_visited(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & IS_VISITED != 0
}
/// Marks this literal record as named by a symbol.
#[inline]
pub fn mark_labeled(&self) {
self.flags.fetch_or(IS_LABELED, std::sync::atomic::Ordering::Relaxed);
}
/// Whether a symbol names this literal record.
#[inline]
pub fn is_labeled(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & IS_LABELED != 0
}
#[inline]
pub fn is_address_taken(&self) -> bool {
self.flags.load(std::sync::atomic::Ordering::Relaxed) & IS_ADDRESS_TAKEN != 0
}
/// Sets the address-taken bit, writing only if it is clear: many
/// references reach the same few subsections from every thread.
#[inline]
pub fn set_address_taken(&self) {
use std::sync::atomic::Ordering::Relaxed;
if self.flags.load(Relaxed) & IS_ADDRESS_TAKEN == 0 {
self.flags.fetch_or(IS_ADDRESS_TAKEN, Relaxed);
}
}
/// Reads and clears the visited bit.
#[inline]
pub fn take_visited(&mut self) -> bool {
let f = self.flags.get_mut();
let v = *f & IS_VISITED != 0;
*f &= !IS_VISITED;
v
}
/// This subsection's bytes. Empty for a zero-fill or empty section;
/// otherwise the `size` bytes at `contents` (which point into the
/// mmap'd input, so they live for the whole link).
#[inline]
pub fn contents(&self) -> &'static [u8] {
if self.contents == 0 {
&[]
} else {
// SAFETY: for a non-empty section `contents` is the start of
// `size` valid bytes in the leaked/mmap'd input.
unsafe { std::slice::from_raw_parts(self.contents as *const u8, self.size as usize) }
}
}
/// The parent section header, through the owning object's section
/// list - mold resolves a section's shdr through its file the same
/// way.
#[inline]
pub fn hdr<'a>(&self, file: &'a ObjectFile) -> &'a MachSection {
&file.sect_hdrs[self.shndx as usize]
}
/// This subsection's output address: its output section's address
/// plus its offset there, as mold's isec.addr(ctx) derives it. A
/// literal-merge loser reports its surviving copy's address; an
/// unplaced subsection reports 0.
#[inline]
pub fn addr<E: Target>(&self, ctx: &Context<E>) -> u64 {
let mut isec = self;
if isec.replacement != NO_REPLACEMENT {
isec = &ctx.isecs[ctx.isecs.resolve(isec.replacement as usize)];
}
let Some(chunk) = isec.output_section() else {
return 0;
};
if isec.offset == u32::MAX {
return 0;
}
ctx.chunk_header(chunk).addr + isec.offset as u64
}
/// The section ordinal (a MachSym's sect) of the chunk this
/// subsection is laid out in; 0 when it has none.
pub fn sect_idx<E: Target>(&self, ctx: &Context<E>) -> u8 {
self.output_section().map_or(0, |id| ctx.chunk_header(id).sect_idx)
}
/// This subsection's relocations, sliced from its object's reloc
/// arena (subsections keep only a rel_offset/nrels range,
/// sold-style).
#[inline]
pub fn rels<'a>(&self, file: &'a ObjectFile) -> &'a [Reloc] {
let off = self.rel_offset as usize;
&file.relocs[off..off + self.nrels as usize]
}
/// The symbol that names this subsection: of those at its start, the
/// one subsec_name_rank ranks first. A literal merged by its content
/// (see input_files::has_merged_subsecs) is named by none of the
/// labels a compiler or assembler makes for itself (see
/// is_private_label), and by nothing at all if one but an ltmpN is
/// among them, as ld-prime has it (mergeable records name their
/// entries so).
pub fn label<E: Target>(&self, ctx: &Context<E>) -> Option<&'static [u8]> {
let obj = &ctx.objs[self.file as usize];
self.label_index(ctx).map(|i| ctx.symbols[obj.symbols[i]].name())
}
/// The index in its object's symbol table of the symbol that names
/// this subsection (see label).
pub fn label_index<E: Target>(&self, ctx: &Context<E>) -> Option<usize> {
use crate::input_files::has_merged_subsecs;
let obj = &ctx.objs[self.file as usize];
let key = Some(self.label_key());
let labels = (0..obj.mach_syms.len())
.filter(|&i| msym_label_key(&obj.mach_syms[i]) == key)
.map(|i| (i, &obj.mach_syms[i], ctx.symbols[obj.symbols[i]].name()));
let merged = has_merged_subsecs(self.hdr(obj));
if merged
&& labels
.clone()
.any(|(_, _, name)| is_private_label(name) && !name.starts_with(b"ltmp"))
{
return None;
}
labels
.filter(|(_, _, name)| !(merged && is_private_label(name)))
.max_by_key(|&(i, n, name)| (subsec_name_rank(n, name), name, i))
.map(|(i, _, _)| i)
}
/// Where the labels at the start of this subsection sit: its
/// section, counted from 1 as MachSyms count them, and its address.
fn label_key(&self) -> (u32, u64) {
(self.shndx + 1, self.input_addr as u64)
}
/// The name of this subsection in a diagnostic: its label (see
/// label), or else its section and its offset there,
/// "__TEXT,__cstring+0x10".
pub fn name<E: Target>(&self, ctx: &Context<E>) -> std::borrow::Cow<'static, [u8]> {
if let Some(name) = self.label(ctx) {
return name.into();
}
let hdr = self.hdr(&ctx.objs[self.file as usize]);
let (seg, sect) = (crate::error::raw(hdr.segname()), crate::error::raw(hdr.sectname()));
let off = self.input_addr as u64 - hdr.addr;
crate::error::render(format_args!("{seg},{sect}+0x{off:x}")).into()
}
/// Names the place `offset` bytes into this subsection:
/// "'NAME'+0xOFF (path)".
pub fn location<E: Target>(&self, ctx: &Context<E>, offset: u32) -> crate::error::Message {
let path = crate::error::RawPath::raw(ctx.objs[self.file as usize].mf.name.as_path());
let name = self.name(ctx);
let name = crate::error::raw(&name);
if offset == 0 {
crate::error::render(format_args!("'{name}' ({path})"))
} else {
crate::error::render(format_args!("'{name}'+0x{offset:X} ({path})"))
}
}
/// Reports a relocation that can't be applied where it is, `offset`
/// bytes into this subsection.
pub fn fixup_error<E: Target>(&self, ctx: &Context<E>, offset: u32, msg: std::fmt::Arguments) {
let file = crate::error::RawPath::raw(ctx.objs[self.file as usize].mf.name.as_path());
let name = self.name(ctx);
let name = crate::error::raw(&name);
crate::error!("{file}: {name}+0x{offset:x}: {msg}");
}
/// Whether the target of relocation `r` of this subsection has an
/// address in the image, as a PC-relative reference that goes
/// through no stub or GOT slot needs (an x86-64 RIP-relative one, an
/// arm64 adrp or the offset into its page): an import has none,
/// which is an error.
pub fn target_has_address<E: Target>(&self, ctx: &Context<E>, r: &Reloc) -> bool {
let file = &ctx.objs[self.file as usize];
let Some(id) = r.sym(file).filter(|&id| ctx.symbols[id].is_imported()) else {
return true;
};
let msg = format_args!("target '{}' does not have address", ctx.symbols[id]);
self.fixup_error(ctx, r.offset, msg);
false
}
/// Notes relocation `i` of `rels`, this subsection's (subsection
/// `isec_id`), whose pointer is at `addr`, if it is a text
/// relocation: in a range of text_reloc_ranges, and needing a fixup.
#[inline]
pub fn check_text_reloc<E: Target>(
&self,
ctx: &Context<E>,
isec_id: usize,
rels: &[Reloc],
i: usize,
addr: u64,
) {
if ctx.text_reloc_ranges.iter().any(|range| range.contains(&addr)) {
self.note_text_reloc(ctx, isec_id, rels, i);
}
}
/// Records a pointer in a read-only segment if dyld (or whatever
/// loads the image) has to bind or slide it, as the fixup builders
/// decide.
#[cold]
fn note_text_reloc<E: Target>(
&self,
ctx: &Context<E>,
isec_id: usize,
rels: &[Reloc],
i: usize,
) {
let file = &ctx.objs[self.file as usize];
let rel = &rels[i];
let slides = ctx.args.pie || ctx.args.output_type != crate::macho::MH_EXECUTE;
let needs_fixup = match rel.sym(file) {
Some(id)
if ctx.symbols[id].binds_at_runtime(ctx) || ctx.symbols[id].binds_to_self(ctx) =>
{
true
}
Some(id) if ctx.symbols[id].is_absolute(ctx) => false,
_ => slides && !rel.refers_to_tls(ctx, file),
};
if needs_fixup {
ctx.text_relocs.lock().unwrap().push((isec_id as u32, i as u32));
}
}
}
/// Where a symbol labels a subsection's start, if it is a label at all.
fn msym_label_key(msym: &crate::macho::MachSym) -> Option<(u32, u64)> {
(!msym.is_stab() && msym.ty() == crate::macho::N_SECT).then_some((msym.sect as u32, msym.value))
}
/// Whether a label is one a compiler or assembler makes for itself: an
/// assembler temporary (L...) or a linker-private label (l...) - the
/// compiler's lCPI0_0 constant-pool and l_.str string labels, the arm64
/// assembler's ltmpN.
pub fn is_private_label(name: &[u8]) -> bool {
name.starts_with(b"L") || name.starts_with(b"l")
}
/// How ld-prime prefers a symbol at a subsection's start to name the
/// subsection in a diagnostic: an exported one before a private extern,
/// a local, a weak definition and an ltmpN label; among equals, the
/// greatest name.
pub fn subsec_name_rank(msym: &MachSym, name: &[u8]) -> u8 {
if name.starts_with(b"ltmp") {
0
} else if msym.desc & N_WEAK_DEF != 0 {
1
} else if !msym.is_extern() {
2
} else if msym.n_type & N_PEXT != 0 {
3
} else {
4
}
}
/// Sentinel for an absent index in `UnwindRecord` (no personality, no
/// LSDA, no FDE).
pub const UNWIND_NONE: u32 = u32::MAX;
/// A record from a __compact_unwind section, describing how to unwind
/// the stack through one function.
///
/// One record per function, walked by unwind-info encoding, dead-strip
/// and ICF, so it is kept to eight u32s (32 bytes): every index is a
/// u32 with `UNWIND_NONE` for "absent" rather than an `Option<usize>`,
/// which is 16 bytes each - as mold's FdeRecord/CieRecord hold u32
/// indices. Read the optional fields through `personality()`, `lsda()`,
/// `fde()`.
#[derive(Clone, Debug)]
pub struct UnwindRecord {
/// The input section holding the function.
pub isec: u32,
/// The function's offset within `isec`.
pub input_offset: u32,
pub code_len: u32,
pub encoding: u32,
/// The personality symbol, or `UNWIND_NONE`.
pub personality_sym: u32,
/// The language-specific data area: an input section (or
/// `UNWIND_NONE`) and an offset within it.
pub lsda_isec: u32,
pub lsda_off: u32,
/// For a record synthesized from DWARF unwind info, the FDE it
/// points to (an index into `ctx.fdes`), or `UNWIND_NONE`.
pub fde_idx: u32,
}
const _: () = assert!(std::mem::size_of::<UnwindRecord>() == 32);
impl UnwindRecord {
#[inline]
pub fn personality(&self) -> Option<SymbolId> {
(self.personality_sym != UNWIND_NONE).then_some(self.personality_sym)
}
#[inline]
pub fn lsda(&self) -> Option<(usize, u32)> {
(self.lsda_isec != UNWIND_NONE).then_some((self.lsda_isec as usize, self.lsda_off))
}
#[inline]
pub fn fde(&self) -> Option<usize> {
(self.fde_idx != UNWIND_NONE).then_some(self.fde_idx as usize)
}
/// The personality routine of the record's function: the record's
/// own, or for one in DWARF mode, its FDE's CIE's.
pub fn function_personality<E: Target>(&self, ctx: &Context<E>) -> Option<SymbolId> {
self.personality().or_else(|| ctx.cies[ctx.fdes[self.fde()?].cie as usize].personality)
}
/// The LSDA of the record's function: the record's own, or for one
/// in DWARF mode, its FDE's.
pub fn function_lsda<E: Target>(&self, ctx: &Context<E>) -> Option<(usize, u32)> {
self.lsda().or_else(|| {
let (isec, off) = ctx.fdes[self.fde()?].lsda?;
Some((isec as usize, off))
})
}
}
/// A DWARF Common Information Entry from an object's __eh_frame.
#[derive(Debug)]
pub struct CieRecord {
/// The owning object (u32 index).
pub obj: u32,
pub input_addr: u32,
/// The CIE bytes: a slice of the object's __eh_frame (with its
/// relocations pre-applied), not a per-record copy - mold's
/// CieRecord borrows its contents the same way.
pub data: &'static [u8],
pub personality: Option<SymbolId>,
pub personality_offset: u32,
/// How the CIE's FDEs encode their function's address and size:
/// its 'R' augmentation, or DW_EH_PE_absptr without one. Each FDE
/// checks it as it is read.
pub fde_enc: u8,
/// How they encode their LSDA pointer, if the CIE has an 'L'
/// augmentation; checked the same way.
pub lsda_enc: Option<u8>,
pub output_offset: u32,
pub is_alive: bool,
}
#[cfg(target_pointer_width = "64")]
const _: () = assert!(std::mem::size_of::<CieRecord>() == 48);
impl CieRecord {
/// The size of the function address and size that start its FDEs'
/// fields: 4 bytes in DW_EH_PE_sdata4 (GCC's 0x1b), 8 in
/// DW_EH_PE_absptr (0x10, what clang writes).
pub fn pc_size(&self) -> usize {
if self.fde_enc & 0xf == DW_EH_PE_SDATA4 { 4 } else { 8 }
}
/// The size of an LSDA pointer of its FDEs, in the same encodings.
pub fn lsda_size(&self) -> usize {
if self.lsda_enc.is_some_and(|enc| enc & 0xf == DW_EH_PE_SDATA4) { 4 } else { 8 }
}
}
/// A DWARF Frame Description Entry from an object's __eh_frame.
#[derive(Debug)]
pub struct FdeRecord {
/// The owning object (u32 index).
pub obj: u32,
pub input_addr: u32,
/// The FDE bytes: a slice of the object's processed __eh_frame.
pub data: &'static [u8],
/// Index of the CIE this FDE points at (ctx.cies).
pub cie: u32,
/// The subsection holding the function.
pub isec: u32,
pub func_offset: u32,
pub code_len: u32,
/// The language-specific data area: a subsection and an offset.
pub lsda: Option<(u32, u32)>,
pub output_offset: u32,
}
// Every index a u32 and the record bytes borrowed, as in mold
// (whose FdeRecord derives even more and is 16 bytes).
#[cfg(target_pointer_width = "64")]
const _: () = assert!(std::mem::size_of::<FdeRecord>() == 56);
impl FdeRecord {
/// The offset of the LSDA pointer: the augmentation data, past its
/// ULEB128 length, after the length, CIE pointer, pc_begin and
/// pc_range (`pc_size` bytes each, see CieRecord::pc_size).
pub fn lsda_pos(&self, pc_size: usize) -> usize {
let mut pos = 8 + 2 * pc_size;
while self.data[pos] & 0x80 != 0 {
pos += 1;
}
pos + 1
}
}
+267
View File
@@ -0,0 +1,267 @@
//! Lazy dylibs (-lazy-l, -lazy_library, -lazy_framework, from macOS 27
//! on): a dylib dyld loads only once the image first uses one of its
//! symbols. Such a dylib has no LC_LOAD_DYLIB: it has an
//! LC_LAZY_LOAD_DYLIB_INFO record (see chunks::lazy_load_info) naming
//! it, a flag word and the symbols the image uses from it, each with a
//! __lazy_load_got slot, which the image reaches through the helpers of
//! chunks::lazy_helpers.
use rayon::prelude::*;
use crate::arch::{LazyRef, Target};
use crate::chunks::lazy_helpers::{LazyHelper, LazyUse};
use crate::chunks::lazy_load_info::{LazyDylib, record_size};
use crate::context::Context;
use crate::error::raw;
use crate::input_files::{FileId, add_data_word};
use crate::symbol::SymbolId;
use crate::util::leak_bytes;
/// Binds __dyld_lazy_load, which the lazy-load helpers call (see
/// create_lazy_loads), in an image that uses a symbol of a lazy dylib,
/// before the dylibs no symbol binds to are dropped.
pub fn bind_dyld_lazy_load<E: Target>(ctx: &mut Context<E>) {
if !ctx.dylibs.iter().any(|d| d.is_lazy) {
return;
}
let ctx_ref: &Context<E> = ctx;
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_used(true);
}
}
/// Makes what the image reaches the symbols of its lazy dylibs through,
/// as ld-prime does. Calls go to a helper per symbol that jumps
/// through the slot once the flag says the dylib is loaded, and first
/// has __dyld_lazy_load (libdyld's, called through a stub as any
/// import) load it and bind the slots; a GOT load calls a helper that
/// goes through the slot likewise (see LazyUse). ld-prime refuses any
/// other reference, such as a pointer in data, which dyld would have
/// to bind at launch; the error names the fixup as it does.
///
/// A mergeable dylib would record a rewritten instruction under the
/// compiler's fixup, for no merge to relink: ld-prime crashes on one
/// that uses a lazy dylib (and makes a dylib no link can merge of one
/// that uses a delay-init dylib), which mold refuses; one that names
/// such a dylib but uses nothing of it links as any other.
pub fn create_lazy_loads<E: Target>(ctx: &mut Context<E>) {
if !ctx.dylibs.iter().any(|d| d.is_lazy) {
return;
}
let uses = lazy_uses(ctx);
if ctx.args.make_mergeable && !uses.is_empty() {
crate::fatal!("-lazy-l/-lazy_library/-lazy_framework cannot be used with -make_mergeable");
}
let (flags, slots) = create_lazy_load_slots(ctx, &uses);
create_lazy_helpers(ctx, &uses, &flags, &slots);
// The helpers call __dyld_lazy_load through its stub (see
// bind_dyld_lazy_load).
if !ctx.lazy_helpers.helpers.is_empty() {
let id = ctx.symbols.lookup(b"__dyld_lazy_load").filter(|&id| ctx.symbols[id].is_defined());
let Some(id) = id else {
crate::fatal!("lazy-load dylibs need __dyld_lazy_load, which no loaded dylib exports");
};
crate::chunks::stubs::add_symbol(ctx, id);
ctx.lazy_helpers.dyld_lazy_load = Some(id);
}
}
/// A reference to a symbol of a lazy or delay-init dylib: the
/// subsection, the relocation's offset in it, the symbol, and how it
/// refers to it.
pub(crate) type LazyUseSite = (u32, u32, SymbolId, LazyRef);
/// A __lazy_load_got slot: its symbol, and whether it is the one the
/// symbol's call helper has to itself (see Target::LAZY_CALL_OWN_SLOT).
type LazySlot = (SymbolId, bool);
/// The references from live subsections, in input order, to the
/// symbols `is_import` picks: a lazy dylib's, or a delay-init one's.
pub(crate) fn import_uses<E: Target>(
ctx: &Context<E>,
is_import: impl Fn(SymbolId) -> bool + Sync,
) -> Vec<LazyUseSite> {
let is_import = &is_import;
(0..ctx.isecs.len())
.into_par_iter()
.filter(|&i| ctx.isecs[i].is_emitted())
.flat_map_iter(|i| {
let isec = &ctx.isecs[i];
let (file, data) = (&ctx.objs[isec.file as usize], isec.contents());
isec.rels(file).iter().filter_map(move |r| {
let id = r.sym(file)?;
is_import(id).then(|| (i as u32, r.offset, id, E::lazy_ref(r, data)))
})
})
.collect()
}
/// 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.symbols[id].is_lazy_import(ctx));
for &(isec, _, id, how) in &uses {
if how == LazyRef::Unsupported {
let sym = &ctx.symbols[id];
let sec = &ctx.isecs[isec as usize];
let file = &ctx.objs[sec.file as usize];
let subsec = if crate::input_files::is_record_list(
sec.hdr(file),
file.subsections_via_symbols,
) {
b"anon"[..].into()
} else {
ctx.isecs[isec as usize].name(ctx)
};
let subsec = raw(&subsec);
crate::error!("use of '{sym}' in '{subsec}' cannot be lazy loaded.");
}
}
// A stub or GOT slot another pass made for one (an unwind
// personality's, a class's whose __objc_classrefs slot stays, see
// objc::fold_objc_classrefs) would be a pointer dyld binds at
// 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) {
let sym = &ctx.symbols[id];
if sym.is_lazy_import(ctx) {
crate::error!("use of '{sym}' in 'anon' cannot be lazy loaded.");
}
}
crate::error::checkpoint();
uses
}
/// The slot a use of a symbol goes through.
fn lazy_slot<E: Target>(id: SymbolId, how: LazyRef) -> LazySlot {
(id, E::LAZY_CALL_OWN_SLOT && how == LazyRef::Call)
}
/// Gives each lazy dylib the image uses its flag word, its symbols
/// their __lazy_load_got slots and it its record, all in the order of
/// the image's first uses. Returns each dylib's flag word's subsection,
/// and each slot's index.
fn create_lazy_load_slots<E: Target>(
ctx: &mut Context<E>,
uses: &[LazyUseSite],
) -> (Vec<u32>, hashbrown::HashMap<LazySlot, u32>) {
// The dylibs used, and each one's slots, which follow one another.
let mut used: Vec<usize> = Vec::new();
let mut by_dylib: Vec<Vec<LazySlot>> = vec![Vec::new(); ctx.dylibs.len()];
let mut seen = hashbrown::HashSet::new();
for &(_, _, id, how) in uses {
let Some(FileId::Dylib(d)) = ctx.symbols[id].file() else { continue };
let slot = lazy_slot::<E>(id, how);
if seen.insert(slot) {
if by_dylib[d as usize].is_empty() {
used.push(d as usize);
}
by_dylib[d as usize].push(slot);
}
}
let mut flags = vec![u32::MAX; ctx.dylibs.len()];
let mut index = hashbrown::HashMap::new();
let mut slots = Vec::new();
let mut offset = 0;
for d in used {
let flag = add_data_word(ctx, 4);
let install_name = &ctx.dylibs[d].install_name;
let leaf = install_name.rsplit(|&c| c == b'/').next().unwrap_or(install_name);
let name = leak_bytes([b"_lazyLoadFlag$", leaf].concat());
ctx.extra_local_syms.push((name, flag));
flags[d] = flag;
let got_start = slots.len() as u32;
let list = std::mem::take(&mut by_dylib[d]);
for &(id, own) in &list {
index.insert((id, own), slots.len() as u32);
// The slot an arm64 ldr loads (see LazyRef::Slot).
if !own {
ctx.symbols.aux_mut(id).lazy_got_idx = slots.len() as u32;
}
let name = leak_bytes([ctx.symbols[id].name(), b"$lazyGOT"].concat());
slots.push((id, name));
}
let syms: Vec<_> = list.into_iter().map(|(id, _)| id).collect();
let size = record_size(ctx, &ctx.dylibs[d].install_name, &syms);
let dylib = d as u32;
ctx.lazy_load_info.dylibs.push(LazyDylib { dylib, flag, syms, got_start, offset, size });
offset += size;
}
ctx.lazy_load_got.slots = slots;
ctx.lazy_load_info.hdr.size = offset as u64;
(flags, index)
}
/// Makes the helpers, in the order of the image's first uses: one per
/// symbol for calls, and one per symbol and register for GOT loads, or
/// per load in arm64 frameless code.
fn create_lazy_helpers<E: Target>(
ctx: &mut Context<E>,
uses: &[LazyUseSite],
flags: &[u32],
slots: &hashbrown::HashMap<LazySlot, u32>,
) {
let mut helpers: Vec<LazyHelper> = Vec::new();
let mut index: hashbrown::HashMap<(SymbolId, LazyUse), usize> = hashbrown::HashMap::new();
let mut sites = hashbrown::HashMap::new();
let mut size = 0;
for &(isec, offset, id, how) in uses {
let kind = match how {
LazyRef::Call => LazyUse::Call,
LazyRef::Cmp => LazyUse::Cmp,
LazyRef::Load => {
let (reg, own) = E::lazy_load_site(ctx.isecs[isec as usize].contents(), offset);
LazyUse::Load { reg, site: own.then_some((isec, offset)) }
}
LazyRef::Slot | LazyRef::Unsupported => continue,
};
let i = *index.entry((id, kind)).or_insert_with(|| {
let sym = ctx.symbols[id].name();
let name = match kind {
LazyUse::Call => [sym, b"$lazyLoadStub"].concat(),
LazyUse::Cmp => [sym, b"$lazyGOT$cmpHelper"].concat(),
LazyUse::Load { reg, site } => load_helper_name(ctx, sym, b"$lazyGOT$", reg, site),
};
let Some(FileId::Dylib(d)) = ctx.symbols[id].file() else { unreachable!() };
let (flag, slot) = (flags[d as usize], slots[&lazy_slot::<E>(id, how)]);
let name = leak_bytes(name);
helpers.push(LazyHelper { sym: id, kind, name, flag, slot, offset: size });
size += E::lazy_helper_size(kind);
helpers.len() - 1
});
if how != LazyRef::Call {
sites.insert((isec, offset), i as u32);
}
}
for (i, h) in helpers.iter().enumerate().filter(|(_, h)| h.kind == LazyUse::Call) {
ctx.symbols.aux_mut(h.sym).lazy_stub_idx = i as u32;
}
ctx.lazy_helpers.sites = sites;
ctx.lazy_helpers.helpers = helpers;
}
/// The name of a helper for GOT loads of symbol `sym` into register
/// `reg`, a lazy dylib's or a delay-init one's, as ld-prime names it:
/// `<sym><infix>loadHelper_<reg>`, and for the helper of one load of
/// its own (see LazyUse::Load), `$for$<subsection>+<offset>` after it.
pub(crate) fn load_helper_name<E: Target>(
ctx: &Context<E>,
sym: &[u8],
infix: &[u8],
reg: u8,
site: Option<(u32, u32)>,
) -> Vec<u8> {
let mut name = [sym, infix, b"loadHelper_", E::lazy_register_name(reg).as_bytes()].concat();
if let Some((isec, offset)) = site {
name.extend_from_slice(b"$for$");
name.extend_from_slice(&ctx.isecs[isec as usize].name(ctx));
name.extend_from_slice(format!("+{offset}").as_bytes());
}
name
}
+38
View File
@@ -0,0 +1,38 @@
//! A high-performance Mach-O linker.
pub mod arch;
pub(crate) mod archive_file;
pub(crate) mod bundle_hook;
pub(crate) mod chunks;
pub(crate) mod cmdline;
pub(crate) mod context;
pub(crate) mod dead_strip;
pub(crate) mod delay_init;
pub mod driver;
pub(crate) mod dtrace;
pub(crate) mod dwarf;
pub(crate) mod error;
pub(crate) mod filetype;
pub(crate) mod icf;
pub(crate) mod input_files;
pub(crate) mod input_sections;
pub(crate) mod lazy_load;
pub(crate) mod lto;
pub mod macho;
mod macho_consts;
pub(crate) mod make_mergeable;
pub(crate) mod mapfile;
pub(crate) mod mapped_file;
pub(crate) mod mergeable;
pub(crate) mod objc;
pub(crate) mod output_file;
pub(crate) mod passes;
pub(crate) mod reader;
pub(crate) mod relocatable;
pub(crate) mod subprocess;
pub(crate) mod symbol;
pub(crate) mod symbol_moves;
pub(crate) mod tapi;
pub(crate) mod thunks;
pub(crate) mod tls;
pub mod util;
+1149
View File
File diff suppressed because it is too large. Load diff
+517
View File
@@ -0,0 +1,517 @@
//! Mach-O file format definitions.
//!
//! All Mach-O targets we support (arm64 and x86-64) are little-endian, so
//! records are defined with plain integer fields and read from and written
//! to file buffers with unaligned copies. Structures in archive members may
//! be misaligned in memory, so records are never referenced in place.
//!
//! The exception is code signatures: their data structures are big-endian,
//! and are serialized by hand in the code-signature chunk.
pub use crate::macho_consts::*;
/// A record that can be copied between memory and a file buffer.
///
/// # Safety
///
/// Implementors must be `#[repr(C)]` with no padding and valid for any bit
/// pattern.
pub unsafe trait FileRecord: Copy + Default {
fn read_from(buf: &[u8]) -> Self {
assert!(buf.len() >= size_of::<Self>());
// SAFETY: the buffer is large enough, and any bit pattern is a
// valid value of the record type.
unsafe { std::ptr::read_unaligned(buf.as_ptr().cast::<Self>()) }
}
fn write_to(&self, buf: &mut [u8]) {
assert!(buf.len() >= size_of::<Self>());
// SAFETY: the buffer is large enough.
unsafe { std::ptr::write_unaligned(buf.as_mut_ptr().cast::<Self>(), *self) }
}
fn as_bytes(&self) -> &[u8] {
// SAFETY: the record is repr(C) with no padding.
unsafe {
std::slice::from_raw_parts(
std::ptr::from_ref::<Self>(self).cast::<u8>(),
size_of::<Self>(),
)
}
}
}
/// Reads an array of `n` records starting at `off`.
pub fn read_array<T: FileRecord>(buf: &[u8], off: usize, n: usize) -> Vec<T> {
let mut vec = Vec::with_capacity(n);
for i in 0..n {
vec.push(T::read_from(&buf[off + i * size_of::<T>()..]));
}
vec
}
/// Writes records one after another starting at `off`.
pub fn write_array<T: FileRecord>(buf: &mut [u8], off: usize, records: &[T]) {
for (i, record) in records.iter().enumerate() {
record.write_to(&mut buf[off + i * size_of::<T>()..]);
}
}
/// Returns the bytes of a 16-byte, NUL-padded section or segment name.
/// A name is bytes, not text: ld-prime takes any but NUL, UTF-8 or
/// not, and writes them to the output as they came.
pub fn name_to_bytes(name: &[u8; 16]) -> &[u8] {
let len = name.iter().position(|&b| b == 0).unwrap_or(16);
&name[..len]
}
/// Whether a 16-byte, NUL-padded section or segment name is `s`.
#[inline]
fn name_is(name: &[u8; 16], s: &[u8]) -> bool {
name.starts_with(s) && name.get(s.len()).is_none_or(|&b| b == 0)
}
/// Converts bytes to a 16-byte, NUL-padded section or segment name.
pub fn bytes_to_name(s: &[u8]) -> [u8; 16] {
let mut name = [0; 16];
name[..s.len()].copy_from_slice(s);
name
}
/// A section or segment name an option gives, cut to fit the 16 bytes
/// of a header's name field - inside a UTF-8 character too, as
/// ld-prime cuts it.
pub fn cut_name(name: &[u8]) -> &[u8] {
&name[..name.len().min(16)]
}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct MachHeader {
pub magic: u32,
pub cputype: u32,
pub cpusubtype: u32,
pub filetype: u32,
pub ncmds: u32,
pub sizeofcmds: u32,
pub flags: u32,
pub reserved: u32,
}
unsafe impl FileRecord for MachHeader {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct LoadCommand {
pub cmd: u32,
pub cmdsize: u32,
}
unsafe impl FileRecord for LoadCommand {}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct SegmentCommand {
pub cmd: u32,
pub cmdsize: u32,
pub segname: [u8; 16],
pub vmaddr: u64,
pub vmsize: u64,
pub fileoff: u64,
pub filesize: u64,
pub maxprot: u32,
pub initprot: u32,
pub nsects: u32,
pub flags: u32,
}
unsafe impl FileRecord for SegmentCommand {}
impl Default for SegmentCommand {
fn default() -> Self {
// SAFETY: all-zero bytes are a valid value for a plain record.
unsafe { std::mem::zeroed() }
}
}
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct MachSection {
pub sectname: [u8; 16],
pub segname: [u8; 16],
pub addr: u64,
pub size: u64,
pub offset: u32,
pub p2align: u32,
pub reloff: u32,
pub nreloc: u32,
pub flags: u32,
pub reserved1: u32,
pub reserved2: u32,
pub reserved3: u32,
}
unsafe impl FileRecord for MachSection {}
impl Default for MachSection {
fn default() -> Self {
// SAFETY: all-zero bytes are a valid value for a plain record.
unsafe { std::mem::zeroed() }
}
}
impl MachSection {
pub fn sectname(&self) -> &[u8] {
name_to_bytes(&self.sectname)
}
pub fn segname(&self) -> &[u8] {
name_to_bytes(&self.segname)
}
/// Whether the section is named `s`, as sectname() == s says but
/// without finding the name's end: for loops over millions of
/// symbols.
#[inline]
pub fn sectname_is(&self, s: &[u8]) -> bool {
name_is(&self.sectname, s)
}
/// Whether the segment is named `s`; see sectname_is.
#[inline]
pub fn segname_is(&self, s: &[u8]) -> bool {
name_is(&self.segname, s)
}
pub fn section_type(&self) -> u32 {
self.flags & SECTION_TYPE
}
/// Whether the section's contents are zeros, whatever the file
/// holds: S_GB_ZEROFILL, zero fill a 32-bit image could place past
/// 4GB, is zero fill as well.
pub fn is_zerofill(&self) -> bool {
matches!(self.section_type(), S_ZEROFILL | S_GB_ZEROFILL | S_THREAD_LOCAL_ZEROFILL)
}
}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct SymtabCommand {
pub cmd: u32,
pub cmdsize: u32,
pub symoff: u32,
pub nsyms: u32,
pub stroff: u32,
pub strsize: u32,
}
unsafe impl FileRecord for SymtabCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct DysymtabCommand {
pub cmd: u32,
pub cmdsize: u32,
pub ilocalsym: u32,
pub nlocalsym: u32,
pub iextdefsym: u32,
pub nextdefsym: u32,
pub iundefsym: u32,
pub nundefsym: u32,
pub tocoff: u32,
pub ntoc: u32,
pub modtaboff: u32,
pub nmodtab: u32,
pub extrefsymoff: u32,
pub nextrefsyms: u32,
pub indirectsymoff: u32,
pub nindirectsyms: u32,
pub extreloff: u32,
pub nextrel: u32,
pub locreloff: u32,
pub nlocrel: u32,
}
unsafe impl FileRecord for DysymtabCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct DylibCommand {
pub cmd: u32,
pub cmdsize: u32,
pub nameoff: u32,
pub timestamp: u32,
pub current_version: u32,
pub compatibility_version: u32,
}
unsafe impl FileRecord for DylibCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct DylinkerCommand {
pub cmd: u32,
pub cmdsize: u32,
pub nameoff: u32,
}
unsafe impl FileRecord for DylinkerCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct UuidCommand {
pub cmd: u32,
pub cmdsize: u32,
pub uuid: [u8; 16],
}
unsafe impl FileRecord for UuidCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct BuildVersionCommand {
pub cmd: u32,
pub cmdsize: u32,
pub platform: u32,
pub minos: u32,
pub sdk: u32,
pub ntools: u32,
}
unsafe impl FileRecord for BuildVersionCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct VersionMinCommand {
pub cmd: u32,
pub cmdsize: u32,
pub version: u32,
pub sdk: u32,
}
unsafe impl FileRecord for VersionMinCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct SourceVersionCommand {
pub cmd: u32,
pub cmdsize: u32,
pub version: u64,
}
unsafe impl FileRecord for SourceVersionCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct EntryPointCommand {
pub cmd: u32,
pub cmdsize: u32,
pub entryoff: u64,
pub stacksize: u64,
}
unsafe impl FileRecord for EntryPointCommand {}
/// LC_ROUTINES_64: the image's -init function, by its unslid address;
/// the fields after it were for the long-gone multi-module dylibs.
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct RoutinesCommand64 {
pub cmd: u32,
pub cmdsize: u32,
pub init_address: u64,
pub init_module: u64,
pub reserved: [u64; 6],
}
unsafe impl FileRecord for RoutinesCommand64 {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct LinkEditDataCommand {
pub cmd: u32,
pub cmdsize: u32,
pub dataoff: u32,
pub datasize: u32,
}
unsafe impl FileRecord for LinkEditDataCommand {}
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct DyldInfoCommand {
pub cmd: u32,
pub cmdsize: u32,
pub rebase_off: u32,
pub rebase_size: u32,
pub bind_off: u32,
pub bind_size: u32,
pub weak_bind_off: u32,
pub weak_bind_size: u32,
pub lazy_bind_off: u32,
pub lazy_bind_size: u32,
pub export_off: u32,
pub export_size: u32,
}
unsafe impl FileRecord for DyldInfoCommand {}
/// A symbol table entry, which Apple calls `nlist_64`.
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct MachSym {
pub stroff: u32,
pub n_type: u8,
pub sect: u8,
pub desc: u16,
pub value: u64,
}
unsafe impl FileRecord for MachSym {}
impl MachSym {
pub fn is_stab(&self) -> bool {
self.n_type & N_STAB != 0
}
pub fn is_extern(&self) -> bool {
self.n_type & N_EXT != 0
}
pub fn ty(&self) -> u8 {
self.n_type & N_TYPE
}
pub fn is_common(&self) -> bool {
!self.is_stab() && self.ty() == N_UNDF && self.is_extern() && self.value != 0
}
/// Whether an external symbol is undefined: a reference, or a
/// tentative definition (see is_common), which is N_UNDF too.
pub fn is_undef(&self) -> bool {
!self.is_stab() && self.is_extern() && self.ty() == N_UNDF
}
/// Whether a MachSym is an external weak definition in a section.
pub fn is_weak_def(&self) -> bool {
!self.is_stab() && self.is_extern() && self.ty() == N_SECT && self.desc & N_WEAK_DEF != 0
}
/// The log2 of a tentative definition's alignment, which desc
/// carries in bits 8 to 11 (Apple's GET_COMM_ALIGN).
pub fn common_p2align(&self) -> u8 {
((self.desc >> 8) & 0xf) as u8
}
}
/// A relocation record, which Apple calls `relocation_info`. `offset`
/// is followed by a bitfield laid out, from the least significant bit,
/// as idx:24, pcrel:1, p2size:2, extern:1, type:4.
#[derive(Clone, Copy, Default, Debug)]
#[repr(C)]
pub struct MachRel {
pub offset: u32,
pub bits: u32,
}
unsafe impl FileRecord for MachRel {}
impl MachRel {
pub fn idx(&self) -> u32 {
self.bits & 0xff_ffff
}
/// The 1-based ordinal of the section a non-extern record refers
/// to: idx's low byte, as a MachSym's sect is one byte.
/// ld-prime ignores the rest of the field.
pub fn sect(&self) -> u32 {
self.bits & 0xff
}
pub fn is_pcrel(&self) -> bool {
self.bits & (1 << 24) != 0
}
/// log2 of the size of the relocated field: 0, 1, 2 or 3.
pub fn p2size(&self) -> u32 {
(self.bits >> 25) & 3
}
pub fn is_extern(&self) -> bool {
self.bits & (1 << 27) != 0
}
pub fn ty(&self) -> u8 {
(self.bits >> 28) as u8
}
}
/// Encodes an X.Y.Z version number for a load command.
pub const fn encode_version(major: u32, minor: u32, patch: u32) -> u32 {
(major << 16) | (minor << 8) | patch
}
/// Formats a load command's version, omitting a zero patch level.
pub fn format_version(version: u32) -> String {
let major = version >> 16;
let minor = (version >> 8) & 0xff;
let patch = version & 0xff;
if patch == 0 { format!("{major}.{minor}") } else { format!("{major}.{minor}.{patch}") }
}
pub fn platform_name(platform: u32) -> String {
match platform {
PLATFORM_MACOS => "macOS",
PLATFORM_IOS => "iOS",
PLATFORM_TVOS => "tvOS",
PLATFORM_WATCHOS => "watchOS",
PLATFORM_BRIDGEOS => "bridgeOS",
PLATFORM_MACCATALYST => "macCatalyst",
PLATFORM_IOSSIMULATOR => "iOS-simulator",
PLATFORM_TVOSSIMULATOR => "tvOS-simulator",
PLATFORM_WATCHOSSIMULATOR => "watchOS-simulator",
PLATFORM_DRIVERKIT => "driverKit",
PLATFORM_VISIONOS => "visionOS",
PLATFORM_VISIONOSSIMULATOR => "visionOS-simulator",
PLATFORM_FIRMWARE => "firmware",
PLATFORM_SEPOS => "sepOS",
_ => return format!("unknown platform ({platform})"),
}
.to_string()
}
/// Whether mold links for a platform: macOS, iOS, tvOS and visionOS,
/// on devices and in their simulators, and firmware. (A watchOS device
/// runs arm64_32 code, an ILP32 target mold has no port to; Mac
/// Catalyst, driverKit and bridgeOS aren't supported either.)
pub fn is_supported_platform(platform: u32) -> bool {
matches!(
platform,
PLATFORM_MACOS
| PLATFORM_IOS
| PLATFORM_IOSSIMULATOR
| PLATFORM_TVOS
| PLATFORM_TVOSSIMULATOR
| PLATFORM_VISIONOS
| PLATFORM_VISIONOSSIMULATOR
| PLATFORM_FIRMWARE
)
}
/// Whether a platform is a simulator's: a mobile OS's processes running
/// on a Mac, on the Mac's own CPU, against the simulator SDK's libraries.
pub fn is_simulator(platform: u32) -> bool {
matches!(
platform,
PLATFORM_IOSSIMULATOR
| PLATFORM_TVOSSIMULATOR
| PLATFORM_WATCHOSSIMULATOR
| PLATFORM_VISIONOSSIMULATOR
)
}
/// The platforms a .tbd file has targets for, for a diagnostic.
pub fn platforms_name(platforms: &[u32]) -> String {
platforms.iter().map(|&p| platform_name(p)).collect::<Vec<_>>().join(" ")
}
+335
View File
@@ -0,0 +1,335 @@
//! Mach-O file format constants.
// Magic numbers
pub const MH_MAGIC_64: u32 = 0xfeed_facf;
pub const FAT_MAGIC: u32 = 0xcafe_babe;
// CPU types
pub const CPU_TYPE_I386: u32 = 7;
pub const CPU_TYPE_X86_64: u32 = 0x0100_0007;
pub const CPU_TYPE_ARM: u32 = 12;
pub const CPU_TYPE_ARM64: u32 = 0x0100_000c;
pub const CPU_TYPE_ARM64_32: u32 = 0x0200_000c;
pub const CPU_TYPE_POWERPC: u32 = 18;
// CPU subtypes
pub const CPU_SUBTYPE_MASK: u32 = 0xff00_0000;
pub const CPU_SUBTYPE_X86_64_ALL: u32 = 3;
pub const CPU_SUBTYPE_X86_64_H: u32 = 8;
pub const CPU_SUBTYPE_ARM64_ALL: u32 = 0;
pub const CPU_SUBTYPE_ARM64E: u32 = 2;
// File types
pub const MH_OBJECT: u32 = 1;
pub const MH_EXECUTE: u32 = 2;
pub const MH_PRELOAD: u32 = 5;
pub const MH_DYLIB: u32 = 6;
pub const MH_DYLINKER: u32 = 7;
pub const MH_DSYM: u32 = 10;
pub const MH_BUNDLE: u32 = 8;
pub const MH_KEXT_BUNDLE: u32 = 11;
// Mach header flags
pub const MH_NOUNDEFS: u32 = 0x1;
pub const MH_BINDATLOAD: u32 = 0x8;
pub const MH_APP_EXTENSION_SAFE: u32 = 0x0200_0000;
pub const MH_SIM_SUPPORT: u32 = 0x0800_0000;
pub const MH_DYLDLINK: u32 = 0x4;
pub const MH_TWOLEVEL: u32 = 0x80;
// Once prebinding's "don't fix up" mark, now -no_dynamic_access's: dyld
// neither dlopen()s the image nor finds its symbols with dlsym().
pub const MH_NOFIXPREBINDING: u32 = 0x400;
pub const MH_PIE: u32 = 0x20_0000;
pub const MH_HAS_TLV_DESCRIPTORS: u32 = 0x80_0000;
pub const MH_NO_REEXPORTED_DYLIBS: u32 = 0x10_0000;
pub const MH_DEAD_STRIPPABLE_DYLIB: u32 = 0x40_0000;
pub const MH_SUBSECTIONS_VIA_SYMBOLS: u32 = 0x2000;
pub const MH_WEAK_DEFINES: u32 = 0x8000;
pub const MH_BINDS_TO_WEAK: u32 = 0x1_0000;
// Load command types
pub const LC_REQ_DYLD: u32 = 0x8000_0000;
pub const LC_SYMTAB: u32 = 0x2;
pub const LC_DYSYMTAB: u32 = 0xb;
pub const LC_LOAD_DYLIB: u32 = 0xc;
pub const LC_ID_DYLIB: u32 = 0xd;
pub const LC_LOAD_DYLINKER: u32 = 0xe;
pub const LC_ID_DYLINKER: u32 = 0xf;
pub const LC_UNIXTHREAD: u32 = 0x5;
pub const LC_LOAD_WEAK_DYLIB: u32 = 0x18 | LC_REQ_DYLD;
pub const LC_SEGMENT_64: u32 = 0x19;
pub const LC_ROUTINES_64: u32 = 0x1a;
pub const LC_UUID: u32 = 0x1b;
pub const LC_RPATH: u32 = 0x1c | LC_REQ_DYLD;
pub const LC_SUB_FRAMEWORK: u32 = 0x12;
pub const LC_SUB_CLIENT: u32 = 0x14;
pub const LC_SEGMENT_SPLIT_INFO: u32 = 0x1e;
pub const LC_CODE_SIGNATURE: u32 = 0x1d;
pub const LC_REEXPORT_DYLIB: u32 = 0x1f | LC_REQ_DYLD;
pub const LC_LOAD_UPWARD_DYLIB: u32 = 0x23 | LC_REQ_DYLD;
// dylib_use_command: a dylib load command with flags, told apart from
// a dylib_command by the marker where that has its timestamp.
pub const DYLIB_USE_MARKER: u32 = 0x1a74_1800;
pub const DYLIB_USE_WEAK_LINK: u32 = 0x1;
pub const DYLIB_USE_REEXPORT: u32 = 0x2;
pub const DYLIB_USE_UPWARD: u32 = 0x4;
pub const DYLIB_USE_DELAYED_INIT: u32 = 0x8;
pub const LC_DYLD_INFO: u32 = 0x22;
pub const LC_DYLD_INFO_ONLY: u32 = 0x22 | LC_REQ_DYLD;
pub const LC_VERSION_MIN_MACOSX: u32 = 0x24;
pub const LC_VERSION_MIN_IPHONEOS: u32 = 0x25;
pub const LC_FUNCTION_STARTS: u32 = 0x26;
pub const LC_DYLD_ENVIRONMENT: u32 = 0x27;
pub const LC_MAIN: u32 = 0x28 | LC_REQ_DYLD;
pub const LC_DATA_IN_CODE: u32 = 0x29;
pub const LC_LINKER_OPTIMIZATION_HINT: u32 = 0x2e;
pub const LC_SOURCE_VERSION: u32 = 0x2a;
pub const LC_ENCRYPTION_INFO_64: u32 = 0x2c;
pub const LC_LINKER_OPTION: u32 = 0x2d;
pub const LC_VERSION_MIN_TVOS: u32 = 0x2f;
pub const LC_VERSION_MIN_WATCHOS: u32 = 0x30;
pub const LC_BUILD_VERSION: u32 = 0x32;
pub const LC_DYLD_EXPORTS_TRIE: u32 = 0x33 | LC_REQ_DYLD;
pub const LC_DYLD_CHAINED_FIXUPS: u32 = 0x34 | LC_REQ_DYLD;
pub const LC_LAZY_LOAD_DYLIB_INFO: u32 = 0x3a;
// A mergeable dylib's record (-make_mergeable), in ld-prime's own
// format, which a link that merges the dylib (-merge_*) reads.
pub const LC_ATOM_INFO: u32 = 0x36;
// Platform identifiers for LC_BUILD_VERSION
pub const PLATFORM_MACOS: u32 = 1;
pub const PLATFORM_IOS: u32 = 2;
pub const PLATFORM_TVOS: u32 = 3;
pub const PLATFORM_WATCHOS: u32 = 4;
pub const PLATFORM_BRIDGEOS: u32 = 5;
pub const PLATFORM_MACCATALYST: u32 = 6;
pub const PLATFORM_IOSSIMULATOR: u32 = 7;
pub const PLATFORM_TVOSSIMULATOR: u32 = 8;
pub const PLATFORM_WATCHOSSIMULATOR: u32 = 9;
pub const PLATFORM_DRIVERKIT: u32 = 10;
pub const PLATFORM_VISIONOS: u32 = 11;
pub const PLATFORM_VISIONOSSIMULATOR: u32 = 12;
pub const PLATFORM_FIRMWARE: u32 = 13;
pub const PLATFORM_SEPOS: u32 = 14;
// Segment protections
pub const VM_PROT_READ: u32 = 1;
pub const VM_PROT_WRITE: u32 = 2;
pub const VM_PROT_EXECUTE: u32 = 4;
// Section types (low 8 bits of the flags field)
pub const SECTION_TYPE: u32 = 0xff;
pub const S_REGULAR: u32 = 0x0;
pub const S_ZEROFILL: u32 = 0x1;
pub const S_CSTRING_LITERALS: u32 = 0x2;
pub const S_4BYTE_LITERALS: u32 = 0x3;
pub const S_8BYTE_LITERALS: u32 = 0x4;
pub const S_LITERAL_POINTERS: u32 = 0x5;
pub const S_NON_LAZY_SYMBOL_POINTERS: u32 = 0x6;
pub const S_LAZY_SYMBOL_POINTERS: u32 = 0x7;
pub const S_SYMBOL_STUBS: u32 = 0x8;
pub const S_MOD_INIT_FUNC_POINTERS: u32 = 0x9;
pub const S_MOD_TERM_FUNC_POINTERS: u32 = 0xa;
pub const S_COALESCED: u32 = 0xb;
pub const S_GB_ZEROFILL: u32 = 0xc;
pub const S_16BYTE_LITERALS: u32 = 0xe;
pub const S_DTRACE_DOF: u32 = 0xf;
pub const S_THREAD_LOCAL_REGULAR: u32 = 0x11;
pub const S_THREAD_LOCAL_ZEROFILL: u32 = 0x12;
pub const S_THREAD_LOCAL_VARIABLES: u32 = 0x13;
pub const S_THREAD_LOCAL_VARIABLE_POINTERS: u32 = 0x14;
pub const S_INIT_FUNC_OFFSETS: u32 = 0x16;
// Section attributes
pub const S_ATTR_PURE_INSTRUCTIONS: u32 = 0x8000_0000;
pub const S_ATTR_NO_DEAD_STRIP: u32 = 0x1000_0000;
pub const S_ATTR_LIVE_SUPPORT: u32 = 0x0800_0000;
pub const S_ATTR_DEBUG: u32 = 0x0200_0000;
pub const S_ATTR_SOME_INSTRUCTIONS: u32 = 0x0000_0400;
pub const S_ATTR_NO_TOC: u32 = 0x4000_0000;
pub const S_ATTR_STRIP_STATIC_SYMS: u32 = 0x2000_0000;
pub const S_ATTR_SELF_MODIFYING_CODE: u32 = 0x0400_0000;
pub const S_ATTR_EXT_RELOC: u32 = 0x0000_0200;
pub const S_ATTR_LOC_RELOC: u32 = 0x0000_0100;
// Symbol types (n_type field of MachSym)
pub const N_STAB: u8 = 0xe0;
pub const N_PEXT: u8 = 0x10;
pub const N_TYPE: u8 = 0x0e;
pub const N_EXT: u8 = 0x01;
pub const N_UNDF: u8 = 0x0;
pub const N_ABS: u8 = 0x2;
pub const N_SECT: u8 = 0xe;
pub const N_INDR: u8 = 0xa;
// Stab symbol types, for debug entries in the symbol table
pub const N_GSYM: u8 = 0x20;
pub const N_AST: u8 = 0x32;
pub const N_FUN: u8 = 0x24;
pub const N_STSYM: u8 = 0x26;
pub const N_SO: u8 = 0x64;
pub const N_OSO: u8 = 0x66;
pub const N_LCSYM: u8 = 0x28;
pub const N_BNSYM: u8 = 0x2e;
pub const N_SLINE: u8 = 0x44;
pub const N_ENSYM: u8 = 0x4e;
pub const N_ECOMM: u8 = 0xe4;
pub const N_ECOML: u8 = 0xe8;
// Symbol descriptions (desc field of MachSym)
pub const N_WEAK_REF: u16 = 0x0040;
pub const N_WEAK_DEF: u16 = 0x0080;
pub const N_NO_DEAD_STRIP: u16 = 0x0020;
pub const N_ALT_ENTRY: u16 = 0x0200;
/// The function is a split-off cold part (.cold.N); ld64 -r keeps it.
pub const N_COLD_FUNC: u16 = 0x0400;
pub const N_SYMBOL_RESOLVER: u16 = 0x0100;
pub const REFERENCED_DYNAMICALLY: u16 = 0x0010;
// ARM64 relocation types
pub const ARM64_RELOC_UNSIGNED: u8 = 0;
pub const ARM64_RELOC_SUBTRACTOR: u8 = 1;
pub const ARM64_RELOC_BRANCH26: u8 = 2;
pub const ARM64_RELOC_PAGE21: u8 = 3;
pub const ARM64_RELOC_PAGEOFF12: u8 = 4;
pub const ARM64_RELOC_GOT_LOAD_PAGE21: u8 = 5;
pub const ARM64_RELOC_GOT_LOAD_PAGEOFF12: u8 = 6;
pub const ARM64_RELOC_POINTER_TO_GOT: u8 = 7;
pub const ARM64_RELOC_TLVP_LOAD_PAGE21: u8 = 8;
pub const ARM64_RELOC_TLVP_LOAD_PAGEOFF12: u8 = 9;
pub const ARM64_RELOC_ADDEND: u8 = 10;
// ARM64 linker optimization hint kinds
pub const LOH_ARM64_ADRP_ADRP: u8 = 1;
pub const LOH_ARM64_ADRP_LDR: u8 = 2;
pub const LOH_ARM64_ADRP_ADD_LDR: u8 = 3;
pub const LOH_ARM64_ADRP_LDR_GOT_LDR: u8 = 4;
pub const LOH_ARM64_ADRP_ADD_STR: u8 = 5;
pub const LOH_ARM64_ADRP_LDR_GOT_STR: u8 = 6;
pub const LOH_ARM64_ADRP_ADD: u8 = 7;
pub const LOH_ARM64_ADRP_LDR_GOT: u8 = 8;
// x86-64 relocation types
pub const X86_64_RELOC_UNSIGNED: u8 = 0;
pub const X86_64_RELOC_SIGNED: u8 = 1;
pub const X86_64_RELOC_BRANCH: u8 = 2;
pub const X86_64_RELOC_GOT_LOAD: u8 = 3;
pub const X86_64_RELOC_GOT: u8 = 4;
pub const X86_64_RELOC_SUBTRACTOR: u8 = 5;
pub const X86_64_RELOC_SIGNED_1: u8 = 6;
pub const X86_64_RELOC_SIGNED_2: u8 = 7;
pub const X86_64_RELOC_SIGNED_4: u8 = 8;
pub const X86_64_RELOC_TLV: u8 = 9;
// Code signature constants. Note that unlike the rest of Mach-O, code
// signature data structures are big-endian.
pub const CSMAGIC_EMBEDDED_SIGNATURE: u32 = 0xfade_0cc0;
pub const CSMAGIC_CODEDIRECTORY: u32 = 0xfade_0c02;
pub const CSSLOT_CODEDIRECTORY: u32 = 0;
pub const CSSLOT_ALTERNATE_CODEDIRECTORIES: u32 = 0x1000;
pub const CS_ADHOC: u32 = 0x2;
pub const CS_LINKER_SIGNED: u32 = 0x2_0000;
pub const CS_SUPPORTSEXECSEG: u32 = 0x2_0400;
pub const CS_HASHTYPE_SHA1: u8 = 1;
pub const CS_HASHTYPE_SHA256: u8 = 2;
pub const CS_EXECSEG_MAIN_BINARY: u64 = 0x1;
pub const SHA1_SIZE: usize = 20;
pub const SHA256_SIZE: usize = 32;
// The page size used for code signature hashing. This is independent of
// the target's virtual memory page size; ld64 always hashes in 4 KiB
// blocks.
pub const CS_PAGE_SIZE: u64 = 4096;
// Special dylib ordinals for two-level namespace binds
pub const BIND_SPECIAL_DYLIB_SELF: i32 = 0;
pub const BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE: i32 = -1;
pub const BIND_SPECIAL_DYLIB_FLAT_LOOKUP: i32 = -2;
pub const BIND_SPECIAL_DYLIB_WEAK_LOOKUP: i32 = -3;
pub const BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: u8 = 0x30;
// Segment flags
pub const SG_NORELOC: u32 = 0x4;
pub const SG_READ_ONLY: u32 = 0x10;
// Bind opcodes, for the LC_DYLD_INFO bind stream. Each opcode byte holds
// the opcode in the high nibble and an immediate in the low nibble.
pub const BIND_TYPE_POINTER: u8 = 1;
pub const BIND_OPCODE_DONE: u8 = 0x00;
pub const BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: u8 = 0x10;
pub const BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: u8 = 0x20;
pub const BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: u8 = 0x40;
pub const BIND_OPCODE_SET_TYPE_IMM: u8 = 0x50;
pub const BIND_OPCODE_SET_ADDEND_SLEB: u8 = 0x60;
pub const BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x70;
pub const BIND_OPCODE_ADD_ADDR_ULEB: u8 = 0x80;
pub const BIND_OPCODE_DO_BIND: u8 = 0x90;
pub const BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: u8 = 0xa0;
pub const BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: u8 = 0xb0;
pub const BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: u8 = 0xc0;
pub const BIND_SYMBOL_FLAGS_WEAK_IMPORT: u8 = 0x1;
pub const BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION: u8 = 0x8;
// Rebase opcodes, for the LC_DYLD_INFO rebase stream
pub const REBASE_TYPE_POINTER: u8 = 1;
pub const REBASE_OPCODE_DONE: u8 = 0x00;
pub const REBASE_OPCODE_SET_TYPE_IMM: u8 = 0x10;
pub const REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: u8 = 0x20;
pub const REBASE_OPCODE_ADD_ADDR_ULEB: u8 = 0x30;
pub const REBASE_OPCODE_ADD_ADDR_IMM_SCALED: u8 = 0x40;
pub const REBASE_OPCODE_DO_REBASE_IMM_TIMES: u8 = 0x50;
pub const REBASE_OPCODE_DO_REBASE_ULEB_TIMES: u8 = 0x60;
pub const REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: u8 = 0x70;
pub const REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: u8 = 0x80;
// __TEXT,__unwind_info section encoding
pub const UNWIND_SECTION_VERSION: u32 = 1;
pub const UNWIND_SECOND_LEVEL_REGULAR: u32 = 2;
pub const UNWIND_SECOND_LEVEL_COMPRESSED: u32 = 3;
pub const UNWIND_HAS_LSDA: u32 = 0x4000_0000;
pub const UNWIND_PERSONALITY_MASK: u32 = 0x3000_0000;
pub const UNWIND_MODE_MASK: u32 = 0x0f00_0000;
pub const UNWIND_ARM64_MODE_DWARF: u32 = 0x0300_0000;
pub const UNWIND_X86_64_MODE_STACK_IND: u32 = 0x0300_0000;
pub const UNWIND_X86_64_MODE_DWARF: u32 = 0x0400_0000;
// Export trie symbol flags
pub const EXPORT_SYMBOL_FLAGS_KIND_MASK: u32 = 0x03;
pub const EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: u32 = 0x01;
pub const EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: u32 = 0x02;
pub const EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION: u32 = 0x04;
pub const EXPORT_SYMBOL_FLAGS_REEXPORT: u32 = 0x08;
// Segment split info (LC_SEGMENT_SPLIT_INFO), version 2
pub const DYLD_CACHE_ADJ_V2_FORMAT: u8 = 0x7f;
pub const DYLD_CACHE_ADJ_V2_POINTER_32: u8 = 0x01;
pub const DYLD_CACHE_ADJ_V2_POINTER_64: u8 = 0x02;
pub const DYLD_CACHE_ADJ_V2_DELTA_32: u8 = 0x03;
pub const DYLD_CACHE_ADJ_V2_DELTA_64: u8 = 0x04;
pub const DYLD_CACHE_ADJ_V2_ARM64_ADRP: u8 = 0x05;
pub const DYLD_CACHE_ADJ_V2_ARM64_OFF12: u8 = 0x06;
pub const DYLD_CACHE_ADJ_V2_ARM64_BR26: u8 = 0x07;
pub const DYLD_CACHE_ADJ_V2_IMAGE_OFF_32: u8 = 0x0c;
// Chained fixups (LC_DYLD_CHAINED_FIXUPS)
pub const DYLD_CHAINED_PTR_64: u16 = 2;
pub const DYLD_CHAINED_PTR_64_OFFSET: u16 = 6;
pub const DYLD_CHAINED_PTR_START_NONE: u16 = 0xffff;
pub const DYLD_CHAINED_IMPORT: u32 = 1;
pub const DYLD_CHAINED_IMPORT_ADDEND: u32 = 2;
pub const DYLD_CHAINED_IMPORT_ADDEND64: u32 = 3;
// Magic values in the indirect symbol table
pub const INDIRECT_SYMBOL_LOCAL: u32 = 0x8000_0000;
pub const INDIRECT_SYMBOL_ABS: u32 = 0x4000_0000;
/// __objc_imageinfo's flag of an image whose categories may have class
/// properties: every object's record has it, or the image's has not.
pub const OBJC_HAS_CATEGORY_CLASS_PROPERTIES: u32 = 0x40;
/// __objc_imageinfo's flag of an image whose class_ro_t pointers are
/// signed (arm64e's), which speaks for an object's classes (see
/// chunks::objc_imageinfo::merge_objc_info).
pub const OBJC_SIGNED_CLASS_RO: u32 = 0x10;
File diff suppressed because it is too large. Load diff
+272
View File
@@ -0,0 +1,272 @@
//! The Objective-C metadata of a mergeable dylib's record: what the
//! link made of the objects' (see crate::objc) as entries, as ld-prime
//! records its own rewrites - the method lists in the relative form,
//! the records category merging wrote, the selector references of the
//! objc stubs and of the lists - and the slots it leaves for lists a
//! merging link may add.
use hashbrown::HashSet;
use super::{Builder, Content, OutEntry, OutFixup, To, record_size};
use crate::arch::Target;
use crate::input_files::DataField;
use crate::mergeable::{ctype, fk, kind, scope, standard_content_type};
use crate::objc::ObjcRef;
impl<E: Target> Builder<'_, E> {
/// The entries of the metadata the link made: the selector
/// references (see add_selref_entries), and, after the imports, the
/// method lists in the relative form and category merging's
/// records, which go by no name (fixups refer to entries by index).
/// A class reference the GOT took over has none: what refers to it
/// refers to the class's GOT entry (see class_ref_fixup). Returns
/// the selector reference slots, in the order of the
/// __objc_selrefs tail.
pub(super) fn add_objc_entries(&mut self) -> Vec<To> {
let ctx = self.ctx;
let slots = self.add_selref_entries();
for list in &ctx.objc_methlist.lists {
let isec = &ctx.isecs[list.isec];
let mut entry = OutEntry::new(scope::LOCAL, kind::ANON, ctype::METHOD_LIST);
entry.size = isec.size;
entry.p2align = 3;
entry.content = self.isec_content(isec, isec.hdr(&ctx.objs[isec.file as usize]));
self.add_linker_isec_entry(list.isec, entry, None);
}
for blob in &ctx.data_blobs {
if ctx.isecs[blob.isec].is_alive() {
self.add_data_blob_entry(blob.isec);
}
}
slots
}
/// The selector references the link made: for each objc stub (with
/// the selector's name where no object has it), which the input
/// references to the selector became, then for each relative method
/// list entry whose selector no input refers to.
fn add_selref_entries(&mut self) -> Vec<To> {
let ctx = self.ctx;
let stubs = &ctx.objc_stubs;
let mut slots = Vec::new();
for (i, (_, sel)) in stubs.symbols.iter().enumerate() {
let osec = ctx.output_section(stubs.methname.unwrap());
let fileoff = osec.hdr.fileoff + osec.tail_off + stubs.methname_offs[i];
let mut entry = coalesced(ctype::METHOD_NAME, sel.len() as u32 + 1, 0);
entry.content = Content::Image(fileoff);
let name = Some(To::Entry(self.push_entry(entry, None)));
slots.push(self.add_selref(name));
}
for &sel in &stubs.extra_selrefs {
let name = self.isec_target(sel);
slots.push(self.add_selref(name));
}
slots
}
/// The entry of a record category merging wrote (the entries of its
/// records, if it is a list).
fn add_data_blob_entry(&mut self, isec: u32) {
let ctx = self.ctx;
let sec = &ctx.isecs[isec];
let hdr = sec.hdr(&ctx.objs[sec.file as usize]);
let content_type = standard_content_type(hdr).unwrap_or(ctype::DATA);
let mut entry = OutEntry::new(scope::LOCAL, kind::ANON, content_type);
entry.no_dead_strip = hdr.flags & crate::macho::S_ATTR_NO_DEAD_STRIP != 0;
entry.size = ctx.isecs[isec].size;
entry.p2align = ctx.isecs[isec].p2align;
entry.content = self.isec_content(&ctx.isecs[isec], hdr);
self.add_linker_isec_entry(isec, entry, record_size(hdr));
}
/// A selector reference to a name, which the link makes without
/// bytes in the record, as ld-prime does: a merging link writes its
/// pointer.
fn add_selref(&mut self, name: Option<To>) -> To {
let mut entry = coalesced(ctype::SELECTOR_REF, 8, 3);
if let Some(name) = name {
entry.fixups.push(OutFixup::new(0, name, fk::PTR64, 0));
}
To::Entry(self.push_entry(entry, None))
}
/// Adds the entry of a subsection the link made, after the imports
/// (the entries of its records, see Builder::split_records).
fn add_linker_isec_entry(&mut self, isec: u32, entry: OutEntry, record: Option<u32>) {
self.isec_entry.insert(isec, To::Tail(self.tail.len() as u32));
let entries = self.split_records(isec, entry, record);
self.tail.extend(entries);
}
/// The fixups of the records the link made, from the references
/// they hold: a relative method list's entries are each three
/// distances from the field (to a selector reference, the types
/// and the implementation), a record's pointers absolute.
pub(super) fn add_objc_fixups(&mut self, slots: &[To]) {
let ctx = self.ctx;
let target = |b: &Self, r: ObjcRef| -> Option<(To, i64)> {
match r {
ObjcRef::Isec(isec, off) => b.isec_target(isec).map(|to| (to, off as i64)),
ObjcRef::Sym(id, addend) => {
let (to, off) = b.sym_target(id);
Some((to, off + addend))
}
ObjcRef::TailSelref(n) => slots.get(n).map(|&to| (to, 0)),
ObjcRef::Null => None,
}
};
for list in &ctx.objc_methlist.lists {
let Some(To::Tail(t)) = self.isec_entry.get(&list.isec).copied() else { continue };
let mut fixups = Vec::new();
for (i, m) in list.methods.iter().enumerate() {
for (k, r) in [m.name, m.types, m.imp].into_iter().enumerate() {
if let Some((to, addend)) = target(self, r) {
let off = (8 + 12 * i + 4 * k) as u32;
fixups.push(OutFixup::new(off, to, fk::PCREL_DELTA32, addend));
}
}
}
self.tail[t as usize].fixups = fixups;
}
self.add_data_blob_fixups(&target);
}
/// The pointers of category merging's records, each a fixup of its
/// record's entry.
fn add_data_blob_fixups(&mut self, target: &impl Fn(&Self, ObjcRef) -> Option<(To, i64)>) {
let ctx = self.ctx;
for blob in &ctx.data_blobs {
let Some(To::Tail(t)) = self.isec_entry.get(&blob.isec).copied() else { continue };
let mut fixups = Vec::new();
let mut off = 0u32;
for field in &blob.fields {
match field {
DataField::Bytes(b) => off += b.len() as u32,
DataField::Ptr(r) => {
if let Some((to, addend)) = target(self, *r) {
fixups.push(OutFixup::new(off, to, fk::PTR64, addend));
}
off += 8;
}
}
}
for (k, f) in self.place_fixups(blob.isec, fixups) {
self.tail[t as usize + k].fixups.push(f);
}
}
}
/// The slots ld-prime leaves for the lists a merging link may add
/// to a class or category of the dylib: a fixup to an entry of no
/// bytes (an "anonPlaceholder") at each null list pointer of a
/// class's and its metaclass's class_ro_t (methods, protocols,
/// properties), and of a category_t (its method, protocol and
/// property lists), in the order of the class and category lists.
/// ld-prime's merging link crashes without them.
pub(super) fn add_objc_placeholders(&mut self) {
let lists: Vec<To> = (0..self.entries.len())
.map(|i| To::Entry(i as u32))
.chain((0..self.tail.len()).map(|i| To::Tail(i as u32)))
.collect();
let mut seen = HashSet::new();
let mut cats = Vec::new();
for &list in &lists {
let entry = self.entry(list);
let is_class = ctype::CLASS_LISTS.contains(&entry.content_type);
let is_cat = ctype::CATEGORY_LISTS.contains(&entry.content_type);
if !is_class && !is_cat {
continue;
}
let listed: Vec<(To, i64)> = (entry.fixups.iter())
.filter(|f| f.kind == fk::PTR64)
.map(|f| self.through_alias(f.target, f.addend))
.collect();
for item in listed {
if is_cat {
cats.push(item);
} else if seen.insert(item) {
for cls in [Some(item), self.pointer_at(item, 0)].into_iter().flatten() {
// class_t.data, whose low bits a Swift class
// uses for flags.
if let Some((ro, off)) = self.pointer_at(cls, 32) {
self.add_placeholders((ro, off & !7), &[32, 40, 64]);
}
}
}
}
}
for cat in cats {
self.add_placeholders(cat, &[16, 24, 32, 40, 48]);
}
}
/// A placeholder for each null pointer field of a record.
fn add_placeholders(&mut self, rec: (To, i64), fields: &[i64]) {
if !matches!(rec.0, To::Entry(_) | To::Tail(_)) {
return;
}
for &field in fields {
let off = rec.1 + field;
let entry = self.entry(rec.0);
if off < 0
|| off + 8 > entry.size as i64
|| entry.fixups.iter().any(|f| f.offset as i64 == off)
{
continue;
}
let mut placeholder = OutEntry::new(scope::LOCAL, kind::ANON_PLACEHOLDER, ctype::DATA);
placeholder.size = 8;
let to = To::Entry(self.push_entry(placeholder, None));
let f = OutFixup::new(off as u32, to, fk::PTR64, 0);
self.entry_mut(rec.0).fixups.push(f);
}
}
fn entry(&self, to: To) -> &OutEntry {
match to {
To::Entry(i) => &self.entries[i as usize],
To::Tail(i) => &self.tail[i as usize],
_ => unreachable!(),
}
}
fn entry_mut(&mut self, to: To) -> &mut OutEntry {
match to {
To::Entry(i) => &mut self.entries[i as usize],
To::Tail(i) => &mut self.tail[i as usize],
_ => unreachable!(),
}
}
/// The entry and offset a reference reaches, an alias's target's.
fn through_alias(&self, to: To, addend: i64) -> (To, i64) {
if matches!(to, To::Entry(_) | To::Tail(_)) {
let entry = self.entry(to);
if entry.kind == kind::ALIAS
&& let Some(f) = entry.fixups.iter().find(|f| f.kind == fk::ALIAS_OF)
{
return (f.target, f.addend + addend);
}
}
(to, addend)
}
/// The target of the pointer at `off` of a record of the image.
fn pointer_at(&self, rec: (To, i64), off: i64) -> Option<(To, i64)> {
if !matches!(rec.0, To::Entry(_) | To::Tail(_)) {
return None;
}
let at = rec.1 + off;
let f = (self.entry(rec.0).fixups.iter())
.find(|f| f.offset as i64 == at && f.kind == fk::PTR64)?;
Some(self.through_alias(f.target, f.addend))
}
}
/// A hidden entry merged by its content, as a literal.
fn coalesced(content_type: u8, size: u32, p2align: u8) -> OutEntry {
let mut entry = OutEntry::new(scope::HIDDEN, kind::ANON_COAL_BY_CONTENT, content_type);
entry.size = size;
entry.p2align = p2align;
entry
}
+275
View File
@@ -0,0 +1,275 @@
//! The record's bytes, laid out as ld-prime writes them.
use super::{Content, DylibRecord, MergeableRecord};
use crate::arch::Target;
use crate::context::Context;
use crate::macho::*;
use crate::mergeable::{
DEBUG_INFO_SIZE, DYLIB_INFO_SIZE, ENTRY_SIZE, FIXUP_SIZE, HEADER_SIZE, MAGIC, SECTION_SIZE,
header,
};
use crate::util::align_to_mod;
/// A name's hash as the record keeps it: its length in the top 20
/// bits, the low 44 of its hash below.
fn name_hash(name: &[u8]) -> u64 {
(name.len() as u64) << 44 | crate::util::cityhash::hash(name) & ((1 << 44) - 1)
}
/// The record being written, and the strings its records point at,
/// which go in a pool after them: each with the place of its record.
struct Writer {
out: Vec<u8>,
cstrings: Vec<(usize, Vec<u8>)>,
}
impl Writer {
fn put32(&mut self, at: usize, v: u32) {
self.out[at..at + 4].copy_from_slice(&v.to_le_bytes());
}
fn put64(&mut self, at: usize, v: u64) {
self.out[at..at + 8].copy_from_slice(&v.to_le_bytes());
}
/// Pads the record to a multiple of `align`, and returns its size.
fn align(&mut self, align: usize) -> usize {
let len = self.out.len().next_multiple_of(align);
self.out.resize(len, 0);
len
}
/// Adds `size` zero bytes 8-aligned, and returns where they start.
fn reserve(&mut self, size: usize) -> usize {
let at = self.align(8);
self.out.resize(at + size, 0);
at
}
/// A table's place in the header: its offset and its count (or
/// size) at `field`.
fn table(&mut self, field: usize, off: usize, count: usize) {
self.put32(field, off as u32);
self.put32(field + 4, count as u32);
}
/// Appends a NUL-terminated string, and points the 16-byte record at
/// `at` (CStringRO_1, SymbolString) at it: its offset from the
/// record, then `tail`.
fn add_string(&mut self, at: usize, s: &[u8], tail: u64) {
let pos = self.out.len();
self.out.extend_from_slice(s);
self.out.push(0);
self.put64(at, (pos as i64 - at as i64) as u64);
self.put64(at + 8, tail);
}
}
/// What the record leaves to fill once it has its place in the file:
/// the content offsets of the entries whose bytes are the image's (at
/// which offset of the record, of which file offset), and the content
/// pool's offset.
pub(super) struct Serialized {
pub bytes: Vec<u8>,
pub image_contents: Vec<(u32, u64)>,
pub pool_offset: u32,
}
impl MergeableRecord {
/// The record's bytes, laid out as ld-prime writes them: the
/// header, the entries, then each 8-aligned after the one before the
/// fixups, large addends, custom sections, linker options (none),
/// name records, this dylib's info, its dependencies', the debug
/// notes, the C string pool, the name pool and the content pool.
pub(super) fn serialize<E: Target>(&self, ctx: &Context<E>) -> Serialized {
let mut w = Writer { out: vec![0; HEADER_SIZE], cstrings: Vec::new() };
self.write_header::<E>(&mut w, ctx);
let entries_at = w.reserve(self.entries.len() * ENTRY_SIZE);
w.table(header::ENTRIES, entries_at, self.entries.len());
self.write_fixups(&mut w);
self.write_sections(&mut w);
let options_at = w.align(8);
w.table(header::LINKER_OPTIONS, options_at, 0);
let names: Vec<&[u8]> = self.entries.iter().filter_map(|e| e.name).collect();
let names_at = w.reserve(names.len() * 16);
w.table(header::NAMES, names_at, names.len());
self.write_dylib_infos(&mut w);
let debug_at = w.align(8);
for d in &self.debug {
write_debug_record(&mut w, d);
}
w.table(header::DEBUG_INFOS, debug_at, self.debug.len());
w.put32(header::DEBUG_INFOS + 8, (self.debug.len() * DEBUG_INFO_SIZE) as u32);
let pool = w.align(8);
// (An empty string is none: its record stays zero.)
for (at, s) in std::mem::take(&mut w.cstrings) {
if !s.is_empty() {
w.add_string(at, &s, s.len() as u64);
}
}
w.table(header::CSTRING_POOL, pool, w.out.len() - pool);
let pool = w.align(8);
for (i, name) in names.iter().enumerate() {
w.add_string(names_at + i * 16, name, name_hash(name));
}
w.table(header::NAME_POOL, pool, w.out.len() - pool);
let (image_contents, pool_offset) = self.write_entries(&mut w, entries_at);
let total = w.out.len() as u32;
w.put32(header::SIZE, total);
Serialized { bytes: w.out, image_contents, pool_offset }
}
/// The header's identification: the magic, the versions of the
/// formats, the largest kinds used, the target and the flags.
fn write_header<E: Target>(&self, w: &mut Writer, ctx: &Context<E>) {
w.out[..8].copy_from_slice(MAGIC);
w.out[8..10].copy_from_slice(&3u16.to_le_bytes());
w.out[10] = 2;
w.out[11] = 2;
w.out[12] = self.entries.iter().map(|e| e.kind).max().unwrap_or(0);
w.out[13] = self.entries.iter().map(|e| e.content_type).max().unwrap_or(0);
let max_kind = |arch: bool| {
let kinds = self.fixups.iter().map(|f| f.kind);
kinds.filter(|&k| (k >= 0x80) == arch).max().unwrap_or(0)
};
w.out[14..16].copy_from_slice(&max_kind(false).to_le_bytes());
w.out[16..18].copy_from_slice(&max_kind(true).to_le_bytes());
w.put32(header::CPUTYPE, E::CPUTYPE);
w.put32(header::CPUSUBTYPE, E::CPUSUBTYPE);
w.put32(header::PLATFORM, ctx.args.platform);
w.put32(header::MINOS, ctx.args.platform_minos);
w.put32(header::SDK, ctx.args.platform_sdk);
w.put64(header::FLAGS, self.flags);
}
/// The fixups, and the addends too large for theirs, each once.
fn write_fixups(&self, w: &mut Writer) {
let at = w.reserve(self.fixups.len() * FIXUP_SIZE);
w.table(header::FIXUPS, at, self.fixups.len());
let mut large: Vec<i64> = Vec::new();
for (i, f) in self.fixups.iter().enumerate() {
let at = at + i * FIXUP_SIZE;
f.write(&mut w.out[at..at + FIXUP_SIZE], &mut large);
}
let at = w.align(8);
for v in &large {
w.out.extend_from_slice(&v.to_le_bytes());
}
w.table(header::LARGE_ADDENDS, at, large.len());
}
/// The custom sections: the protection of the segment, the flags,
/// and the names, NUL-terminated.
fn write_sections(&self, w: &mut Writer) {
let at = w.align(8);
for s in &self.sections {
let mut rec = [0u8; SECTION_SIZE];
let prot: u32 = if s.segname == bytes_to_name(b"__TEXT") { 5 } else { 3 };
rec[0..4].copy_from_slice(&prot.to_le_bytes());
rec[4..8].copy_from_slice(&s.flags.to_le_bytes());
rec[8..24].copy_from_slice(&s.segname);
rec[26..42].copy_from_slice(&s.sectname);
w.out.extend_from_slice(&rec);
}
w.table(header::SECTIONS, at, self.sections.len());
}
/// This dylib's identity, then each of its dependencies'.
fn write_dylib_infos(&self, w: &mut Writer) {
let at = w.align(8);
write_dylib_record(w, &self.own);
w.table(header::OWN_DYLIB, at, w.out.len() - at);
let at = w.align(8);
for d in &self.deps {
write_dylib_record(w, d);
}
let size = w.align(8) - at;
w.table(header::DYLIBS, at, self.deps.len());
w.put32(header::DYLIBS + 8, size as u32);
}
/// The entries, and the content pool after everything else: the
/// bytes the image has none of, each at its alignment. Returns the
/// entries whose bytes are the image's, and the pool's offset.
fn write_entries(&self, w: &mut Writer, entries_at: usize) -> (Vec<(u32, u64)>, u32) {
let pool_align = (self.entries.iter())
.filter(|e| matches!(e.content, Content::Pool(_)))
.map(|a| 1usize << a.p2align)
.fold(16, usize::max);
let pool = w.align(pool_align);
let mut image = Vec::new();
let mut names = 0u32;
for (i, entry) in self.entries.iter().enumerate() {
let at = entries_at + i * ENTRY_SIZE;
let content = match entry.content {
Content::None => -1,
// The offset goes in once the record has its place.
Content::Image(fileoff) => {
image.push(((at + 24) as u32, fileoff));
0
}
Content::Pool(bytes) => {
let size = (w.out.len() - pool) as u64;
let off = align_to_mod(size, 1 << entry.p2align, entry.modulus as u64);
w.out.resize(pool + off as usize, 0);
w.out.extend_from_slice(bytes);
off as i32
}
};
let name = entry.name.map(|_| {
names += 1;
names - 1
});
let fixups = (entry.fixups.len() as u32, self.first_fixup[i]);
entry.write(&mut w.out[at..at + ENTRY_SIZE], i as u32, fixups, name, content);
}
w.table(header::CONTENT_POOL, pool, w.out.len() - pool);
(image, pool as u32)
}
}
/// A DebugNoteFileInfoRO_2: the object's modification time and CPU
/// subtype (its N_OSO's value and section), then its source directory
/// and name (N_SO), its path (N_OSO), and this dylib's install name.
fn write_debug_record(w: &mut Writer, d: &super::DebugRecord) {
let at = w.out.len();
w.out.resize(at + DEBUG_INFO_SIZE, 0);
w.put32(at, d.mtime);
w.out[at + 4] = d.cpusubtype;
let strings = [&d.source_dir, &d.source_name, &d.object_path, &d.install_name];
for (k, s) in strings.into_iter().enumerate() {
w.cstrings.push((at + 8 + 16 * k, s.clone()));
}
}
/// A DylibFileInfoRO_2: the versions, the install name, the platforms,
/// and the lists of re-exports and allowed clients (and a third
/// ld-prime leaves empty), each of strings for the pool.
fn write_dylib_record(w: &mut Writer, d: &DylibRecord) {
let at = w.out.len();
w.out.resize(at + DYLIB_INFO_SIZE, 0);
w.put32(at, d.current_version);
w.put32(at + 4, d.compatibility_version);
w.cstrings.push((at + 8, d.install_name.clone()));
if !d.platforms.is_empty() {
let off = w.out.len() - at;
for &p in &d.platforms {
w.out.extend_from_slice(&p.to_le_bytes());
}
w.align(8);
w.table(at + 0x28, off, d.platforms.len());
}
for (field, list) in [(0x30, &d.reexports), (0x38, &d.clients)] {
if list.is_empty() {
continue;
}
w.table(at + field, w.out.len() - at, list.len());
for s in list {
let pos = w.out.len();
w.out.resize(pos + 16, 0);
w.cstrings.push((pos, s.clone()));
}
}
}
+783
View File
@@ -0,0 +1,783 @@
//! The link's reports: the -map file (where every object file, section
//! and symbol ended up, in ld64's format), -trace_symbol_layout,
//! -sdk_imports with the API list it may be limited to, -dependency_info
//! and the trace files.
use std::borrow::Cow;
use std::io::Write;
use std::path::{Path, PathBuf};
use rayon::prelude::*;
use serde_json::{Value, json};
use crate::arch::Target;
use crate::chunks::{ChunkHeader, ChunkId, OutputSectionId};
use crate::context::Context;
use crate::error::RawPath;
use crate::input_files::{DylibFile, FileId, NameSource};
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::path_bytes;
/// Xcode's version-1 API import report. Despite its name, sdkImports
/// includes imports from non-SDK dylibs too, grouped by install name in
/// the order of the image's load commands - those an
/// -sdk_imports_api_list lists only, if there is one, whose version the
/// report records. An image with none to report has no input in the
/// report. Each library's symbols are listed once, by name: ld-prime
/// lists one a time for each reference to it (its stub, its GOT slot,
/// ...).
pub fn write_sdk_imports<E: Target>(ctx: &Context<E>) {
let Some(path) = &ctx.args.sdk_imports else { return };
let api_list = ctx.args.sdk_imports_api_list.as_ref();
let listed = |name: &&[u8]| api_list.is_none_or(|list| list.apis.contains(*name));
let mut imports = std::collections::BTreeMap::<(i32, &[u8]), Vec<&[u8]>>::new();
for (dylib, names) in ctx.dylibs.iter().zip(dylib_imports(ctx)) {
let names: Vec<&[u8]> = names.into_iter().filter(listed).collect();
if !names.is_empty() {
let key = (dylib.dylib_idx, dylib.install_name.as_slice());
imports.entry(key).or_default().extend(names);
}
}
// JSON is text: a path, install name or symbol name outside UTF-8 is
// spelled lossily.
let output = ctx.args.output.to_string_lossy();
let libraries: Vec<Value> = (imports.into_iter())
.map(|((_, name), mut symbols)| {
symbols.sort_unstable();
symbols.dedup();
json!({ "installName": String::from_utf8_lossy(name), "symbols": json_names(&symbols) })
})
.collect();
let inputs = match libraries.is_empty() {
true => Vec::new(),
false => vec![json!({ "path": output, "sdkImports": libraries })],
};
let report = json!({
"version": 1,
"output": output,
"arch": E::NAME,
"linker": concat!("mold-macho-", env!("CARGO_PKG_VERSION")),
"apiListVersion": api_list.map_or(0, |list| list.version),
"platform": platform_name(ctx.args.platform),
"deploymentVersion": format_version(ctx.args.platform_minos),
"sdkVersion": format_version(ctx.args.platform_sdk),
"inputs": inputs,
});
let Ok(file) = std::fs::File::create(path) else {
crate::warn!("can't open SDK imports file for writing at '{}'", path.raw());
return;
};
let mut out = std::io::BufWriter::new(file);
let _ = serde_json::to_writer_pretty(&mut out, &report);
let _ = out.write_all(b"\n");
}
/// Writes the -dependency_info file: Xcode's incremental build system
/// reads it to learn which files the link consumed and wrote. The
/// format is binary: an opcode byte then a NUL-terminated string - 0x00
/// the linker's version, 0x10 an input, 0x11 a file looked for and
/// missing, 0x40 an output -, the entries sorted by opcode and then
/// path. The inputs are every file the command line names - objects,
/// archives whether or not a member loads, dylibs whether or not
/// -dead_strip_dylibs keeps them, the -bundle_loader -, the -filelist
/// and -sectcreate files, the libraries auto-link options load and
/// those loaded as another's re-exports, each once. The missing files
/// are those the searches for inputs looked for, each once, as spelled
/// (see reader::Prober): a build system links again when one appears.
/// The outputs are the image, the -map and the -sdk_imports file.
/// Inputs and outputs are named by absolute paths.
pub fn write_dependency_info<E: Target>(ctx: &Context<E>) {
let Some(path) = &ctx.args.dependency_info else {
return;
};
let mut entries: Vec<(u8, Vec<u8>)> =
dependency_inputs(ctx).into_iter().map(|path| (0x10, path)).collect();
let missing = ctx.missing_files.lock().unwrap();
let mut missing: Vec<&[u8]> = missing.iter().map(|path| path_bytes(path)).collect();
missing.sort_unstable();
missing.dedup();
entries.extend(missing.into_iter().map(|path| (0x11, path.to_vec())));
let outputs = [Some(&ctx.args.output), ctx.args.map.as_ref(), ctx.args.sdk_imports.as_ref()];
entries.extend(outputs.into_iter().flatten().map(|path| (0x40, dependency_path(path))));
entries.sort();
let Ok(file) = std::fs::File::create(path) else {
crate::warn!("Could not open or create -dependency_info file: {}", path.raw());
return;
};
let mut out = std::io::BufWriter::new(file);
let mut emit = |op: u8, s: &[u8]| {
let _ = out.write_all(&[op]);
let _ = out.write_all(s);
let _ = out.write_all(&[0]);
};
emit(0x00, format!("{}\n", crate::cmdline::VERSION_BANNER).as_bytes());
for (op, path) in &entries {
emit(*op, path);
}
}
/// The -dependency_info file's inputs (see write_dependency_info).
fn dependency_inputs<E: Target>(ctx: &Context<E>) -> Vec<Vec<u8>> {
use crate::cmdline::InputArg;
// The object LTO compiled is no input (a build system can't depend
// on it), whatever -object_path_lto made of it, nor is the hook for
// the classes of mergeable libraries (see bundle_hook).
let mut paths: Vec<&Path> = ctx
.objs
.iter()
.enumerate()
.filter(|&(i, o)| {
o.is_reachable && !ctx.is_internal(i) && !ctx.is_bundle_hook(i) && !o.is_lto_obj()
})
.map(|(_, o)| o.mf.parent.map_or(o.mf.name.as_path(), |p| p.name.as_path()))
.collect();
paths.extend(ctx.visited_files.iter().map(PathBuf::as_path));
paths.extend(ctx.args.inputs.iter().filter_map(|arg| match arg {
InputArg::BundleLoader(path) => Some(path.as_path()),
_ => None,
}));
paths.extend(ctx.args.filelists.iter().map(PathBuf::as_path));
paths.extend(ctx.args.sectcreate.iter().filter_map(|sc| sc.path.as_deref()));
paths.extend(ctx.reexport_files.iter().map(PathBuf::as_path));
// A fat file's slice is the file's.
let mut paths: Vec<Vec<u8>> = (paths.into_iter())
.map(|path| crate::filetype::without_fat_arch(&dependency_path(path)))
.collect();
paths.sort_unstable();
paths.dedup();
paths
}
/// A path as the -dependency_info file has it: absolute.
fn dependency_path(path: &Path) -> Vec<u8> {
match std::path::absolute(path) {
Ok(path) => path_bytes(&path).to_vec(),
Err(_) => path_bytes(path).to_vec(),
}
}
/// -trace_file, -trace_file_shared_cache and -trace_symbols_file: the
/// records Apple's build system has a final link append to these files,
/// a line of JSON each, as ld-prime writes them, naming the output by
/// its leaf name and UUID. -trace_file lists the files of the dylibs it
/// loads, by kind - "dynamic" (weak ones too), "upward-dynamic",
/// "re-exports", "weak", "delay-init" -, in load command order, and the
/// archives a member loads from; -trace_file_shared_cache the same
/// dylibs by install name, but for the archives and the weak ones among
/// the "dynamic", and the output by its path (a dylib by its install
/// name). -trace_symbols_file
/// gives more: a dylib's exports, the symbols each dylib provides (and
/// a re-exported one exports), and the global symbols the members
/// loaded from each archive define, with the archives one isn't loaded
/// from. A library dyld loads lazily is in that one alone. Without a
/// UUID (-no_uuid) ld-prime writes no trace but that last, which then
/// lacks its "uuid". A file it can't write fails the link.
pub fn write_trace_files<E: Target>(ctx: &Context<E>) {
let args = &ctx.args;
if args.trace_file.is_none()
&& args.trace_file_shared_cache.is_none()
&& args.trace_symbols_file.is_none()
{
return;
}
let uuid = args.uuid.then(|| {
let u = *ctx.uuid.lock().unwrap();
let hex = |r: std::ops::Range<usize>| -> String {
u[r].iter().map(|b| format!("{b:02X}")).collect()
};
format!("{}-{}-{}-{}-{}", hex(0..4), hex(4..6), hex(6..8), hex(8..10), hex(10..16))
});
let traces = TraceInputs::new(ctx);
if let Some(uuid) = &uuid {
if let Some(path) = &args.trace_file {
append_trace(path, &traces.dylibs_json(ctx, uuid, false));
}
if let Some(path) = &args.trace_file_shared_cache {
append_trace(path, &traces.dylibs_json(ctx, uuid, true));
}
}
if let Some(path) = &args.trace_symbols_file {
append_trace(path, &traces.symbols_json(ctx, uuid.as_deref()));
}
if let Some(path) = args.trace_symbols_dir.as_deref().and_then(trace_symbols_dir_file::<E>) {
append_trace(&path, &traces.symbols_json(ctx, uuid.as_deref()));
}
}
/// The file of its own -trace_symbols_file's record goes to in `dir`,
/// $LD_TRACE_SYMBOLS_DIR (see cmdline::trace_env), which ld-prime makes
/// first as need be: <parent pid>.<pid>.<arch>.<microseconds since
/// 1970>.json, a name no other link takes.
fn trace_symbols_dir_file<E: Target>(dir: &Path) -> Option<PathBuf> {
if std::fs::create_dir_all(dir).is_err() {
// ld-prime reports errno, which mkpath_np leaves alone.
crate::error!("call to mkpath_np({}) failed due to: Undefined error: 0", dir.raw());
return None;
}
// SAFETY: getppid has no preconditions.
let ppid = unsafe { libc::getppid() };
let now = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH);
let usec = now.map_or(0, |d| d.as_micros());
let mut path = dir.as_os_str().to_os_string();
path.push(format!("/{ppid}.{}.{}.{usec}.json", std::process::id(), E::NAME));
Some(PathBuf::from(path))
}
/// Appends a record, in a single write, as other links may be appending
/// theirs to the same file.
fn append_trace(path: &Path, record: &Value) {
let file = std::fs::OpenOptions::new().append(true).create(true).open(path);
match file {
Ok(mut file) => {
let mut line = Vec::new();
let _ = serde_json::to_writer(&mut line, record);
line.push(b'\n');
let _ = file.write_all(&line);
}
Err(e) => crate::error!(
"Could not open or create trace file (errno={}): {}",
e.raw_os_error().unwrap_or(0),
path.raw()
),
}
}
/// What the traces list (see write_trace_files): the dylibs with load
/// commands, in their order, and those dyld loads lazily; the archives
/// a member loads from, with the global symbols the loaded members
/// define, and those with a member that doesn't load, by path.
struct TraceInputs<'a> {
dylibs: Vec<(usize, &'a DylibFile)>,
lazy: Vec<(usize, &'a DylibFile)>,
archives: Vec<(String, Vec<&'a [u8]>)>,
unused_archives: Vec<String>,
}
impl<'a> TraceInputs<'a> {
fn new<E: Target>(ctx: &'a Context<E>) -> Self {
let mut dylibs: Vec<(usize, &DylibFile)> = ctx.dylibs.iter().enumerate().collect();
dylibs.retain(|(_, d)| !d.is_bundle_loader);
dylibs.sort_by_key(|(_, d)| d.dylib_idx);
let (lazy, dylibs) = dylibs.into_iter().partition(|(_, d)| d.is_lazy);
// By archive: whether a member loads, whether one doesn't, and
// the global symbols the loaded ones define.
let mut members: hashbrown::HashMap<&Path, (bool, bool, Vec<&[u8]>)> =
hashbrown::HashMap::new();
for obj in &ctx.objs {
let Some(archive) = obj.mf.parent else { continue };
let entry = members.entry(archive.name.as_path()).or_default();
if !obj.is_reachable {
entry.1 = true;
continue;
}
entry.0 = true;
for (msym, &sym) in obj.mach_syms.iter().zip(&obj.symbols) {
if msym.is_extern() && !msym.is_stab() && matches!(msym.ty(), N_SECT | N_ABS) {
entry.2.push(ctx.symbols[sym].name());
}
}
}
let mut archives = Vec::new();
let mut unused_archives = Vec::new();
for (path, (used, unused, mut syms)) in members {
let path = trace_path(path);
if unused {
unused_archives.push(path.clone());
}
if used {
syms.sort_unstable();
syms.dedup();
archives.push((path, syms));
}
}
archives.sort_unstable();
unused_archives.sort_unstable();
Self { dylibs, lazy, archives, unused_archives }
}
/// The archives a member loads from.
fn archive_paths(&self) -> Vec<&str> {
self.archives.iter().map(|(path, _)| path.as_str()).collect()
}
/// The record of -trace_file, or with `shared_cache` of
/// -trace_file_shared_cache.
fn dylibs_json<E: Target>(&self, ctx: &Context<E>, uuid: &str, shared_cache: bool) -> Value {
let name = match ctx.args.output_type {
MH_DYLIB if shared_cache => {
String::from_utf8_lossy(ctx.args.output_install_name()).into_owned()
}
_ if shared_cache => ctx.args.output.to_string_lossy().into_owned(),
_ => output_leaf(ctx),
};
let mut record = json!({ "uuid": uuid, "name": name, "arch": E::NAME });
let id = |d: &DylibFile| match shared_cache {
true => String::from_utf8_lossy(&d.install_name).into_owned(),
false => trace_path(&d.path),
};
let weak = |d: &DylibFile| d.is_weak || d.is_weak_asserted;
// The list a dylib is in, but for "weak", which takes the weak
// ones besides.
let list_of = |d: &DylibFile| match () {
_ if d.is_reexported => "re-exports",
_ if d.is_upward => "upward-dynamic",
_ if d.delay_init.is_some() => "delay-init",
_ if shared_cache && weak(d) => "",
_ => "dynamic",
};
for key in ["dynamic", "upward-dynamic", "re-exports", "weak", "delay-init"] {
let items: Vec<String> = (self.dylibs.iter())
.filter(|(_, d)| if key == "weak" { weak(d) } else { list_of(d) == key })
.map(|(_, d)| id(d))
.collect();
if !items.is_empty() {
record[key] = json!(items);
}
}
if !shared_cache && !self.archives.is_empty() {
record["archives"] = json!(self.archive_paths());
}
record
}
/// The record of -trace_symbols_file.
fn symbols_json<E: Target>(&self, ctx: &Context<E>, uuid: Option<&str>) -> Value {
let args = &ctx.args;
let imports = dylib_imports(ctx);
let dylib_entry = |i: usize, d: &DylibFile, attrs: Vec<&str>| {
json!({
"path": trace_path(&d.path),
"install-name": String::from_utf8_lossy(&d.install_name),
"arch": E::NAME,
"attributes": attrs,
"imported-symbols": json_names(&imports[i]),
})
};
let mut dylibs: Vec<Value> = (self.dylibs.iter())
.map(|&(i, d)| {
let attrs = [
(d.is_reexported, "re-export"),
(d.is_weak || d.is_weak_asserted, "weak"),
(d.is_upward, "upward"),
(d.delay_init.is_some(), "delay-init"),
];
let attrs = attrs.iter().filter(|a| a.0).map(|a| a.1).collect();
let mut entry = dylib_entry(i, d, attrs);
if d.is_reexported {
let mut exports: Vec<&[u8]> = d.exports.iter().copied().collect();
exports.sort_unstable();
entry["exported-symbols"] = json!(json_names(&exports));
}
entry
})
.collect();
dylibs.extend(self.lazy.iter().map(|&(i, d)| dylib_entry(i, d, vec!["lazy-load"])));
let archives: Vec<Value> = (self.archives.iter())
.map(|(path, syms)| {
json!({ "path": path, "arch": E::NAME, "imported-symbols": json_names(syms) })
})
.collect();
let version = args.platform_minos;
let mut record = json!({
"version": "2",
"minor-version": 1,
"name": output_leaf(ctx),
"arch": E::NAME,
"platforms": [{
"name": platform_name(args.platform),
"min-version": {
"major": (version >> 16).to_string(),
"minor": ((version >> 8) & 0xff).to_string(),
},
}],
"exports": json_names(&own_exports(ctx)),
"linked-dylibs": dylibs,
"archives": self.archive_paths(),
"unused-archives": self.unused_archives,
"linked-archives": archives,
});
if args.output_type == MH_DYLIB {
let eligible = if args.shared_region { "yes" } else { "no" };
record["install-name"] = json!(String::from_utf8_lossy(args.output_install_name()));
record["shared-cache-eligible"] = json!(eligible);
}
if let Some(uuid) = uuid {
record["uuid"] = json!(uuid);
}
record
}
}
/// Symbol names as JSON strings, which are text: a name with a byte
/// that isn't UTF-8 is spelled with U+FFFD in its place (ld-prime writes
/// the byte, which leaves the JSON malformed).
fn json_names<'a>(names: &[&'a [u8]]) -> Vec<Cow<'a, str>> {
names.iter().map(|name| String::from_utf8_lossy(name)).collect()
}
/// The output's name in the traces: its leaf name, a dylib's install
/// name's.
fn output_leaf<E: Target>(ctx: &Context<E>) -> String {
let path = match ctx.args.output_type {
MH_DYLIB => Path::new(crate::util::os_str(ctx.args.output_install_name())),
_ => ctx.args.output.as_path(),
};
path.file_name().unwrap_or(path.as_os_str()).to_string_lossy().into_owned()
}
/// A file's path as the traces give it: its real path, a fat file's
/// slice by the file's.
fn trace_path(path: &Path) -> String {
let (path, _) = crate::filetype::split_fat_arch(crate::util::path_bytes(path));
let path = Path::new(crate::util::os_str(path));
let path = std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf());
path.to_string_lossy().into_owned()
}
/// The symbols a dylib output exports of its own definitions, sorted:
/// what its export trie lists but for re-exports.
fn own_exports<E: Target>(ctx: &Context<E>) -> Vec<&'static [u8]> {
if ctx.args.output_type != MH_DYLIB {
return Vec::new();
}
let mut names: Vec<&[u8]> = (0..ctx.symbols.syms.len() as SymbolId)
.filter(|&id| {
let sym = &ctx.symbols[id];
matches!(sym.file(), Some(FileId::Obj(obj)) if !ctx.is_internal(obj as usize))
&& sym.is_extern()
&& !sym.is_private_extern()
&& sym
.input_section()
.is_none_or(|isec| ctx.isecs[ctx.isecs.resolve(isec as usize)].is_alive())
&& !ctx.indirect_aliases.iter().any(|&(alias, _)| alias == id)
})
.map(|id| ctx.symbols[id].name())
.collect();
names.sort_unstable();
names
}
/// The symbols the output imports from each dylib, sorted, by dylib.
fn dylib_imports<E: Target>(ctx: &Context<E>) -> Vec<Vec<&'static [u8]>> {
let mut imports: Vec<Vec<&[u8]>> = vec![Vec::new(); ctx.dylibs.len()];
for sym in &ctx.symbols.syms {
if let Some(FileId::Dylib(i)) = sym.file()
&& sym.is_imported()
&& sym.is_used()
&& let Some(list) = imports.get_mut(i as usize)
{
list.push(sym.name());
}
}
for list in &mut imports {
list.sort_unstable();
list.dedup();
}
imports
}
/// -trace_symbol_layout prints, or -trace_symbol_layout_file writes,
/// the output section each symbol the map lists (see is_map_symbol)
/// went to, in symbol order: "symbol '_x', mapped to __TEXT/__text". A
/// file that can't be written is a warning, ending with a blank line as
/// ld-prime's does, and the trace goes nowhere then. A -r link reports
/// nothing.
pub fn trace_symbol_layout<E: Target>(ctx: &Context<E>) {
let args = &ctx.args;
if args.relocatable {
return;
}
let mut out: Box<dyn std::io::Write> = match &args.trace_symbol_layout_file {
Some(path) => match std::fs::File::create(path) {
Ok(file) => Box::new(std::io::BufWriter::new(file)),
Err(e) => {
crate::warn!(
"could not open -trace_symbol_layout_file {} for writing ({})\n",
path.raw(),
e.raw_os_error().unwrap_or(0)
);
return;
}
},
None if args.trace_symbol_layout => Box::new(std::io::stdout().lock()),
None => return,
};
for sym in &ctx.symbols.syms {
let Some(isec) = sym.input_section() else { continue };
if !is_map_symbol(sym) {
continue;
}
let Some(chunk) = ctx.isecs[ctx.isecs.resolve(isec as usize)].output_section() else {
continue;
};
let hdr = ctx.chunk_header(chunk);
let line = [b"symbol '", sym.name(), b"', mapped to ", hdr.segname, b"/", hdr.sectname];
let _ = out.write_all(&line.concat());
let _ = out.write_all(b"\n");
}
}
/// Whether a symbol names a row of the map: any named one but a local
/// label a compiler or assembler makes for itself (see
/// input_sections::is_private_label).
pub(crate) fn is_map_symbol(sym: &crate::symbol::Symbol) -> bool {
let name = sym.name();
!name.is_empty() && (sym.is_extern() || !crate::input_sections::is_private_label(name))
}
/// A row of the map's symbol list: the address and size of a
/// subsection, or of the part of one a symbol names, the number of the
/// file it came from (see MapFiles), and its name.
struct Row<'a> {
addr: u64,
size: u64,
file: usize,
name: Cow<'a, [u8]>,
}
/// A section of the map: its header, and the output section whose
/// members it holds, if the inputs make it.
pub type MapSection<'a> = (&'a ChunkHeader, Option<OutputSectionId>);
/// A symbol the map lists, by place: (subsection, offset, name).
type Label = (u32, u64, &'static [u8]);
/// The files of the map, numbered from 1: the live objects in link
/// order (an archive's member as "lib.a(foo.o)", the object LTO
/// compiled as itself), then the dylibs, each once. Number 0 stands
/// for the linker, which makes the stubs, the unwind info and such,
/// and the subsections of its own object.
struct MapFiles<'a> {
paths: Vec<&'a Path>,
/// The number of each object, 0 for one not listed.
objs: Vec<usize>,
}
impl<'a> MapFiles<'a> {
fn new<E: Target>(ctx: &'a Context<E>) -> Self {
let mut paths = Vec::new();
let mut objs = vec![0; ctx.objs.len()];
for (i, obj) in ctx.objs.iter().enumerate() {
if obj.is_reachable && !ctx.is_internal(i) && !ctx.is_bundle_hook(i) {
paths.push(obj.mf.name.as_path());
objs[i] = paths.len();
}
}
// (A dylib that stands for a library exports moved to is no
// file of its own.)
let dylibs = ctx.dylibs.iter().filter(|dylib| dylib.name_source != NameSource::Moved);
paths.extend(dylibs.map(|dylib| dylib.path.as_path()));
Self { paths, objs }
}
}
/// Writes the -map file of a final image, if asked for (see
/// print_map_of).
pub fn print_map<E: Target>(ctx: &Context<E>) {
if ctx.args.map.is_none() {
return;
}
let mut sections: Vec<MapSection> = Vec::new();
for &id in ctx.segments.iter().flat_map(|seg| &seg.chunks) {
let hdr = ctx.chunk_header(id);
if hdr.is_sect {
let osec = match id {
ChunkId::Output(osec) => Some(osec),
_ => None,
};
sections.push((hdr, osec));
}
}
print_map_of(ctx, &sections);
}
/// Writes the -map file, if asked for, of an output of `sections`: the
/// output and its architecture, its files (see MapFiles), its sections
/// and its symbols, and under -dead_strip, the subsections it dropped.
/// Each subsection has a row for each symbol of it the map lists (see
/// subsec_rows); each section the linker makes, and the part of one
/// past its subsections that the linker makes, one row of the
/// linker's, named after the section; and each branch island one, as
/// the symbol table names it. A -r link calls this with the sections
/// it writes.
pub fn print_map_of<E: Target>(ctx: &Context<E>, sections: &[MapSection]) {
let Some(path) = &ctx.args.map else { return };
let files = MapFiles::new(ctx);
let labels = subsec_labels(ctx);
let section_name = |hdr: &ChunkHeader| Cow::Owned([hdr.segname, b",", hdr.sectname].concat());
let mut rows = Vec::new();
if ctx.args.output_type == MH_EXECUTE && !ctx.args.preload && !ctx.args.relocatable {
let addr = ctx.mach_header.hdr.addr;
rows.push(Row { addr, size: 0, file: 0, name: Cow::Borrowed(b"__mh_execute_header") });
}
for &(hdr, osec) in sections {
let Some(osec) = osec else {
if hdr.size > 0 {
rows.push(Row { addr: hdr.addr, size: hdr.size, file: 0, name: section_name(hdr) });
}
continue;
};
let osec = ctx.output_section(osec);
let members: Vec<Vec<Row>> = (osec.members.par_iter())
.fold(Vec::new, |mut rows, &id| {
let addr = ctx.isecs[id as usize].addr(ctx);
subsec_rows(ctx, &files, &labels, id as usize, addr, &mut rows);
rows
})
.collect();
rows.extend(members.into_iter().flatten());
// What the linker adds after the members: a tail.
if osec.tail != crate::chunks::output_section::Tail::None {
let (addr, size) = (hdr.addr + osec.tail_off, hdr.size - osec.tail_off);
rows.push(Row { addr, size, file: 0, name: section_name(hdr) });
}
}
for osec in &ctx.output_sections {
for (addr, name) in crate::chunks::output_section::island_symbols(ctx, osec) {
rows.push(Row { addr, size: E::THUNK_SIZE, file: 0, name: Cow::Borrowed(name) });
}
}
rows.par_sort_by_key(|row| row.addr);
let dead = dead_rows(ctx, &files, &labels);
write_map(ctx, path, &files, sections, &rows, &dead);
}
/// The symbols the map lists (see is_map_symbol), sorted by place: a
/// symbol of a subsection merged into an identical one is at the
/// survivor's place, after the survivor's own.
fn subsec_labels<E: Target>(ctx: &Context<E>) -> Vec<Label> {
let mut labels: Vec<_> = (ctx.symbols.syms.par_iter())
.filter_map(|sym| {
let own = sym.input_section()? as usize;
let isec = ctx.isecs.resolve(own);
is_map_symbol(sym).then_some(((isec as u32, sym.value, isec != own), sym.name()))
})
.collect();
labels.par_sort_by_key(|&(place, _)| place);
labels.into_iter().map(|((isec, value, _), name)| (isec, value, name)).collect()
}
/// Appends the rows of subsection `id` at `addr` to `rows`: one for
/// each of its `labels`, the first at a place sized to the next place
/// or the subsection's end, the others there of no size; and an "anon"
/// one for the bytes before the first, if any.
fn subsec_rows<E: Target>(
ctx: &Context<E>,
files: &MapFiles,
labels: &[Label],
id: usize,
addr: u64,
rows: &mut Vec<Row>,
) {
let isec = &ctx.isecs[id];
let (size, file) = (isec.size as u64, files.objs[isec.file as usize]);
let start = labels.partition_point(|l| (l.0 as usize) < id);
let len = labels[start..].partition_point(|l| l.0 as usize == id);
let labels = &labels[start..start + len];
let first = labels.first().map_or(size, |l| l.1);
if first > 0 {
rows.push(Row { addr, size: first, file, name: Cow::Borrowed(b"anon") });
}
let mut places = labels.chunk_by(|a, b| a.1 == b.1).peekable();
while let Some(place) = places.next() {
let value = place[0].1;
let end = places.peek().map_or(size, |next| next[0].1);
for (j, &(_, _, name)) in place.iter().enumerate() {
let size = if j == 0 { end.saturating_sub(value) } else { 0 };
rows.push(Row { addr: addr + value, size, file, name: Cow::Borrowed(name) });
}
}
}
/// The rows of the dead-stripped list: under -dead_strip, the
/// subsections of the listed objects that the output doesn't have,
/// but those merged into an identical one, by object, as the live
/// ones' (see subsec_rows).
fn dead_rows<E: Target>(ctx: &Context<E>, files: &MapFiles, labels: &[Label]) -> Vec<Row<'static>> {
if !ctx.args.dead_strip {
return Vec::new();
}
let objs: Vec<Vec<Row>> = (0..ctx.objs.len())
.into_par_iter()
.filter(|&i| files.objs[i] != 0)
.map(|i| {
let mut rows = Vec::new();
for &id in &ctx.objs[i].subsecs {
let isec = &ctx.isecs[id];
if !isec.is_alive() && isec.replacement == crate::input_sections::NO_REPLACEMENT {
subsec_rows(ctx, files, labels, id as usize, 0, &mut rows);
}
}
rows
})
.collect();
objs.into_iter().flatten().collect()
}
/// Writes the map: the output and its architecture, the `files`, the
/// `sections`, the `rows` of the subsections and those of the `dead`
/// ones.
fn write_map<E: Target>(
ctx: &Context<E>,
path: &Path,
files: &MapFiles,
sections: &[MapSection],
rows: &[Row],
dead: &[Row],
) {
let Ok(file) = std::fs::File::create(path) else {
crate::warn!("could not write map file: {}", path.raw());
return;
};
let mut out = std::io::BufWriter::new(file);
let _ = write!(out, "# Path: ");
let _ = out.write_all(path_bytes(&ctx.args.output));
let _ = writeln!(out);
let _ = writeln!(out, "# Arch: {}", E::NAME);
let _ = writeln!(out, "# Object files:");
let _ = writeln!(out, "[ 0] linker synthesized");
// A fat file's slice, and a fat archive's member, by the file's own
// path: "libfoo.a(foo.o)".
for (i, path) in files.paths.iter().enumerate() {
let _ = write!(out, "[{:3}] ", i + 1);
let _ = out.write_all(&crate::filetype::without_fat_arch(path_bytes(path)));
let _ = writeln!(out);
}
let _ = writeln!(out, "# Sections:");
let _ = writeln!(out, "# Address\tSize \tSegment\tSection");
for (hdr, _) in sections {
let _ = write!(out, "0x{:08X}\t0x{:08X}\t", hdr.addr, hdr.size);
let _ = out.write_all(&[hdr.segname, b"\t", hdr.sectname].concat());
let _ = writeln!(out);
}
let _ = writeln!(out, "# Symbols:");
let _ = writeln!(out, "# Address\tSize \tFile Name");
for row in rows {
let _ = write!(out, "0x{:08X}\t0x{:08X}\t[{:3}] ", row.addr, row.size, row.file);
let _ = out.write_all(&row.name);
let _ = writeln!(out);
}
// The subsections -dead_strip removed come in a list of their own,
// with "<<dead>>" in the address column, as in ld64's map.
if !dead.is_empty() {
let _ = writeln!(out);
let _ = writeln!(out, "# Dead Stripped Symbols:");
let _ = writeln!(out, "# \tSize \tFile Name");
for row in dead {
let _ = write!(out, "<<dead>>\t0x{:08X}\t[{:3}] ", row.size, row.file);
let _ = out.write_all(&row.name);
let _ = writeln!(out);
}
}
}
+206
View File
@@ -0,0 +1,206 @@
//! Input file access.
//!
//! Every input file is read once and kept in memory for the whole link,
//! because sections, symbol names and relocation tables of object files
//! are used until the output is written. Files are therefore leaked with a
//! `'static` lifetime rather than tracked with reference counts, which
//! keeps lifetimes out of every data structure that refers to file
//! contents.
use std::collections::HashMap;
use std::fs::File;
use std::io::{self, Read};
use std::os::unix::fs::MetadataExt;
use std::path::{Path, PathBuf};
use std::sync::{Mutex, OnceLock};
use crate::error;
use crate::error::RawPath;
use crate::fatal;
/// Opens are memoized by path: a file named twice (a library on the
/// command line and in an auto-link option, an archive listed
/// repeatedly) gets one mapping, which also lets downstream caches key
/// by data address.
static FILE_CACHE: Mutex<Option<HashMap<PathBuf, &'static MappedFile>>> = Mutex::new(None);
/// The contents of the files opened so far, by device and inode, size
/// and modification time (the inode of a file deleted since may be
/// another's): a file reached by another path shares them, and so do
/// the caches keyed by data address. An SDK framework's stub is reached
/// by two: `-framework Foundation` finds Foundation.framework/
/// Foundation.tbd, a symbolic link to the Versions/C/Foundation.tbd that
/// a re-exported install name finds, and tapi::parse_cached parses the
/// 6 MB file once. A file is read by the first to open it while those
/// opening it by another path wait.
type Contents = HashMap<(u64, u64, u64, i64, i64), &'static OnceLock<&'static [u8]>>;
static CONTENTS: Mutex<Option<Contents>> = Mutex::new(None);
// Files up to this size are read into malloc'ed memory rather than
// mmap'ed. mmap(2) takes the process's address space lock, so with tens
// of thousands of input files, the calls serialize at a few microseconds
// each no matter how many threads make them. read(2) takes no such lock,
// but copying costs memory bandwidth in proportion to the file size, so
// large files are still mmap'ed.
const READ_THRESHOLD: u64 = 32 * 1024;
/// The `size` bytes of an open file, read or mapped.
fn read_contents(file: &File, size: u64, path: &Path) -> &'static [u8] {
let display = path.raw();
if size == 0 {
&[]
} else if size <= READ_THRESHOLD {
let mut buf = Vec::with_capacity(size as usize);
file.take(size)
.read_to_end(&mut buf)
.unwrap_or_else(|e| fatal!("{display}: read failed: {e}"));
if buf.len() as u64 != size {
fatal!("{display}: file is shorter than its reported size");
}
Vec::leak(buf)
} else {
// SAFETY: the mapping outlives every reference (it is
// leaked), and linkers conventionally assume inputs are
// not modified during the link.
let map = unsafe { memmap2::Mmap::map(file) }
.unwrap_or_else(|e| fatal!("{display}: mmap failed: {e}"));
Box::leak(Box::new(map))
}
}
// MappedFile represents an input file that is either mmap'ed or read into
// memory. Either way, its contents are accessible through `data()`.
#[derive(Debug)]
pub struct MappedFile {
pub name: PathBuf,
/// The bytes, deliberately leaked with the input file.
pub(crate) data: &'static [u8],
/// The archive this file is a member of.
pub parent: Option<&'static Self>,
/// The modification time to note for the file in debug stabs when
/// it is not the one of a file on disk: an archive member's, as
/// its header records it, or 0 for the object LTO compiled in
/// memory.
pub mtime: Option<u64>,
}
impl MappedFile {
/// A path that is not a regular file (a framework directory, say)
/// reads as not found.
fn open_impl(path: &Path) -> io::Result<&'static Self> {
if let Some(&mf) = FILE_CACHE.lock().unwrap().get_or_insert_with(HashMap::new).get(path) {
return Ok(mf);
}
let file = File::open(path)?;
let metadata = file.metadata()?;
if !metadata.is_file() {
return Err(io::Error::from(io::ErrorKind::NotFound));
}
let md = &metadata;
let id = (md.dev(), md.ino(), md.size(), md.mtime(), md.mtime_nsec());
let cell: &'static OnceLock<&'static [u8]> = CONTENTS
.lock()
.unwrap()
.get_or_insert_with(HashMap::new)
.entry(id)
.or_insert_with(|| Box::leak(Box::new(OnceLock::new())));
let data = *cell.get_or_init(|| read_contents(&file, metadata.len(), path));
let mf: &'static Self =
Box::leak(Box::new(Self { name: path.to_path_buf(), data, parent: None, mtime: None }));
FILE_CACHE.lock().unwrap().get_or_insert_with(HashMap::new).insert(path.to_path_buf(), mf);
Ok(mf)
}
/// Opens a file, returning `None` if it doesn't exist. Any other
/// failure - permission denied, an unmappable file - is reported
/// with the operating system's own words rather than as "not
/// found".
pub fn open(path: impl AsRef<Path>) -> Option<&'static Self> {
let path = path.as_ref();
match Self::open_impl(path) {
Ok(mf) => Some(mf),
Err(e) if e.kind() == io::ErrorKind::NotFound => None,
Err(e) => fatal!("cannot open {}: {e}", path.raw()),
}
}
/// Opens a file, handing any failure to the caller to word.
pub fn try_open(path: impl AsRef<Path>) -> io::Result<&'static Self> {
Self::open_impl(path.as_ref())
}
/// Opens a file that must exist.
pub fn must_open(path: impl AsRef<Path>) -> &'static Self {
let path = path.as_ref();
Self::open_impl(path).unwrap_or_else(|e| fatal!("cannot open {}: {e}", path.raw()))
}
/// Returns a view of a member of this archive (or of a fat file).
pub fn slice(&'static self, name: PathBuf, start: usize, size: usize) -> &'static Self {
self.part(name, start, size, None)
}
/// An archive member, with the modification time its header records.
pub fn member(
&'static self,
name: PathBuf,
start: usize,
size: usize,
date: u64,
) -> &'static Self {
self.part(name, start, size, Some(date))
}
fn part(
&'static self,
name: PathBuf,
start: usize,
size: usize,
mtime: Option<u64>,
) -> &'static Self {
assert!(start <= self.size() && size <= self.size() - start);
Box::leak(Box::new(Self {
name,
data: &self.data[start..start + size],
parent: Some(self),
mtime,
}))
}
/// A file the linker made in memory, under the name of the input it
/// stands for (see mergeable::synthesize_object).
pub fn synthesized(name: PathBuf, data: Vec<u8>) -> &'static Self {
let data = Vec::leak(data);
Box::leak(Box::new(Self { name, data, parent: None, mtime: None }))
}
pub fn size(&self) -> usize {
self.data.len()
}
#[inline]
pub fn data(&self) -> &'static [u8] {
self.data
}
}
/// The words for a file MappedFile::try_open failed on with `e`. Every
/// input is read whole, and an empty one refused, so a file that is
/// there but no regular one (which MappedFile takes for none) is one
/// that can't be mapped - a directory - or an empty one.
pub fn unreadable_file(path: &Path, e: &std::io::Error) -> error::Message {
let p = path.raw();
let found = std::fs::metadata(path).ok().filter(|_| e.kind() == std::io::ErrorKind::NotFound);
match found {
Some(md) if md.len() == 0 => b"file is empty".to_vec(),
Some(_) => {
let e = std::io::Error::from_raw_os_error(libc::EINVAL);
let errno = crate::error::strerror(&e);
error::render(format_args!("cannot map {p}: {errno}"))
}
None => {
let errno = crate::error::strerror(e);
error::render(format_args!("cannot open {p}: {errno}"))
}
}
}
+749
View File
@@ -0,0 +1,749 @@
//! Mergeable dylibs: a dylib ld-prime links with -make_mergeable keeps
//! a record of the subsections and symbols of its objects, which a
//! later link's -merge_framework, -merge_library or -merge-l takes in
//! place of a load command.
//!
//! LC_ATOM_INFO points at the record, a blob in __LINKEDIT in a format
//! of ld-prime's own (magic "nldprecr", file version 3): a header, then
//! tables of 40-byte entries, 16-byte fixups, large addends, custom
//! sections, symbol names, the dylib's own identity and the dylibs it
//! links, debug notes, and pools of strings and contents. An entry
//! stands for a subsection, a symbol or an import of the objects. Its
//! content is mostly the dylib's own linked bytes, which a negative
//! offset from the content pool reaches back to, so its fixups have to
//! be applied again: a pointer holds a chained fixup there, an
//! instruction its final immediate, a GOT load ld-prime relaxed an add
//! (a leaq on x86-64), while the fixup records what the object had.
//! Compact unwind records, which have no place in the image, are in the
//! content pool as the objects had them.
//!
//! This module has the format and reads the record; to merge, mold
//! turns the entries back into the object file they stand for (see
//! object.rs) and links that. make_mergeable writes the record in a
//! dylib linked with -make_mergeable.
use std::path::Path;
use crate::input_files::load_commands;
use crate::macho::*;
use crate::mapped_file::MappedFile;
use crate::util::{read16, read32, read64, sign_extend};
mod object;
pub use object::synthesize_object;
/// What ld-prime's -make_mergeable writes: a record whose entries
/// point into the image around it ("nldpatom" and "nldprdcr" are the
/// plain and the image-backed kinds it reads besides).
pub(crate) const MAGIC: &[u8; 8] = b"nldprecr";
/// The record header's size in file version 3; the entries follow it
/// directly.
pub(crate) const HEADER_SIZE: usize = 0xf8;
pub(crate) const ENTRY_SIZE: usize = 40;
pub(crate) const FIXUP_SIZE: usize = 16;
pub(crate) const SECTION_SIZE: usize = 44;
pub(crate) const DYLIB_INFO_SIZE: usize = 0x88;
pub(crate) const DEBUG_INFO_SIZE: usize = 0x98;
/// Where the header has its fields: the target, the flags, the place of
/// the image around the record, and the tables, each by its offset in
/// the record and its count (or a pool's size) after it.
pub(crate) mod header {
pub const CPUTYPE: usize = 0x14;
pub const CPUSUBTYPE: usize = 0x18;
pub const PLATFORM: usize = 0x1c;
pub const MINOS: usize = 0x20;
pub const SDK: usize = 0x24;
pub const FLAGS: usize = 0x28;
/// The image's offset from the record (negative), and its size.
pub const IMAGE: usize = 0x54;
/// The record's own size.
pub const SIZE: usize = 0x5c;
pub const ENTRIES: usize = 0x60;
pub const FIXUPS: usize = 0x68;
pub const LARGE_ADDENDS: usize = 0x70;
pub const SECTIONS: usize = 0x78;
pub const LINKER_OPTIONS: usize = 0x80;
pub const NAMES: usize = 0x88;
pub const NAME_POOL: usize = 0x90;
pub const CONTENT_POOL: usize = 0x98;
pub const CSTRING_POOL: usize = 0xa0;
/// The dylib's own info: its offset and size.
pub const OWN_DYLIB: usize = 0xa8;
/// The infos of the dylibs it links, and their total size after the
/// count.
pub const DYLIBS: usize = 0xb0;
/// The debug notes, and their total size after the count.
pub const DEBUG_INFOS: usize = 0xbc;
}
/// An entry's kind (bits 3-7 of its flags word).
pub mod kind {
pub const REGULAR: u8 = 0;
pub const WEAK_DEF: u8 = 1;
pub const RESOLVER: u8 = 2;
pub const ANON: u8 = 3;
pub const ANON_COAL_BY_CONTENT: u8 = 4;
pub const TENTATIVE_DEF: u8 = 5;
pub const ABSOLUTE: u8 = 6;
pub const DYLIB_EXPORT: u8 = 7;
pub const DYLIB_EXPORT_WEAK_DEF: u8 = 8;
pub const DYLIB_EXPORT_FORCE_LOAD: u8 = 9;
pub const UNDEFINE: u8 = 10;
pub const UNDEFINE_WEAK_IMPORT: u8 = 11;
pub const ALIAS: u8 = 12;
pub const ANON_PLACEHOLDER: u8 = 18;
pub const WEAK_DEF_ALIAS: u8 = 19;
}
/// An entry's scope (bits 0-2 of its flags word).
pub mod scope {
/// Local to its object.
pub const LOCAL: u8 = 0;
pub const HIDDEN: u8 = 1;
pub const AUTO_HIDE: u8 = 2;
pub const GLOBAL: u8 = 3;
pub const NEVER_STRIP: u8 = 4;
}
/// The content types (bits 8-14 of an entry's flags word) the reader
/// or the writer names; STANDARD_SECTIONS has the rest.
pub(crate) mod ctype {
/// No bytes: an alias, an import.
pub const NONE: u8 = 1;
pub const METHOD_NAME: u8 = 12;
pub const METHOD_LIST: u8 = 15;
pub const DATA: u8 = 27;
pub const CFI: u8 = 31;
pub const COMPACT_UNWIND: u8 = 32;
pub const CLASS_REF: u8 = 33;
pub const SELECTOR_REF: u8 = 35;
/// __objc_classlist and __objc_nlclslist.
pub const CLASS_LISTS: [u8; 2] = [40, 44];
/// __objc_catlist, __objc_nlcatlist and __objc_catlist2.
pub const CATEGORY_LISTS: [u8; 3] = [41, 45, 70];
pub const OBJC_IMAGE_INFO: u8 = 43;
pub const INIT_OFFSET: u8 = 55;
/// A section of the custom table.
pub const CUSTOM: u8 = 63;
pub const COMMON: u8 = 66;
}
/// The fixup kinds (ld-prime's Fixup::Kind), generic and per target.
pub(crate) mod fk {
pub const KEEP_ALIVE: u16 = 1;
pub const PTR64: u16 = 2;
pub const PTR32: u16 = 3;
pub const DIFF32: u16 = 4;
pub const DIFF64: u16 = 5;
pub const IMAGE_OFFSET32: u16 = 6;
pub const PCREL32_TO_GOT: u16 = 7;
pub const PCREL_DELTA32: u16 = 8;
pub const SWIFT_REL32_TO_GOT: u16 = 9;
pub const ALIAS_OF: u16 = 11;
pub const TLV_OFFSET: u16 = 12;
pub const PTR64_TO_GOT: u16 = 14;
pub const ARM64_B26: u16 = 0x80;
pub const ARM64_B26_ADDEND: u16 = 0x81;
pub const ARM64_ADRP: u16 = 0x82;
pub const ARM64_ADRP_ADDEND: u16 = 0x83;
pub const ARM64_LO12: u16 = 0x84;
pub const ARM64_LO12_ADDEND: u16 = 0x85;
pub const ARM64_ADRP_GOT: u16 = 0x86;
pub const ARM64_LD12_GOT: u16 = 0x87;
pub const ARM64_ADRP_LO12: u16 = 0x88;
pub const ARM64_ADRP_LO12_ADDEND: u16 = 0x89;
pub const ARM64_ADRP_LDR_GOT: u16 = 0x8a;
pub const ARM64_ADRP_TLV: u16 = 0x8b;
pub const ARM64_LD12_TLV: u16 = 0x8c;
pub const ARM64_AUTH_PTR: u16 = 0x8d;
pub const ARM64_ADD_GOT: u16 = 0x8e;
pub const ARM64_ADRP_ADD_GOT: u16 = 0x8f;
pub const ARM64_ADRP_GOT_NO_OPT: u16 = 0x90;
pub const ARM64_LD12_GOT_NO_OPT: u16 = 0x91;
pub const ARM64_ADRP_LDR_GOT_NO_OPT: u16 = 0x92;
pub const X86_64_CALL: u16 = 0x100;
pub const X86_64_RIP: u16 = 0x101;
pub const X86_64_RIP_GOT: u16 = 0x102;
pub const X86_64_RIP1: u16 = 0x103;
pub const X86_64_RIP2: u16 = 0x104;
pub const X86_64_RIP4: u16 = 0x105;
pub const X86_64_RIP_GOT_LOAD: u16 = 0x106;
pub const X86_64_RIP_TLV_LOAD: u16 = 0x107;
pub const X86_64_BRANCH8: u16 = 0x108;
pub const X86_64_RIP1_GOT: u16 = 0x109;
}
/// How a fixup kind uses its last word and where its addend is
/// (ld-prime's Fixup::KindInfo extrasUsage).
pub(crate) fn extras_usage(kind: u16) -> u8 {
use fk::*;
match kind {
0
| ARM64_B26
| ARM64_ADRP
| ARM64_ADRP_GOT
| ARM64_ADRP_TLV
| ARM64_ADRP_GOT_NO_OPT
| X86_64_RIP_GOT
| X86_64_RIP_GOT_LOAD
| X86_64_RIP_TLV_LOAD
| X86_64_RIP1_GOT => 0,
DIFF32 | DIFF64 => 1,
ARM64_AUTH_PTR => 3,
ARM64_B26_ADDEND | ARM64_ADRP_ADDEND => 4,
ARM64_LO12
| ARM64_LO12_ADDEND
| ARM64_LD12_GOT
| ARM64_LD12_TLV
| ARM64_ADD_GOT
| ARM64_LD12_GOT_NO_OPT => 5,
ARM64_ADRP_LO12
| ARM64_ADRP_LO12_ADDEND
| ARM64_ADRP_LDR_GOT
| ARM64_ADRP_ADD_GOT
| ARM64_ADRP_LDR_GOT_NO_OPT => 7,
_ => 2,
}
}
/// An entry of the record: a subsection, a symbol or an import of the
/// mergeable dylib's objects. `C` and `F` are where its bytes and its
/// fixups are: for the reader, the bytes (none for zero fill, or what
/// the linker made) and a range of MergeableRecord::fixups; for the
/// writer, see make_mergeable::OutEntry.
#[derive(Clone, Debug)]
pub struct Entry<C = Option<&'static [u8]>, F = std::ops::Range<usize>> {
pub name: Option<&'static [u8]>,
pub scope: u8,
pub kind: u8,
pub content_type: u8,
pub cold: bool,
/// Live if what refers to it is (an unwind record, an alias).
pub dds_if_refs_live: bool,
pub no_dead_strip: bool,
/// An import's strength: 1 a weak import, 2 a strong one, 0 none
/// given.
pub import: u8,
/// An index into the custom sections.
pub custom_section: Option<u8>,
pub size: u32,
pub content: C,
/// An import's library, by index into the dylibs the mergeable one
/// links.
pub dylib: Option<u8>,
pub p2align: u8,
pub modulus: u16,
/// 1-based index into the debug notes, 0 for none.
pub debug: u16,
pub fixups: F,
}
/// An entry's name's index in its 40 bytes when it has none.
const NO_NAME: u32 = 0xff_ffff;
impl<C, F> Entry<C, F> {
/// The flags word: the scope (bits 0-2), kind (3-7) and content
/// type (8-14), whether it is cold (15), live if what refers to it
/// is (16) or never dead stripped (17), an import's strength
/// (19-20) and the custom section (21-28, 0xff for none).
fn flags(&self) -> u32 {
self.scope as u32
| (self.kind as u32) << 3
| (self.content_type as u32) << 8
| (self.cold as u32) << 15
| (self.dds_if_refs_live as u32) << 16
| (self.no_dead_strip as u32) << 17
| (self.import as u32) << 19
| (self.custom_section.unwrap_or(0xff) as u32) << 21
}
/// Writes the entry's 40 bytes (see Reader::entry): its index, the
/// count and first index of its fixups, its name's index among the
/// named entries, its flags word, its size and its content's offset
/// from the content pool (-1 for none), then its import's library
/// (0xff for none), alignment, modulus and debug notes.
pub(crate) fn write(
&self,
out: &mut [u8],
index: u32,
fixups: (u32, u32),
name: Option<u32>,
content: i32,
) {
out[0..4].copy_from_slice(&index.to_le_bytes());
out[4..8].copy_from_slice(&fixups.0.to_le_bytes());
out[8..12].copy_from_slice(&fixups.1.to_le_bytes());
out[12..16].copy_from_slice(&name.unwrap_or(NO_NAME).to_le_bytes());
out[16..20].copy_from_slice(&self.flags().to_le_bytes());
out[20..24].copy_from_slice(&self.size.to_le_bytes());
out[24..28].copy_from_slice(&content.to_le_bytes());
out[0x1c] = self.dylib.unwrap_or(0xff);
out[0x1d] = self.p2align;
out[0x1e..0x20].copy_from_slice(&self.modulus.to_le_bytes());
out[0x20..0x22].copy_from_slice(&self.debug.to_le_bytes());
}
}
/// A fixup of an entry: its place, target and addend, and what its kind
/// carries besides - the entry subtracted (a difference), or an arm64
/// page offset's access size and, for the fused pairs, the distance to
/// the second instruction. `T` refers to an entry: by its index in the
/// record, or as the writer does until it numbers the entries.
#[derive(Clone, Copy, Debug)]
pub struct Fixup<T = u32> {
pub offset: u32,
pub target: T,
pub kind: u16,
pub addend: i64,
pub from: Option<T>,
/// How many bytes a page offset's load or store moves (1 for an add).
pub scale: u8,
/// The second instruction's distance from the first, in instructions.
pub second: u8,
}
/// Bit 10 of a fixup's third word: bits 11-31 are the index of its
/// addend among the large ones.
const LARGE_ADDEND: u32 = 0x400;
impl Fixup {
/// Reads a fixup's 16 bytes: its offset and target, then its kind
/// (bits 0-9) and its addend (bits 11-31, or see LARGE_ADDEND), and
/// a last word its kind uses as extras_usage says: for the rest of
/// the addend, for the entry subtracted, or for the second
/// instruction's distance (byte 0) and the access size (byte 1).
fn read(c: &[u8], large_addends: &[i64]) -> Self {
let (w2, w3) = (read32(&c[8..]), read32(&c[12..]));
let kind = (w2 & 0x3ff) as u16;
let usage = extras_usage(kind);
let addend = if w2 & LARGE_ADDEND != 0 {
large_addends[(w2 >> 11) as usize]
} else {
match usage {
2 => w3 as i32 as i64,
3 => sign_extend(((w2 >> 11) | ((w3 >> 25) << 21)) as u64, 28),
4..=8 => sign_extend(((w2 >> 11) | ((w3 >> 16) << 21)) as u64, 32),
_ => sign_extend((w2 >> 11) as u64, 21),
}
};
let arm64 = (4..=8).contains(&usage);
Self {
offset: read32(c),
target: read32(&c[4..]),
kind,
addend,
from: (usage == 1).then_some(w3),
scale: if arm64 { (w3 >> 8) as u8 } else { 0 },
second: if arm64 { w3 as u8 } else { 0 },
}
}
/// Writes the fixup's 16 bytes (see read). An addend too large for
/// its kind's bits goes among the `large` ones, each value once.
pub(crate) fn write(&self, out: &mut [u8], large: &mut Vec<i64>) {
let usage = extras_usage(self.kind);
let kind = self.kind as u32;
let addend = self.addend;
let fits = |bits: u32| addend >= -(1 << (bits - 1)) && addend < 1 << (bits - 1);
let extras = match usage {
1 => self.from.unwrap_or(0),
4..=8 => (self.scale as u32) << 8 | self.second as u32,
_ => 0,
};
let (low, high) = (addend as u32 & 0x1f_ffff, addend as u32 >> 21);
let (w2, w3) = match usage {
2 if fits(32) => (kind, addend as u32),
4..=8 if fits(32) => (kind | low << 11, extras | high << 16),
0 | 1 | 3 if fits(21) => (kind | low << 11, extras),
_ => {
let i = large.iter().position(|&v| v == addend).unwrap_or_else(|| {
large.push(addend);
large.len() - 1
});
(kind | LARGE_ADDEND | (i as u32) << 11, extras)
}
};
out[0..4].copy_from_slice(&self.offset.to_le_bytes());
out[4..8].copy_from_slice(&self.target.to_le_bytes());
out[8..12].copy_from_slice(&w2.to_le_bytes());
out[12..16].copy_from_slice(&w3.to_le_bytes());
}
}
/// A section that is none of ld-prime's standard ones.
#[derive(Clone, Debug)]
pub struct CustomSection {
pub segname: [u8; 16],
pub sectname: [u8; 16],
pub flags: u32,
}
/// A dylib's identity, as -make_mergeable records the mergeable dylib
/// and each dylib it links.
#[derive(Clone, Debug, Default)]
pub struct DylibInfo {
pub install_name: Vec<u8>,
pub current_version: u32,
pub compatibility_version: u32,
}
/// What a debug note (N_SO, N_OSO) says of the object an entry came from.
#[derive(Clone, Debug)]
pub struct DebugInfo {
pub mtime: u32,
pub source_dir: Vec<u8>,
pub source_name: Vec<u8>,
pub object_path: Vec<u8>,
}
/// A mergeable dylib's record, which LC_ATOM_INFO points at.
pub struct MergeableRecord {
pub cputype: u32,
pub cpusubtype: u32,
pub platform: u32,
pub minos: u32,
pub sdk: u32,
/// The record's flags word: the Swift versions of the objects'
/// __objc_imageinfo (bits 0-23), MH_SUBSECTIONS_VIA_SYMBOLS (24),
/// whether there was an image info (26), signed class ro data (28),
/// category class properties (29) and classes (31).
pub flags: u64,
pub entries: Vec<Entry>,
pub fixups: Vec<Fixup>,
pub sections: Vec<CustomSection>,
/// The mergeable dylib's own identity.
pub own: DylibInfo,
pub dylibs: Vec<DylibInfo>,
pub debug_infos: Vec<DebugInfo>,
}
/// A dylib a mergeable dylib links, which the merging image loads in
/// its place: by the install name and versions recorded, exporting
/// what the entries import from it.
pub struct Dependency {
pub path: std::path::PathBuf,
pub info: DylibInfo,
pub exports: Vec<&'static [u8]>,
pub weak_exports: Vec<&'static [u8]>,
}
/// A mergeable dylib merged into the image: its install name, the OS
/// version it was built for, and the object its record makes.
pub struct MergedLibrary {
pub install_name: Vec<u8>,
pub minos: u32,
pub obj: &'static crate::mapped_file::MappedFile,
}
/// Bit 31 of the record's flags: the dylib defines Objective-C or
/// Swift classes, for which ld-prime adds its hook to a merging image.
pub const FLAG_HAS_CLASSES: u64 = 1 << 31;
pub(crate) const FLAG_HAS_OBJC_INFO: u64 = 1 << 26;
pub(crate) const FLAG_SIGNED_CLASS_RO: u64 = 1 << 28;
pub(crate) const FLAG_CATEGORY_CLASS_PROPERTIES: u64 = 1 << 29;
/// Where a dylib's LC_ATOM_INFO data is in its file, if it has one.
fn record_range(data: &[u8]) -> Option<(usize, usize)> {
let (_, cmd) = load_commands(data).find(|&(cmd, _)| cmd == LC_ATOM_INFO)?;
let cmd = LinkEditDataCommand::read_from(cmd);
Some((cmd.dataoff as usize, cmd.datasize as usize))
}
impl MergeableRecord {
/// Reads the record of a mergeable dylib (see
/// input_files::is_mergeable) as -make_mergeable wrote it: file
/// version 3, the entries right after the header. A broken one may
/// make it panic.
pub fn read(mf: &'static MappedFile) -> Self {
let file = mf.data();
let (base, size) = record_range(file).unwrap();
let blob = &file[base..base + size];
let r = Reader { file, base, blob };
let symbols = r.symbol_names();
let fixups = r.fixups();
let sections: Vec<CustomSection> = r
.array(header::SECTIONS, SECTION_SIZE)
.chunks(SECTION_SIZE)
.map(|c| CustomSection {
segname: c[8..24].try_into().unwrap(),
sectname: c[26..42].try_into().unwrap(),
flags: read32(&c[4..]),
})
.collect();
let (own, _) = r.dylib_info(r.table(header::OWN_DYLIB).0);
let (at, count) = r.table(header::DYLIBS);
let dylibs = r.dylib_infos(at, count);
let debug_infos = r.debug_infos();
let entries = r.entries(&symbols);
Self {
cputype: read32(&blob[header::CPUTYPE..]),
cpusubtype: read32(&blob[header::CPUSUBTYPE..]),
platform: read32(&blob[header::PLATFORM..]),
minos: read32(&blob[header::MINOS..]),
sdk: read32(&blob[header::SDK..]),
flags: read64(&blob[header::FLAGS..]),
entries,
fixups,
sections,
own,
dylibs,
debug_infos,
}
}
/// The dylibs the mergeable one links, each with the symbols its
/// entries import from it.
pub fn dependencies(&self, path: &Path) -> Vec<Dependency> {
let mut deps: Vec<Dependency> = self
.dylibs
.iter()
.map(|info| Dependency {
path: path.to_path_buf(),
info: info.clone(),
exports: Vec::new(),
weak_exports: Vec::new(),
})
.collect();
for entry in &self.entries {
let (Some(dylib), Some(name)) = (entry.dylib, entry.name) else { continue };
let dep = &mut deps[dylib as usize];
dep.exports.push(name);
if entry.kind == kind::DYLIB_EXPORT_WEAK_DEF {
dep.weak_exports.push(name);
}
}
deps
}
/// Whether the dylib's objects define Objective-C (or Swift)
/// classes, as ld-prime records it.
pub fn defines_classes(&self) -> bool {
self.flags & FLAG_HAS_CLASSES != 0
}
}
/// Access to the record's tables.
struct Reader<'a> {
file: &'static [u8],
base: usize,
blob: &'a [u8],
}
impl Reader<'_> {
/// A table's offset in the record and its count (or a pool's
/// size), which the header has at `field`.
fn table(&self, field: usize) -> (usize, usize) {
(read32(&self.blob[field..]) as usize, read32(&self.blob[field + 4..]) as usize)
}
/// The bytes of a table of `elem`-byte elements (see table).
fn array(&self, field: usize, elem: usize) -> &[u8] {
let (off, count) = self.table(field);
&self.blob[off..off + count * elem]
}
/// The `len` bytes `rel` bytes from blob offset `at`, which may
/// reach back into the dylib around the blob.
fn string_at(&self, at: usize, rel: i64, len: usize) -> &'static [u8] {
let start = ((self.base + at) as i64 + rel) as usize;
&self.file[start..start + len]
}
/// A CStringRO_1: an offset from the record, then a length.
fn cstring(&self, at: usize) -> Vec<u8> {
let len = read64(&self.blob[at + 8..]) as usize;
if len == 0 {
return Vec::new();
}
self.string_at(at, read64(&self.blob[at..]) as i64, len).to_vec()
}
fn symbol_names(&self) -> Vec<&'static [u8]> {
let (first, count) = self.table(header::NAMES);
(0..count)
.map(|i| {
let at = first + i * 16;
let len = (read64(&self.blob[at + 8..]) >> 44) as usize;
self.string_at(at, read64(&self.blob[at..]) as i64, len)
})
.collect()
}
/// The fixups, and the addends too large for theirs.
fn fixups(&self) -> Vec<Fixup> {
let large: Vec<i64> =
self.array(header::LARGE_ADDENDS, 8).chunks(8).map(|c| read64(c) as i64).collect();
let table = self.array(header::FIXUPS, FIXUP_SIZE);
table.chunks(FIXUP_SIZE).map(|c| Fixup::read(c, &large)).collect()
}
/// A DylibFileInfoRO_2 at blob offset `at`, and its size.
fn dylib_info(&self, at: usize) -> (DylibInfo, usize) {
let info = DylibInfo {
install_name: self.cstring(at + 8),
current_version: read32(&self.blob[at..]),
compatibility_version: read32(&self.blob[at + 4..]),
};
let nplatforms = read32(&self.blob[at + 0x2c..]) as usize;
let lists: usize =
[0x34, 0x3c, 0x44].iter().map(|&f| read32(&self.blob[at + f..]) as usize).sum();
(info, DYLIB_INFO_SIZE + (4 * nplatforms).next_multiple_of(8) + 16 * lists)
}
/// `count` DylibFileInfoRO_2 records one after another from `at`.
fn dylib_infos(&self, at: usize, count: usize) -> Vec<DylibInfo> {
let mut out = Vec::with_capacity(count);
let mut pos = at;
for _ in 0..count {
let (info, size) = self.dylib_info(pos);
out.push(info);
pos += size;
}
out
}
fn debug_infos(&self) -> Vec<DebugInfo> {
let (first, count) = self.table(header::DEBUG_INFOS);
(0..count)
.map(|i| {
let at = first + i * DEBUG_INFO_SIZE;
DebugInfo {
mtime: read32(&self.blob[at..]),
source_dir: self.cstring(at + 8),
source_name: self.cstring(at + 0x18),
object_path: self.cstring(at + 0x28),
}
})
.collect()
}
fn entries(&self, symbols: &[&'static [u8]]) -> Vec<Entry> {
let table = self.array(header::ENTRIES, ENTRY_SIZE);
table.chunks(ENTRY_SIZE).map(|c| self.entry(c, symbols)).collect()
}
/// Reads an entry's 40 bytes (see Entry::write), its name by its
/// index into `symbols`, its bytes from the content pool.
fn entry(&self, c: &[u8], symbols: &[&'static [u8]]) -> Entry {
let (nfixups, first_fixup) = (read32(&c[4..]) as usize, read32(&c[8..]) as usize);
let name = match read32(&c[12..]) {
NO_NAME => None,
n => Some(symbols[n as usize]),
};
let flags = read32(&c[16..]);
let size = read32(&c[20..]);
let (pool, _) = self.table(header::CONTENT_POOL);
let content = match read32(&c[24..]) as i32 {
-1 => None,
off => Some(self.string_at(pool, off as i64, size as usize)),
};
let custom = (flags >> 21) as u8;
Entry {
name,
scope: (flags & 7) as u8,
kind: ((flags >> 3) & 0x1f) as u8,
content_type: ((flags >> 8) & 0x7f) as u8,
cold: flags & (1 << 15) != 0,
dds_if_refs_live: flags & (1 << 16) != 0,
no_dead_strip: flags & (1 << 17) != 0,
import: ((flags >> 19) & 3) as u8,
custom_section: (custom != 0xff).then_some(custom),
size,
content,
dylib: (c[0x1c] != 0xff).then_some(c[0x1c]),
p2align: c[0x1d],
modulus: read16(&c[0x1e..]),
debug: read16(&c[0x20..]),
fixups: first_fixup..first_fixup + nfixups,
}
}
}
/// The sections ld-prime gives the entries of a content type (its
/// StandardSection::fromContentType): segment and section names and
/// Mach-O flags, as an object would have them.
const STANDARD_SECTIONS: &[(u8, &[u8], &[u8], u32)] = &[
(2, b"__TEXT", b"__text", TEXT),
(9, b"__TEXT", b"__const", 0),
(10, b"__TEXT", b"__cstring", S_CSTRING_LITERALS),
(11, b"__TEXT", b"__objc_classname", S_CSTRING_LITERALS),
(12, b"__TEXT", b"__objc_methname", S_CSTRING_LITERALS),
(13, b"__TEXT", b"__objc_methtype", S_CSTRING_LITERALS),
(14, b"__TEXT", b"__oslogstring", S_CSTRING_LITERALS),
(15, b"__TEXT", b"__objc_methlist", 0),
(16, b"__TEXT", b"__ustring", 0),
(17, b"__TEXT", b"__literal4", S_4BYTE_LITERALS),
(18, b"__TEXT", b"__literal8", S_8BYTE_LITERALS),
(19, b"__TEXT", b"__literal16", S_16BYTE_LITERALS),
// A slot of an object's GOT, which an object has as a regular
// section (one of non-lazy pointers is refused).
(22, b"__DATA", b"__got", 0),
(26, b"__DATA", b"__const", 0),
(27, b"__DATA", b"__data", 0),
(28, b"__DATA", b"__cfstring", 0),
(29, b"__DATA", b"__const_cfobj2", 0),
(30, b"__TEXT", b"__gcc_except_tab", 0),
(31, b"__TEXT", b"__eh_frame", EH_FRAME),
(32, b"__LD", b"__compact_unwind", S_ATTR_DEBUG),
(33, b"__DATA", b"__objc_classrefs", S_ATTR_NO_DEAD_STRIP),
(34, b"__DATA", b"__objc_superrefs", S_ATTR_NO_DEAD_STRIP),
(35, b"__DATA", b"__objc_selrefs", S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP),
(36, b"__DATA", b"__objc_protorefs", S_COALESCED | S_ATTR_NO_DEAD_STRIP),
(37, b"__DATA", b"__objc_ivar", 0),
(38, b"__DATA", b"__objc_data", 0),
(39, b"__DATA", b"__objc_const", 0),
(40, b"__DATA", b"__objc_classlist", S_ATTR_NO_DEAD_STRIP),
(41, b"__DATA", b"__objc_catlist", S_ATTR_NO_DEAD_STRIP),
(42, b"__DATA", b"__objc_protolist", S_COALESCED),
(43, b"__DATA", b"__objc_imageinfo", 0),
(44, b"__DATA", b"__objc_nlclslist", S_ATTR_NO_DEAD_STRIP),
(45, b"__DATA", b"__objc_nlcatlist", S_ATTR_NO_DEAD_STRIP),
(46, b"__DATA", b"__objc_intobj", 0),
(47, b"__DATA", b"__objc_floatobj", 0),
(48, b"__DATA", b"__objc_doubleobj", 0),
(49, b"__DATA", b"__objc_dateobj", 0),
(50, b"__DATA", b"__objc_dictobj", 0),
(51, b"__DATA", b"__objc_arrayobj", 0),
(52, b"__DATA", b"__objc_arraydata", 0),
(53, b"__DATA", b"__mod_init_func", S_MOD_INIT_FUNC_POINTERS),
(54, b"__DATA", b"__mod_term_func", S_MOD_TERM_FUNC_POINTERS),
// An initializer offset the dylib's link made of a pointer is the
// pointer again (see object::Synth::place).
(55, b"__DATA", b"__mod_init_func", S_MOD_INIT_FUNC_POINTERS),
(56, b"__TEXT", b"__StaticInit", TEXT),
(57, b"__DATA", b"__thread_vars", S_THREAD_LOCAL_VARIABLES),
(58, b"__DATA", b"__thread_ptrs", S_THREAD_LOCAL_VARIABLE_POINTERS),
(64, b"__DATA", b"__thread_data", S_THREAD_LOCAL_REGULAR),
(65, b"__DATA", b"__thread_bss", S_THREAD_LOCAL_ZEROFILL),
(66, b"__DATA", b"__common", S_ZEROFILL),
(67, b"__DATA", b"__bss", S_ZEROFILL),
(70, b"__DATA", b"__objc_catlist2", S_ATTR_NO_DEAD_STRIP),
(72, b"__DATA", b"__objc_clsrolist", S_ATTR_NO_DEAD_STRIP),
(73, b"__LD", b"__func_variants", 0),
];
const TEXT: u32 = S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS;
const EH_FRAME: u32 = S_COALESCED | S_ATTR_NO_TOC | S_ATTR_STRIP_STATIC_SYMS | S_ATTR_LIVE_SUPPORT;
/// The standard section of a content type: its segment and section
/// names and flags.
pub(crate) fn standard_section(ct: u8) -> Option<(&'static [u8], &'static [u8], u32)> {
let &(_, seg, sect, flags) = STANDARD_SECTIONS.iter().find(|s| s.0 == ct)?;
Some((seg, sect, flags))
}
/// The content type of the standard section an object's section is, by
/// its names and type: a section of initializer pointers is one of
/// them, not of the offsets made of them.
pub(crate) fn standard_content_type(hdr: &MachSection) -> Option<u8> {
let &(ct, ..) = STANDARD_SECTIONS.iter().find(|&&(ct, seg, sect, flags)| {
ct != ctype::INIT_OFFSET
&& hdr.segname() == seg
&& hdr.sectname() == sect
&& hdr.section_type() == flags & SECTION_TYPE
})?;
Some(ct)
}
File diff suppressed because it is too large. Load diff
+1666
View File
File diff suppressed because it is too large. Load diff
+283
View File
@@ -0,0 +1,283 @@
//! Output file writing.
//!
//! Unlike mold for ELF, the output is built in an anonymous buffer and
//! written with pwrite, not through a shared mapping of the file. That
//! is deliberate: on macOS, a vnode that has ever had a writable shared
//! mapping fails ad-hoc code-signature validation at exec time - the
//! binary is killed with SIGKILL even though codesign reports it valid
//! on disk, msync changes nothing, and rename()ing a mapped-written temp
//! file over the destination does not help because the taint follows
//! the vnode. clonefile() of the temp file does yield a fresh vnode
//! that executes, but every way of making mapped-written pages visible
//! to it - clonefile, msync, munmap - is a synchronous per-page push
//! about half as fast as pwrite's cluster write, which has reached the
//! drive by the time it returns (a following fsync is ~2ms for 195MB).
//! LLVM's FileOutputBuffer falls back to in-memory buffers for
//! executables on Darwin for the same reason.
//!
//! What can be had instead is overlap. The write is bound by the
//! kernel's page-cache and drive path, not by our CPU work, so a byte
//! range is handed to writer threads as soon as nothing will modify it
//! again, while the symbol table, the page hashes and the signature are
//! still being produced on the other cores; finish() waits for the last
//! block.
use std::ffi::CString;
use std::io;
use std::ops::Range;
use std::os::unix::fs::{FileExt, FileTypeExt, OpenOptionsExt};
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::mpsc::{Sender, channel};
use std::sync::{Arc, Mutex};
use std::thread::JoinHandle;
use crate::error::RawPath;
use crate::error::strerror;
use crate::fatal;
/// The output path of the in-progress link, removed on a fatal error or
/// a crash so that a failed link doesn't leave a partial file behind.
static OUTPUT_PATH: AtomicPtr<libc::c_char> = AtomicPtr::new(std::ptr::null_mut());
fn set_output_path(path: Option<&Path>) {
// Published paths live until process exit: a signal on another thread
// may still be using the old pointer when this registration changes.
let ptr = path.map_or(std::ptr::null_mut(), |path| {
CString::new(path.as_os_str().as_encoded_bytes())
.expect("output path contains NUL")
.into_raw()
});
OUTPUT_PATH.store(ptr, Ordering::Release);
}
/// Opens the output file, returning it and whether this link created
/// it. An existing file is removed first, so that a fresh file takes
/// `mode` (less the umask: 0777 for an image, 0644 for an object), but
/// a character device (/dev/null), which is written in place, as is a
/// file that can't be removed (in a directory the link may not write),
/// which keeps its mode.
fn open(path: &Path, mode: u32) -> (std::fs::File, bool) {
if !std::fs::metadata(path).is_ok_and(|m| m.file_type().is_char_device()) {
let _ = std::fs::remove_file(path);
}
let created = std::fs::symlink_metadata(path).is_err();
let file = std::fs::OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(mode)
.open(path)
.unwrap_or_else(|e| fatal!("cannot open {}: {}", path.raw(), strerror(&e)));
(file, created)
}
/// Returns the length of the buffer that holds a file of the given size.
/// No Rust object can be larger than isize::MAX bytes, which limits the
/// output to 2 GiB on 32-bit hosts.
pub fn buffer_len(path: &Path, size: u64) -> usize {
if isize::try_from(size).is_err() {
fatal!("{}: output file is too large for this host: {size} bytes", path.raw());
}
size as usize
}
fn write_error(path: &Path, e: &io::Error) -> ! {
fatal!("cannot write {}: {}", path.raw(), strerror(e))
}
/// Removes a partially written output file.
pub fn cleanup() {
let path = OUTPUT_PATH.swap(std::ptr::null_mut(), Ordering::AcqRel);
if !path.is_null() {
// SAFETY: path is a published, NUL-terminated string that is never
// freed. This is also called from a signal handler, so it must not
// lock, allocate or drop owned storage. unlink is signal-safe.
unsafe { libc::unlink(path) };
}
}
/// The output buffer as the writer threads see it: a bare pointer,
/// because the linking thread keeps its &mut to the buffer while ranges
/// of it are being written. A range is queued only once nothing will
/// write it again, so a writer's reads never overlap a write.
#[derive(Clone, Copy)]
struct SharedBuf {
ptr: *const u8,
len: usize,
}
// SAFETY: see the invariant above; the pointee outlives the OutputFile.
unsafe impl Send for SharedBuf {}
unsafe impl Sync for SharedBuf {}
impl SharedBuf {
/// The queued block at `off..off + n`.
fn block(&self, off: usize, n: usize) -> &[u8] {
assert!(off + n <= self.len);
// SAFETY: within the buffer, which outlives the writers, and
// not modified once queued.
unsafe { std::slice::from_raw_parts(self.ptr.add(off), n) }
}
}
/// Queued ranges are cut into blocks of this size so the writers share
/// them.
const BLOCK: usize = 8 << 20;
/// The page size of the file cache: 16 KiB on Apple silicon, and a
/// multiple of the 4 KiB pages elsewhere. Two pwrites to different bytes of one page must
/// not run at once: when neither covers the whole page, the kernel may
/// zero-fill the part of a newly cached page its write doesn't cover,
/// over the other's bytes. So the writers get only whole pages, and a
/// range's partial first and last pages are written once they are done.
const PAGE: usize = 1 << 14;
/// The writers mostly sit in the kernel, and the copy into the page
/// cache scales only a little with threads, but the exposed tail after
/// the last range is queued shrinks with more of them (debug clang:
/// 18ms with one writer, 5ms with four) and that outweighs the cores
/// they take from the work they overlap: four measured best on the
/// debug clang and nushell links, one a percent behind.
const WRITERS: usize = 4;
/// An output file being written by background threads while the buffer
/// is finished.
pub struct OutputFile {
path: PathBuf,
file: Arc<std::fs::File>,
buf: SharedBuf,
len: usize,
tx: Option<Sender<(usize, usize)>>,
threads: Vec<JoinHandle<io::Result<()>>>,
/// The partial pages at queued ranges' ends, written by finish().
edges: Mutex<Vec<(usize, usize)>>,
}
impl OutputFile {
/// Creates the output file for the `len`-byte buffer at `buf`, with
/// permissions `mode` less the umask (see open), and starts the
/// writers. The buffer must outlive the OutputFile, and a range must
/// not be modified after it has been queued.
pub fn open(path: &Path, mode: u32, buf: *const u8, len: usize) -> Self {
// An existing file is removed first (see open). Overwriting a
// running executable is an error on some systems, and on macOS
// the kernel caches code signature state per vnode, so a fresh
// file avoids stale-signature kills. Only a file this link
// created is removed again if it fails.
let (file, created) = open(path, mode);
if created {
set_output_path(Some(path));
}
if let Err(e) = file.set_len(len as u64) {
fatal!("cannot set the size of {}: {}", path.raw(), strerror(&e));
}
let file = Arc::new(file);
let (tx, rx) = channel::<(usize, usize)>();
let rx = Arc::new(Mutex::new(rx));
let shared = SharedBuf { ptr: buf, len };
let threads = (0..WRITERS)
.map(|_| {
let file = Arc::clone(&file);
let rx = Arc::clone(&rx);
std::thread::spawn(move || -> io::Result<()> {
loop {
let job = rx.lock().unwrap().recv();
let Ok((off, n)) = job else {
return Ok(());
};
// A method call captures the whole SharedBuf
// (a field alone would capture the bare pointer,
// which is not Send).
file.write_all_at(shared.block(off, n), off as u64)?;
}
})
})
.collect();
Self {
path: path.to_path_buf(),
file,
buf: shared,
len,
tx: Some(tx),
threads,
edges: Mutex::new(Vec::new()),
}
}
/// Queues the buffer's bytes at `off..off + len` for writing. They
/// must be final.
pub fn queue(&self, off: usize, len: usize) {
debug_assert!(off + len <= self.len);
let tx = self.tx.as_ref().unwrap();
let end = off + len;
// The whole pages go to the writers; the partial ones at either
// end wait for finish(), since a neighboring range may still be
// written into the same pages.
let lo = off.next_multiple_of(PAGE).min(end);
let hi = (end / PAGE * PAGE).max(lo);
let mut edges = self.edges.lock().unwrap();
if off < lo {
edges.push((off, lo - off));
}
if hi < end {
edges.push((hi, end - hi));
}
drop(edges);
let mut pos = lo;
while pos < hi {
let n = BLOCK.min(hi - pos);
// A closed channel means a writer failed; finish() reports it.
let _ = tx.send((pos, n));
pos += n;
}
}
/// Waits for every queued range to reach the file.
pub fn close(mut self) {
drop(self.tx.take());
for thread in self.threads.drain(..) {
match thread.join() {
Ok(Ok(())) => {}
Ok(Err(e)) => write_error(&self.path, &e),
Err(_) => fatal!("cannot write {}: writer thread panicked", self.path.raw()),
}
}
for &(off, n) in self.edges.get_mut().unwrap().iter() {
if let Err(e) = self.file.write_all_at(self.buf.block(off, n), off as u64) {
write_error(&self.path, &e);
}
}
set_output_path(None);
}
}
/// Writes a complete buffer: for output that is built in full before
/// anything can be written (-r, an object, which is not executable).
pub fn write(path: &Path, buf: &[u8]) {
let out = OutputFile::open(path, 0o644, buf.as_ptr(), buf.len());
out.queue(0, buf.len());
out.close();
}
/// Borrows several disjoint ranges of a buffer mutably at once.
pub fn split_ranges<'a>(buf: &'a mut [u8], ranges: &[Range<u64>]) -> Vec<&'a mut [u8]> {
let mut order: Vec<usize> = (0..ranges.len()).collect();
order.sort_by_key(|&i| ranges[i].start);
let mut result: Vec<Option<&'a mut [u8]>> = (0..ranges.len()).map(|_| None).collect();
let mut rest = buf;
let mut pos = 0u64;
for i in order {
let r = &ranges[i];
assert!(r.start >= pos, "overlapping output ranges");
rest.split_off_mut(..(r.start - pos) as usize).unwrap();
let slice = rest.split_off_mut(..(r.end - r.start) as usize).unwrap();
result[i] = Some(slice);
pos = r.end;
}
result.into_iter().map(|s| s.unwrap()).collect()
}
+5403
View File
File diff suppressed because it is too large. Load diff
+1757
View File
File diff suppressed because it is too large. Load diff
+1249
View File
File diff suppressed because it is too large. Load diff
+100
View File
@@ -0,0 +1,100 @@
//! Process management: forking a child to hide exit latency, and
//! signal handling that removes a partial output file.
use std::fs::File;
use std::io::{Read, Write};
use std::os::unix::io::{FromRawFd, OwnedFd};
use std::sync::Mutex;
static PIPE_WRITER: Mutex<Option<OwnedFd>> = Mutex::new(None);
// Exiting from a program with large memory usage is slow --
// it may take a few hundred milliseconds. To hide the latency,
// we fork a child and let it do the actual linking work.
pub fn fork_child() {
let mut pipefd = [0i32; 2];
// SAFETY: pipe initializes both descriptors on success; each then has
// exactly one owner in this process.
let (reader, writer) = unsafe {
if libc::pipe(pipefd.as_mut_ptr()) == -1 {
eprintln!("mold: pipe failed");
std::process::exit(1);
}
(OwnedFd::from_raw_fd(pipefd[0]), OwnedFd::from_raw_fd(pipefd[1]))
};
// SAFETY: this runs before the linker starts its worker threads. The
// parent only waits for completion and exits; the child continues linking.
unsafe {
let pid = libc::fork();
if pid == -1 {
eprintln!("mold: fork failed");
std::process::exit(1);
}
if pid > 0 {
// Parent
drop(writer);
if File::from(reader).read_exact(&mut [0u8]).is_ok() {
libc::_exit(0);
}
// If SIGCHLD is ignored, which is inherited across exec, a
// child that exits before waitpid begins is reaped
// automatically and waitpid fails. (macOS still gives the
// status to a parent already waiting, as this one mostly is.)
// Its exit status is lost then, so report a failure.
let mut status = 0;
if libc::waitpid(pid, &raw mut status, 0) == -1 {
libc::_exit(1);
}
if libc::WIFEXITED(status) {
libc::_exit(libc::WEXITSTATUS(status));
}
if libc::WIFSIGNALED(status) {
libc::raise(libc::WTERMSIG(status));
}
libc::_exit(1);
}
}
// Child
drop(reader);
*PIPE_WRITER.lock().unwrap() = Some(writer);
}
/// Tells the parent that the output is complete.
pub fn notify_parent() {
let Some(writer) = PIPE_WRITER.lock().unwrap().take() else {
return;
};
let _ = File::from(writer).write_all(&[1]);
}
extern "C" fn on_signal(
signo: libc::c_int,
_info: *mut libc::siginfo_t,
_context: *mut libc::c_void,
) {
// The output is written in place as its ranges are finished, so a
// crash in the middle of a link would leave a truncated file at the
// output path. Remove it before dying of the signal.
crate::output_file::cleanup();
// Re-throw the signal
// SAFETY: restoring the default handlers and re-raising.
unsafe {
libc::signal(libc::SIGSEGV, libc::SIG_DFL);
libc::signal(libc::SIGBUS, libc::SIG_DFL);
libc::raise(signo);
}
}
pub fn install_signal_handler() {
// SAFETY: installing a signal handler with the three-argument SA_SIGINFO
// calling convention.
unsafe {
let mut action: libc::sigaction = std::mem::zeroed();
action.sa_sigaction = on_signal as *const () as libc::sighandler_t;
libc::sigemptyset(&raw mut action.sa_mask);
action.sa_flags = libc::SA_SIGINFO;
libc::sigaction(libc::SIGSEGV, &raw const action, std::ptr::null_mut());
libc::sigaction(libc::SIGBUS, &raw const action, std::ptr::null_mut());
}
}
+1060
View File
File diff suppressed because it is too large. Load diff
+336
View File
@@ -0,0 +1,336 @@
//! -move_to_rw_segment, -move_to_ro_segment and -dirty_data_list: lists
//! of symbols whose subsections move to another segment - data written
//! at run time (rw) or code and constants (ro), to put them on pages of
//! their own, and the data a shared-cache dylib dirties, to
//! __DATA_DIRTY. Each option takes the first of its lists that names one
//! of a subsection's symbols, or matches it with a pattern, and moves
//! the subsection to the section of its name in the list's segment (see
//! passes::assign_input_sections). A new segment follows the
//! linker's own, read-write as any other ld-prime doesn't know - but one
//! made for moved code, which mold makes executable (see
//! chunks::segment_prots). Fixups, symbols and -order_file treat a moved
//! subsection as any other.
use std::os::unix::ffi::OsStrExt;
use crate::arch::Target;
use crate::cmdline::SymbolMove;
use crate::context::Context;
use crate::error::{RawPath, raw};
use crate::input_files::{FileId, canonical_section_flags, should_write_to_local_symtab};
use crate::macho::*;
use crate::symbol::SymbolId;
use crate::util::leak_bytes;
/// The option that moves a subsection.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum MoveOption {
Rw,
Ro,
Dirty,
}
/// Where a symbol move sends a subsection.
#[derive(Clone, Copy)]
pub struct Move {
pub option: MoveOption,
/// The segment, cut to 16 bytes.
pub segment: &'static [u8],
}
/// What a subsection holds, as ld-prime tells which option may move it:
/// code, with the constants the compiler makes along with it in __TEXT
/// (strings, literals and exception tables), that -move_to_rw_segment
/// does not move, data written at run time (by the program, or the
/// Objective-C runtime) that -move_to_ro_segment does not, and anything
/// else, other constants and sections of other names, that either does.
#[derive(Clone, Copy, PartialEq, Eq)]
enum Content {
Code,
Data,
Other,
}
/// The subsection a symbol names (an absolute symbol is one of its
/// own): the subsection to move, the segment it is in and what it holds.
struct Subsec<'a> {
/// The subsection the link kept (see InputSections::resolve) - a
/// record the linker rewrote in place of the input's too (see
/// rewritten_records); none for an absolute symbol, or for a
/// subsection the linker places itself (an input class reference
/// folded into the GOT).
isec: Option<u32>,
segment: &'a [u8],
content: Content,
/// Thread-local data, the template each thread's copy starts from,
/// which -dirty_data_list leaves in place.
tlv_template: bool,
}
/// The file of a symbol that names a subsection (see
/// for_each_subsec_symbol): an input object, or for an -alias name the
/// command line, where the name stands for the subsection of its base,
/// which it gives as well.
#[derive(Clone, Copy)]
enum SymbolFile {
Obj(usize),
Aliases(SymbolId),
}
/// What a subsection of an input section holds, by the section's
/// canonical flags (see canonical_section_flags).
fn content_of(seg: &[u8], sect: &[u8], flags: u32) -> Content {
use Content::*;
if flags & S_ATTR_PURE_INSTRUCTIONS != 0 {
return Code;
}
match flags & SECTION_TYPE {
S_ZEROFILL | S_GB_ZEROFILL => return Data,
S_THREAD_LOCAL_REGULAR | S_THREAD_LOCAL_ZEROFILL | S_THREAD_LOCAL_VARIABLES => return Data,
S_4BYTE_LITERALS | S_8BYTE_LITERALS | S_16BYTE_LITERALS | S_CSTRING_LITERALS => {
return Code;
}
_ => {}
}
match (seg, sect) {
(b"__TEXT", b"__gcc_except_tab") => Code,
(
b"__DATA",
b"__data" | b"__cfstring" | b"__auth_ptr" | b"__objc_data" | b"__objc_const"
| b"__objc_ivar" | b"__objc_superrefs" | b"__objc_protolist" | b"__objc_protorefs",
) => Data,
_ => Other,
}
}
/// Decides which subsections the lists move to which segments, by
/// subsection, and warns about each symbol a list names (not one a
/// pattern matches) that its option cannot move. The two options look at a
/// subsection apart, each with the first of its lists that names it:
/// one whose segment is the subsection's own leaves it in place
/// silently, -move_to_rw_segment's wins over the other's, and
/// -dirty_data_list's applies only if neither moves the subsection, nor
/// to the thread-local template (and only in __DATA, see
/// passes::SectionMap::moved_name). A -r link moves nothing
/// (cmdline::check_relocatable).
pub(crate) fn find_moves<E: Target>(ctx: &Context<E>) -> hashbrown::HashMap<u32, Move> {
let args = &ctx.args;
let mut moves = hashbrown::HashMap::new();
if args.relocatable
|| (args.move_to_rw.is_empty() && args.move_to_ro.is_empty() && args.dirty_data.is_empty())
{
return moves;
}
let segments = |lists: &[SymbolMove]| -> Vec<&'static [u8]> {
lists.iter().map(|list| leak_bytes(cut_name(&list.segment).to_vec())).collect()
};
let (rw_segs, ro_segs) = (segments(&args.move_to_rw), segments(&args.move_to_ro));
let warn = |file: SymbolFile, id: SymbolId, subsec: &Subsec, list: &SymbolMove| {
let file = match file {
SymbolFile::Obj(obj) => ctx.objs[obj].mf.name.raw(),
SymbolFile::Aliases(_) => raw(b"-alias"),
};
let what = if subsec.content == Content::Code { "code" } else { "data" };
crate::warn!(
"cannot move symbol '{}' ({file}) to segment '{}' because it is {what}",
raw(ctx.symbols[id].name()),
raw(&list.segment),
);
};
for_each_subsec_symbol(ctx, |file, id, subsec| {
// A list entry file:name names the symbol of an object of that
// leaf name. An -alias name stands for its base's subsection,
// which a list names by either name.
let (leaf, base) = match file {
SymbolFile::Obj(obj) => (ctx.objs[obj].mf.name.file_name(), None),
SymbolFile::Aliases(base) => (None, Some(base)),
};
let base_name = base.map(|base| ctx.symbols[base].name());
let names = [Some(ctx.symbols[id].name()), base_name];
let find = |lists: &[SymbolMove]| {
lists.iter().enumerate().find_map(|(i, list)| {
let found =
names.iter().flatten().map(|&name| match (list.symbols.find(name), leaf) {
(-1, Some(leaf)) => {
list.symbols.find(&[leaf.as_bytes(), b":", name].concat())
}
(found, _) => found,
});
let found = found.max().unwrap();
(found >= 0).then_some((i, found == 1))
})
};
let mut chosen = None;
if let Some((i, named)) = find(&args.move_to_rw)
&& rw_segs[i] != subsec.segment
{
if subsec.content != Content::Code {
chosen = Some(Move { option: MoveOption::Rw, segment: rw_segs[i] });
} else if named {
warn(file, id, &subsec, &args.move_to_rw[i]);
}
}
if let Some((i, named)) = find(&args.move_to_ro)
&& ro_segs[i] != subsec.segment
{
if subsec.content != Content::Data {
chosen = chosen.or(Some(Move { option: MoveOption::Ro, segment: ro_segs[i] }));
} else if named {
warn(file, id, &subsec, &args.move_to_ro[i]);
}
}
if chosen.is_none() && find(&args.dirty_data).is_some() && !subsec.tlv_template {
chosen = Some(Move { option: MoveOption::Dirty, segment: b"__DATA_DIRTY" });
}
if let (Some(m), Some(isec)) = (chosen, subsec.isec) {
moves.entry(isec).or_insert(m);
}
});
moves
}
/// Calls `f` with each symbol that names a live subsection, with its
/// file (see subsec_named): the objects' symbols, then the
/// -alias names of a definition in one, each with its base's
/// subsection.
fn for_each_subsec_symbol<'a, E: Target>(
ctx: &'a Context<E>,
mut f: impl FnMut(SymbolFile, SymbolId, Subsec<'a>),
) {
let commons = common_owners(ctx);
let rewritten = rewritten_records(ctx);
for (i, obj) in ctx.objs.iter().enumerate() {
if !obj.is_reachable || ctx.is_internal(i) {
continue;
}
for (msym, &id) in obj.mach_syms.iter().zip(&obj.symbols) {
if let Some(subsec) = subsec_named(ctx, &commons, &rewritten, i, msym, id) {
f(SymbolFile::Obj(i), id, subsec);
}
}
}
for (base, alias) in object_aliases(ctx) {
let obj = match ctx.symbols[base].file() {
Some(FileId::Obj(obj)) => obj as usize,
_ => continue,
};
let Some(i) = ctx.objs[obj].symbols.iter().position(|&id| id == base) else {
continue;
};
let msym = &ctx.objs[obj].mach_syms[i];
if let Some(subsec) = subsec_named(ctx, &commons, &rewritten, obj, msym, base) {
f(SymbolFile::Aliases(base), alias, subsec);
}
}
}
/// The object each common symbol's subsection stands for the tentative
/// definition of, by subsection: the one declaring the largest size,
/// the first of equals, as in ld-prime.
fn common_owners<E: Target>(ctx: &Context<E>) -> hashbrown::HashMap<u32, u32> {
let mut decls: hashbrown::HashMap<crate::symbol::SymbolId, (u64, u32)> =
hashbrown::HashMap::new();
for (i, obj) in ctx.objs.iter().enumerate().filter(|(_, obj)| obj.is_reachable) {
let r = obj.global_range();
for (msym, &sym) in obj.mach_syms[r.clone()].iter().zip(&obj.symbols[r]) {
if !msym.is_stab() && msym.ty() == N_UNDF && msym.is_common() {
let decl = decls.entry(sym).or_insert((msym.value, i as u32));
if msym.value > decl.0 {
*decl = (msym.value, i as u32);
}
}
}
}
// A symbol a real definition took is no common symbol's.
decls
.into_iter()
.filter_map(|(sym, (_, obj))| Some((ctx.symbols[sym].input_section()?, obj)))
.filter(|&(isec, _)| ctx.is_internal(ctx.isecs[isec as usize].file as usize))
.collect()
}
/// The -alias names of definitions in objects, in the options' order,
/// each with its base, whose subsection the name stands for (see
/// passes::add_synthetic_symbols; a dylib's base leaves an indirect
/// symbol, and a name an input defines stays the input's).
pub(crate) fn object_aliases<E: Target>(
ctx: &Context<E>,
) -> impl Iterator<Item = (SymbolId, SymbolId)> + '_ {
ctx.args.aliases.iter().filter_map(|(base, alias)| {
let (base, alias) = (ctx.symbols.lookup(base)?, ctx.symbols.lookup(alias)?);
let (b, a) = (&ctx.symbols[base], &ctx.symbols[alias]);
let defined = matches!(b.file(), Some(FileId::Obj(_)))
&& (a.file(), a.input_section(), a.value) == (b.file(), b.input_section(), b.value);
defined.then_some((base, alias))
})
}
/// The records the linker rewrote in place of input subsections, which
/// the input's symbols name and a symbol move takes along: the
/// Objective-C data category merging rebuilt (see
/// objc::merge_objc_categories), false, and the method lists rewritten
/// in the relative form (see objc::convert_objc_method_lists), true.
fn rewritten_records<E: Target>(ctx: &Context<E>) -> hashbrown::HashMap<u32, bool> {
let blobs = ctx.data_blobs.iter().map(|b| (b.isec, false));
let lists = ctx.objc_methlist.lists.iter().map(|l| (l.isec, true));
blobs.chain(lists).collect()
}
/// The subsection the symbol `id` of object `obj`, whose entry is
/// `msym`, names: a definition the link kept, external or local but no
/// assembler label (see input_files::should_write_to_local_symtab), in
/// a section or absolute; or a common symbol, if the object's tentative
/// definition is the one its subsection stands for (see common_owners).
/// A method list rewritten in the relative form (see rewritten_records)
/// is code, as ld-prime counts it.
fn subsec_named<'a, E: Target>(
ctx: &'a Context<E>,
commons: &hashbrown::HashMap<u32, u32>,
rewritten: &hashbrown::HashMap<u32, bool>,
obj: usize,
msym: &MachSym,
id: SymbolId,
) -> Option<Subsec<'a>> {
let sym = &ctx.symbols[id];
if msym.is_common() {
let isec = sym.input_section().filter(|isec| commons.get(isec) == Some(&(obj as u32)))?;
let content = Content::Data;
return Some(Subsec { isec: Some(isec), segment: b"__DATA", content, tlv_template: false });
}
if msym.is_stab()
|| !matches!(msym.ty(), N_SECT | N_ABS)
|| sym.file() != Some(FileId::Obj(obj as u32))
|| (!msym.is_extern() && !should_write_to_local_symtab(sym.name()))
{
return None;
}
// An absolute symbol is a subsection of its own, in no segment.
let Some(isec) = sym.input_section() else {
let content = Content::Data;
return Some(Subsec { isec: None, segment: b"", content, tlv_template: false });
};
let kept = ctx.isecs.resolve(isec as usize);
if !ctx.isecs[kept].is_alive() {
return None;
}
let isec = &ctx.isecs[isec as usize];
let hdr = isec.hdr(&ctx.objs[isec.file as usize]);
let flags = canonical_section_flags(hdr.segname(), hdr.sectname(), hdr.flags);
let rewritten = rewritten.get(&(kept as u32));
let content = match rewritten {
Some(true) => Content::Code,
_ => content_of(hdr.segname(), hdr.sectname(), flags),
};
let tlv_template =
matches!(flags & SECTION_TYPE, S_THREAD_LOCAL_REGULAR | S_THREAD_LOCAL_ZEROFILL);
let movable = rewritten.is_some() || !ctx.isecs[kept].is_placed();
Some(Subsec {
isec: movable.then_some(kept as u32),
segment: hdr.segname(),
content,
tlv_template,
})
}
+1006
View File
File diff suppressed because it is too large. Load diff
+459
View File
@@ -0,0 +1,459 @@
//! Range-extension thunks.
//!
//! An arm64 b/bl reaches +-128 MiB; code larger than that needs
//! thunks, trampolines placed among the code that branch anywhere
//! within 4 GiB, for the branches that can't reach their targets.
//! ld-prime makes its branch islands only for such branches (one that
//! spans more than 124 MiB of its layout without islands), so an image
//! whose code - from its first code section to the end of its last,
//! __stubs and __objc_stubs included - fits within a branch's reach
//! gets none. need_thunks bounds that span before placement and lays
//! the code out without thunks if it fits.
//!
//! Otherwise every code section is laid out with thunks as mold does:
//! a thunk is placed for each batch of code at D, the farthest point
//! from the batch start that a thunk placed there stays within reach
//! of the whole batch. The subsections up to D thus have their final
//! offsets when the batch is scanned, so a branch to one of them needs
//! an entry only if it really is out of reach; a target beyond D, a
//! branch reach minus a batch away, is assumed to be. Branches to the
//! other code sections and the stubs are judged by bounds on the room
//! the code span takes before and after the section.
//!
//! As in mold, a thunk entry belongs to a *symbol*, not to a
//! relocation: a symbol that some branch of the batch may not reach
//! gets an entry, deduplicated by an atomic mark on the symbol inside
//! the parallel scan, and keeps its mark, getting no second entry, for
//! as long as that entry stays within reach of the batches that follow;
//! gather_thunk_addresses records each symbol's entry addresses so that
//! applying an out-of-range branch just picks the one within reach.
//! mold also trims the entries that turn out unneeded once addresses
//! are final (remove_redundant_thunks) and lays the section out again;
//! ours does not, as the rescan of every branch and the second __TEXT
//! placement (which re-encodes __unwind_info) cost 5% of a debug clang
//! link. The extra entries are dead code.
use rayon::prelude::*;
use crate::arch::Target;
use crate::chunks::{ChunkId, OutputSection, OutputSectionId};
use crate::context::Context;
use crate::input_files::FileId;
use crate::input_sections::InputSectionId;
use crate::macho::{S_ATTR_PURE_INSTRUCTIONS, S_ATTR_SOME_INSTRUCTIONS};
use crate::symbol::SymbolId;
use crate::util::align_to;
/// We create a thunk for each 10 MiB batch of code (mold: 32 MiB).
const BATCH_SIZE: u64 = 10 << 20;
/// We assume that a single thunk is smaller than 1 MiB (mold: 16 MiB).
const MAX_THUNK_SIZE: u64 = 1 << 20;
const THUNK_ALIGN: u64 = 16;
/// A subsection offset that hasn't been assigned yet.
const UNPLACED: u32 = u32::MAX;
/// A range-extension thunk: a block of jump entries placed inside an
/// output section so that branches whose targets are further than the
/// instruction's reach can hop through it.
#[derive(Debug)]
pub struct Thunk {
/// Offset of the thunk within the output section.
pub offset: u64,
pub syms: Vec<SymbolId>,
}
/// Whether the link needs range-extension thunks: Some(false) if its
/// code spans no more than a branch reaches, Some(true) if it may span
/// more, and None if only the placement can tell - the span then holds
/// a chunk sized as it is placed (a shared-region image's __stubs come
/// after __unwind_info) or crosses segments. With -no_branch_islands it
/// gets none: a branch out of reach is then a fixup error.
pub fn need_thunks<E: Target>(ctx: &Context<E>) -> Option<bool> {
if E::THUNK_SIZE == 0 || ctx.args.no_branch_islands {
return Some(false);
}
let Some((first, last)) = code_range(ctx) else {
return Some(false);
};
let segname = ctx.chunk_header(ctx.chunks[first]).segname;
let span = ctx.chunks[first..=last]
.iter()
.map(|&id| chunk_room(ctx, id, segname, false))
.sum::<Option<u64>>()?;
Some(span > E::BRANCH_RANGE / 2)
}
/// The span of the placed code: from the start of the first code chunk
/// to the end of the last.
pub fn code_span<E: Target>(ctx: &Context<E>) -> u64 {
let (lo, hi) = ctx
.chunks
.iter()
.filter(|&&id| is_code(ctx, id))
.map(|&id| ctx.chunk_header(id))
.fold((u64::MAX, 0), |(lo, hi), hdr| (lo.min(hdr.addr), hi.max(hdr.addr + hdr.size)));
hi.saturating_sub(lo)
}
/// Lays out every code section with range-extension thunks, in output
/// order, so that the sections before the one being laid out have
/// their final sizes.
pub fn create_range_extension_thunks<E: Target>(ctx: &mut Context<E>) {
let _t = ctx.timer("create_range_extension_thunks");
let Some((first, last)) = code_range(ctx) else {
return;
};
for pos in first..=last {
if let ChunkId::Output(id) = ctx.chunks[pos]
&& is_code(ctx, ctx.chunks[pos])
{
let mut reach = Reach::new(ctx, id, first, pos, last);
create_thunks(ctx, &mut reach);
}
}
}
/// Whether a chunk is code, which gets thunks: a non-empty executable
/// section, in whatever segment.
fn is_code<E: Target>(ctx: &Context<E>, id: ChunkId) -> bool {
let hdr = ctx.chunk_header(id);
hdr.flags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SOME_INSTRUCTIONS) != 0 && hdr.size > 0
}
/// The positions in the output order of the first and the last code
/// chunk.
fn code_range<E: Target>(ctx: &Context<E>) -> Option<(usize, usize)> {
let first = ctx.chunks.iter().position(|&id| is_code(ctx, id))?;
let last = ctx.chunks.iter().rposition(|&id| is_code(ctx, id))?;
Some((first, last))
}
/// An upper bound on the room a chunk takes in the code span: its size
/// and the padding its alignment may put before it, plus, for a code
/// section still to get its thunks (`grows`), those thunks. None for a
/// chunk outside `segname`, which the segments' placement may put
/// anywhere, or one whose size is only known once it is placed.
fn chunk_room<E: Target>(
ctx: &Context<E>,
id: ChunkId,
segname: &[u8],
grows: bool,
) -> Option<u64> {
let hdr = ctx.chunk_header(id);
if hdr.segname != segname || matches!(id, ChunkId::MachHeader | ChunkId::UnwindInfo) {
return None;
}
let align = 1 << hdr.p2align;
let mut room = hdr.size + align - 1;
if let ChunkId::Output(osec) = id
&& grows
&& is_code(ctx, id)
{
// Every two batches cover at least BATCH_SIZE bytes, and each
// thunk may shift the member after it to a new alignment.
let members = ctx.output_section(osec).members.len() as u64;
let thunks = members.min(2 * hdr.size.div_ceil(BATCH_SIZE) + 1);
room += thunks * (MAX_THUNK_SIZE + THUNK_ALIGN + align);
}
Some(room)
}
/// Where a code chunk lies relative to the section being laid out:
/// within the code span before or after it, or outside the span.
#[derive(Clone, Copy, PartialEq)]
enum Side {
Before,
After,
Outside,
}
/// What a branch from the code section being laid out can reach.
struct Reach {
osec: OutputSectionId,
/// The side of each output section, and of the stubs.
sides: Vec<Side>,
stubs: Side,
objc_stubs: Side,
/// Upper bounds on the room the code span takes before the section
/// and after it, if known.
before: Option<u64>,
after: Option<u64>,
/// Whether all of the code before the current batch's end, and all
/// of it after the batch's start, is within reach of the batch.
backward: bool,
forward: bool,
}
impl Reach {
fn new<E: Target>(
ctx: &Context<E>,
osec: OutputSectionId,
first: usize,
pos: usize,
last: usize,
) -> Self {
// The span is the code of the section's own segment: the
// segments' placement may put another's code anywhere.
let hdr = ctx.chunk_header(ctx.chunks[pos]);
let in_span = |i: &usize| {
let id = ctx.chunks[*i];
is_code(ctx, id) && ctx.chunk_header(id).segname == hdr.segname
};
let first = (first..=pos).find(in_span).unwrap();
let last = (pos..=last).rev().find(in_span).unwrap();
let side = |i: usize| match i {
_ if i < first || last < i => Side::Outside,
_ if i < pos => Side::Before,
_ => Side::After,
};
let mut sides = vec![Side::Outside; ctx.output_sections.len()];
let (mut stubs, mut objc_stubs) = (Side::Outside, Side::Outside);
for (i, &id) in ctx.chunks.iter().enumerate() {
match id {
ChunkId::Output(id) => sides[id.index()] = side(i),
ChunkId::Stubs => stubs = side(i),
ChunkId::ObjcStubs => objc_stubs = side(i),
_ => {}
}
}
let room = |ids: &[ChunkId], grows| {
ids.iter().map(|&id| chunk_room(ctx, id, hdr.segname, grows)).sum::<Option<u64>>()
};
let before = room(&ctx.chunks[first..pos], false).map(|room| room + (1 << hdr.p2align) - 1);
let after = room(&ctx.chunks[pos + 1..=last], true);
Self { osec, sides, stubs, objc_stubs, before, after, backward: false, forward: false }
}
}
/// Lays out one code section with range-extension thunks, as mold's
/// create_range_extension_thunks does. The layout proceeds with four
/// member indices that only move forward, A <= B <= C <= D: [B, C) is
/// the current batch, D the first member not yet placed - the thunk
/// for the batch goes there, the farthest a thunk stays within reach
/// of B - and A the first member within reach of C, before which
/// thunks are out of the batch's reach.
fn create_thunks<E: Target>(ctx: &mut Context<E>, reach: &mut Reach) {
let id = reach.osec;
let members = std::mem::take(&mut ctx.output_sections[id.index()].members);
for &m in &members {
ctx.isecs[m].offset = UNPLACED;
}
let distance = E::BRANCH_RANGE / 2;
let n = members.len();
let mut thunks: Vec<Thunk> = Vec::new();
let (mut a, mut b, mut d) = (0, 0, 0);
let mut offset = 0;
// The first thunk still within reach of the current batch.
let mut t = 0;
while b < n {
// Move D forward as far as a thunk there stays within reach of B.
while d < n {
let isec = &ctx.isecs[members[d]];
let start = isec.align_offset(offset);
let end = start + isec.size as u64;
if b != d
&& align_to(end, THUNK_ALIGN) + MAX_THUNK_SIZE
> ctx.isecs[members[b]].offset as u64 + distance
{
break;
}
ctx.isecs[members[d]].offset = start as u32;
offset = end;
d += 1;
}
let c = batch_end(ctx, &members, b, d);
// Unmark the symbols of the thunks out of reach of C, so that
// they can take new entries.
let c_offset = if c == d { offset } else { ctx.isecs[members[c]].offset as u64 };
a += members[a..b]
.partition_point(|&m| (ctx.isecs[m].offset as u64) < c_offset.saturating_sub(distance));
while t < thunks.len() && thunks[t].offset < ctx.isecs[members[a]].offset as u64 {
for &sym in &thunks[t].syms {
ctx.symbols[sym].unmark();
}
t += 1;
}
// The code after the section is within reach of the batch if
// the section's end is known closely enough: once all of it is
// placed, only the thunks of the batches left, this one's
// included, still go in, at its end.
let b_offset = ctx.isecs[members[b]].offset as u64;
reach.backward = reach.before.is_some_and(|before| before + c_offset <= distance);
reach.forward = d == n
&& reach.after.is_some_and(|after| {
let batches = batches_left(ctx, &members, b);
offset + batches * (MAX_THUNK_SIZE + THUNK_ALIGN) + after - b_offset <= distance
});
// Create a thunk for the batch and place it at D.
let syms = scan_batch(ctx, &members[b..c], reach);
if !syms.is_empty() {
offset = align_to(offset, THUNK_ALIGN);
let size = syms.len() as u64 * E::THUNK_SIZE;
debug_assert!(size <= MAX_THUNK_SIZE);
thunks.push(Thunk { offset, syms });
offset += size;
}
b = c;
}
// Marks of the thunks still in reach at the end are cleared too.
for thunk in &thunks[t..] {
for &sym in &thunk.syms {
ctx.symbols[sym].unmark();
}
}
let osec = &mut ctx.output_sections[id.index()];
osec.hdr.size = offset;
osec.thunks = thunks;
osec.members = members;
}
/// The end of the batch that starts at member `b`: it takes the members
/// that end within BATCH_SIZE bytes of it, one at least, and none at D
/// or beyond.
fn batch_end<E: Target>(ctx: &Context<E>, members: &[InputSectionId], b: usize, d: usize) -> usize {
let limit = ctx.isecs[members[b]].offset as u64 + BATCH_SIZE;
b + 1
+ members[b + 1..d].partition_point(|&m| {
let isec = &ctx.isecs[m];
(isec.offset as u64 + isec.size as u64) < limit
})
}
/// The number of batches from member `b` to the end of a section whose
/// members are all placed.
fn batches_left<E: Target>(ctx: &Context<E>, members: &[InputSectionId], b: usize) -> u64 {
let mut batches = 0;
let mut i = b;
while i < members.len() {
i = batch_end(ctx, members, i, members.len());
batches += 1;
}
batches
}
/// Scans `batch`'s branch relocations in parallel and returns the
/// symbols that need an entry in the batch's thunk: those whose target
/// may be out of reach and that no thunk still within reach covers (a
/// symbol claims its entry with mark()). A thunk entry jumps to its
/// symbol, so a branch with an addend never gets one; applying it
/// reports it if it is out of reach. mold scans each batch's members
/// with par_iter and dedups with the symbol's atomic mark the same way.
fn scan_batch<E: Target>(
ctx: &Context<E>,
batch: &[InputSectionId],
reach: &Reach,
) -> Vec<SymbolId> {
let mut syms: Vec<SymbolId> = batch
.par_iter()
.fold(Vec::new, |mut syms, &id| {
let isec = &ctx.isecs[id];
let file = &ctx.objs[isec.file as usize];
for rel in isec.rels(file) {
if !rel.is_func_call::<E>() || rel.addend != 0 {
continue;
}
let Some(sym) = rel.sym(file) else {
continue;
};
let p = isec.offset as u64 + rel.offset as u64;
if requires_thunk(ctx, reach, p, sym) && ctx.symbols[sym].mark() {
syms.push(sym);
}
}
syms
})
.reduce(Vec::new, |mut syms, mut other| {
syms.append(&mut other);
syms
});
// Deterministic entry order regardless of which thread claimed
// each symbol.
syms.par_sort_unstable();
syms
}
/// Whether a branch at offset `p` of the section being laid out may not
/// reach `sym`, where it goes: its subsection, its stub (an import or a
/// weak definition that may be interposed) or its _objc_msgSend stub.
fn requires_thunk<E: Target>(ctx: &Context<E>, reach: &Reach, p: u64, id: SymbolId) -> bool {
let sym = &ctx.symbols[id];
let stub = sym.stub_idx(&ctx.symbols).is_some();
let side = match sym.file() {
_ if stub && sym.is_interposable(ctx) => reach.stubs,
Some(FileId::Dylib(_)) if stub => reach.stubs,
Some(FileId::Obj(_)) => match sym.input_section() {
Some(target) => {
let target = &ctx.isecs[ctx.isecs.resolve(target as usize)];
match target.output_section() {
Some(ChunkId::Output(osec)) if osec == reach.osec => {
if target.offset == UNPLACED {
Side::After
} else {
let distance = (E::BRANCH_RANGE / 2) as i64;
let t = target.offset as u64 + sym.value;
return !(-distance..distance).contains(&(t.wrapping_sub(p) as i64));
}
}
Some(ChunkId::Output(osec)) => reach.sides[osec.index()],
_ => Side::Outside,
}
}
None if sym.objc_stub_idx(&ctx.symbols).is_some() => reach.objc_stubs,
None => Side::Outside,
},
// A DTrace symbol, at address 0: a probe site needs no thunk,
// being no branch in the output (see dtrace).
None => return false,
_ => Side::Outside,
};
match side {
Side::Before => !reach.backward,
Side::After => !reach.forward,
Side::Outside => true,
}
}
/// Records every thunk entry's address on its symbol (SymbolAux::
/// thunk_addrs), in address order, so that applying an out-of-range
/// branch can pick the entry within reach. mold's
/// gather_thunk_addresses.
pub fn gather_thunk_addresses<E: Target>(ctx: &mut Context<E>) {
// The sections are read while the symbols' aux data is written, so
// the borrows are split.
let chunks = &ctx.chunks;
let output_sections = &ctx.output_sections;
let symbols = &mut ctx.symbols;
for &id in chunks {
let ChunkId::Output(id) = id else { continue };
let osec = &output_sections[id.index()];
let base = osec.hdr.addr;
for thunk in &osec.thunks {
for (i, &sym) in thunk.syms.iter().enumerate() {
let addr = base + thunk.offset + i as u64 * E::THUNK_SIZE;
symbols.aux_mut(sym).thunk_addrs.push(addr);
}
}
}
}
/// Writes an output section's range-extension thunks, between its
/// members, to `buf`, its contents.
pub fn copy_buf<E: Target>(ctx: &Context<E>, osec: &OutputSection, buf: &mut [u8]) {
for thunk in &osec.thunks {
let off = thunk.offset as usize;
let end = off + thunk.syms.len() * E::THUNK_SIZE as usize;
E::write_thunk(ctx, osec.hdr.addr + thunk.offset, &thunk.syms, &mut buf[off..end]);
}
}
+117
View File
@@ -0,0 +1,117 @@
//! This file contains helper functions for thread-local storage (TLS).
//! This comment explains how TLS works on Mach-O, assuming that you
//! already know ELF's TLS.
//!
//! On ELF, there are several TLS access models, and the linker may
//! rewrite instructions to use a cheaper one. On Mach-O, there's only
//! one way to access a thread-local variable (TLV). In ELF terms, it
//! uses TLSDESC for all TLVs, and the function it calls always does what
//! __tls_get_addr() does for General Dynamic.
//!
//! For each TLV, the compiler creates a three-word data structure called
//! a "TLV descriptor" in the __thread_vars section. For example, for the
//! following code
//!
//! thread_local int foo = 3;
//!
//! the compiler emits something like this:
//!
//! .section __DATA,__thread_data
//! _foo$tlv$init:
//! .long 3
//!
//! .section __DATA,__thread_vars
//! _foo:
//! .quad __tlv_bootstrap // function pointer
//! .quad 0 // key
//! .quad _foo$tlv$init // offset
//!
//! Note that the symbol `_foo` refers to the descriptor, not to the
//! variable itself. The variable's initial value is in __thread_data
//! under a different name, or in __thread_bss if it's zero. The
//! descriptor is in __thread_vars in either case.
//!
//! To access a TLV, the code obtains its descriptor's address and calls
//! the function pointer in the descriptor with the descriptor as an
//! argument. The function returns the TLV's address for the current
//! thread. On ARM64, the code looks like this:
//!
//! adrp x0, _foo@TLVPPAGE
//! ldr x0, [x0, _foo@TLVPPAGEOFF]
//! ldr x8, [x0]
//! blr x8
//!
//! Mach-O doesn't have a PT_TLS-like segment. A Mach-O file's TLS
//! template image consists of a section of type S_THREAD_LOCAL_REGULAR
//! (__thread_data) and a section of type S_THREAD_LOCAL_ZEROFILL
//! (__thread_bss), which are next to each other in memory. dyld treats
//! the range from the beginning of the first section of these types to
//! the end of the last one as the template.
//!
//! At load time, dyld rewrites the contents of the TLV descriptors in
//! __thread_vars. The function pointer is set to dyld's function, and
//! the key is set to a number that dyld assigns to the Mach-O file,
//! which is like ELF's module ID. dyld does this only if the
//! MH_HAS_TLV_DESCRIPTORS flag is set in the Mach-O header.
//!
//! On ELF, the thread pointer (TP) points to the initial TLS block, so a
//! TLV in it can be accessed by adding a constant to TP. On macOS, TP
//! (TPIDRRO_EL0 on ARM64 and %gs on x86-64) doesn't point to TLVs. It
//! points to a per-thread array that has a pointer for each Mach-O file,
//! and the key is an index into the array. dyld's function works like
//! this:
//!
//! void *get_addr(TLVDescriptor *desc) {
//! char **array = thread_pointer;
//! if (!array[desc->key]) {
//! MachOFile *file = files[desc->key];
//! array[desc->key] = malloc(file->template_size);
//! memcpy(array[desc->key], file->template, file->template_size);
//! }
//! return array[desc->key] + desc->offset;
//! }
//!
//! That means, unlike ELF, every TLS block is allocated when it is first
//! accessed, even the main executable's. TLVs are not at a fixed
//! distance from TP, so Mach-O has nothing like Initial Exec or Local
//! Exec.
//!
//! Here is what the linker has to do for TLVs:
//!
//! - The function pointer in a descriptor refers to __tlv_bootstrap,
//! which is defined in libSystem. We handle it as an ordinary pointer
//! to an imported symbol.
//!
//! - The offset in a descriptor refers to the variable's initial value
//! with an ordinary 64-bit absolute relocation, but we write the
//! offset from the beginning of the template instead of the address.
//! Since it's not an address, it doesn't need a rebase. There's no
//! relocation type for it like ELF's DTPOFF, so we recognize it by
//! the type of the section it refers to.
//!
//! - The adrp and ldr instructions in the above code load the
//! descriptor's address from a GOT entry. If the descriptor's address
//! is known at link-time, we relax the load to an address computation
//! as we do for a GOT load. Otherwise, for example if the TLV is
//! defined in another dylib, dyld sets the descriptor's address to
//! the GOT entry.
//!
//! - If the output has __thread_vars, we set MH_HAS_TLV_DESCRIPTORS in
//! the Mach-O header.
//!
//! We don't rewrite any other instructions for TLVs.
use crate::arch::Target;
use crate::context::Context;
/// Returns the address of the TLS template image. A TLV descriptor's
/// offset is relative to this address.
pub fn tls_begin<E: Target>(ctx: &Context<E>) -> u64 {
ctx.chunks
.iter()
.map(|&id| ctx.chunk_header(id))
.filter(|hdr| hdr.is_thread_local())
.map(|hdr| hdr.addr)
.min()
.unwrap_or(0)
}
+143
View File
@@ -0,0 +1,143 @@
//! libc++'s std::hash of a string, which a mergeable record hashes the
//! names with: its CityHash64, __murmur2_or_cityhash<size_t, 64>, in
//! the ABI that hashes 4 to 8 bytes with a 32-bit shift. The functions
//! are libc++'s, by name.
const K0: u64 = 0xc3a5_c85c_97cb_3127;
const K1: u64 = 0xb492_b66f_be98_f273;
const K2: u64 = 0x9ae1_6a3b_2f90_404f;
const K3: u64 = 0xc949_d7c7_509e_6557;
pub(crate) fn hash(s: &[u8]) -> u64 {
let len = s.len();
if len <= 16 {
return hash_len_0_to_16(s);
}
if len <= 32 {
return hash_len_17_to_32(s);
}
if len <= 64 {
return hash_len_33_to_64(s);
}
// A longer string's end first, then its 64-byte chunks, with 56
// bytes of state: v, w, x, y and z.
let n = len as u64;
let mut x = load64(s, len - 40);
let mut y = load64(s, len - 16).wrapping_add(load64(s, len - 56));
let mut z = hash_len_16(load64(s, len - 48).wrapping_add(n), load64(s, len - 24));
let mut v = weak_hash_len_32_with_seeds(&s[len - 64..], n, z);
let mut w = weak_hash_len_32_with_seeds(&s[len - 32..], y.wrapping_add(K1), x);
x = x.wrapping_mul(K1).wrapping_add(load64(s, 0));
for c in s[..(len - 1) & !63].as_chunks::<64>().0 {
x = (x.wrapping_add(y).wrapping_add(v.0).wrapping_add(load64(c, 8)))
.rotate_right(37)
.wrapping_mul(K1);
y = (y.wrapping_add(v.1).wrapping_add(load64(c, 48))).rotate_right(42).wrapping_mul(K1);
x ^= w.1;
y = y.wrapping_add(v.0).wrapping_add(load64(c, 40));
z = z.wrapping_add(w.0).rotate_right(33).wrapping_mul(K1);
v = weak_hash_len_32_with_seeds(c, v.1.wrapping_mul(K1), x.wrapping_add(w.0));
let seed = y.wrapping_add(load64(c, 16));
w = weak_hash_len_32_with_seeds(&c[32..], z.wrapping_add(w.1), seed);
std::mem::swap(&mut z, &mut x);
}
hash_len_16(
hash_len_16(v.0, w.0).wrapping_add(shift_mix(y).wrapping_mul(K1)).wrapping_add(z),
hash_len_16(v.1, w.1).wrapping_add(x),
)
}
fn load64(s: &[u8], at: usize) -> u64 {
u64::from_le_bytes(s[at..at + 8].try_into().unwrap())
}
fn load32(s: &[u8], at: usize) -> u32 {
u32::from_le_bytes(s[at..at + 4].try_into().unwrap())
}
fn shift_mix(val: u64) -> u64 {
val ^ (val >> 47)
}
fn hash_len_16(u: u64, v: u64) -> u64 {
const MUL: u64 = 0x9ddf_ea08_eb38_2d69;
let mut a = (u ^ v).wrapping_mul(MUL);
a ^= a >> 47;
let mut b = (v ^ a).wrapping_mul(MUL);
b ^= b >> 47;
b.wrapping_mul(MUL)
}
fn hash_len_0_to_16(s: &[u8]) -> u64 {
let len = s.len();
if len > 8 {
let a = load64(s, 0);
let b = load64(s, len - 8);
return hash_len_16(a, b.wrapping_add(len as u64).rotate_right(len as u32)) ^ b;
}
if len >= 4 {
let a = load32(s, 0);
let b = load32(s, len - 4);
// `a << 3` in 32 bits: this ABI's.
return hash_len_16((len as u64).wrapping_add((a << 3) as u64), b as u64);
}
if len > 0 {
let (a, b, c) = (s[0] as u32, s[len >> 1] as u32, s[len - 1] as u32);
let y = a.wrapping_add(b << 8);
let z = (len as u32).wrapping_add(c << 2);
return shift_mix((y as u64).wrapping_mul(K2) ^ (z as u64).wrapping_mul(K3))
.wrapping_mul(K2);
}
K2
}
fn hash_len_17_to_32(s: &[u8]) -> u64 {
let len = s.len();
let a = load64(s, 0).wrapping_mul(K1);
let b = load64(s, 8);
let c = load64(s, len - 8).wrapping_mul(K2);
let d = load64(s, len - 16).wrapping_mul(K0);
hash_len_16(
a.wrapping_sub(b).rotate_right(43).wrapping_add(c.rotate_right(30)).wrapping_add(d),
a.wrapping_add((b ^ K3).rotate_right(20)).wrapping_sub(c).wrapping_add(len as u64),
)
}
/// A 16-byte hash of the 32 bytes at `s` and the seeds `a` and `b`.
fn weak_hash_len_32_with_seeds(s: &[u8], a: u64, b: u64) -> (u64, u64) {
let (w, x, y, z) = (load64(s, 0), load64(s, 8), load64(s, 16), load64(s, 24));
let mut a = a.wrapping_add(w);
let mut b = b.wrapping_add(a).wrapping_add(z).rotate_right(21);
let c = a;
a = a.wrapping_add(x).wrapping_add(y);
b = b.wrapping_add(a.rotate_right(44));
(a.wrapping_add(z), b.wrapping_add(c))
}
fn hash_len_33_to_64(s: &[u8]) -> u64 {
let len = s.len();
let mut z = load64(s, 24);
let mut a =
load64(s, 0).wrapping_add((len as u64).wrapping_add(load64(s, len - 16)).wrapping_mul(K0));
let mut b = a.wrapping_add(z).rotate_right(52);
let mut c = a.rotate_right(37);
a = a.wrapping_add(load64(s, 8));
c = c.wrapping_add(a.rotate_right(7));
a = a.wrapping_add(load64(s, 16));
let vf = a.wrapping_add(z);
let vs = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
a = load64(s, 16).wrapping_add(load64(s, len - 32));
z = z.wrapping_add(load64(s, len - 8));
b = a.wrapping_add(z).rotate_right(52);
c = a.rotate_right(37);
a = a.wrapping_add(load64(s, len - 24));
c = c.wrapping_add(a.rotate_right(7));
a = a.wrapping_add(load64(s, len - 16));
let wf = a.wrapping_add(z);
let ws = b.wrapping_add(a.rotate_right(31)).wrapping_add(c);
let r = shift_mix(
vf.wrapping_add(ws).wrapping_mul(K2).wrapping_add(wf.wrapping_add(vs).wrapping_mul(K0)),
);
shift_mix(r.wrapping_mul(K0).wrapping_add(vs)).wrapping_mul(K2)
}
+34
View File
@@ -0,0 +1,34 @@
//! Symbol name demangling for diagnostics.
/// Demangles an Itanium C++ ABI symbol name, if `name` is one the
/// demangler reads: its identifiers are ASCII, so a name with other
/// bytes stays as it is.
pub fn demangle_cpp(name: &[u8]) -> Option<String> {
if !name.starts_with(b"_Z") {
return None;
}
cpp_demangle::Symbol::new(name).ok()?.demangle().ok()
}
/// A Mach-O symbol name as diagnostics spell it (see display_name).
pub struct DisplayName<'a>(&'a [u8]);
/// A Mach-O symbol name as diagnostics spell it: demangled when
/// -demangle is in effect. Mach-O prefixes every C-level name with an
/// underscore, so an Itanium name reads `__Z...` here; symbol lookup and
/// output always use the original spelling. A name that isn't demangled
/// prints as its bytes are (see error::raw), UTF-8 or not.
pub fn display_name(name: &[u8]) -> DisplayName<'_> {
DisplayName(name)
}
impl std::fmt::Display for DisplayName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if crate::error::demangle_enabled()
&& let Some(demangled) = self.0.strip_prefix(b"_").and_then(demangle_cpp)
{
return f.write_str(&demangled);
}
crate::error::raw(self.0).fmt(f)
}
}
+720
View File
@@ -0,0 +1,720 @@
//! This file implements the glob matcher used for symbol name patterns.
//! Exact, prefix and suffix patterns are matched directly, and simple
//! substring patterns are combined into an Aho-Corasick matcher. The
//! remaining patterns are matched with the non-recursive algorithm described
//! at https://research.swtch.com/glob. If there are many such patterns, a
//! bit-parallel NFA matches them together.
//! This file implements the Aho-Corasick algorithm to search multiple
//! strings within an input string simultaneously. It is essentially a
//! trie with additional links. For details, see
//! https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
//!
//! We use it for simple glob patterns in version scripts or dynamic
//! list files. Here are some examples of glob patterns:
//!
//! qt_private_api_tag*
//! *16QAccessibleCache*
//! *32QAbstractFileIconProviderPrivate*
//! *17QPixmapIconEngine*
//!
//! Aho-Corasick can do only substring search, so it cannot handle
//! complex glob patterns such as `*foo*bar*`. We handle such patterns
//! with the [`Glob`] type.
use std::collections::VecDeque;
#[derive(Clone, Debug)]
enum Token {
Str(Vec<u8>),
Star,
Question,
Bracket(Box<[bool; 256]>),
}
#[derive(Clone, Debug)]
struct Pattern {
tokens: Vec<Token>,
value: i64,
}
impl Pattern {
fn compile(mut pat: &[u8], value: i64) -> Option<Self> {
let mut tokens: Vec<Token> = Vec::new();
while let Some((&c, rest)) = pat.split_first() {
pat = rest;
match c {
b'[' => {
let end = pat.iter().position(|&c| c == b']')?;
tokens.push(Token::Bracket(bracket(&pat[..end])?));
pat = &pat[end + 1..];
}
b'?' => tokens.push(Token::Question),
b'*' => {
if !matches!(tokens.last(), Some(Token::Star)) {
tokens.push(Token::Star);
}
}
// A backslash takes the next character as itself, or is
// itself at the end.
b'\\' => match pat.split_first() {
Some((&escaped, rest)) => {
pat = rest;
push_char(&mut tokens, escaped);
}
None => push_char(&mut tokens, c),
},
_ => push_char(&mut tokens, c),
}
}
Some(Self { tokens, value })
}
fn matches(&self, s: &[u8]) -> bool {
if let Some(Token::Str(suffix)) = self.tokens.last()
&& !s.ends_with(suffix)
{
return false;
}
let mut x = 0;
let mut y = 0;
let mut next: Option<(usize, usize)> = None;
while x < s.len() || y < self.tokens.len() {
if y < self.tokens.len() {
let tok = &self.tokens[y];
match tok {
Token::Str(literal) => {
if s[x..].starts_with(literal) {
x += literal.len();
y += 1;
continue;
}
}
Token::Star => {
next = Some((x + 1, y));
y += 1;
if let Some(Token::Str(literal)) = self.tokens.get(y) {
let Some(pos) = memchr::memmem::find(&s[x..], literal) else {
return false;
};
let pos = x + pos;
next = Some((pos + 1, y - 1));
x = pos + literal.len();
y += 1;
}
continue;
}
Token::Question => {
if x < s.len() {
x += 1;
y += 1;
continue;
}
}
Token::Bracket(chars) => {
if x < s.len() && chars[s[x] as usize] {
x += 1;
y += 1;
continue;
}
}
}
}
// Retry the last star after assigning one more input byte to it.
match next {
Some((nx, ny)) if nx <= s.len() => {
x = nx;
y = ny;
}
_ => return false,
}
}
true
}
}
/// The characters a bracket expression matches, given what is between
/// its brackets, as ld-prime reads one: it ends at the first `]` (so
/// `[]` matches nothing) and knows no negation or escapes - `[!a]`
/// matches `!` or `a`, `[\a]` `\` or `a`. A `-` between two characters
/// makes a range from the one before it, which after a range is that
/// `-` itself (`[a-b-d]` is `a`-`b` and `-`-`d`), to the one after it;
/// a reversed range is empty. A `-` first or last makes the expression,
/// and so its pattern, malformed (None): it matches nothing.
fn bracket(class: &[u8]) -> Option<Box<[bool; 256]>> {
let mut chars = Box::new([false; 256]);
let mut prev = None;
let mut i = 0;
while i < class.len() {
let c = class[i];
if c == b'-' {
let (Some(start), Some(&end)) = (prev, class.get(i + 1)) else {
return None;
};
for x in start..=end {
chars[x as usize] = true;
}
i += 1;
} else {
chars[c as usize] = true;
}
prev = Some(c);
i += 1;
}
Some(chars)
}
fn push_char(tokens: &mut Vec<Token>, c: u8) {
if let Some(Token::Str(literal)) = tokens.last_mut() {
literal.push(c);
} else {
tokens.push(Token::Str(vec![c]));
}
}
// Nfa matches many glob patterns in parallel by representing each state
// with one bit. It is used only for large pattern sets; matching individual
// patterns is faster when there are only a few of them.
#[derive(Debug, Default)]
struct Nfa {
initial_states: Vec<u64>,
star_states: Vec<u64>,
accept_states: Vec<u64>,
char_masks: Vec<u64>,
values: Vec<i64>,
}
impl Nfa {
fn compile(patterns: &[Pattern]) -> Self {
let mut num_states = 0;
for pattern in patterns {
num_states += 1;
for tok in &pattern.tokens {
match tok {
Token::Str(literal) => num_states += literal.len(),
Token::Star => {}
_ => num_states += 1,
}
}
}
let num_words = num_states.div_ceil(64);
let mut nfa = Self {
initial_states: vec![0; num_words],
star_states: vec![0; num_words],
accept_states: vec![0; num_words],
char_masks: vec![0; 256 * num_words],
values: vec![-1; num_states],
};
let set_bit = |vec: &mut [u64], pos: usize| vec[pos / 64] |= 1 << (pos % 64);
let mut state = 0;
for pattern in patterns {
set_bit(&mut nfa.initial_states, state);
for tok in &pattern.tokens {
match tok {
Token::Str(literal) => {
for &c in literal {
state += 1;
nfa.char_masks[c as usize * num_words + state / 64] |=
1 << (state % 64);
}
}
Token::Star => set_bit(&mut nfa.star_states, state),
Token::Question => {
state += 1;
for c in 0..256 {
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
}
}
Token::Bracket(chars) => {
state += 1;
for c in 0..256 {
if chars[c] {
nfa.char_masks[c * num_words + state / 64] |= 1 << (state % 64);
}
}
}
}
}
set_bit(&mut nfa.accept_states, state);
nfa.values[state] = pattern.value;
state += 1;
}
nfa
}
fn is_empty(&self) -> bool {
self.initial_states.is_empty()
}
fn matches(&self, s: &[u8]) -> i64 {
let num_words = self.initial_states.len();
let mut states = self.initial_states.clone();
for &c in s {
let mask = &self.char_masks[c as usize * num_words..][..num_words];
let mut carry = 0;
for i in 0..num_words {
let old = states[i];
let next = (old << 1) | carry;
states[i] = (old & self.star_states[i]) | (next & mask[i]);
carry = old >> 63;
}
}
let mut value = -1;
for (i, &state) in states.iter().enumerate() {
let mut word = state & self.accept_states[i];
while word != 0 {
let bit = word.trailing_zeros() as usize;
value = value.max(self.values[i * 64 + bit]);
word &= word - 1;
}
}
value
}
}
/// An Aho-Corasick automaton for patterns that are plain substring
/// searches, such as `*foo*`, `foo*` or `*foo`. Anchors are represented by
/// a NUL byte at the beginning or end of the pattern.
#[derive(Clone, Debug, Default)]
struct AhoCorasick {
// Most trie nodes have only one child. The root uses a dense table because
// it is visited for almost every input byte; other edges are stored sparsely.
root_children: Vec<i32>,
nodes: Vec<TrieNode>,
max_value: i64,
}
#[derive(Clone, Debug)]
struct TrieNode {
value: i64,
suffix_link: i32,
first_child: i32,
next_sibling: i32,
ch: u8,
}
impl Default for TrieNode {
fn default() -> Self {
Self { value: -1, suffix_link: -1, first_child: -1, next_sibling: -1, ch: 0 }
}
}
impl AhoCorasick {
fn can_handle(pat: &[u8]) -> bool {
let pat = pat.strip_prefix(b"*").unwrap_or(pat);
let pat = pat.strip_suffix(b"*").unwrap_or(pat);
!pat.is_empty() && !pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
}
fn find_child(&self, node: i32, ch: u8) -> i32 {
if node == 0 {
return self.root_children[ch as usize];
}
let mut i = self.nodes[node as usize].first_child;
while i != -1 {
if self.nodes[i as usize].ch == ch {
return i;
}
i = self.nodes[i as usize].next_sibling;
}
-1
}
fn add_child(&mut self, node: i32, ch: u8) -> i32 {
let child = self.find_child(node, ch);
if child != -1 {
return child;
}
let child = self.nodes.len() as i32;
let sibling = self.nodes[node as usize].first_child;
self.nodes.push(TrieNode { next_sibling: sibling, ch, ..TrieNode::default() });
self.nodes[node as usize].first_child = child;
if node == 0 {
self.root_children[ch as usize] = child;
}
child
}
fn add(&mut self, pat: &[u8], value: i64) {
debug_assert!(Self::can_handle(pat));
self.max_value = self.max_value.max(value);
if self.nodes.is_empty() {
self.root_children = vec![-1; 256];
self.nodes.push(TrieNode::default());
}
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
// substring matching, so we use \0 as a beginning/end-of-string
// markers.
let mut idx = 0;
if !pat.starts_with(b"*") {
idx = self.add_child(idx, 0);
}
for &c in pat {
if c != b'*' {
idx = self.add_child(idx, c);
}
}
if !pat.ends_with(b"*") {
idx = self.add_child(idx, 0);
}
let node = &mut self.nodes[idx as usize];
node.value = node.value.max(value);
}
fn compile(&mut self) {
if self.nodes.is_empty() {
return;
}
// A failure link may refer to any node at the previous depth, so failure
// links must be constructed breadth-first.
let mut queue = VecDeque::new();
let mut child = self.nodes[0].first_child;
while child != -1 {
self.nodes[child as usize].suffix_link = 0;
queue.push_back(child);
child = self.nodes[child as usize].next_sibling;
}
while let Some(idx) = queue.pop_front() {
let mut child = self.nodes[idx as usize].first_child;
while child != -1 {
let ch = self.nodes[child as usize].ch;
let mut suffix = self.nodes[idx as usize].suffix_link;
while suffix != 0 && self.find_child(suffix, ch) == -1 {
suffix = self.nodes[suffix as usize].suffix_link;
}
let next = self.find_child(suffix, ch);
if next != -1 {
suffix = next;
}
self.nodes[child as usize].suffix_link = suffix;
let suffix_value = self.nodes[suffix as usize].value;
let node = &mut self.nodes[child as usize];
node.value = node.value.max(suffix_value);
queue.push_back(child);
child = self.nodes[child as usize].next_sibling;
}
}
}
// This runs once per byte while matching symbol names. Keep the state
// in the caller's registers instead of outlining the loop body.
#[inline(always)]
fn walk(&self, c: u8, idx: &mut i32, value: &mut i64) -> i64 {
let mut j = *idx;
while j != -1 {
let child = self.find_child(j, c);
if child != -1 {
*idx = child;
*value = (*value).max(self.nodes[child as usize].value);
return *value;
}
j = self.nodes[j as usize].suffix_link;
}
*idx = 0;
*value
}
fn find(&self, s: &[u8]) -> i64 {
if self.nodes.is_empty() {
return -1;
}
let mut idx = 0;
let mut value = -1;
self.walk(0, &mut idx, &mut value);
for &c in s {
if self.walk(c, &mut idx, &mut value) == self.max_value {
return self.max_value;
}
}
self.walk(0, &mut idx, &mut value)
}
}
#[derive(Clone, Debug)]
struct Literal {
pat: Vec<u8>,
value: i64,
}
/// A set of glob patterns, each associated with a value. Looking up a
/// string returns the largest value among the matching patterns, or -1.
#[derive(Debug)]
pub struct Glob {
// Patterns that need only a literal string comparison are kept out
// of the automaton-based matchers below, which scan the entire input
// string per query. Real version scripts consist almost entirely of
// such patterns (e.g. `local: *;` or `v8dbg_*;`), and we match them
// against every defined symbol name.
match_all: i64, // "*"
max_value: i64,
exacts: Vec<Literal>,
prefixes: Vec<Literal>,
suffixes: Vec<Literal>,
patterns: Vec<Pattern>, // "foo*bar"
aho_corasick: AhoCorasick,
nfa: Nfa,
}
/// Collects patterns before compiling an immutable matcher.
#[derive(Debug, Default)]
pub struct GlobBuilder {
glob: Glob,
}
fn is_literal(pat: &[u8]) -> bool {
!pat.iter().any(|&c| matches!(c, b'*' | b'?' | b'[' | b'\\'))
}
impl Default for Glob {
fn default() -> Self {
Self {
match_all: -1,
max_value: -1,
exacts: Vec::new(),
prefixes: Vec::new(),
suffixes: Vec::new(),
patterns: Vec::new(),
aho_corasick: AhoCorasick::default(),
nfa: Nfa::default(),
}
}
}
impl GlobBuilder {
/// Adds a pattern. Returns false if the pattern is malformed.
pub fn add(&mut self, pat: &[u8], value: i64) -> bool {
debug_assert!(value >= 0);
self.glob.max_value = self.glob.max_value.max(value);
// Match-all, exact, prefix and suffix patterns are handled with
// plain string comparisons instead of the matchers below, which
// have to scan the entire input string on every query.
if pat == b"*" {
self.glob.match_all = self.glob.match_all.max(value);
return true;
}
if is_literal(pat) {
self.glob.exacts.push(Literal { pat: pat.to_vec(), value });
return true;
}
if let Some(prefix) = pat.strip_suffix(b"*").filter(|p| is_literal(p)) {
self.glob.prefixes.push(Literal { pat: prefix.to_vec(), value });
return true;
}
if let Some(suffix) = pat.strip_prefix(b"*").filter(|p| is_literal(p)) {
self.glob.suffixes.push(Literal { pat: suffix.to_vec(), value });
return true;
}
// If the pattern requires only a single substring search, the
// Aho-Corasick algorithm is even faster than our glob matcher.
if AhoCorasick::can_handle(pat) {
self.glob.aho_corasick.add(pat, value);
return true;
}
match Pattern::compile(pat, value) {
Some(pattern) => {
self.glob.patterns.push(pattern);
true
}
None => false,
}
}
/// Adds a string that matches itself alone, wildcards and all.
pub fn add_literal(&mut self, s: &[u8], value: i64) {
debug_assert!(value >= 0);
self.glob.max_value = self.glob.max_value.max(value);
self.glob.exacts.push(Literal { pat: s.to_vec(), value });
}
/// Consumes the construction state. The result supports parallel queries.
pub fn build(self) -> Glob {
let mut glob = self.glob;
if glob.match_all == glob.max_value {
return glob;
}
// For duplicate names, retain the largest value.
glob.exacts.sort_by(|a, b| a.pat.cmp(&b.pat).then(b.value.cmp(&a.value)));
glob.exacts.dedup_by(|a, b| a.pat == b.pat);
if glob.patterns.len() >= 64 {
glob.nfa = Nfa::compile(&glob.patterns);
glob.patterns = Vec::new();
}
glob.aho_corasick.compile();
glob
}
}
impl Glob {
pub fn new() -> Self {
Self::default()
}
pub fn is_empty(&self) -> bool {
self.max_value < 0
}
/// Returns the largest value of a matching pattern, or -1 if none match.
pub fn find(&self, s: &[u8]) -> i64 {
let mut value = self.match_all;
if value == self.max_value {
return value;
}
if let Ok(i) = self.exacts.binary_search_by(|l| l.pat.as_slice().cmp(s)) {
value = value.max(self.exacts[i].value);
}
for p in &self.prefixes {
if value < p.value && s.starts_with(&p.pat) {
value = p.value;
}
}
for p in &self.suffixes {
if value < p.value && s.ends_with(&p.pat) {
value = p.value;
}
}
if value == self.max_value {
return value;
}
value = value.max(self.aho_corasick.find(s));
if value == self.max_value {
return value;
}
if !self.nfa.is_empty() {
value = value.max(self.nfa.matches(s));
}
for p in &self.patterns {
if value < p.value && p.matches(s) {
value = p.value;
}
}
value
}
}
#[cfg(test)]
mod tests {
use super::*;
fn glob(pats: &[&str]) -> Glob {
let mut g = GlobBuilder::default();
for (i, p) in pats.iter().enumerate() {
assert!(g.add(p.as_bytes(), i as i64));
}
g.build()
}
#[test]
fn default_has_no_matches() {
let g = Glob::default();
assert!(g.is_empty());
assert_eq!(g.find(b"missing"), -1);
let mut g = GlobBuilder::default();
assert!(g.add(b"present", 7));
let g = g.build();
assert!(!g.is_empty());
assert_eq!(g.find(b"present"), 7);
assert_eq!(g.find(b"missing"), -1);
}
#[test]
fn shared_priorities_preserve_highest_match() {
let mut g = GlobBuilder::default();
for (pattern, priority) in
[("*", 0), ("*inner*", 2), ("prefix*", 2), ("*suffix", 2), ("exact", 3), ("p?efix*", 4)]
{
assert!(g.add(pattern.as_bytes(), priority));
}
let g = g.build();
for (name, expected) in
[("none", 0), ("hasinnersuffix", 2), ("suffix", 2), ("exact", 3), ("prefixsuffix", 4)]
{
assert_eq!(g.find(name.as_bytes()), expected);
}
let mut g = GlobBuilder::default();
assert!(g.add(b"*inner*", 0));
assert!(g.add(b"*suffix", 0));
let g = g.build();
assert_eq!(g.find(b"innersuffix"), 0);
assert_eq!(g.find(b"none"), -1);
}
#[test]
fn literals() {
let g = glob(&["foo", "bar*", "*baz", "*"]);
assert_eq!(g.find(b"foo"), 3);
assert_eq!(g.find(b"barx"), 3);
assert_eq!(g.find(b"xbaz"), 3);
let g = glob(&["foo", "bar*", "*baz"]);
assert_eq!(g.find(b"foo"), 0);
assert_eq!(g.find(b"barx"), 1);
assert_eq!(g.find(b"xbaz"), 2);
assert_eq!(g.find(b"nothing"), -1);
}
#[test]
fn complex() {
let g = glob(&["a*b*c", "x?z", "[a-c]d", "*mid*", "[!x]y"]);
assert_eq!(g.find(b"axxbyyc"), 0);
assert_eq!(g.find(b"xyz"), 1);
assert_eq!(g.find(b"bd"), 2);
assert_eq!(g.find(b"dd"), -1);
assert_eq!(g.find(b"leftmidright"), 3);
assert_eq!(g.find(b"!y"), 4);
assert_eq!(g.find(b"xy"), 4);
assert_eq!(g.find(b"ay"), -1);
}
#[test]
fn ld_prime_brackets() {
let g = glob(&["a[a-b-d]", "b[c-a]", "c[\\a]", "d[]x", "e\\*", "f\\"]);
for (name, expected) in [
("ab", 0),
("a-", 0),
("aA", 0),
("ac", 0),
("a,", -1),
("bc", 1),
("bb", -1),
("c\\", 2),
("ca", 2),
("d]x", -1),
("dx", -1),
("e*", 4),
("ex", -1),
("f\\", 5),
] {
assert_eq!(g.find(name.as_bytes()), expected, "{name}");
}
let mut g = GlobBuilder::default();
for pat in ["x[", "y[a-]", "z[-a]"] {
assert!(!g.add(pat.as_bytes(), 0));
}
}
#[test]
fn many_patterns_use_nfa() {
let pats: Vec<String> = (0..100).map(|i| format!("p{i}*q?r")).collect();
let refs: Vec<&str> = pats.iter().map(String::as_str).collect();
let g = glob(&refs);
assert_eq!(g.find(b"p42xxqyr"), 42);
assert_eq!(g.find(b"p42xxqr"), -1);
}
}
+273
View File
@@ -0,0 +1,273 @@
//! Small helpers shared across the linker.
pub(crate) mod cityhash;
pub mod demangle;
pub mod glob;
pub mod perf;
pub(crate) mod siphash;
pub(crate) mod worker_local;
/// Rounds `value` up to a multiple of `align`, which must be zero or a power
/// of two. Zero means "no alignment".
#[inline]
pub fn align_to(value: u64, align: u64) -> u64 {
if align == 0 {
return value;
}
debug_assert!(align.is_power_of_two());
(value + align - 1) & !(align - 1)
}
/// Rounds `val` up to the next value congruent to `modulus` modulo
/// `align`: the smallest x >= val with x % align == modulus. ld64 places
/// every subsection this way, keeping the offset it had within its
/// section modulo the section's alignment, not merely rounding up to the
/// section's alignment.
pub fn align_to_mod(val: u64, align: u64, modulus: u64) -> u64 {
debug_assert!(align.is_power_of_two() && modulus < align);
if val <= modulus { modulus } else { align_to(val - modulus, align) + modulus }
}
// Returns [hi:lo] bits of val.
#[inline]
pub fn bits(value: u64, hi: u32, lo: u32) -> u64 {
(value >> lo) & ((1u64 << (hi - lo + 1)) - 1)
}
// Cast val to a signed N bit integer.
// For example, sign_extend(x, 32) == (i32)x for any integer x.
pub fn sign_extend(value: u64, n: u32) -> i64 {
((value << (64 - n)) as i64) >> (64 - n)
}
// Little-endian reads and writes of the integer at the start of a
// slice: the targets' instructions and relocated fields, and the
// fields of a mergeable dylib's record.
pub fn read16(loc: &[u8]) -> u16 {
u16::from_le_bytes(loc[..2].try_into().unwrap())
}
pub fn read32(loc: &[u8]) -> u32 {
u32::from_le_bytes(loc[..4].try_into().unwrap())
}
pub fn read64(loc: &[u8]) -> u64 {
u64::from_le_bytes(loc[..8].try_into().unwrap())
}
pub fn write32(loc: &mut [u8], val: u32) {
loc[..4].copy_from_slice(&val.to_le_bytes());
}
pub fn write64(loc: &mut [u8], val: u64) {
loc[..8].copy_from_slice(&val.to_le_bytes());
}
// Little-endian appends of an integer to a buffer: the fields of the
// tables the linker builds in full before writing them out (the chained
// fixups, the unwind info, a DOF section).
pub fn push16(buf: &mut Vec<u8>, val: u16) {
buf.extend_from_slice(&val.to_le_bytes());
}
pub fn push32(buf: &mut Vec<u8>, val: u32) {
buf.extend_from_slice(&val.to_le_bytes());
}
pub fn push64(buf: &mut Vec<u8>, val: u64) {
buf.extend_from_slice(&val.to_le_bytes());
}
/// A sort key that orders byte strings like the strings themselves but
/// settles most comparisons on one integer: the first eight bytes,
/// big-endian, zero-padded. Symbol names cannot contain NULs, so
/// (prefix, name) order equals plain name order. Mach-O sorts its
/// global symbols and export-trie input by name (ELF mold never
/// name-sorts), and mangled names share long prefixes, which makes
/// plain slice comparison the sort's bottleneck.
pub fn name_sort_key(name: &[u8]) -> (u64, &[u8]) {
let mut p = [0u8; 8];
let n = name.len().min(8);
p[..n].copy_from_slice(&name[..n]);
(u64::from_be_bytes(p), name)
}
/// Appends `value` in unsigned LEB128 encoding.
pub fn encode_uleb(out: &mut Vec<u8>, mut value: u64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
if value == 0 {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
/// Appends `value` in signed LEB128 encoding.
pub fn encode_sleb(out: &mut Vec<u8>, mut value: i64) {
loop {
let byte = (value & 0x7f) as u8;
value >>= 7;
let negative = byte & 0x40 != 0;
if (value == 0 && !negative) || (value == -1 && negative) {
out.push(byte);
return;
}
out.push(byte | 0x80);
}
}
/// Writes `value` in unsigned LEB128 encoding at the start of `buf`,
/// returning its length.
pub fn write_uleb(buf: &mut [u8], mut value: u64) -> usize {
let mut i = 0;
loop {
let mut byte = (value & 0x7f) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80;
}
buf[i] = byte;
i += 1;
if value == 0 {
return i;
}
}
}
/// The length of `value` in unsigned LEB128 encoding.
pub fn uleb_size(mut value: u64) -> usize {
let mut len = 1;
while value >= 0x80 {
value >>= 7;
len += 1;
}
len
}
/// Reads an unsigned LEB128 value, advancing `bytes` past it.
#[inline]
pub fn read_uleb(bytes: &mut &[u8]) -> u64 {
let mut value = 0;
let mut shift = 0;
loop {
let (&byte, rest) = bytes.split_first().expect("truncated LEB128");
*bytes = rest;
if shift < 64 {
value |= ((byte & 0x7f) as u64) << shift;
}
shift += 7;
if byte & 0x80 == 0 {
return value;
}
}
}
/// Converts bytes from a response file, a load command or a file list
/// to an OS string. Unix paths can contain arbitrary non-NUL bytes.
pub fn os_str(bytes: &[u8]) -> &std::ffi::OsStr {
std::os::unix::ffi::OsStrExt::from_bytes(bytes)
}
/// The bytes of a path, as the file system and Mach-O load commands
/// hold them.
pub fn path_bytes(path: &std::path::Path) -> &[u8] {
std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str())
}
/// Whether a byte is white space as isspace() takes it in the C locale.
pub fn is_space(c: u8) -> bool {
matches!(c, b' ' | b'\t' | b'\n' | 0x0b | 0x0c | b'\r')
}
/// A byte string without the white space (see is_space) it starts and
/// ends with.
pub fn trim_space(mut bytes: &[u8]) -> &[u8] {
while let [first, rest @ ..] = bytes
&& is_space(*first)
{
bytes = rest;
}
while let [rest @ .., last] = bytes
&& is_space(*last)
{
bytes = rest;
}
bytes
}
/// The lines of a text file, as str::lines splits a string: at each
/// '\n', a '\r' before it dropped, the last line ended or not. Lists
/// of names (symbols, files) are bytes, whatever their encoding.
pub fn lines(text: &[u8]) -> impl Iterator<Item = &[u8]> {
let n = if text.is_empty() { 0 } else { usize::MAX };
let text = text.strip_suffix(b"\n").unwrap_or(text);
let lines = text.split(|&c| c == b'\n').take(n);
lines.map(|line| line.strip_suffix(b"\r").unwrap_or(line))
}
/// Splits a byte string at the first `sep`, as str::split_once does a
/// string.
pub fn split_once(bytes: &[u8], sep: u8) -> Option<(&[u8], &[u8])> {
let i = memchr::memchr(sep, bytes)?;
Some((&bytes[..i], &bytes[i + 1..]))
}
/// Makes room in one of the link's arenas (the symbols, the subsections,
/// ...) for `additional` more elements, with an eighth of the whole to
/// spare when it grows: the auto-link rounds append a few objects' worth
/// to arenas the command line's objects filled exactly, which they would
/// take by copying themselves whole (the 57 MB of symbols and 82 MB of
/// subsections of ASan iTerm2, 10 ms). The room left over costs address
/// space until it is written.
pub fn reserve_arena<T>(v: &mut Vec<T>, additional: usize) {
if v.capacity() - v.len() < additional {
v.reserve_exact(additional + (v.len() + additional) / 8);
}
}
/// Leaks a byte string for the rest of the process's lifetime: names
/// in the output string table outlive every data structure of a link,
/// and the process exits as soon as the link is done.
pub fn leak_bytes(bytes: Vec<u8>) -> &'static [u8] {
Vec::leak(bytes)
}
/// Fills `buf` with random bytes from the operating system.
pub fn random_bytes(buf: &mut [u8]) {
getrandom::fill(buf).unwrap_or_else(|err| crate::fatal!("cannot get random bytes: {err}"));
}
/// Computes the SHA-256 hash of `data` into `out`.
pub fn sha256(data: &[u8], out: &mut [u8; 32]) {
use sha2::Digest;
out.copy_from_slice(&sha2::Sha256::digest(data));
}
/// Computes the SHA-1 hash of `data` into `out`.
pub fn sha1(data: &[u8], out: &mut [u8; 20]) {
use sha1::Digest;
out.copy_from_slice(&sha1::Sha1::digest(data));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn uleb_roundtrip() {
for &value in &[0u64, 1, 127, 128, 300, u32::MAX as u64, u64::MAX] {
let mut buf = Vec::new();
encode_uleb(&mut buf, value);
assert_eq!(uleb_size(value), buf.len());
let mut written = [0xff; 10];
assert_eq!(write_uleb(&mut written, value), buf.len());
assert_eq!(&written[..buf.len()], buf);
let mut slice = buf.as_slice();
assert_eq!(read_uleb(&mut slice), value);
assert!(slice.is_empty());
}
}
}
+248
View File
@@ -0,0 +1,248 @@
//! Wall-clock and CPU time accounting for the passes of a link.
use std::sync::{Arc, Mutex};
use std::time::Instant;
#[derive(Debug)]
struct Record {
name: String,
start: Instant,
end: Option<Instant>,
user_start: f64,
sys_start: f64,
user: f64,
sys: f64,
parent: Option<usize>,
children: Vec<usize>,
}
// Infer implicit parents from complete intervals, as C++ mold does. Overlapping
// timers need not be nested, and explicitly named parents take precedence.
fn nest_records(records: &mut [Record]) {
for record in records.iter_mut() {
record.children.clear();
}
for i in 0..records.len() {
if records[i].parent.is_none() {
let inner = &records[i];
records[i].parent = records[..i].iter().rposition(|outer| {
outer.start <= inner.start && inner.end.unwrap() <= outer.end.unwrap()
});
}
if let Some(parent) = records[i].parent {
records[parent].children.push(i);
}
}
}
/// Collects wall-clock and CPU timing records for the passes of a link.
/// Cloning shares the underlying records.
#[derive(Clone, Debug)]
pub struct Timers {
records: Option<Arc<Mutex<Vec<Record>>>>,
}
/// A running timer, stopped when dropped.
pub struct Timer {
timers: Timers,
index: usize,
stopped: bool,
}
/// A clonable handle for starting timers beneath a running timer.
#[derive(Clone)]
pub struct TimerHandle {
timers: Timers,
parent: usize,
}
fn rusage() -> (f64, f64) {
// SAFETY: `usage` is a valid, writable rusage struct.
let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
unsafe { libc::getrusage(libc::RUSAGE_SELF, &raw mut usage) };
let to_secs = |t: libc::timeval| t.tv_sec as f64 + t.tv_usec as f64 / 1_000_000.0;
(to_secs(usage.ru_utime), to_secs(usage.ru_stime))
}
impl Default for Timers {
fn default() -> Self {
Self::new()
}
}
impl Timers {
pub fn new() -> Self {
Self { records: Some(Arc::new(Mutex::new(Vec::new()))) }
}
/// Skips clock reads, system calls and shared recording when
/// -print_statistics is off.
pub fn disabled() -> Self {
Self { records: None }
}
fn inactive(&self) -> Timer {
Timer { timers: self.clone(), index: 0, stopped: true }
}
/// Starts a timer whose nesting is inferred from completed time intervals.
pub fn start(&self, name: &str) -> Timer {
self.start_child(name, None)
}
fn start_child(&self, name: impl std::fmt::Display, parent: Option<usize>) -> Timer {
let Some(records) = &self.records else {
return self.inactive();
};
let (user, sys) = rusage();
let mut records = records.lock().unwrap();
let index = records.len();
records.push(Record {
name: name.to_string(),
start: Instant::now(),
end: None,
user_start: user,
sys_start: sys,
user: 0.0,
sys: 0.0,
parent,
children: Vec::new(),
});
Timer { timers: self.clone(), index, stopped: false }
}
fn stop(&self, index: usize) {
let (user, sys) = rusage();
let mut records = self.records.as_ref().unwrap().lock().unwrap();
let record = &mut records[index];
if record.end.is_none() {
record.end = Some(Instant::now());
record.user = user - record.user_start;
record.sys = sys - record.sys_start;
}
}
/// Prints the timer tree to stderr, where ld64 reports its
/// -print_statistics figures.
pub fn print(&self) {
let Some(records) = &self.records else {
return;
};
let mut records = records.lock().unwrap();
let now = Instant::now();
for r in records.iter_mut() {
if r.end.is_none() {
r.end = Some(now);
}
}
nest_records(&mut records);
fn print_rec(records: &[Record], i: usize, indent: usize) {
let r = &records[i];
let real = r.end.unwrap().duration_since(r.start).as_secs_f64();
eprintln!(
" {:8.3} {:8.3} {:8.3} {}{}",
r.user,
r.sys,
real,
" ".repeat(indent * 2),
r.name
);
// nest_records adds children in their recorded start order.
for &child in &r.children {
print_rec(records, child, indent + 1);
}
}
eprintln!(" User System Real Name");
for i in 0..records.len() {
if records[i].parent.is_none() {
print_rec(&records, i, 0);
}
}
}
}
impl Timer {
/// Starts a timer nested in this one. Timers started from parallel
/// tasks name their parent this way, since which timer started last
/// says nothing about nesting then.
pub fn child(&self, name: impl std::fmt::Display) -> Self {
self.timers.start_child(name, Some(self.index))
}
pub fn handle(&self) -> TimerHandle {
TimerHandle { timers: self.timers.clone(), parent: self.index }
}
pub fn stop(&mut self) {
if !self.stopped {
self.stopped = true;
self.timers.stop(self.index);
}
}
}
impl TimerHandle {
pub fn child(&self, name: impl std::fmt::Display) -> Timer {
self.timers.start_child(name, Some(self.parent))
}
}
impl Drop for Timer {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implicit_timer_parents_follow_completed_intervals() {
let timers = Timers::new();
let root = timers.start("root");
let _running = [
timers.start("foreground"),
timers.start("nested"),
timers.start("background"),
timers.start("next foreground"),
root.child("explicit child"),
];
let mut records = timers.records.as_ref().unwrap().lock().unwrap();
let start = records[0].start;
for (record, (begin, end)) in
records.iter_mut().zip([(0, 100), (10, 40), (15, 25), (20, 70), (50, 80), (60, 65)])
{
record.start = start + std::time::Duration::from_millis(begin);
record.end = Some(start + std::time::Duration::from_millis(end));
}
nest_records(&mut records);
let children: Vec<_> = records.iter().map(|r| r.children.clone()).collect();
drop(records);
assert_eq!(children, [vec![1, 3, 4, 5], vec![2], vec![], vec![], vec![], vec![]]);
}
#[test]
fn optional_recording_includes_nested_and_background_timers() {
for timers in [Timers::new(), Timers::disabled()] {
let mut root = timers.start("root");
let child = root.child("child");
let handle = root.handle();
std::thread::spawn(move || drop(handle.child("background"))).join().unwrap();
drop(child);
root.stop();
root.stop();
if let Some(records) = &timers.records {
let mut records = records.lock().unwrap();
nest_records(&mut records);
assert_eq!(records.len(), 3);
assert!(records.iter().all(|record| record.end.is_some()));
assert_eq!(records[0].children, [1, 2]);
} else {
assert!(root.timers.records.is_none());
}
}
}
}
+166
View File
@@ -0,0 +1,166 @@
//! A configurable implementation of SipHash.
//!
//! The compression rounds, finalization rounds and 64- or 128-bit output are
//! selected with const generic parameters. This implementation is based on
//! the reference implementation at https://github.com/rui314/siphash.
pub struct SipHashTmpl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize> {
v0: u64,
v1: u64,
v2: u64,
v3: u64,
buf: [u8; 8],
buflen: u8,
sum: u8,
}
impl<const C_ROUNDS: usize, const D_ROUNDS: usize, const OUTLEN: usize>
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN>
{
#[inline]
pub(crate) fn new(key: &[u8; 16]) -> Self {
const { assert!(OUTLEN == 64 || OUTLEN == 128) };
let k0 = u64::from_le_bytes(key[..8].try_into().unwrap());
let k1 = u64::from_le_bytes(key[8..].try_into().unwrap());
let mut hasher = Self {
v0: 0x736f_6d65_7073_6575 ^ k0,
v1: 0x646f_7261_6e64_6f6d ^ k1,
v2: 0x6c79_6765_6e65_7261 ^ k0,
v3: 0x7465_6462_7974_6573 ^ k1,
buf: [0; 8],
buflen: 0,
sum: 0,
};
if OUTLEN == 128 {
hasher.v1 ^= 0xee;
}
hasher
}
// ICF repeatedly hashes fixed-size digests. Inlining lets the optimizer
// remove the buffering paths for these word-aligned updates.
#[inline(always)]
pub(crate) fn update(&mut self, mut msg: &[u8]) {
self.sum = self.sum.wrapping_add(msg.len() as u8);
if self.buflen != 0 {
let buflen = self.buflen as usize;
if buflen + msg.len() < 8 {
self.buf[buflen..buflen + msg.len()].copy_from_slice(msg);
self.buflen += msg.len() as u8;
return;
}
let n = 8 - buflen;
self.buf[buflen..].copy_from_slice(&msg[..n]);
self.compress(u64::from_le_bytes(self.buf));
msg = &msg[n..];
self.buflen = 0;
}
let (words, msg) = msg.as_chunks::<8>();
for &word in words {
self.compress(u64::from_le_bytes(word));
}
self.buf[..msg.len()].copy_from_slice(msg);
self.buflen = msg.len() as u8;
}
/// Hashes a word in little-endian byte order. The common aligned case
/// bypasses the byte buffer, including in loops of fixed-size updates.
#[inline(always)]
pub(crate) fn update_u64(&mut self, word: u64) {
if self.buflen != 0 {
self.update(&word.to_le_bytes());
return;
}
self.sum = self.sum.wrapping_add(8);
self.compress(word);
}
#[inline]
pub(crate) fn finish(mut self, out: &mut [u8]) {
assert_eq!(out.len() * 8, OUTLEN);
self.buf[self.buflen as usize..].fill(0);
self.compress((u64::from(self.sum) << 56) | u64::from_le_bytes(self.buf));
self.v2 ^= if OUTLEN == 128 { 0xee } else { 0xff };
self.finalize();
out[..8].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
if OUTLEN == 128 {
self.v1 ^= 0xdd;
self.finalize();
out[8..].copy_from_slice(&(self.v0 ^ self.v1 ^ self.v2 ^ self.v3).to_le_bytes());
}
}
#[inline]
fn round(&mut self) {
self.v0 = self.v0.wrapping_add(self.v1);
self.v1 = self.v1.rotate_left(13);
self.v1 ^= self.v0;
self.v0 = self.v0.rotate_left(32);
self.v2 = self.v2.wrapping_add(self.v3);
self.v3 = self.v3.rotate_left(16);
self.v3 ^= self.v2;
self.v0 = self.v0.wrapping_add(self.v3);
self.v3 = self.v3.rotate_left(21);
self.v3 ^= self.v0;
self.v2 = self.v2.wrapping_add(self.v1);
self.v1 = self.v1.rotate_left(17);
self.v1 ^= self.v2;
self.v2 = self.v2.rotate_left(32);
}
#[inline]
fn compress(&mut self, m: u64) {
self.v3 ^= m;
for _ in 0..C_ROUNDS {
self.round();
}
self.v0 ^= m;
}
#[inline]
fn finalize(&mut self) {
for _ in 0..D_ROUNDS {
self.round();
}
}
}
pub type SipHash13_128 = SipHashTmpl<1, 3, 128>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn word_updates_match_bytes_with_partial_buffers_and_length_wraparound() {
let key = std::array::from_fn(|i| i as u8);
let data: Vec<u8> = (0..1040).map(|i| (i * 97) as u8).collect();
for prefix in 0..8 {
for words in 0..128 {
let end = prefix + words * 8;
let mut bytes = SipHash13_128::new(&key);
bytes.update(&data[..end + 3]);
let mut mixed = SipHash13_128::new(&key);
mixed.update(&data[..prefix]);
for &word in data[prefix..end].as_chunks::<8>().0 {
mixed.update_u64(u64::from_le_bytes(word));
}
mixed.update(&data[end..end + 3]);
let (mut expected, mut actual) = ([0; 16], [0; 16]);
bytes.finish(&mut expected);
mixed.finish(&mut actual);
assert_eq!(actual, expected, "prefix={prefix}, words={words}");
}
}
}
}
+46
View File
@@ -0,0 +1,46 @@
//! Scratch storage per Rayon worker, as mold keeps it.
use std::sync::{Mutex, MutexGuard};
// Keep independent workers' locks and buffers off the same cache line.
#[repr(align(128))]
struct Worker<T>(Mutex<T>);
/// Persistent scratch storage per Rayon worker, plus a slot for callers
/// outside the pool. Look up and lock once per job, not once per item.
pub struct WorkerLocal<T>(Vec<Worker<T>>);
impl<T> WorkerLocal<T> {
pub fn new(init: impl Fn() -> T) -> Self {
Self((0..=rayon::current_num_threads()).map(|_| Worker(Mutex::new(init()))).collect())
}
pub fn get(&self) -> MutexGuard<'_, T> {
let fallback = self.0.len() - 1;
let worker = rayon::current_thread_index().unwrap_or(fallback).min(fallback);
self.0[worker].0.lock().unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rayon::prelude::*;
#[test]
fn reuses_worker_storage_and_keeps_an_outside_slot() {
let pool = rayon::ThreadPoolBuilder::new().num_threads(2).build().unwrap();
let slots = pool.install(|| {
let slots = WorkerLocal::new(Vec::new);
for batch in 0..3 {
(0..100).into_par_iter().for_each(|i| slots.get().push(batch * 100 + i));
}
slots
});
slots.get().push(300);
let mut values: Vec<_> =
slots.0.into_iter().flat_map(|worker| worker.0.into_inner().unwrap()).collect();
values.sort_unstable();
assert_eq!(values, (0..301).collect::<Vec<_>>());
}
}
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "mold-macho-tests"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "Test support for mold's shell test suite"
license.workspace = true
publish = false
[lib]
path = "lib.rs"
[dependencies]
libc.workspace = true
serde_json = "1.0.151"
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
source "$(dirname "$0")"/common.inc
cat <<EOF > $t/a.c
#include <stdio.h>
void hello() { printf("Hello world\n"); }
int main(){ hello(); }
EOF
$CC -o $t/a.o -c -g $t/a.c
$CC --ld-path=$mold -o $t/exe1 $t/a.o -g
nm -pa $t/exe1 | grep -w OSO
$CC --ld-path=$mold -o $t/exe2 $t/a.o -g -Wl,-S
nm -pa $t/exe2 > $t/log2
! grep -qw OSO $t/log2 || false
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
source "$(dirname "$0")"/common.inc
cat <<EOF | $CC -o $t/a.o -c -xc -
int foo();
int main() { foo(); }
EOF
$CC --ld-path=$mold -o $t/exe $t/a.o -Wl,-U,_foo
nm $t/exe | grep 'U _foo$'
Loaded 100 of 768 files, more files were not shown because too many files have changed in this diff. Show more