mirror of
https://github.com/rui314/mold.git
synced 2026-11-07 09:16:04 -10:00
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:
commit
5d05ee37c7
768 files changed
+83137
-1
No files matched your search
@@ -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
|
||||
@@ -1,6 +1,8 @@
|
||||
/target
|
||||
/out
|
||||
/dist
|
||||
/bench
|
||||
/.claude
|
||||
/.codex
|
||||
*~
|
||||
__pycache__/
|
||||
Generated
+146
@@ -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
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
@@ -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"] }
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Executable
+15
@@ -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.
@@ -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);
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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(§)
|
||||
);
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() });
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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());
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)*))
|
||||
};
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+517
@@ -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(" ")
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, §ions);
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
+1757
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
+459
@@ -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
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<_>>());
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
Executable
+17
@@ -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
|
||||
Executable
+10
@@ -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
Reference in new issue
Block a user