Compare commits

..
2 Commits
Author SHA1 Message Date
Rui Ueyama bda68f94a4 Bump mold version to 1.0.3 2022-01-30 19:39:38 +09:00
Rui Ueyama 601b9e691c Fix build-static.sh script
`build-static.sh` didn't create a statically-linked binary.

Fixes https://github.com/rui314/mold/issues/315
2022-01-30 18:21:02 +09:00
2393 changed files with 375335 additions and 69011 deletions

No files matched your search

-7
View File
@@ -1,7 +0,0 @@
# rustc's LoongArch targets use the medium code model, but the cc crate
# compiles C code with the C compiler's default normal code model, whose
# direct calls reach only ±128 MiB, unless -Ccode-model is given
# explicitly. A debug build of mold has more text than that, so we pass
# the model explicitly to make the cc crate pass -mcmodel=medium.
[target.'cfg(target_arch = "loongarch64")']
rustflags = ["-Ccode-model=medium"]
-13
View File
@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: [rui314] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+10
View File
@@ -0,0 +1,10 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
Delete this message and describe the bug here. Note that we only fix bugs in the most recent release of mold, so please make sure that you are using the most recent version.
-86
View File
@@ -1,86 +0,0 @@
name: Build all tarballs
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
jobs:
check-changes:
runs-on: ubuntu-24.04
outputs:
has_changes: ${{ github.event_name == 'workflow_dispatch' || steps.check.outputs.has_changes }}
steps:
- name: Check for new commits since last successful run
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
last=$(gh run list -R ${{ github.repository }} -w build-all.yml -b main -s success -L1 --json createdAt -q '.[0].createdAt')
if [ -z "$last" ] || [ "$last" = "null" ] || [ "$(gh api repos/${{ github.repository }}/commits?sha=main\&since=$last\&per_page=1 | jq length)" -gt 0 ]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
build-linux:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
build-windows:
needs: check-changes
if: needs.check-changes.outputs.has_changes == 'true'
runs-on: windows-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
-44
View File
@@ -1,44 +0,0 @@
name: Build native tarballs
on:
push:
workflow_dispatch:
jobs:
build-tarballs:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
runs-on: ${{ matrix.os }}
permissions:
contents: read
packages: write
steps:
- name: Checkout Repository
uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build a tarball
run: ./dist.sh ${{ matrix.target }}
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
compression-level: 0
-16
View File
@@ -1,16 +0,0 @@
$ErrorActionPreference = 'Stop'
Set-Location "$PSScriptRoot/../.."
cargo build --release --locked --package mold-cli
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$metadata = cargo metadata --locked --no-deps --format-version 1 | ConvertFrom-Json
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
$version = ($metadata.packages | Where-Object name -eq 'mold-cli').version
$stage = 'mold-install'
New-Item -ItemType Directory -Force "$stage/bin", "$stage/share/man/man1", "$stage/share/doc/mold", dist | Out-Null
Copy-Item target/release/mold.exe "$stage/bin/"
Copy-Item docs/mold.1 "$stage/share/man/man1/"
Copy-Item LICENSE "$stage/share/doc/mold/"
Compress-Archive -Path "$stage/*" -DestinationPath "dist/mold-$version-x86_64-windows.zip" -Force
+45 -181
View File
@@ -1,192 +1,56 @@
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test-all-targets:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
# Incremental artifacts and mold's own debug info are unused in CI.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
build-clang:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- name: install test dependencies
- uses: actions/checkout@v2
- name: apt-get
run: sudo dpkg --add-architecture i386 && sudo apt-get update && sudo apt-get install build-essential libstdc++-10-dev cmake clang libssl-dev zlib1g-dev libxxhash-dev libtbb-dev git bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make
run: |
sudo ./install-test-deps.sh
sudo .github/workflows/install-extras.sh
- run: cargo build --locked
- name: check the build for 32-bit hosts
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make CC=clang CXX=clang++ DEBUG=1 -j$(nproc)
- name: make test
run: |
rustup target add armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu \
powerpc-unknown-linux-gnu
cargo check --locked --workspace --all-targets --target armv7-unknown-linux-gnueabihf
# i386 aligns 64-bit integers to 4 bytes
cargo check --locked --workspace --all-targets --target i686-unknown-linux-gnu
# 32-bit PowerPC has no 64-bit atomics
cargo check --locked --workspace --all-targets --target powerpc-unknown-linux-gnu
- name: verify all target toolchains are available
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make test -j$(nproc)
build-gcc11:
runs-on: ubuntu-20.04
container: gcc:11.1.0
steps:
- uses: actions/checkout@v2
- name: apt-get
run: dpkg --add-architecture i386 && apt-get update && apt-get install -y sudo libssl-dev zlib1g-dev libxxhash-dev cmake clang dwarfdump bsdextrautils libgcc-9-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-dev:i386 lib32gcc-10-dev qemu-user
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make
run: |
cargo test --locked -p mold-cli --test integration -- --list |
tee "$RUNNER_TEMP/test-inventory.txt"
! grep -q '^unavailable:' "$RUNNER_TEMP/test-inventory.txt"
# All workspace crates were built above. Avoid linking empty test binaries
# for the target crates and command.
- run: cargo test --locked -p mold -p mold-tests -p mold-cli --lib --test integration
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-all-targets
path: target/debug/mold-test/out/test
test-sanitizers:
strategy:
matrix:
include:
- sanitizer: address
# Input files intentionally remain allocated until process exit.
runtime-options: detect_leaks=0:halt_on_error=1
test-prefix: ''
test-timeout: 30
- sanitizer: thread
runtime-options: halt_on_error=1
# TSan cannot model the atomic-fence synchronization in Rayon's
# work-stealing queue. Two CPUs still exercise parallel linker code.
test-prefix: taskset -c 0,1
test-timeout: 60
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
CARGO_TARGET_DIR: target/${{ matrix.sanitizer }}
RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: cargo +nightly build --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64
- run: |
if [ '${{ matrix.sanitizer }}' = address ]; then
export ASAN_OPTIONS='${{ matrix.runtime-options }}'
else
export TSAN_OPTIONS='${{ matrix.runtime-options }}'
fi
${{ matrix.test-prefix }} cargo +nightly test --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ matrix.sanitizer }}
path: target/${{ matrix.sanitizer }}/x86_64-unknown-linux-gnu/debug/mold-test/out/test
build-msan:
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@nightly
with:
components: rust-src
- run: sudo ./install-test-deps.sh
- run: sudo apt-get install -y clang llvm
- run: .github/workflows/run-msan.sh
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-msan
path: target/msan/x86_64-unknown-linux-gnu/debug/mold-test/out/test
test-distros:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian:12
- fedora
- gentoo/stage3
- opensuse/tumbleweed
- ubuntu:22.04
- ubuntu:25.04
runs-on: ubuntu-latest
timeout-minutes: 60
# Tumbleweed does not declare PATH in its container configuration.
container:
image: ${{ matrix.distro }}
env:
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
steps:
- uses: actions/checkout@v6
- run: ./install-test-deps.sh
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
- run: cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
- name: sanitize distro name
if: failure()
id: sanitize
run: echo "name=$(echo '${{ matrix.distro }}' | tr '/:' '--')" >> "$GITHUB_OUTPUT"
- name: archive test results
uses: actions/upload-artifact@v7
if: failure()
with:
name: test-results-${{ steps.sanitize.outputs.name }}
path: target/debug/mold-test/out/test
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make CC=gcc CXX=g++ DEBUG=1 -j$(nproc)
- name: make test
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make test -j$(nproc)
build-macos:
runs-on: macos-latest
timeout-minutes: 60
runs-on: macos-11
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
build-windows:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
build-msys:
runs-on: windows-latest
timeout-minutes: 60
env:
CARGO_NET_GIT_FETCH_WITH_CLI: true
steps:
- uses: actions/checkout@v6
- uses: msys2/setup-msys2@v2
with:
msystem: UCRT64
update: true
install: git
pacboy: rust:p cc:p dlfcn:p
- shell: msys2 {0}
run: cargo build --locked -p mold-cli --no-default-features --features x86_64
build-freebsd:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
# The VM has little disk space, so omit unused build artifacts.
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
steps:
- uses: actions/checkout@v6
- uses: vmactions/freebsd-vm@v1
with:
usesh: true
run: |
pkg install -y rust
./install-test-deps.sh
cargo build --locked -p mold-cli --no-default-features --features x86_64
cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
- uses: actions/checkout@v2
- name: brew
run: brew install xxhash
- name: ccache
uses: hendrikmuhs/ccache-action@v1
- name: make
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make DEBUG=1 -j$(sysctl -n hw.logicalcpu)
- name: make test
run: |
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
make test -j$(sysctl -n hw.logicalcpu)
-75
View File
@@ -1,75 +0,0 @@
#!/bin/bash -x
apt-get update
apt-get install -y wget xz-utils
# Install a 32-bit RISC-V toolchain
mkdir /rv32
wget -O- --progress=dot:mega https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.06.13/riscv32-glibc-ubuntu-24.04-gcc-nightly-2025.06.13-nightly.tar.xz | tar -C /rv32 --strip-components=1 --xz -xf -
ln -sf /rv32/sysroot /usr/riscv32-linux-gnu
echo '/rv32/bin/riscv32-unknown-linux-gnu-gcc -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-gcc
echo '/rv32/bin/riscv32-unknown-linux-gnu-g++ -L/usr/riscv32-linux-gnu "$@"' > /usr/bin/riscv32-linux-gnu-g++
chmod 755 /usr/bin/riscv32-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /rv32/bin/riscv32-unknown-linux-gnu-$i /usr/bin/riscv32-linux-gnu-$i
done
# Install a LoongArch toolchain
mkdir /larch
wget -O- --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/x86_64-cross-tools-loongarch64-binutils_2.44-gcc_15.1.0-glibc_2.41.tar.xz | tar -C /larch --strip-components=1 --xz -xf -
cp -r /larch/loongarch64-unknown-linux-gnu/lib/* /larch/target/lib64
ln -sf /larch/target /usr/loongarch64-linux-gnu
for i in gcc g++ objdump objcopy strip; do
ln -sf /larch/bin/loongarch64-unknown-linux-gnu-$i /usr/bin/loongarch64-linux-gnu-$i
done
wget -O /usr/local/bin/qemu-loongarch64 --progress=dot:mega https://github.com/loongson/build-tools/releases/download/2025.06.06/qemu-loongarch64
chmod 755 /usr/local/bin/qemu-loongarch64
# Install ARM32 big-endian toolchain
mkdir /armeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/armebv7-eabihf/tarballs/armebv7-eabihf--glibc--stable-2024.05-1.tar.xz | tar -C /armeb --strip-components=1 --xz -xf -
ln -sf /armeb/armeb-buildroot-linux-gnueabihf/sysroot /usr/armeb-linux-gnueabihf
echo '/armeb/bin/armeb-linux-gcc -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-gcc
echo '/armeb/bin/armeb-linux-g++ -L/usr/armeb-linux-gnu "$@"' > /usr/bin/armeb-linux-gnueabihf-g++
chmod 755 /usr/bin/armeb-linux-gnueabihf-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /armeb/bin/armeb-linux-$i /usr/bin/armeb-linux-gnueabihf-$i
done
# Install ARM64 big-endian toolchain
mkdir /aarch64be
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/aarch64be/tarballs/aarch64be--glibc--stable-2024.05-1.tar.xz | tar -C /aarch64be --strip-components=1 --xz -xf -
ln -sf /aarch64be/aarch64_be-buildroot-linux-gnu/sysroot /usr/aarch64_be-linux-gnu
echo '/aarch64be/bin/aarch64_be-linux-gcc -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-gcc
echo '/aarch64be/bin/aarch64_be-linux-g++ -L/usr/aarch64_be-linux-gnu "$@"' > /usr/bin/aarch64_be-linux-gnu-g++
chmod 755 /usr/bin/aarch64_be-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /aarch64be/bin/aarch64_be-linux-$i /usr/bin/aarch64_be-linux-gnu-$i
done
# Install SH4 big-endian toolchain
mkdir /sh4aeb
wget -O- --progress=dot:mega https://toolchains.bootlin.com/downloads/releases/toolchains/sh-sh4aeb/tarballs/sh-sh4aeb--glibc--stable-2024.05-1.tar.xz | tar -C /sh4aeb --strip-components=1 --xz -xf -
ln -sf /sh4aeb/sh4aeb-buildroot-linux-gnu/sysroot /usr/sh4aeb-linux-gnu
echo '/sh4aeb/bin/sh4aeb-linux-gcc -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-gcc
echo '/sh4aeb/bin/sh4aeb-linux-g++ -L/usr/sh4aeb-linux-gnu "$@"' > /usr/bin/sh4aeb-linux-gnu-g++
chmod 755 /usr/bin/sh4aeb-linux-gnu-{gcc,g++}
for i in objdump objcopy strip; do
ln -sf /sh4aeb/bin/sh4aeb-linux-$i /usr/bin/sh4aeb-linux-gnu-$i
done
# Install Intel SDE CPU emulator for CET-related tests
mkdir /sde
wget -O- --progress=dot:mega https://downloadmirror.intel.com/850782/sde-external-9.53.0-2025-03-16-lin.tar.xz | tar -C /sde --strip-components=1 --xz -xf -
ln -s /sde/sde64 /usr/bin
-97
View File
@@ -1,97 +0,0 @@
name: Build & attach tarballs on tag push
on:
push:
tags: ['v[0-9]*']
permissions:
contents: write
packages: write
id-token: write
env:
TAG: ${{ github.ref_name }}
jobs:
build-linux:
strategy:
matrix:
include:
- { target: x86_64, os: ubuntu-24.04 }
- { target: aarch64, os: ubuntu-24.04-arm }
- { target: arm, os: ubuntu-24.04-arm }
- { target: riscv64, os: ubuntu-24.04 }
- { target: ppc64le, os: ubuntu-24.04 }
- { target: s390x, os: ubuntu-24.04 }
- { target: loongarch64, os: ubuntu-24.04 }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- name: Install Podman
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
- name: Login to GitHub Container Registry
uses: redhat-actions/podman-login@v2
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- run: ./dist.sh ${{ matrix.target }}
- uses: actions/upload-artifact@v7
with:
name: ${{ matrix.target }}
path: dist/mold-*.tar.gz
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@stable
- name: Build and Archive
shell: pwsh
run: ./.github/workflows/build-windows.ps1
- uses: actions/upload-artifact@v7
with:
name: win-x86_64
path: dist/mold-*.*
publish:
needs: [build-linux, build-windows]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
path: dist
pattern: '*'
merge-multiple: true
- name: Ensure release exists (create if missing)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
if gh release view "$TAG" >& /dev/null; then
echo "Existing release found for $TAG"
else
echo "Creating draft release for $TAG"
gh release create "$TAG" --draft \
--title "$TAG" \
--notes "Automated draft – fill the changelog later."
fi
- name: Upload tarballs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "$TAG" dist/* --clobber
gh release view "$TAG" --json assets --jq '.assets[].name'
-34
View File
@@ -1,34 +0,0 @@
#!/bin/bash
set -euo pipefail
cd "$(dirname "$0")/../.."
# MSan requires instrumented Rust, std, and native dependencies:
# https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#memorysanitizer
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target/msan}"
export RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins=2'
export RUSTDOCFLAGS="$RUSTFLAGS"
export CC_x86_64_unknown_linux_gnu=clang
export CFLAGS_x86_64_unknown_linux_gnu='-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer'
export LIBZ_SYS_STATIC=1
export MSAN_OPTIONS=halt_on_error=1
# The instrumented linker is too slow unoptimized for the heaviest tests
# to finish within the per-test timeout.
export CARGO_PROFILE_DEV_OPT_LEVEL=1
# Use instrumentable implementations instead of BLAKE3/Zstd assembly, and
# select mold as well as mold-cli so Cargo enables its dependency features.
args=(
--locked -Zbuild-std --target x86_64-unknown-linux-gnu
--package mold --package mold-cli --no-default-features
--features mold-cli/system-allocator,mold-cli/x86_64,blake3/pure,zstd/no_asm
--test integration
)
cargo +nightly test "${args[@]}" --no-run
# The preload wrapper runs inside uninstrumented compiler processes, not
# inside mold. Rebuild it without MSan so those processes can load it.
clang -shared -fPIC -o "$CARGO_TARGET_DIR/x86_64-unknown-linux-gnu/debug/mold-wrapper.so" \
c/mold-wrapper.c -ldl
cargo +nightly test "${args[@]}" -- --native --test-threads 1 --timeout 60 "$@"
-36
View File
@@ -1,36 +0,0 @@
name: Update manpage
on:
push:
paths:
- 'docs/mold.md'
branches:
- main
workflow_dispatch:
permissions:
contents: write
jobs:
update-manpage:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install ronn
run: sudo apt-get update && sudo apt-get install -y ronn
- name: Generate mold.1 from mold.md
run: ronn --roff docs/mold.md
- name: Configure Git
run: |
git config --global user.name 'Rui Ueyama'
git config --global user.email '[email protected]'
- name: Commit and push if mold.1 is updated
run: |
git add docs/mold.1
git diff --staged --quiet || (git commit -m "Update mold.1 (automated commit)" && git push)
+12 -5
View File
@@ -1,6 +1,13 @@
/target
/dist
/bench
/.claude
/.codex
*.o
*.so
*~
.cache
compile_commands.json
/mold
/test/tmp
options.inc
core
ld
ld64.mold
gentoo
out
+54
View File
@@ -0,0 +1,54 @@
# Contributing to mold
Summary: When you send a patch, you must agree that the patch is
released under the dual license of the GNU AGPLv3 and the MIT license.
As an indication of agreement, please add a `Signed-off-by` line to
your commit message using `git commit -s`.
Here is the long official version:
mold is currently licensed under the GNU AGPLv3, but I want to relicense
it under a more liberal license in the future. To make this possible,
I want all patches to be submitted under the dual license of the GNU
AGPLv3 and the MIT license (the "Dual License") from the beginning.
To make it clear that contributors have agreed to release their
patches under the Dual License, I introduced a sign-off procedure just
like the Linux kernel project did. The sign-off is a simple line at
the end of the git commit message, which certifies that you wrote it
or otherwise have the right to pass it on as an open-source patch. The
rules are pretty simple: if you can certify the below:
> ## Developer's Certificate of Origin
>
> By making a contribution to this project, I certify that:
>
> 1. The contribution was created in whole or in part by me and I
> have the right to submit it under the Dual License; or
>
> 2. The contribution is based upon previous work that, to the best
> of my knowledge, is covered under the the Dual License and I
> have the right under the Dual License to submit that work with
> modifications, whether created in whole or in part by me, under
> the Dual License (unless I am permitted to submit under
> different licenses), as indicated in the file; or
>
> 3. The contribution was provided directly to me by some other
> person who certified (1), (2) or (3) and I have not modified it.
>
> 4. I understand and agree that this project and the contribution
> are public and that a record of the contribution (including all
> personal information I submit with it, including my sign-off) is
> maintained indefinitely and may be redistributed consistent with
> this project or the open source license(s) involved.
then you just add a line saying:
```
Signed-off-by: Random J Developer <[email protected]>
```
using your real name (sorry, no pseudonyms or anonymous contributions.)
This will be done for you automatically if you use ``git commit -s``.
Reverts should also include "Signed-off-by". ``git revert -s`` does that
for you.
Generated
-590
View File
@@ -1,590 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "allocator-api2"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "arrayvec"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
[[package]]
name = "blake3"
version = "1.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
dependencies = [
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"cpufeatures",
]
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"regex-automata",
"serde_core",
]
[[package]]
name = "cc"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [
"find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "constant_time_eq"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
[[package]]
name = "cpp_demangle"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
dependencies = [
"cfg-if",
]
[[package]]
name = "cpufeatures"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "either"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "find-msvc-tools"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
[[package]]
name = "flate2"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
dependencies = [
"crc32fast",
"libz-sys",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "hashbrown"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "jobserver"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
"getrandom",
"libc",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libmimalloc-sys"
version = "0.1.49"
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
dependencies = [
"cc",
]
[[package]]
name = "libz-sys"
version = "1.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memmap2"
version = "0.9.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
dependencies = [
"libc",
]
[[package]]
name = "mimalloc"
version = "0.1.52"
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mold"
version = "3.0.0"
dependencies = [
"blake3",
"bstr",
"cc",
"cpp_demangle",
"crc32fast",
"flate2",
"getrandom",
"hashbrown",
"libc",
"libz-sys",
"memchr",
"memmap2",
"portable-atomic",
"rayon",
"rustc-demangle",
"uuid",
"xxhash-rust",
"zstd",
]
[[package]]
name = "mold-cli"
version = "3.0.0"
dependencies = [
"mimalloc",
"mold",
"mold-target-arm32",
"mold-target-arm32be",
"mold-target-arm64",
"mold-target-arm64be",
"mold-target-i386",
"mold-target-loongarch32",
"mold-target-loongarch64",
"mold-target-m68k",
"mold-target-ppc32",
"mold-target-ppc64v1",
"mold-target-ppc64v2",
"mold-target-riscv32",
"mold-target-riscv32be",
"mold-target-riscv64",
"mold-target-riscv64be",
"mold-target-s390x",
"mold-target-sh4",
"mold-target-sh4be",
"mold-target-sparc64",
"mold-target-x86_64",
"mold-tests",
]
[[package]]
name = "mold-target-arm32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm32be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-arm64be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-i386"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-loongarch32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-loongarch64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-m68k"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc64v1"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-ppc64v2"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv32"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv32be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-riscv64be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-s390x"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sh4"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sh4be"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-sparc64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-target-x86_64"
version = "3.0.0"
dependencies = [
"mold",
]
[[package]]
name = "mold-tests"
version = "3.0.0"
dependencies = [
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "portable-atomic"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rayon"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "regex-automata"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
[[package]]
name = "rustc-demangle"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "syn"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "uuid"
version = "1.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
dependencies = [
"getrandom",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "xxhash-rust"
version = "0.8.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "7.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.0.16+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
dependencies = [
"cc",
"pkg-config",
]
-53
View File
@@ -1,53 +0,0 @@
[package]
name = "mold"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "A high-performance ELF linker, rewritten in Rust"
license.workspace = true
# tests/lib.rs belongs to the mold-tests workspace crate.
autotests = false
# The linker is generic over the target. Instantiating it for all twenty
# targets in one crate keeps the compiler on a single core for minutes, so
# each target is instantiated in a crate of its own under arch/, and the
# executable in cli/ dispatches to them.
[workspace]
members = ["cli", "arch/*", "tests"]
default-members = [".", "cli", "arch/*", "tests"]
[workspace.package]
version = "3.0.0"
edition = "2024"
rust-version = "1.95"
license = "MIT"
[workspace.dependencies]
libc = "0.2"
mold = { path = "." }
[dependencies]
blake3 = "1.8.7"
bstr = "1"
cpp_demangle = "0.4"
crc32fast = "1"
flate2 = { version = "1", default-features = false, features = ["zlib"] }
getrandom = "0.4.3"
hashbrown = "0.17.1"
libc.workspace = true
libz-sys = "1.1.29"
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"
rustc-demangle = "0.1"
uuid = { version = "1.27", default-features = false, features = ["v4"] }
xxhash-rust = { version = "0.8", features = ["xxh3"] }
zstd = "0.13"
[build-dependencies]
cc = "1"
[profile.release]
panic = "abort"
+667 -17
View File
@@ -1,21 +1,671 @@
MIT License
Note: I'm looking for a sponsor who wants to purchase the copyright
of this work and relicense it under a more liberal license such as
the MIT license. For now, mold is released under the GNU AGPL v3.
Copyright (c) 2023 Rui Ueyama
If you just want to use mold but do not want to use AGPL software
in your organization, a commercial license is available. Please
contact [email protected].
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
--
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.
+197
View File
@@ -0,0 +1,197 @@
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
LIBDIR = $(PREFIX)/lib
LIBEXECDIR = $(PREFIX)/libexec
MANDIR = $(PREFIX)/share/man
INSTALL = install
INSTALL_PROGRAM = $(INSTALL)
INSTALL_DATA = $(INSTALL) -m 644
D = $(DESTDIR)
# CXX defaults to `g++`. Rewrite it with a vendor-neutral compiler
# name `c++`.
ifeq ($(origin CXX), default)
CXX = c++
endif
# If you want to keep symbols in the installed binary, run make with
# `STRIP=true` to run /bin/true instead of the strip command.
STRIP = strip
SRCS=$(wildcard *.cc elf/*.cc macho/*.cc)
HEADERS=$(wildcard *.h elf/*.h macho/*.h)
OBJS=$(SRCS:%.cc=out/%.o)
OS = $(shell uname -s)
ARCH = $(shell uname -m)
IS_ANDROID = 0
ifneq ($(findstring -android,$(shell $(CC) -dumpmachine)),)
IS_ANDROID = 1
endif
# If you want to compile mold for debugging, invoke make as
# `make CXXFLAGS=-g`.
CFLAGS = -O2
CXXFLAGS = -O2
MOLD_CXXFLAGS = -std=c++20 -fno-exceptions -fno-unwind-tables \
-fno-asynchronous-unwind-tables -DMOLD_VERSION=\"1.0.3\" \
-DLIBDIR="\"$(LIBDIR)\""
MOLD_LDFLAGS = -pthread -lz -lm
GIT_HASH = $(shell [ -d .git ] && git rev-parse HEAD)
ifneq ($(GIT_HASH),)
MOLD_CXXFLAGS += -DGIT_HASH=\"$(GIT_HASH)\"
endif
LTO = 0
ifeq ($(LTO), 1)
CXXFLAGS += -flto -O3
LDFLAGS += -flto
endif
# By default, we want to use mimalloc as a memory allocator. mimalloc
# is disabled on macOS and Android because it didn't work on those hosts.
USE_MIMALLOC = 1
ifeq ($(OS), Darwin)
USE_MIMALLOC = 0
else ifeq ($(IS_ANDROID), 1)
USE_MIMALLOC = 0
endif
ifeq ($(USE_MIMALLOC), 1)
ifdef SYSTEM_MIMALLOC
MOLD_LDFLAGS += -lmimalloc
else
MIMALLOC_LIB = out/mimalloc/libmimalloc.a
MOLD_CXXFLAGS += -Ithird-party/mimalloc/include
MOLD_LDFLAGS += -Wl,-whole-archive $(MIMALLOC_LIB) -Wl,-no-whole-archive
endif
endif
ifdef SYSTEM_TBB
MOLD_LDFLAGS += -ltbb
else
TBB_LIB = out/tbb/libs/libtbb.a
MOLD_LDFLAGS += $(TBB_LIB)
MOLD_CXXFLAGS += -Ithird-party/tbb/include
endif
ifdef SYSTEM_XXHASH
MOLD_LDFLAGS += -lxxhash
else
XXHASH_LIB = third-party/xxhash/libxxhash.a
MOLD_LDFLAGS += $(XXHASH_LIB)
MOLD_CXXFLAGS += -Ithird-party/xxhash
endif
ifeq ($(OS), Linux)
ifeq ($(IS_ANDROID), 0)
# glibc before 2.17 need librt for clock_gettime
MOLD_LDFLAGS += -lrt
endif
endif
# Use pkg-config to know where libcrypto resides.
ifneq ($(OS), Darwin)
MOLD_CXXFLAGS += $(shell pkg-config --cflags-only-I openssl)
MOLD_LDFLAGS += $(shell pkg-config --libs-only-L openssl) -lcrypto
endif
# '-latomic' flag is needed building on riscv64 system
# RV32 system doesn't tested yet
# seems like '-atomic' would be better but not working.
ifeq ($(ARCH), riscv64)
MOLD_LDFLAGS += -latomic
endif
# -Wc++11-narrowing is a fatal error on Android, so disable it.
ifeq ($(IS_ANDROID), 1)
MOLD_CXXFLAGS += -Wno-c++11-narrowing
endif
ifeq ($(OS), Linux)
MOLD_WRAPPER_LDFLAGS = -Wl,-push-state -Wl,-no-as-needed -ldl -Wl,-pop-state
endif
all: mold mold-wrapper.so
mold: $(OBJS) $(MIMALLOC_LIB) $(TBB_LIB) $(XXHASH_LIB)
$(CXX) $(OBJS) -o $@ $(MOLD_LDFLAGS) $(LDFLAGS)
ln -sf mold ld
ln -sf mold ld64.mold
mold-wrapper.so: elf/mold-wrapper.c Makefile
$(CC) $(CFLAGS) -fPIC -shared -o $@ $< $(MOLD_WRAPPER_LDFLAGS) $(LDFLAGS)
out/%.o: %.cc $(HEADERS) Makefile out/elf/.keep out/macho/.keep
$(CXX) $(MOLD_CXXFLAGS) $(CXXFLAGS) -c -o $@ $<
out/elf/.keep:
mkdir -p out/elf
touch $@
out/macho/.keep:
mkdir -p out/macho
touch $@
$(MIMALLOC_LIB):
mkdir -p out/mimalloc
(cd out/mimalloc; CFLAGS=-DMI_USE_ENVIRON=0 cmake -G'Unix Makefiles' ../../third-party/mimalloc)
$(MAKE) -C out/mimalloc mimalloc-static
$(TBB_LIB):
mkdir -p out/tbb
(cd out/tbb; cmake -G'Unix Makefiles' -DBUILD_SHARED_LIBS=OFF -DTBB_TEST=OFF -DCMAKE_CXX_FLAGS=-D__TBB_DYNAMIC_LOAD_ENABLED=0 -DTBB_STRICT=OFF ../../third-party/tbb)
$(MAKE) -C out/tbb tbb
(cd out/tbb; ln -sf *_relwithdebinfo libs)
$(XXHASH_LIB):
$(MAKE) -C third-party/xxhash libxxhash.a
test tests check: all
ifeq ($(OS), Darwin)
$(MAKE) -C test -f Makefile.darwin --no-print-directory
else
$(MAKE) -C test -f Makefile.linux --no-print-directory --output-sync
endif
@if test -t 1; then \
printf '\e[32mPassed all tests\e[0m\n'; \
else \
echo 'Passed all tests'; \
fi
install: all
$(INSTALL) -d $D$(BINDIR)
$(INSTALL_PROGRAM) mold $D$(BINDIR)
$(STRIP) $D$(BINDIR)/mold
$(INSTALL) -d $D$(LIBDIR)/mold
$(INSTALL_DATA) mold-wrapper.so $D$(LIBDIR)/mold
$(STRIP) $D$(LIBDIR)/mold/mold-wrapper.so
$(INSTALL) -d $D$(LIBEXECDIR)/mold
ln -sf $(BINDIR)/mold $D$(LIBEXECDIR)/mold/ld
$(INSTALL) -d $D$(MANDIR)/man1
$(INSTALL_DATA) docs/mold.1 $D$(MANDIR)/man1
ln -sf mold $D$(BINDIR)/ld.mold
ln -sf mold $D$(BINDIR)/ld64.mold
uninstall:
rm -f $D$(BINDIR)/mold $D$(BINDIR)/ld.mold $D$(BINDIR)/ld64.mold
rm -f $D$(MANDIR)/man1/mold.1
rm -rf $D$(LIBDIR)/mold
clean:
rm -rf *~ mold mold-wrapper.so out ld ld64.mold
$(MAKE) -C third-party/xxhash clean
.PHONY: all test tests check clean
+129 -254
View File
@@ -1,191 +1,133 @@
# mold: A Modern Linker
mold is a high-performance drop-in replacement for existing Unix linkers,
designed to speed up builds. In our August 2026 benchmarks, it links 4.9x
faster than [LLVM lld](https://lld.llvm.org) and 1.9x faster than
[wild](https://github.com/wild-linker/wild) at the median; see
[Benchmark](#benchmark) for the full results.
mold is a faster drop-in replacement for existing Unix linkers.
It is several times faster than LLVM lld linker, the second-fastest
open-source linker which I originally created a few years ago.
mold is created for increasing developer productivity by reducing
build time especially in rapid debug-edit-rebuild cycles.
mold is written by the original developer of LLVM lld, the linker that
Android, Chrome, FreeBSD, PlayStation, Nintendo Switch, and other production
systems are built with. mold started as an effort to build an even faster
linker from scratch, free of the architectural limits its author had run into
while optimizing lld. It has been in production use since 2021, and today it
is the default linker of many large open-source projects and is used
internally by many companies.
Here is a performance comparison of GNU gold, LLVM lld, and mold for
linking final debuginfo-enabled executables of major large programs
on a simulated 8-core 16-threads machine.
mold supports x86-64, i386, ARM 32/64, RISC-V 32/64, PowerPC 32/64, s390x,
LoongArch 32/64, SPARC64, m68k, and SH-4.
![Link speed comparison](docs/comparison.png)
## Why does linking speed matter?
| Program (linker output size) | GNU gold | LLVM lld | mold
|-------------------------------|----------|----------|--------
| Chrome 96 (1.89 GiB) | 53.86s | 11.74s | 2.21s
| Clang 13 (3.18 GiB) | 64.12s | 5.82s | 2.90s
| Firefox 89 libxul (1.64 GiB) | 32.95s | 6.80s | 1.42s
If you are using a compiled language such as C, C++, or Rust, a build consists
of two phases. In the first phase, a compiler compiles source files into
object files (`.o` files). In the second phase, a linker takes all object
files and combines them into a single executable or shared library file.
mold is so fast that it is only 2x _slower_ than `cp` on the same
machine. Feel free to [file a bug](https://github.com/rui314/mold/issues)
if you find mold is not faster than other linkers.
The second phase can be time-consuming if your build output is large. mold can
speed up this process, saving you time and preventing distractions while
waiting for a lengthy build to finish. The difference is most noticeable
during rapid debug-edit-rebuild cycles.
mold currently supports x86-64, i386 and ARM64.
## Benchmark
## Why does the speed of linking matter?
Here is a performance comparison of lld, wild, and mold when linking nine
large programs on two machines:
If you are using a compiled language such as C, C++ or Rust, a build
consists of two phases. In the first phase, a compiler compiles a
source file into an object file (`.o` files). In the second phase,
a linker takes all object files to combine them into a single executable
or a shared library file.
- AMD Ryzen Threadripper 7980X (64 cores) running Ubuntu 24.04
- Apple M1 Ultra (16 performance cores and 4 efficiency cores) running Fedora
Asahi Remix 42; the benchmark is restricted to the performance cores
The second phase takes a long time if your build output is large.
mold can make it faster, saving your time and keeping you from being
distracted while waiting for a long build to finish. The difference is
most noticeable when you are in rapid debug-edit-rebuild cycles.
The Threadripper represents many-core workstation and server processors, and
the M1 Ultra represents high-performance desktop processors.
## Install
All three linkers were built from source in release configuration as of
2026-08-28 and ran with their default options. Times are wall-clock times as
seen by the caller, the median of three runs after a warm-up. The ARM64 rows
are the ARM64 builds of the same programs. The benchmark suite, including all
linker inputs, is available on [Zenodo](https://zenodo.org/records/21882261).
**AMD Ryzen Threadripper 7980X, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 4.72s | 1.98s | 0.86s | 2.3x | 5.5x
| Chromium 145 (4.51 GiB) | 16.64s | 3.98s | 1.65s | 2.4x | 10.1x
| Chromium 145 ARM64 (4.76 GiB) | 20.91s | N/A | 1.83s | N/A | 11.4x
| Clang 21 (4.19 GiB) | 6.19s | 3.71s | 1.34s | 2.8x | 4.6x
| ClickHouse 26.1 (5.58 GiB) | 6.62s | 4.40s | 0.98s | 4.5x | 6.7x
| Firefox 149 (2.37 GiB) | 5.11s | N/A | 0.78s | N/A | 6.5x
| Firefox 149 ARM64 (2.43 GiB) | 6.40s | N/A | 0.93s | N/A | 6.9x
| Godot 4.6 (1.01 GiB) | 1.77s | 1.08s | 0.46s | 2.3x | 3.8x
| LibreOffice 26.2 (0.98 GiB) | 3.46s | 1.41s | 0.44s | 3.2x | 7.9x
| PyTorch 2.9 (3.51 GiB) | 4.35s | 2.48s | 0.80s | 3.1x | 5.4x
| TensorFlow 2.21 (9.55 GiB) | 50.73s | N/A | 3.15s | N/A | 16.1x
**AMD Ryzen Threadripper 7980X, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.85s | 0.30s | 0.20s | 1.5x | 4.2x
| Chromium 145 (0.58 GiB) | 6.48s | N/A | 0.64s | N/A | 10.2x
| Chromium 145 ARM64 (0.60 GiB) | 7.91s | N/A | 0.73s | N/A | 10.8x
| Clang 21 (0.21 GiB) | 0.53s | 0.23s | 0.11s | 2.2x | 5.0x
| ClickHouse 26.1 (1.23 GiB) | 3.18s | 2.07s | 0.41s | 5.0x | 7.7x
| Firefox 149 (0.22 GiB) | 1.01s | 0.41s | 0.21s | 2.0x | 4.9x
| Godot 4.6 (0.15 GiB) | 0.44s | 0.21s | 0.08s | 2.7x | 5.8x
| LibreOffice 26.2 (0.19 GiB) | 1.13s | 0.57s | 0.19s | 3.0x | 6.0x
| PyTorch 2.9 (0.31 GiB) | 0.68s | 0.32s | 0.15s | 2.2x | 4.7x
| TensorFlow 2.21 (0.73 GiB) | 9.62s | N/A | 0.70s | N/A | 13.7x
**Apple M1 Ultra, debug builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|--------|-------|-------|-----------|---------
| Blender 5.2 (2.46 GiB) | 3.21s | 1.81s | 1.56s | 1.2x | 2.1x
| Chromium 145 (4.51 GiB) | 9.54s | 3.49s | 2.22s | 1.6x | 4.3x
| Chromium 145 ARM64 (4.76 GiB) | 12.67s | N/A | 2.31s | N/A | 5.5x
| Clang 21 (4.19 GiB) | 4.40s | 2.78s | 2.96s | 0.9x | 1.5x
| ClickHouse 26.1 (5.58 GiB) | 4.92s | 3.50s | 1.71s | 2.1x | 2.9x
| Firefox 149 (2.37 GiB) | 3.21s | N/A | 1.12s | N/A | 2.9x
| Firefox 149 ARM64 (2.43 GiB) | 4.13s | N/A | 1.12s | N/A | 3.7x
| Godot 4.6 (1.01 GiB) | 1.12s | 0.81s | 0.62s | 1.3x | 1.8x
| LibreOffice 26.2 (0.98 GiB) | 2.08s | 0.94s | 0.63s | 1.5x | 3.3x
| PyTorch 2.9 (3.51 GiB) | 3.07s | 2.10s | 1.45s | 1.4x | 2.1x
| TensorFlow 2.21 (9.55 GiB) | 43.68s | N/A | 4.43s | N/A | 9.9x
**Apple M1 Ultra, release builds**
| Program (output size) | lld | wild | mold | wild/mold | lld/mold
|-----------------------------------|-------|-------|-------|-----------|---------
| Blender 5.2 (0.24 GiB) | 0.56s | 0.20s | 0.25s | 0.8x | 2.3x
| Chromium 145 (0.58 GiB) | 4.25s | N/A | 0.78s | N/A | 5.5x
| Chromium 145 ARM64 (0.60 GiB) | 5.17s | N/A | 0.91s | N/A | 5.7x
| Clang 21 (0.21 GiB) | 0.30s | 0.15s | 0.14s | 1.0x | 2.1x
| ClickHouse 26.1 (1.23 GiB) | 1.94s | 0.95s | 0.55s | 1.7x | 3.5x
| Firefox 149 (0.22 GiB) | 0.56s | 0.23s | 0.20s | 1.1x | 2.7x
| Godot 4.6 (0.15 GiB) | 0.26s | 0.11s | 0.11s | 1.0x | 2.3x
| LibreOffice 26.2 (0.19 GiB) | 0.62s | 0.30s | 0.23s | 1.3x | 2.7x
| PyTorch 2.9 (0.31 GiB) | 0.43s | 0.22s | 0.17s | 1.3x | 2.5x
| TensorFlow 2.21 (0.73 GiB) | 8.07s | N/A | 0.68s | N/A | 11.9x
N/A indicates that the linker cannot link that program. wild's Chromium
release links are also marked N/A because wild does not implement `--icf=all`
and links without identical code folding.
## Why is mold so fast?
mold owes its speed to pervasive parallelism and to efficient data structures
and algorithms. For details, read our paper "mold: A Massively Parallel
Linker" (ASPLOS 2027), available at https://arxiv.org/abs/2608.23228.
## Installation
Binary packages for the following systems are currently available:
Binary packages for the following distros are currently available.
[![Packaging status](https://repology.org/badge/vertical-allrepos/mold.svg)](https://repology.org/project/mold/versions)
Prebuilt binaries for Linux on x86-64, ARM64, ARM32, RISC-V, PPC64LE, s390x,
and LoongArch are also attached to each
[GitHub release](https://github.com/rui314/mold/releases).
## How to build
## How to Build
mold is written in C++20, so if you build mold yourself, you need a
recent version of a C++ compiler and a C++ standard library. GCC 10.2
or Clang 12.0.0 as well as libstdc++ 10 or libc++ 7 are recommended.
mold is written in Rust and built with Cargo. It requires Rust 1.95 or later;
if your system's Rust is older, install the stable toolchain with
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
Clang, which you can install with your system's package manager.
I'm using Ubuntu 20.04 as a development platform. In that environment,
you can build mold by the following commands.
### Install dependencies
#### Ubuntu 20.04 and later / Debian 11 and later
```shell
sudo apt-get update
sudo apt-get install -y build-essential git clang cmake libstdc++-10-dev libssl-dev libxxhash-dev zlib1g-dev pkg-config
```
#### Fedora 34 and later
```shell
sudo dnf install -y git clang cmake openssl-devel xxhash-devel zlib-devel libstdc++-devel
```
### Compile mold
```shell
git clone https://github.com/rui314/mold.git
cd mold
cargo build --release
git checkout v1.0.3
make -j$(nproc) CXX=clang++
sudo make install
```
The executable and its companion preload library are
`target/release/mold` and `target/release/mold-wrapper.so`.
You may need to pass a C++20 compiler command name to `make`.
In the above case, `clang++` is passed. If it doesn't work for you,
try a specific version of a compiler such as `g++-10` or `clang++-12`.
### Install mold
By default, `mold` is installed to `/usr/local/bin`.
Run `sudo ./install-mold.sh` to install mold under `/usr/local`. To install it
under a different prefix, set `PREFIX`, as in
`sudo PREFIX=/usr ./install-mold.sh`. `mold-wrapper.so` is installed to
`$PREFIX/lib/mold`. If your system uses another library directory, such as
`/usr/lib64`, set `MOLD_LIBDIR` to it for both `cargo build` and
`install-mold.sh`.
If you don't use a recent enough Linux distribution, or if for any reason `make`
in the above commands doesn't work for you, you can use Docker to build it in
a Docker environment. To do so, just run `./build-static.sh` in this
directory instead of running `make -j$(nproc)`. The shell script creates a
Ubuntu 20.04 Docker image, installs necessary tools and libraries to it,
and builds mold as a statically-linked executable.
You can also run `target/release/mold` directly without installing it.
`make test` depends on a few more packages. To install, run the following commands:
```shell
sudo dpkg --add-architecture i386
sudo apt update
sudo apt-get install bsdmainutils dwarfdump libc6-dev:i386 lib32gcc-10-dev libstdc++-10-dev-arm64-cross gcc-10-aarch64-linux-gnu g++-10-aarch64-linux-gnu
```
## How to use
<details><summary>A classic way to use mold</summary>
On Unix, the linker command (usually `/usr/bin/ld`) is indirectly invoked by
the compiler driver (typically `cc`, `gcc`, or `clang`), which is in turn
indirectly invoked by `make` or other build system commands.
On Unix, the linker command (which is usually `/usr/bin/ld`) is
invoked indirectly by the compiler driver (which is usually `cc`,
`gcc` or `clang`), which is typically in turn indirectly invoked by
`make` or some other build system command.
If you can specify an additional command line option for your compiler driver
by modifying the build system's config files, add one of the following flags
to use mold instead of `/usr/bin/ld`:
If you can specify an additional command line option to your compiler
driver by modifying build system's config files, add one of the
following flags to use `mold` instead of `/usr/bin/ld`:
- For Clang: pass `-fuse-ld=mold`
- Clang: pass `-fuse-ld=mold`
- For GCC 12.1.0 or later: pass `-fuse-ld=mold`
- GCC 12.1.0 (upcoming version) or later: pass `-fuse-ld=mold`
- For GCC before 12.1.0: the `-fuse-ld` option does not accept `mold` as a
valid argument, so you need to use the `-B` option instead. The `-B` option
tells GCC where to look for external commands like `ld`.
- GCC before 12.1.0: `-fuse-ld` does not accept `mold` as a valid
argument, so you need to use `-B` option instead. `-B` is an option
to tell GCC where to look for external commands such as `ld`.
If you installed mold using the script above, there is a directory named
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
prefix, use that prefix instead.
If you have installed mold with `make install`, there should be a
directory named `/usr/libexec/mold` (or `/usr/local/libexec/mold`,
depending on your $PREFIX), and `ld` command should be there. The
`ld` is actually a symlink to `mold`. So, all you need is to pass
`-B/usr/libexec/mold` (or `-B/usr/local/libexec/mold`) to GCC.
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
accept an absolute path as an argument for `-fuse-ld`.
If you haven't installed `mold` to any `$PATH`, you can still pass
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. GCC does not
take an absolute path as an argument for `-fuse-ld` though.
</details>
@@ -193,144 +135,77 @@ accept an absolute path as an argument for `-fuse-ld`.
Create `.cargo/config.toml` in your project directory with the following:
```toml
[target.'cfg(target_os = "linux")']
```
[target.x86_64-unknown-linux-gnu]
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=/path/to/mold"]
```
where `/path/to/mold` is an absolute path to the mold executable. In the
example above, we use `clang` as a linker driver since it always accepts the
`-fuse-ld` option. If your GCC is recent enough to recognize the option, you
may be able to remove the `linker = "clang"` line.
where `/path/to/mold` is an absolute path to `mold` exectuable.
```toml
[target.'cfg(target_os = "linux")']
rustflags = ["-C", "link-arg=-fuse-ld=mold"]
```
If you want to use mold for all projects, add the above snippet to
If you want to use mold for all projects, put the above snippet to
`~/.cargo/config.toml`.
</details>
<details><summary>If you are using Nim</summary>
Create `config.nims` in your project directory with the following:
```nim
when findExe("mold").len > 0 and defined(linux):
switch("passL", "-fuse-ld=mold")
```
where `mold` must be included in the `PATH` environment variable. In this
example, `gcc` is used as the linker driver. Use the `-fuse-ld` option if your
GCC is recent enough to recognize this option.
If you want to use mold for all projects, add the above snippet to
`~/.config/config.nims`.
</details>
<details><summary>If you are using Conan package manager</summary>
You can configure [Conan](https://github.com/conan-io) to download the latest
version of `mold` and use it as the linker when building your dependencies and
projects from source. Please see the instructions [here](https://conan.io/center/recipes/mold).
</details>
<details><summary>mold -run</summary>
It is sometimes very hard to pass an appropriate command line option to `cc`
to specify an alternative linker. To address this situation, mold has a
feature to intercept all invocations of `ld`, `ld.bfd`, `ld.lld`, or `ld.gold`
and redirect them to itself. To use this feature, run `make` (or another build
command) as a subcommand of mold as follows:
It is sometimes very hard to pass an appropriate command line option
to `cc` to specify an alternative linker. To deal with the situation,
mold has a feature to intercept all invocations of `ld`, `ld.lld` or
`ld.gold` and redirect it to itself. To use the feature, run `make`
(or another build command) as a subcommand of mold as follows:
```shell
mold -run make <make-options-if-any>
```
Internally, mold invokes a given command with the `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object file
intercepts all function calls to `exec(3)`-family functions to replace
`argv[0]` with `mold` if it is `ld`, `ld.bfd`, `ld.gold`, or `ld.lld`.
Here's an example showing how to link Rust code when using the
cargo package manager:
```shell
mold -run cargo build
```
Internally, mold invokes a given command with `LD_PRELOAD` environment
variable set to its companion shared object file. The shared object
file intercepts all function calls to `exec(3)`-family functions to
replace `argv[0]` with `mold` if it is `ld`, `ld.gold` or `ld.lld`.
</details>
<details><summary>GitHub Actions</summary>
You can use our [setup-mold](https://github.com/rui314/setup-mold) GitHub
Action to speed up GitHub-hosted continuous builds. Although GitHub Actions
run on a 4 core machine, mold is still significantly faster than the default
GNU linker, especially when linking large programs.
</details>
<details><summary>Verify that you are using mold</summary>
mold leaves its identification string in the `.comment` section of an output
mold leaves its identification string in `.comment` section in an output
file. You can print it out to verify that you are actually using mold.
```shell
$ readelf -p .comment <executable-file>
readelf -p .comment <executable-file>
String dump of section '.comment':
[ 0] GCC: (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
[ 2b] mold 9a1679b47d9b22012ec7dfbda97c8983956716f7
```
If `mold` is present in the `.comment` section, the file was created by mold.
If `mold` is in `.comment`, the file is created by mold.
</details>
## Why is mold so fast?
<details><summary>Online manual</summary>
One reason is because it simply uses faster algorithms and efficient
data structures than other linkers do. The other reason is that the
new linker is highly parallelized.
Since mold is a drop-in replacement, you should be able to use it without
reading its manual. However, if you need it, [mold's man page](docs/mold.md)
is available online. You can read the same manual by running `man mold`.
Here is a side-by-side comparison of per-core CPU usage of lld (left)
and mold (right). They are linking the same program, Chromium
executable.
</details>
![CPU usage comparison in htop animation](docs/htop.gif)
## Stability
As you can see, mold uses all available cores throughout its execution
and finishes quickly. On the other hand, lld failed to use available
cores most of the time. In this demo, the maximum parallelism is
artificially capped to 16 so that the bars fit in the GIF.
mold has been developed in the open since 2020 and has more than 140
contributors. Its test suite, which covers every linker feature, runs in CI for
all supported target CPU architectures, natively or under QEMU, and under ASAN
and TSAN. Before each release, we try to build all of Gentoo Linux's roughly
19,000 packages with mold, using GNU ld as a control, to find regressions before
they reach a release.
For details, please read [design notes](docs/design.md).
## Sponsors
# Logo
mold is free to use, but keeping it maintained is continuous work: supporting
new architectures and toolchain features, keeping up with the projects that
depend on it, and making it faster. That work is funded by sponsors. If mold
saves you or your company time, please consider becoming a
[GitHub sponsor](https://github.com/sponsors/rui314).
We thank everyone who sponsors the project. In particular, we'd like to
acknowledge the following people and organizations who have sponsored
$128/month or more:
### Corporate sponsors
<a href="https://mercury.com"><img src="docs/mercury-logo.png" align=center height=120 width=400 alt=Mercury></a>
<a href="https://cybozu-global.com"><img src="docs/cyboze-logo.png" align=center height=120 width=133 alt=Cybozu></a>
<a href="https://www.emergetools.com"><img src="docs/emerge-tools-logo.png" align=center height=120 width=240 alt="Emerge Tools"></a><br>
- [G-Research](https://www.gresearch.co.uk)
- [Signal Slot Inc.](https://github.com/signal-slot)
- [GlareDB](https://github.com/GlareDB)
### Individual sponsors
- [Wei Wu](https://github.com/lazyparser)
- [kyle-elliott](https://github.com/kyle-elliott)
- [Bryant Biggs](https://github.com/bryantbiggs)
- [kraptor23](https://github.com/kraptor23)
- [Jinkyu Yi](https://github.com/jincreator)
- [Pedro Navarro](https://github.com/pedronavf)
-![mold image](docs/mold.jpg)
-11
View File
@@ -1,11 +0,0 @@
# Architecture crates
Each subdirectory is a small crate that instantiates the generic linker for
one target architecture. Separate crates let Cargo compile the architectures
in parallel. Instantiating all targets in one crate leaves a long serial
compilation step.
The shared linker implementation lives in [`src/`](../src/), with
target-specific code in [`src/target/`](../src/target/). These crates provide
entry points that the [`cli/`](../cli/) executable selects for the input
files' target.
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm32be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm32Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-arm64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for arm64be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Arm64Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-i386"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for i386"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::I386>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-loongarch32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for loongarch32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::LoongArch32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-loongarch64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for loongarch64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::LoongArch64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-m68k"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for m68k"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::M68k>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc64v1"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc64v1"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc64V1>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-ppc64v2"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for ppc64v2"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Ppc64V2>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv32"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv32"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv32>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv32be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv32be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv32Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-riscv64be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for riscv64be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Riscv64Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-s390x"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for s390x"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::S390x>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sh4"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sh4"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sh4>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sh4be"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sh4be"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sh4Be>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-sparc64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for sparc64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::Sparc64>(cmdline)
}
-13
View File
@@ -1,13 +0,0 @@
[package]
name = "mold-target-x86_64"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold linker instantiated for x86_64"
license.workspace = true
[lib]
path = "lib.rs"
[dependencies]
mold.workspace = true
-3
View File
@@ -1,3 +0,0 @@
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
mold::driver::link::<mold::target::X86_64>(cmdline)
}
+153
View File
@@ -0,0 +1,153 @@
#pragma once
#include "mold.h"
#include "filetype.h"
namespace mold {
struct ArHdr {
char ar_name[16];
char ar_date[12];
char ar_uid[6];
char ar_gid[6];
char ar_mode[8];
char ar_size[10];
char ar_fmag[2];
bool starts_with(std::string_view s) const {
return std::string_view(ar_name, s.size()) == s;
}
bool is_strtab() const {
return starts_with("// ");
}
bool is_symtab() const {
return starts_with("/ ") || starts_with("/SYM64/ ");
}
std::string read_name(std::string_view strtab, u8 *&ptr) const {
// BSD-style long filename
if (starts_with("#1/")) {
int namelen = atoi(ar_name + 3);
std::string name{(char *)ptr, (size_t)namelen};
ptr += namelen;
if (size_t pos = name.find('\0'))
name = name.substr(0, pos);
return name;
}
// SysV-style long filename
if (starts_with("/")) {
const char *start = strtab.data() + atoi(ar_name + 1);
return {start, (const char *)strstr(start, "/\n")};
}
// Short fileanme
if (const char *end = (char *)memchr(ar_name, '/', sizeof(ar_name)))
return {ar_name, end};
return {ar_name, sizeof(ar_name)};
}
};
template <typename C>
std::vector<MappedFile<C> *>
read_thin_archive_members(C &ctx, MappedFile<C> *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::string_view strtab;
while (data < begin + mf->size) {
// Each header is aligned to a 2 byte boundary.
if ((begin - data) % 2)
data++;
ArHdr &hdr = *(ArHdr *)data;
u8 *body = data + sizeof(hdr);
u64 size = atol(hdr.ar_size);
// Read a string table.
if (hdr.is_strtab()) {
strtab = {(char *)body, (size_t)size};
data = body + size;
continue;
}
// Skip a symbol table.
if (hdr.is_symtab()) {
data = body + size;
continue;
}
if (!hdr.starts_with("#1/") && !hdr.starts_with("/"))
Fatal(ctx) << mf->name << ": filename is not stored as a long filename";
std::string name = hdr.read_name(strtab, body);
// Skip if symbol table
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
continue;
std::string path = name.starts_with('/') ?
name : (filepath(mf->name).parent_path() / name).string();
vec.push_back(MappedFile<C>::must_open(ctx, path));
data = body;
}
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_fat_archive_members(C &ctx, MappedFile<C> *mf) {
u8 *begin = mf->data;
u8 *data = begin + 8;
std::vector<MappedFile<C> *> vec;
std::string_view strtab;
while (begin + mf->size - data >= 2) {
if ((begin - data) % 2)
data++;
ArHdr &hdr = *(ArHdr *)data;
u8 *body = data + sizeof(hdr);
u64 size = atol(hdr.ar_size);
data = body + size;
// Read if string table
if (hdr.is_strtab()) {
strtab = {(char *)body, (size_t)size};
continue;
}
// Skip if symbol table
if (hdr.is_symtab())
continue;
// Read the name field
std::string name = hdr.read_name(strtab, body);
// Skip if symbol table
if (name == "__.SYMDEF" || name == "__.SYMDEF SORTED")
continue;
vec.push_back(mf->slice(ctx, name, body - begin, data - body));
}
return vec;
}
template <typename C>
std::vector<MappedFile<C> *>
read_archive_members(C &ctx, MappedFile<C> *mf) {
switch (get_file_type(mf)) {
case FileType::AR:
return read_fat_archive_members(ctx, mf);
case FileType::THIN_AR:
return read_thin_archive_members(ctx, mf);
default:
unreachable();
}
}
} // namespace mold
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash -x
# This is a shell script to build a statically-linked mold
# executable using Docker, so that it is easy to build mold
# on non-Ubuntu 20.04 machines.
#
# A docker image used for building mold is persistent.
# Run `docker image rm mold-build-ubuntu20` to remove the image
# from disk.
set -e
# If the existing file is not statically-linked, remove it.
[ -f mold ] && ! ldd mold 2>&1 | grep -q 'not a dynamic executable' && \
rm mold
cat <<EOF | docker build -t mold-build-ubuntu20 -
FROM ubuntu:20.04
RUN apt-get update && \
TZ=Europe/London apt-get install -y tzdata && \
apt-get install -y --no-install-recommends build-essential clang lld \
bsdmainutils file gcc-multilib git pkg-config \
cmake libstdc++-10-dev zlib1g-dev libssl-dev && \
apt clean && \
rm -rf /var/lib/apt/lists/*
EOF
LDFLAGS='-fuse-ld=lld -static'
# libstdc++'s `std::__glibcxx_rwlock_rdlock` refers these symbols
# as weak symbols, although they need to be defined. Otherwise,
# the program crashes after juping to address 0.
# So, we force loading symbols as a workaround.
LDFLAGS="$LDFLAGS -Wl,-u,pthread_rwlock_rdlock"
LDFLAGS="$LDFLAGS -Wl,-u,pthread_rwlock_unlock"
LDFLAGS="$LDFLAGS -Wl,-u,pthread_rwlock_wrlock"
docker_args=(-v "`pwd`:/mold:Z" -u "$(id -u)":"$(id -g)")
if docker --version | grep -q podman; then
docker_args+=(--userns=keep-id)
fi
docker run -it --rm "${docker_args[@]}" \
mold-build-ubuntu20 \
make -C /mold -j"$(nproc)" CC=clang CXX=clang++ LDFLAGS="$LDFLAGS" "$@"
-115
View File
@@ -1,115 +0,0 @@
//! Builds the two small C parts of mold.
//!
//! `mold-wrapper.so` is the preload library behind `mold -run`. It
//! interposes the exec family, including the C-variadic `execl` functions
//! that stable Rust cannot define, so it is a C file. It is placed next to
//! the `mold` executable, where `-run` looks for it.
//!
//! `lto-message.c` adapts the LTO plugin's printf-like diagnostics
//! callback, which is C-variadic as well, and is linked into mold.
use std::path::{Path, PathBuf};
use std::process::Command;
// Embed the current git commit hash into the mold executable. We ask git
// instead of parsing files under .git because the repository format varies
// (e.g. packed refs, reftable). When building from a source tarball, there's
// no .git, and the hash is simply omitted.
fn git_hash(source_dir: &Path) -> Option<String> {
let dot_git = source_dir.join(".git");
if !dot_git.exists() {
// Source archives use the package version. If Git metadata is added
// later, a clean rebuild is needed to embed the new commit hash.
return None;
}
if dot_git.is_file() {
println!("cargo:rerun-if-changed={}", dot_git.display());
}
let git_path = |name: &str| -> Option<PathBuf> {
let output = Command::new("git")
.arg("-C")
.arg(source_dir)
.args(["rev-parse", "--git-path", name])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
Some(if path.is_absolute() { path } else { source_dir.join(path) })
};
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", head.display());
if let Ok(contents) = std::fs::read_to_string(&head)
&& let Some(reference) =
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
&& let Some(path) = git_path(reference)
{
// A packed reference may acquire a loose file later.
// Watch its nearest existing directory until that happens.
if let Some(path) = path.ancestors().find(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
}
}
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
println!("cargo:rerun-if-changed={}", path.display());
}
let output =
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
}
fn main() {
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
let version = match git_hash(&source_dir) {
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
None => format!("mold {version} (compatible with GNU ld)"),
};
println!("cargo:rustc-env=MOLD_VERSION={version}");
println!("cargo:rerun-if-changed=c/mold-wrapper.c");
println!("cargo:rerun-if-changed=c/lto-message.c");
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
if target_os == "windows" && target_env == "msvc" {
return;
}
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
let mut build = cc::Build::new();
build.opt_level(2).pic(true);
if let Ok(sanitizer) = std::env::var("CARGO_CFG_SANITIZE") {
build.flag(format!("-fsanitize={sanitizer}"));
}
build.file("c/lto-message.c").compile("ltomessage");
if target_os == "windows" || target_os == "macos" {
return;
}
// The build directory is inside the actual profile directory. PROFILE
// only describes inheritance and need not be that directory's name.
let profile_dir = out_dir
.ancestors()
.find(|path| path.file_name().is_some_and(|name| name == "build"))
.and_then(Path::parent)
.expect("OUT_DIR must be inside Cargo's build directory");
let wrapper = profile_dir.join("mold-wrapper.so");
let mut command = build.get_compiler().to_command();
command.args(["-shared", "-o"]);
command.arg(&wrapper).arg("c/mold-wrapper.c");
if target_os == "android" || target_os == "linux" {
command.arg("-ldl");
}
let status = command.status();
if !matches!(status, Ok(s) if s.success()) {
println!("cargo:warning=could not build mold-wrapper.so; `mold -run` will not work");
}
}
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <cstdint>
namespace mold {
template <typename T>
class BigEndian {
public:
BigEndian() : BigEndian(0) {}
BigEndian(T x) {
*this = x;
}
operator T() const {
// We don't need to optimize this code because compilers are
// usually smart enough to compile this loop into a single
// byte-swap instruction such as x86's bswap.
T ret = 0;
for (int i = 0; i < sizeof(T); i++)
ret = (ret << 8) | val[i];
return ret;
}
BigEndian &operator=(T x) {
for (int i = 0; i < sizeof(T); i++)
val[sizeof(T) - 1 - i] = x >> (i * 8);
return *this;
}
BigEndian &operator++() {
return *this = *this + 1;
}
BigEndian operator++(int) {
T ret = *this;
*this = *this + 1;
return ret;
}
BigEndian &operator--() {
return *this = *this - 1;
}
BigEndian operator--(int) {
T ret = *this;
*this = *this - 1;
return ret;
}
BigEndian &operator+=(T x) {
return *this = *this + x;
}
BigEndian &operator&=(T x) {
return *this = *this & x;
}
BigEndian &operator|=(T x) {
return *this = *this | x;
}
private:
uint8_t val[sizeof(T)];
};
using ibig16 = BigEndian<int16_t>;
using ibig32 = BigEndian<int32_t>;
using ibig64 = BigEndian<int64_t>;
using ubig16 = BigEndian<uint16_t>;
using ubig32 = BigEndian<uint32_t>;
using ubig64 = BigEndian<uint64_t>;
} // namespace mold
-18
View File
@@ -1,18 +0,0 @@
// The linker plugin reports diagnostics through a printf-like callback.
// C-variadic functions can't be defined in stable Rust, so this adapter
// formats the message and hands it to Rust.
#include <stdarg.h>
#include <stdio.h>
void mold_lto_report(int level, const char *msg);
int mold_lto_message(int level, const char *fmt, ...) {
char buf[1000];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
mold_lto_report(level, buf);
return 0;
}
-146
View File
@@ -1,146 +0,0 @@
#define _GNU_SOURCE 1
#include <dlfcn.h>
#include <spawn.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#if __has_include(<alloca.h>)
# include <alloca.h>
#endif
extern char **environ;
static char *get_mold_path() {
char *path = getenv("MOLD_PATH");
if (path)
return path;
fprintf(stderr, "MOLD_PATH is not set\n");
exit(1);
}
static void debug_print(const char *fmt, ...) {
if (!getenv("MOLD_WRAPPER_DEBUG"))
return;
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "mold-wrapper.so: ");
vfprintf(stderr, fmt, ap);
fflush(stderr);
va_end(ap);
}
static int count_args(va_list *ap) {
va_list aq;
va_copy(aq, *ap);
int i = 0;
while (va_arg(aq, char *))
i++;
va_end(aq);
return i;
}
static void copy_args(char **argv, const char *arg0, va_list *ap) {
int i = 1;
char *arg;
while ((arg = va_arg(*ap, char *)))
argv[i++] = arg;
((const char **)argv)[0] = arg0;
((const char **)argv)[i] = NULL;
}
static bool is_ld(const char *path) {
const char *ptr = path + strlen(path);
while (path < ptr && ptr[-1] != '/')
ptr--;
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
!strcmp(ptr, "ld.gold") || !strcmp(ptr, "ld.bfd") ||
!strcmp(ptr, "ld.mold");
}
int execvpe(const char *file, char *const *argv, char *const *envp) {
debug_print("execvpe %s\n", file);
if (!strcmp(file, "ld") || is_ld(file))
file = get_mold_path();
for (int i = 0; envp[i]; i++)
putenv(envp[i]);
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvp");
return real(file, argv, environ);
}
int execve(const char *path, char *const *argv, char *const *envp) {
debug_print("execve %s\n", path);
if (is_ld(path))
path = get_mold_path();
typeof(execve) *real = dlsym(RTLD_NEXT, "execve");
return real(path, argv, envp);
}
int execl(const char *path, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
copy_args(argv, arg0, &ap);
va_end(ap);
return execve(path, argv, environ);
}
int execlp(const char *file, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
copy_args(argv, arg0, &ap);
va_end(ap);
return execvpe(file, argv, environ);
}
int execle(const char *path, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char **argv = alloca((count_args(&ap) + 2) * sizeof(char *));
copy_args(argv, arg0, &ap);
char **env = va_arg(ap, char **);
va_end(ap);
return execve(path, argv, env);
}
int execv(const char *path, char *const *argv) {
return execve(path, argv, environ);
}
int execvp(const char *file, char *const *argv) {
return execvpe(file, argv, environ);
}
int posix_spawn(pid_t *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const *argv, char *const *envp) {
debug_print("posix_spawn %s\n", path);
if (is_ld(path))
path = get_mold_path();
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
return real(pid, path, file_actions, attrp, argv, envp);
}
int posix_spawnp(pid_t *pid, const char *file,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const *argv, char *const *envp) {
debug_print("posix_spawnp %s\n", file);
if (is_ld(file))
file = get_mold_path();
typeof(posix_spawnp) *real = dlsym(RTLD_NEXT, "posix_spawnp");
return real(pid, file, file_actions, attrp, argv, envp);
}
-72
View File
@@ -1,72 +0,0 @@
[package]
name = "mold-cli"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
description = "The mold executable"
license.workspace = true
[[bin]]
name = "mold"
path = "main.rs"
[[test]]
name = "integration"
path = "integration.rs"
harness = false
[dependencies]
mimalloc = { git = "https://github.com/rui314/mimalloc_rust", rev = "3979460494f1cd1e7f936cb8e10f41e927c9f698" }
mold.workspace = true
mold-target-x86_64 = { path = "../arch/x86_64", optional = true }
mold-target-i386 = { path = "../arch/i386", optional = true }
mold-target-arm32 = { path = "../arch/arm32", optional = true }
mold-target-arm32be = { path = "../arch/arm32be", optional = true }
mold-target-arm64 = { path = "../arch/arm64", optional = true }
mold-target-arm64be = { path = "../arch/arm64be", optional = true }
mold-target-riscv64 = { path = "../arch/riscv64", optional = true }
mold-target-riscv64be = { path = "../arch/riscv64be", optional = true }
mold-target-riscv32 = { path = "../arch/riscv32", optional = true }
mold-target-riscv32be = { path = "../arch/riscv32be", optional = true }
mold-target-ppc64v2 = { path = "../arch/ppc64v2", optional = true }
mold-target-ppc32 = { path = "../arch/ppc32", optional = true }
mold-target-ppc64v1 = { path = "../arch/ppc64v1", optional = true }
mold-target-s390x = { path = "../arch/s390x", optional = true }
mold-target-sparc64 = { path = "../arch/sparc64", optional = true }
mold-target-m68k = { path = "../arch/m68k", optional = true }
mold-target-sh4 = { path = "../arch/sh4", optional = true }
mold-target-sh4be = { path = "../arch/sh4be", optional = true }
mold-target-loongarch64 = { path = "../arch/loongarch64", optional = true }
mold-target-loongarch32 = { path = "../arch/loongarch32", optional = true }
[dev-dependencies]
mold-tests = { path = "../tests" }
# One feature per target. All are built by default; a build for one
# target alone (`--no-default-features --features x86_64`) is much faster.
[features]
default = [
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
]
system-allocator = []
x86_64 = ["dep:mold-target-x86_64"]
i386 = ["dep:mold-target-i386"]
arm32 = ["dep:mold-target-arm32"]
arm32be = ["dep:mold-target-arm32be"]
arm64 = ["dep:mold-target-arm64"]
arm64be = ["dep:mold-target-arm64be"]
riscv64 = ["dep:mold-target-riscv64"]
riscv64be = ["dep:mold-target-riscv64be"]
riscv32 = ["dep:mold-target-riscv32"]
riscv32be = ["dep:mold-target-riscv32be"]
ppc64v2 = ["dep:mold-target-ppc64v2"]
ppc32 = ["dep:mold-target-ppc32"]
ppc64v1 = ["dep:mold-target-ppc64v1"]
s390x = ["dep:mold-target-s390x"]
sparc64 = ["dep:mold-target-sparc64"]
m68k = ["dep:mold-target-m68k"]
sh4 = ["dep:mold-target-sh4"]
sh4be = ["dep:mold-target-sh4be"]
loongarch64 = ["dep:mold-target-loongarch64"]
loongarch32 = ["dep:mold-target-loongarch32"]
-15
View File
@@ -1,15 +0,0 @@
//! Links libatomic into mold on hosts without 64-bit atomics, such as
//! 32-bit PowerPC. mimalloc uses 64-bit atomics, which C compilers
//! implement as calls into libatomic on those hosts.
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let widths = std::env::var("CARGO_CFG_TARGET_HAS_ATOMIC").unwrap_or_default();
if !widths.split(',').any(|w| w == "64") {
// rustc places libraries named by this crate before its dependencies
// on the linker command line, where --as-needed would drop libatomic.
// Link arguments come after all libraries.
println!("cargo:rustc-link-arg-bins=-latomic");
}
}
-8
View File
@@ -1,8 +0,0 @@
use std::path::Path;
use std::process::ExitCode;
fn main() -> ExitCode {
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
let cases = [root.join("tests")];
mold_tests::run(&cases, Path::new(env!("CARGO_BIN_EXE_mold")))
}
-79
View File
@@ -1,79 +0,0 @@
//! The `mold` executable: runs the linker instantiated for the target the
//! inputs are for. Each target is instantiated in a crate of its own so
//! that the compiler can build them in parallel, and a feature per target
//! decides which of them are built in.
// A Rust executable can define only one global allocator, so select mimalloc
// here rather than in the linker library.
#[cfg(not(feature = "system-allocator"))]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
type LinkFn = fn(mold::driver::Cmdline) -> mold::driver::LinkResult;
// Each target has its own monomorphized link function. Start with the first
// enabled target and switch to the matching function if the inputs differ.
const TARGETS: &[(&str, LinkFn)] = &[
#[cfg(feature = "x86_64")]
("x86_64", mold_target_x86_64::link),
#[cfg(feature = "i386")]
("i386", mold_target_i386::link),
#[cfg(feature = "arm32")]
("arm32", mold_target_arm32::link),
#[cfg(feature = "arm32be")]
("arm32be", mold_target_arm32be::link),
#[cfg(feature = "arm64")]
("arm64", mold_target_arm64::link),
#[cfg(feature = "arm64be")]
("arm64be", mold_target_arm64be::link),
#[cfg(feature = "riscv64")]
("riscv64", mold_target_riscv64::link),
#[cfg(feature = "riscv64be")]
("riscv64be", mold_target_riscv64be::link),
#[cfg(feature = "riscv32")]
("riscv32", mold_target_riscv32::link),
#[cfg(feature = "riscv32be")]
("riscv32be", mold_target_riscv32be::link),
#[cfg(feature = "ppc64v2")]
("ppc64v2", mold_target_ppc64v2::link),
#[cfg(feature = "ppc32")]
("ppc32", mold_target_ppc32::link),
#[cfg(feature = "ppc64v1")]
("ppc64v1", mold_target_ppc64v1::link),
#[cfg(feature = "s390x")]
("s390x", mold_target_s390x::link),
#[cfg(feature = "sparc64")]
("sparc64", mold_target_sparc64::link),
#[cfg(feature = "m68k")]
("m68k", mold_target_m68k::link),
#[cfg(feature = "sh4")]
("sh4", mold_target_sh4::link),
#[cfg(feature = "sh4be")]
("sh4be", mold_target_sh4be::link),
#[cfg(feature = "loongarch64")]
("loongarch64", mold_target_loongarch64::link),
#[cfg(feature = "loongarch32")]
("loongarch32", mold_target_loongarch32::link),
];
fn link_for_target(target: &str, cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
for &(name, link) in TARGETS {
if name == target {
return link(cmdline);
}
}
eprintln!(
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
);
std::process::exit(1);
}
fn main() {
let Some(&(initial_target, _)) = TARGETS.first() else {
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
std::process::exit(1);
};
let args = std::env::args_os().collect();
let status = mold::driver::main(args, initial_target, link_for_target);
std::process::exit(status);
}
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "mold.h"
namespace mold {
template <typename C>
std::vector<std::string_view>
read_response_file(C &ctx, std::string_view path) {
std::vector<std::string_view> vec;
MappedFile<C> *mf = MappedFile<C>::must_open(ctx, std::string(path));
u8 *data = mf->data;
auto read_quoted = [&](i64 i, char quote) {
std::string buf;
while (i < mf->size && data[i] != quote) {
if (data[i] == '\\') {
buf.append(1, data[i + 1]);
i += 2;
} else {
buf.append(1, data[i++]);
}
}
if (i >= mf->size)
Fatal(ctx) << path << ": premature end of input";
vec.push_back(save_string(ctx, buf));
return i + 1;
};
auto read_unquoted = [&](i64 i) {
std::string buf;
while (i < mf->size && !isspace(data[i]))
buf.append(1, data[i++]);
vec.push_back(save_string(ctx, buf));
return i;
};
for (i64 i = 0; i < mf->size;) {
if (isspace(data[i]))
i++;
else if (data[i] == '\'')
i = read_quoted(i + 1, '\'');
else if (data[i] == '\"')
i = read_quoted(i + 1, '\"');
else
i = read_unquoted(i);
}
return vec;
}
template <typename C>
std::vector<std::string_view> expand_response_files(C &ctx, char **argv) {
std::vector<std::string_view> vec;
for (i64 i = 0; argv[i]; i++) {
if (argv[i][0] == '@')
append(vec, read_response_file(ctx, argv[i] + 1));
else
vec.push_back(argv[i]);
}
return vec;
}
} // namespace mold
+166
View File
@@ -0,0 +1,166 @@
// This file implements a multi-threaded zlib compression routine.
//
// Multiple pieces of raw compressed data in zlib-format can be merged
// just by concatenation as long as each zlib stream is flushed with
// Z_SYNC_FLUSH. In this file, we split input data into multiple
// shards, compress them individually and concatenate them. We then
// append a header, a trailer and a checksum so that the concatenated
// data is valid zlib-format data.
//
// Using threads to compress data has a downside. Since the dictionary
// is reset on boundaries of shards, compression ratio is sacrificed
// a little bit. However, if a shard size is large enough, that loss
// is negligible in practice.
#include "mold.h"
#include <tbb/parallel_for_each.h>
#include <zlib.h>
namespace mold {
static constexpr i64 SHARD_SIZE = 1024 * 1024;
static std::vector<std::string_view> split(std::string_view input) {
std::vector<std::string_view> shards;
while (input.size() >= SHARD_SIZE) {
shards.push_back(input.substr(0, SHARD_SIZE));
input = input.substr(SHARD_SIZE);
}
if (!input.empty())
shards.push_back(input);
return shards;
}
static std::vector<u8> do_compress(std::string_view input) {
// Initialize zlib stream. Since debug info is generally compressed
// pretty well, we chose compression level 3.
z_stream strm;
strm.zalloc = Z_NULL;
strm.zfree = Z_NULL;
strm.opaque = Z_NULL;
int r = deflateInit2(&strm, 3, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
assert(r == Z_OK);
// Set an input buffer
strm.avail_in = input.size();
strm.next_in = (u8 *)input.data();
// Set an output buffer. deflateBound() returns an upper bound
// on the compression size. +16 for Z_SYNC_FLUSH.
std::vector<u8> buf(deflateBound(&strm, strm.avail_in) + 16);
strm.avail_out = buf.size();
strm.next_out = buf.data();
r = deflate(&strm, Z_SYNC_FLUSH);
assert(r == Z_OK);
assert(strm.avail_out > 0);
buf.resize(buf.size() - strm.avail_out);
deflateEnd(&strm);
return buf;
}
ZlibCompressor::ZlibCompressor(std::string_view input) {
std::vector<std::string_view> inputs = split(input);
std::vector<u64> adlers(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
adlers[i] = adler32(1, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = do_compress(inputs[i]);
});
// Combine checksums
checksum = adlers[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
}
i64 ZlibCompressor::size() const {
i64 size = 2; // +2 for header
for (const std::vector<u8> &shard : shards)
size += shard.size();
return size + 6; // +6 for trailer and checksum
}
void ZlibCompressor::write_to(u8 *buf) {
// Write a zlib-format header
buf[0] = 0x78;
buf[1] = 0x9c;
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 2; // +2 for header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + size();
end[-6] = 3;
end[-5] = 0;
// Write a checksum
*(ubig32 *)(end - 4) = checksum;
}
GzipCompressor::GzipCompressor(std::string_view input) {
std::vector<std::string_view> inputs = split(input);
std::vector<u32> crc(inputs.size());
shards.resize(inputs.size());
// Compress each shard
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
crc[i] = crc32(0, (u8 *)inputs[i].data(), inputs[i].size());
shards[i] = do_compress(inputs[i]);
});
// Combine checksums
checksum = crc[0];
for (i64 i = 1; i < inputs.size(); i++)
checksum = crc32_combine(checksum, crc[i], inputs[i].size());
uncompressed_size = input.size();
}
i64 GzipCompressor::size() const {
i64 size = 10; // +10 for header
for (const std::vector<u8> &shard : shards)
size += shard.size();
return size + 10; // +10 for trailer and checksum
}
void GzipCompressor::write_to(u8 *buf) {
// Write a zlib-format header
memset(buf, 0, 10);
buf[0] = 0x1f; // magic
buf[1] = 0x8b; // magic
buf[2] = 0x08; // compression method is zlib
buf[9] = 0xff; // made on unknown OS
// Copy compressed data
std::vector<i64> offsets(shards.size());
offsets[0] = 10; // +10 for header
for (i64 i = 1; i < shards.size(); i++)
offsets[i] = offsets[i - 1] + shards[i - 1].size();
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
memcpy(&buf[offsets[i]], shards[i].data(), shards[i].size());
});
// Write a trailer
u8 *end = buf + size();
end[-10] = 0x3; // two-byte zlib stream terminator
end[-9] = 0;
*(u32 *)(end - 8) = checksum;
*(u32 *)(end - 4) = uncompressed_size;
}
} // namespace mold
+24
View File
@@ -0,0 +1,24 @@
#include "mold.h"
#include <cstdlib>
#include <cxxabi.h>
namespace mold {
std::string_view demangle(std::string_view name) {
if (name.starts_with("_Z")) {
static thread_local char *buf;
static thread_local size_t buflen;
int status;
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
if (status == 0) {
buf = p;
return p;
}
}
return name;
}
} // namespace mold
-198
View File
@@ -1,198 +0,0 @@
#!/bin/bash
#
# This script creates a mold binary distribution. The output is written to
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
# `mold-2.42.0-x86_64-linux.tar.gz`).
#
# This script aims to produce reproducible outputs. The container images,
# Rust toolchain, Cargo dependencies and file timestamps are pinned so that
# the same git commit can produce a bit-for-bit identical binary file. This
# property serves as a strong safeguard against supply chain attacks. With
# a reproducible build, anyone can independently verify that the binary
# files published on our GitHub release page were built from the git commit
# tagged for release by rebuilding the binaries themselves.
#
# Debian provides snapshot.debian.org to host all historical binary
# packages. Distro package repositories must be pinned as well as the base
# image before a build is fully reproducible. The loongarch64 build still
# uses the live Debian sid repository, so it does not yet have that property.
#
# The mold executable created by this script is dynamically linked to the
# system C runtime and other standard system libraries. We can't statically
# link glibc because doing so would disable dlopen(), which is required to
# load the LTO linker plugin.
#
# We use a reasonably old Debian version for the build environment because
# a binary dynamically linked against a newer version of glibc won't work
# on a system with an older version of glibc.
#
# The Rust toolchain is downloaded from the official Rust distribution
# site. Its version and SHA-256 hash are recorded below, so the toolchain
# is another pinned build input.
#
# This script can be used to create non-native binaries (e.g., building
# aarch64 binary on x86-64) because Podman automatically runs everything
# under QEMU if the container image is not native. To use this script for
# non-native builds, you may need to install the qemu-user-static package.
set -e -x
cd "$(dirname "$0")"
# The executable embeds the hash of the Git commit it is built from, and
# the commit's timestamp is used as the file timestamp, so this script
# must run in a Git clone. A worktree doesn't work either because its
# `.git` file refers to a directory outside the build container.
if [ ! -d .git ]; then
echo "$0: must be run in a Git clone of mold" >&2
exit 1
fi
usage() {
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
exit 1
}
case $# in
0)
arch=$(uname -m)
if [ "$arch" = arm64 ]; then
arch=aarch64
elif [[ "$arch" = arm* ]]; then
arch=arm
fi
;;
1)
arch=$1
;;
*)
usage
;;
esac
rust_version=1.97.1
# Switch to the pinned snapshot.debian.org sources that the Debian images
# ship commented out; the live mirrors no longer carry every architecture.
apt_setup="sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list"
case $arch in
x86_64)
# Debian 9 (Stretch) released in June 2017.
#
# We use a Google-provided mirror (mirror.gcr.io) instead of the official
# Docker Hub (docker.io) because docker.io has a strict rate limit policy.
base_image=mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
rust_target=x86_64-unknown-linux-gnu
rust_sha256=b4cdbc7cc6b0ee0a2666b1872769fdb2ad8393b28b63952f6493b4b400e4832b
;;
aarch64)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=aarch64-unknown-linux-gnu
rust_sha256=2f2496c70bd336a66a4c8baf2d303ba161f3552f192444c3639ba903c7c1e2c5
;;
arm)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=armv7-unknown-linux-gnueabihf
rust_sha256=e89c5e33aaddc6ef56857000c9117875c2997e9a1a500bd7b16277c9874b002f
;;
riscv64)
base_image=mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
apt_setup="sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources"
rust_target=riscv64gc-unknown-linux-gnu
rust_sha256=59bec35d8febb2ab918fa41cffbaa5b07146a63bdc33f029ff756d70a3151ece
;;
ppc64le)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=powerpc64le-unknown-linux-gnu
rust_sha256=ff524eef5a59d801df09ccad5cdaf9ea1f0a07d75cbed2a7e9f013a9eb76a3c1
;;
s390x)
# Debian 11 (Bullseye) released in August 2021.
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
rust_target=s390x-unknown-linux-gnu
rust_sha256=808268af9e880d41b8cb32b242e38c9bd3ea7aba6409b02fbffa0fbc5370c538
;;
loongarch64)
base_image=mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
apt_setup="echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list"
rust_target=loongarch64-unknown-linux-gnu
rust_sha256=d5a925962854730ae7641420d8337af93988ea4ff47b503a856ec53776c87841
;;
*)
usage
;;
esac
# Create a Podman image containing the native C tools and a pinned Rust
# toolchain. The downloaded archive is checked before it is unpacked.
image=mold-rust-builder-$arch
archive=rust-$rust_version-$rust_target.tar.gz
podman build --arch "$arch" -t "$image" - <<EOF
FROM $base_image
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
RUN $apt_setup && \
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
apt-get update && \
apt-get install -y --no-install-recommends build-essential ca-certificates git wget && \
rm -rf /var/lib/apt/lists
RUN mkdir /tmp/rust && \
cd /tmp/rust && \
wget --progress=dot:mega https://static.rust-lang.org/dist/$archive && \
echo '$rust_sha256 $archive' | sha256sum -c && \
tar xf $archive && \
./${archive%.tar.gz}/install.sh --prefix=/usr/local --disable-ldconfig && \
cd / && \
rm -rf /tmp/rust
EOF
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml)
dest=mold-$version-$arch-linux
# We use the timestamp of the last Git commit as the file timestamp
# for build artifacts.
timestamp=$(git log -1 --format=%ct)
# `uname -m` in an ARM32 container running on an ARM64 host reports it
# not as ARM32 but as ARM64. Keep the reported machine consistent with
# the ARM32 userspace for any build tool that inspects it.
setarch=
[ "$arch" = arm ] && setarch='setarch linux32'
mkdir -p dist "target/dist-vendor-$arch"
# Cargo verifies registry packages against the checksums in Cargo.lock and
# checks out Git dependencies at the commit recorded there. Vendor them in a
# separate networked step so that the actual build can run without a network.
# Cargo's own cache is a tmpfs because the libgit2 inside a 32-bit cargo can't
# read a bind-mounted ext4 directory (readdir fails with EOVERFLOW under QEMU).
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 \
--pull=never --env CARGO_HOME=/cargo --tmpfs /cargo -v "$(pwd):/mold:ro" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor" "$image" $setarch \
bash -c 'cd /mold && cargo vendor --locked /vendor/sources > /vendor/config.toml'
# Build mold in a container.
#
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
# build artifacts to appear as if they were built at a specific time.
# Fixed source, vendor and target paths keep embedded build paths stable.
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 --network=none \
--pull=never --env SOURCE_DATE_EPOCH="$timestamp" --env DEST="$dest" \
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" \
-v "$(pwd)/target/dist-vendor-$arch:/vendor:ro" "$image" \
$setarch bash -c '
set -e
export CARGO_TARGET_DIR=/build/target
cd /mold
cargo build --release --frozen --config /vendor/config.toml --package mold-cli
stage=/build/$DEST
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
strip --strip-unneeded "$stage/bin/mold" "$stage/lib/mold/mold-wrapper.so"
find "$stage" -print | xargs touch --no-dereference --date="@$SOURCE_DATE_EPOCH"
cd /build
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
sha256sum "/dist/$DEST.tar.gz"
'
+150
View File
@@ -0,0 +1,150 @@
This is a note about interesting bugs that I met during the
development of the mold linker.
## GNU IFUNC
Problem: A statically-linked "hello world" program mysteriously
crashed in `__libc_start_main` function which is called just after
`_start`.
Investigation: I opened up gdb and found that the program reads a
bogus value from some array. It looks like `memcpy` failed to copy
proper data there. After some investigation, I noticed that `memcpy`
did't copy data at all but instead returned the address of
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
optimized for machines with the AVX registers.
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
function symbol has type `STT_GNU_IFUNC`, the function does not do
what its name suggests to do but instead returns a pointer to a
function that does the actual job. In this case, `memcpy` is an IFUNC
function, and it returns an address of `__memcpy_avx_unaligned` which
is a real `memcpy` function.
IFUNC function addresses are stored to `.got` section in an ELF
executable. The dynamic loader executes all IFUNC functions at
startup to replace their GOT entries with their return values. This
mechanism allows programs to choose the best implementation among
variants of the same function at runtime based on the machine info.
If a program is statically-linked, there's no dynamic loader that
rewrites the GOT entries. Therefore, if a program is
statically-linked, a libc's startup routine does that on behalf of the
dynamic loader. Concretely, a startup routine interprets all dynamic
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
symbols. It is linker's responsibility to emit dynamic relocations
for IFUNC symbols even if it is linking a statically-linked program
and mark the beginning and the ending of a `.rela.dyn` section with
the symbols, so that the startup routine can find the relocations.
The bug was my linker didn't define `__rela_iplt_start` and
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
initialized to zero. From the point of the initializer function,
there's no dynamic relocations between `__rela_iplt_start` and
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
untouched.
The proper fix was to emit dynamic relocations for IFUNC symbols and
define the linker-synthesized symbols. I did that, and the bug was
fixed.
## stdio buffering
Problem: A statically-linked "Hello world" program prints out the
message if executed as `./hello`, but it doesn't output anything if
executed as `./hello | cat`.
Investigation: I knew that the default buffering mode for stdout is
line buffering (buffer is flushed on every '\n'), but if it is not
connected to the terminal (i.e. `isatty(2)` returns 0 on
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
is flushed when it becomes full). So, it looks like libc failed to
flush the stdout on program exit for some reason.
I traced all function calls using gdb and noticed that `__libc_atexit`
was not called. That function seemed to be responsible for buffer
flushing. I don't know how exactly I found the root cause, but after
spending an hour or two, I found that `__start___libc_atexit` and
`__stop___libc_atexit` have value 0 in my linker's output while they
mark a section containing the address of `__libc_atexit` in GNU ld's
output.
So, libc doesn't directly call `__libc_atexit` but instead call all
function pointers between `__start___libc_atexit` and
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
`_libc_atexit` section, expecting that the linker automatically
creates the start and the end marker symbols for the section.
There's an obscure linker feature: if a section name is valid as a C
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
automatically creates marker symbols by prepending `__start_` and
`__stop_` to the section name. My linker lacked the feature.
I implemented the feature, and the bug was fixed.
## TLS variable initialization
Problem: A statically-linked "hello world" program crashes after
reading a thread-local variable.
Investigation: Thread-local variables are very different from other
types of varaibles because there may be more than one instance of the
same variable in memory. Each thread has its copy of thread-local
varaibles. `%fs` segment register points the end of the variable area
for the current thread, and the variables are accessed as an offset
from `%fs`.
Thread-local variables may be initialized (e.g. `thread_local int x =
5;`). The linker gathers all thread-local variables and put them into
`PT_TLS` segment. At runtime, the contents of the segment is used as
an "initialization image" for new threads. When a new thread is
created, the image is memcpy'ed to the new thread's thread-local
variable area. The initialization image itself is read-only at
runtime.
It took more than a day to find out that memcpy copies the
initialization image to a different place than the thread-local
variables reside. That means, thread-local variables had garbage as
initial values, and the program crashed when using them.
The problem is that I set a very large value (4096) to the alignment
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
the page boundary, so I use the same value for `PT_TLS`, but that was
a mistake. When a thread initialization routine sets a value to `%fs`,
it first aligns the end of the thread-local variable area address to
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
alignment, `%fs` is set to a wrong place.
I fixed `PT_TLS` alignment, and the bug was gone.
## stdio buffering (another issue)
I noticed that a dynamically-linked "hello world" program didn't
flush its stdout buffer on exit. The cause of the problem was that the
executable had more than one DT_NEEDED entry for `libc.so`.
DT_NEEDED entries in `.dynamic` section specify a list of shared
object file names which need to be linked at runtime. I added one
DT_NEEDED entry for each library specified with the `-l` option.
The pitfall is, unlike object files, libraries are allowed to
appear more than once in a command line, and the linker has to
de-duplicate them before processing. Adding more than one DT_NEEDED
entry for the same shared object causes mysterious issues like this.
# Copy relocations and symbol aliases
environ, _environ and __environ point to the same location in libc.so,
so when we create a copy relocation for one of the symbols, we need to
do that for all of them. Otherwise, they'll end up pointing to different
places which causes a very mysterious issue.
# DT_DEBUG and gdb
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
`info sharedlibrary` command doesn't print out a list of shared
libraries loaded to memory. The value of the entry doesn't matter, so
it can be just zero. The existence of it is important.
#
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

+486
View File
@@ -0,0 +1,486 @@
## Design and implementation of mold
For the rest of this documentation, I'll explain the design and the
implementation of mold. If you are only interested in using mold, you
don't need to read the below.
### Motivation
Here is why I'm writing a new linker:
- Even though lld has significantly improved the situation, linking is
still one of the slowest steps in a build. It is especially
annoying when I changed one line of code and had to wait for a few
seconds or even more for a linker to complete. It should be
instantaneous. There's a need for a faster linker.
- The number of cores on a PC has increased a lot lately, and this
trend is expected to continue. However, the existing linkers can't
take the advantage of the trend because they don't scale well for more
cores. I have a 64-core/128-thread machine, so my goal is to create
a linker that uses the CPU nicely. mold should be much faster than
other linkers on 4 or 8-core machines too, though.
- It looks to me that the designs of the existing linkers are somewhat
too similar, and I believe there are a lot of drastically different
designs that haven't been explored yet. Developers generally don't
care about linkers as long as they work correctly, and they don't
even think about creating a new one. So there may be lots of low
hanging fruits there in this area.
### Basic design
- In order to achieve a `cp`-like performance, the most important
thing is to fix the layout of an output file as quickly as possible, so
that we can start copying actual data from input object files to an
output file as soon as possible.
- Copying data from input files to an output file is I/O-bounded, so
there should be room for doing computationally-intensive tasks while
copying data from one file to another.
- We should allow the linker to preload object files from disk and
parse them in memory before a complete set of input object files
is ready. To do so, we need
to split the linker into two in such a way that the latter half of
the process finishes as quickly as possible by speculatively parsing
and preprocessing input files in the first half of the process.
- One of the most computationally-intensive stage among linker stages
is symbol resolution. To resolve symbols, we basically have to throw
all symbol strings into a hash table to match undefined symbols with
defined symbols. But this can be done in the preloading stage using
[string interning](https://en.wikipedia.org/wiki/String_interning).
- Object files may contain a special section called a mergeable string
section. The section contains lots of null-terminated strings, and
the linker is expected to gather all mergeable string sections and
merge their contents. So, if two object files contain the same
string literal, for example, the resulting output will contain a
single merged string. This step is computationally intensive, but string
merging can be done in the preloading stage using string interning.
- Static archives (.a files) contain object files, but the static
archive's string table contains only defined symbols of member
object files and lacks other types of symbols. That makes static
archives unsuitable for speculative parsing. Therefore, the linker
should ignore the symbol table of static archive and directly read
static archive members.
- If there's a relocation that uses a GOT of a symbol, then we have to
create a GOT entry for that symbol. Otherwise, we shouldn't. That
means we need to scan all relocation tables to fix the length and
the contents of a .got section. This is computationally intensive,
but this step is parallelizable.
### Linker Script
Linker script is an embedded language for the linker. It is mainly
used to control how input sections are mapped to output sections and
the layout of the output, but it can also do a lot of tricky stuff.
Its feature is useful especially for embedded programming, but it's
also an awfully underdocumented and complex language.
We have to implement a subset of the linker script language anwyay,
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
name) not a shared object file but actually an ASCII file containing
linker script code to load the _actual_ libc.so file. But the feature
set for this purpose is very limited, and it is okay to implement them
to mold.
Besides that, we really don't want to implement the linker script
language. But at the same time, we want to satisfy the user needs that
are currently satisfied with the linker script language. So, what
should we do? Here is my observation:
- Linker script allows doing a lot of tricky stuff, such as specifying
the exact layout of a file, inserting arbitrary bytes between
sections, etc. But most of them can be done with a post-link binary
editing tool (such as `objcopy`).
- It looks like there are two things that truly cannot be done by a
post-link editing tool: (a) mapping input sections to output
sections, and (b) applying relocations.
From the above observation, I believe we need to provide only the
following features instead of the entire linker script language:
- A method to specify how input sections are mapped to output
sections, and
- a method to set addresses to output sections, so that relocations
are applied based on desired addresses.
I believe everything else can be done with a post-link binary editing
tool.
### Details
- As we aim to the 1-second goal for Chromium, every millisecond
counts. We can't ignore the latency of process exit. If we mmap a
lot of files, \_exit(2) is not instantaneous but takes a few hundred
milliseconds because the kernel has to clean up a lot of
resources. As a workaround, we should organize the linker command as
two processes; the first process forks the second process, and the
second process does the actual work. As soon as the second process
writes a result file to a filesystem, it notifies the first process,
and the first process exits. The second process can take time to
exit, because it is not an interactive process.
- At least on Linux, it looks like the filesystem's performance to
allocate new blocks to a new file is the limiting factor when
creating a new large file and filling its contents using mmap.
If you already have a large file in the buffer cache, writing to it is
much faster than creating a new fresh file and writing to it.
Based on this observation, mold overwrites to an existing
executable file if exists. My quick benchmark showed that I could
save 300 milliseconds when creating a 2 GiB output file.
Linux doesn't allow to open an executable for writing if it is
running (you'll get a "text busy" error if you attempt). mold
falls back to the usual way if it fails to open an output file.
- The output from the linker should be deterministic for the sake of
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
and ease of debugging. This might add a little bit of overhead to
the linker, but that shouldn't be too much.
- A .build-id, a unique ID embedded to an output file, is usually
computed by applying a cryptographic hash function (e.g. SHA-1) to
an output file. This is a slow step, but we can speed it up by
splitting a file into small chunks, computing SHA-1 for each chunk,
and then computing SHA-1 of the concatenated SHA-1 hashes
(i.e. constructing a [Merkle
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
Modern x86 processors have purpose-built instructions for SHA-1 and
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
milliseconds.
- BFD, gold, and lld support section garbage collection. That is, a
linker runs a mark-sweep garbage collection on an input graph, where
sections are vertices and relocations are edges, to discard all
sections that are not reachable from the entry point symbol
(i.e. `_start`) or a few other root sections. In mold, we are using
multiple threads to mark sections concurrently.
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
as yet another size optimization. ICF merges two or more read-only
sections that happen to have the same contents and relocations.
To do that, we have to find isomorphic subgraphs from larger graphs.
I implemented a new algorithm for mold, which is 5x faster than lld
to do ICF for Chromium (from 5 seconds to 1 second).
- [Intel Threading Building
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
library for parallel execution and has several concurrent
containers. We are particularly interested in using
`parallel_for_each` and `concurrent_hash_map`.
- TBB provides `tbbmalloc` which works better for multi-threaded
applications than the glib'c malloc, but it looks like
[jemalloc](https://github.com/jemalloc/jemalloc) and
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
more scalable than `tbbmalloc`.
### Size of the problem
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
total. The data contains the following items:
| Data item | Number
| ------------------------ | ------
| Object files | 30,723
| Public undefined symbols | 1,428,149
| Mergeable strings | 1,579,996
| Comdat groups | 9,914,510
| Regular sections¹ | 10,345,314
| Public defined symbols | 10,512,135
| Symbols | 23,953,607
| Sections | 27,543,225
| Relocations against SHF_ALLOC sections | 39,496,375
| Relocations | 62,024,719
¹ Sections that have to be copied from input object files to an
output file. Sections that contain relocations or symbols are for
example excluded.
### Internals
In this section, I'll explain the internals of mold linker.
#### A brief history of Unix and the Unix linker
Conceptually, what a linker does is pretty simple. A compiler compiles
a fragment of a program (a single source file) into a fragment of
machine code and data (an object file, which typically has the .o
extension), and a linker stitches them together into a single
executable or a shared library image.
In reality, modern linkers for Unix-like systems are much more
complicated than the naive understanding because they have gradually
gained one feature at a time over the 50 years history of Unix, and
they are now something like a bag of lots of miscellaneous features in
which none of the features is more important than the others. It is
very easy to miss the forest for the trees, since for those who don't
know the details of the Unix linker, it is not clear which feature is
essential and which is not.
That being said, one thing is clear that at any point of Unix history,
a Unix linker has a coherent feature set for the Unix of that age. So,
let me entangle the history to see how the operating system, runtime,
and linker have gained features that we see today. That should give
you an idea of why a particular feature has been added to a linker in the
first place.
1. Original Unix didn't support shared libraries, and a program was
always loaded to a fixed address. An executable was something like
a memory dump that was just loaded to a particular address by the
kernel. After loading, the kernel started executing the program by
setting the instruction pointer to a particular address.
The most essential feature for any linker is relocation processing.
The original Unix linker of course supported that. Let me explain
what that is.
Individual object files are inevitably incomplete as a program,
because when a compiler created them, it only see a part of an
entire program. For example, if an object file contains a function
call that refers to another object file, the `call` instruction in the
object cannot be complete, as the compiler has no idea as to what
is the called function's address. To deal with this, the compiler
emits a placeholder value (typically just zero) instead of a real
address and leaves metadata in an object file saying "fix offset X
of this file with an address of Y". That metadata is called
"relocation". Relocations are typically processed by the linker.
It is easy for a linker to apply relocations for the original Unix
because a program is always loaded to a fixed address. It exactly
knows the addresses of all functions and data when linking a
program.
Static library support, which is still an important feature of Unix
linker, also dates back to this early period of Unix history.
To understand what it is, imagine that you are trying to compile
a program for the early Unix. You don't want to waste time to
compile libc functions every time you compile your program (the
computers of the era were incredibly slow), so you have already
placed each libc function into a separate source file and compiled
them individually. That means you have object files for each libc
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
Given this configuration, all you have to do to link your program
against libc functions is to pick up the right set of libc object
files and give them to the linker along with the object files of your
program. But, keeping the linker command line in sync with the
libc functions you are using in your program is bothersome. You can
be conservative; you can specify all libc object files to the
command line, but that leads to program bloat because the linker
unconditionally link all object files given to it no matter whether
they are used or not. So, a new feature was added to the linker to
fix the problem. That is the static library, which is also called
the archive file.
An archive file is just a bundle of object files, just like zip
file but in an uncompressed form. An archive file typically has the
.a file extension and named after its contents. For example, the
archive file containing all libc objects is named `libc.a`.
If you pass an archive file along with other object files to the
linker, the linker pulls out an object file from the archive _only
when_ it is referenced by other object files. In other words,
unlike object files directly given to a linker, object files
wrapped in an archive are not linked to the output by default.
An archive works as a supplement to complete your program.
Even today, you can still find a libc archive file. Run `ar t
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
of object files in the libc archive.
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
time, added shared library support to their Unix variant, SunOS.
(This section is incomplete.)
### Concurrency strategy
In this section, I'll explain the high-level concurrency strategy of
mold.
In most places, mold adopts data parallelism. That is, we have a huge
number of pieces of data of the same kind, and we process each of them
individually using parallel for-loop. For example, after identifying
the exact set of input object files, we need to scan all relocation
tables to determine the sizes of .got and .plt sections. We do that
using a parallel for-loop. The granularity of parallel processing in
this case is the relocation table.
Data parallelism is very efficient and scalable because there's no
need for threads to communicate with each other while working on each
element of data. In addition to that, data parallelism is easy to
understand, as it is just a for-loop in which multiple iterations may
be executed in parallel. We don't use high-level communication or
synchronization mechanisms such as channels, futures, promises,
latches or something like that in mold.
In some cases, we need to share a little bit of data between threads
while executing a parallel for-loop. For example, the loop to scan
relocations turns on "requires GOT" or "requires PLT" flags in a
symbol. Symbol is a shared resource, and writing to them from multiple
threads without synchronization is unsafe. To deal with it, we made
the flag an atomic variable.
The other common pattern you can find in mold which is build on top of
the parallel for-loop is the map-reduce pattern. That is, we run a
parallel for-loop on a large data set to produce a small data set and
process the small data set with a single thread. Let me take a
build-id computation as an example. Build-id is typically computed by
applying a cryptographic hash function such as SHA-1 on a linker's
output file. To compute it, we first consider an output as a sequence
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
hashes to get a final build-id.
Finally, we use concurrent hashmap at a few places in mold. Concurrent
hashmap is a hashmap to which multiple threads can safely insert items
in parallel. We use it in the symbol resolution stage, for example.
To resolve symbols, we basically have to throw in all defined symbols
into a hash table, so that we can find a matching defined symbol for
an undefined symbol by name. We do the hash table insertion from a
parallel for-loop which iterates over a list of input files.
Overall, even though mold is highly scalable, it succeeded to avoid
complexties you often find in complex parallel programs. From high
level, mold just serially executes the linker's internal passes one by
one. Each pass is parallelized using parallel for-loops.
### Rejected ideas
In this section, I'll explain the alternative designs I currently do
not plan to implement and why I turned them down.
- Placing variable-length sections at end of an output file and start
copying file contents before fixing the output file layout
Idea: Fixing the layout of regular sections seems easy, and if we
place them at beginning of a file, we can start copying their
contents from their input files to an output file. While copying
file contents, we can compute the sizes of variable-length sections
such as .got or .plt and place them at end of the file.
Reason for rejection: I did not choose this design because I doubt
if it could actually shorten link time and I think I don't need it
anyway.
The linker has to de-duplicate comdat sections (i.e. inline
functions that are included in multiple object files), so we
cannot compute the layout of regular sections until we resolve all
symbols and de-duplicate comdats. That takes a few hundred
milliseconds. After that, we can compute the sizes of
variable-length sections in less than 100 milliseconds. It's quite
fast, so it doesn't seem to make much sense to proceed without
fixing the final file layout.
The other reason to reject this idea is because there's good a
chance for this idea to have a negative impact on linker's overall
performance. If we copy file contents before fixing the layout, we
can't apply relocations to them while copying because symbol
addresses are not available yet. If we fix the file layout first, we
can apply relocations while copying, which is effectively zero-cost
due to a very good data locality. On the other hand, if we apply
relocations long after we copy file contents, it's pretty expensive
because section contents are very likely to have been evicted from
CPU cache.
- Incremental linking
Idea: Incremental linking is a technique to patch a previous linker's
output file so that only functions or data that are updated from the
previous build are written to it. It is expected to significantly
reduce the amount of data copied from input files to an output file
and thus speed up linking. GNU BFD and gold linkers support it.
Reason for rejection: I turned it down because it (1) is
complicated, (2) doesn't seem to speed it up that much and (3) has
several practical issues. Let me explain each of them.
First, incremental linking for real C/C++ programs is not as easy as
one might think. Let me take malloc as an example. malloc is usually
defined by libc, but you can implement it in your program, and if
that's the case, the symbol `malloc` will be resolved to your
function instead of the one in libc. If you include a library that
defines malloc (such as libjemalloc or libtbbmallc) before libc,
their malloc will override libc's malloc.
Assume that you are using a nonstandard malloc. What if you remove
your malloc from your code, or remove `-ljemalloc` from your
Makefile? The linker has to include a malloc from libc, which may
include more object files to satisfy its dependencies. Such code
change can affect the entire program rather than just replacing one
function. The same is true for adding malloc to your program. Making
a local change doesn't necessarily result in a local change in the
binary level. It can easily have cascading effects.
Some ELF fancy features make incremental linking even harder to
implement. Take the weak symbol as an example. If you define `atoi`
as a weak symbol in your program, and if you are not using `atoi`
at all in your program, that symbol will be resolved to address
0. But if you start using some libc function that indirectly calls
`atoi`, then `atoi` will be included in your program, and your weak
symbol will be resolved to that function. I don't know how to
efficiently fix up a binary for this case.
This is a hard problem, so existing linkers don't try too hard to
solve it. For example, IIRC, gold falls back to full link if any
function is removed from a previous build. If you want to not annoy
users in the fallback case, you need to make full link fast anyway.
Second, incremental linking itself has an overhead. It has to detect
updated files, patch an existing output file and write additional
data to an output file for future incremental linking. GNU gold, for
instance, takes almost 30 seconds on my machine to do a null
incremental link (i.e. no object files are updated from a previous
build) for chrome. It's just too slow.
Third, there are other practical issues in incremental linking. It's
not reproducible, so your binary isn't going to be the same as other
binaries even if you are compiling the same source tree using the
same compiler toolchain. Or, it is complex and there might be a bug
in it. If something doesn't work correctly, "remove --incremental
from your Makefile and try again" could be a piece of advice, but
that isn't ideal.
So, all in all, incremental linking is tricky. I wanted to make full
link as fast as possible, so that we don't have to think about how
to work around the slowness of full link.
- Defining a completely new file format and use it
Idea: Sometimes, the ELF file format itself seems to be a limiting
factor in improving the linker's performance. We might be able to make a
far better one if we create a new file format.
Reason for rejection: I rejected the idea because it apparently has
a practical issue (backward compatibility issue) and also doesn't
seem to improve the performance of linkers that much. As clearly
demonstrated by mold, we can create a fast linker for ELF. I believe
ELF isn't that bad, after all. The semantics of the existing Unix
linkers, such as the name resolution algorithm or the linker script,
have slowed the linkers down, but that's not a problem of the file
format itself.
- Watching object files using inotify(2)
Idea: When mold is running as a daemon for preloading, use
inotify(2) to watch file system updates so that it can reload files
as soon as they are updated.
Reason for rejection: Just like the maximum number of files you can
simultaneously open, the maximum number of files you can watch using
inotify(2) isn't that large. Maybe just a single instance of mold is
fine with inotify(2), but it may fail if you run multiple of it.
The other reason for not doing it is because mold is quite fast
without it anyway. Invoking stat(2) on each file for file update
check takes less than 100 milliseconds for Chrome, and if most of
the input files are not updated, parsing updated files takes almost
no time.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 596 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

+950 -584
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

-1009
View File
File diff suppressed because it is too large. Load diff
+794
View File
@@ -0,0 +1,794 @@
#include "mold.h"
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
using E = ARM64;
static void write_adr(u8 *buf, u64 val) {
u32 hi = (val & 0x1ffffc) << 3;
u32 lo = (val & 3) << 29;
*(u32 *)buf = (*(u32 *)buf & 0x9f00001f) | hi | lo;
}
// Returns [hi:lo] bits of val.
static u64 bits(u64 val, u64 hi, u64 lo) {
return (val >> lo) & (((u64)1 << (hi - lo + 1)) - 1);
}
static u64 page(u64 val) {
return val & ~(u64)0xfff;
}
template <>
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
u64 *buf = (u64 *)(ctx.buf + this->shdr.sh_offset);
// The first slot of .got.plt points to _DYNAMIC.
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
buf[1] = 0;
buf[2] = 0;
for (Symbol<E> *sym : ctx.plt->symbols)
buf[sym->get_gotplt_idx(ctx)] = ctx.plt->shdr.sh_addr;
}
static void write_plt_header(Context<E> &ctx, u8 *buf) {
// Write PLT header
static const u8 plt0[] = {
0xf0, 0x7b, 0xbf, 0xa9, // stp x16, x30, [sp,#-16]!
0x10, 0x00, 0x00, 0x90, // adrp x16, .got.plt[2]
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, .got.plt[2]]
0x10, 0x02, 0x00, 0x91, // add x16, x16, .got.plt[2]
0x20, 0x02, 0x1f, 0xd6, // br x17
0x1f, 0x20, 0x03, 0xd5, // nop
0x1f, 0x20, 0x03, 0xd5, // nop
0x1f, 0x20, 0x03, 0xd5, // nop
};
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
u64 plt = ctx.plt->shdr.sh_addr;
memcpy(buf, plt0, sizeof(plt0));
write_adr(buf + 4, bits(page(gotplt) - page(plt + 4), 32, 12));
*(u32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
*(u32 *)(buf + 12) |= ((gotplt) & 0xfff) << 10;
}
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
static const u8 data[] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, .got.plt[n]
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, .got.plt[n]]
0x10, 0x02, 0x00, 0x91, // add x16, x16, .got.plt[n]
0x20, 0x02, 0x1f, 0xd6, // br x17
};
u64 gotplt = sym.get_gotplt_addr(ctx);
u64 plt = sym.get_plt_addr(ctx);
memcpy(ent, data, sizeof(data));
write_adr(ent, bits(page(gotplt) - page(plt), 32, 12));
*(u32 *)(ent + 4) |= bits(gotplt, 11, 3) << 10;
*(u32 *)(ent + 8) |= (gotplt & 0xfff) << 10;
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
write_plt_header(ctx, buf);
for (Symbol<E> *sym : symbols)
write_plt_entry(ctx, buf, *sym);
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
for (Symbol<E> *sym : symbols) {
u8 *ent = buf + sym->get_pltgot_idx(ctx) * ARM64::pltgot_size;
static const u8 data[] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, GOT[n]
0x11, 0x02, 0x40, 0xf9, // ldr x17, [x16, GOT[n]]
0x20, 0x02, 0x1f, 0xd6, // br x17
0x1f, 0x20, 0x03, 0xd5, // nop
};
u64 got = sym->get_got_addr(ctx);
u64 plt = sym->get_plt_addr(ctx);
memcpy(ent, data, sizeof(data));
write_adr(ent, bits(page(got) - page(plt), 32, 12));
*(u32 *)(ent + 4) |= bits(got, 11, 3) << 10;
}
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
u64 loc, u64 val) {
u8 *base = ctx.buf + this->shdr.sh_offset;
switch (rel.r_type) {
case R_AARCH64_ABS64:
*(u64 *)(base + loc) = val;
return;
case R_AARCH64_PREL32:
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
return;
case R_AARCH64_PREL64:
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
return;
}
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_AARCH64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
#define GOT ctx.got->shdr.sh_addr
if (needs_dynrel[i]) {
*dynrel++ = {P, R_AARCH64_ABS64, (u32)sym.get_dynsym_idx(ctx), A};
*(u64 *)loc = A;
continue;
}
if (needs_baserel[i]) {
if (!is_relr_reloc(ctx, rel))
*dynrel++ = {P, R_AARCH64_RELATIVE, 0, (i64)(S + A)};
*(u64 *)loc = S + A;
continue;
}
switch (rel.r_type) {
case R_AARCH64_ABS64:
*(u64 *)loc = S + A;
continue;
case R_AARCH64_LDST8_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
continue;
case R_AARCH64_LDST16_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 1) << 10;
continue;
case R_AARCH64_LDST32_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 2) << 10;
continue;
case R_AARCH64_LDST64_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 3) << 10;
continue;
case R_AARCH64_LDST128_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 4) << 10;
continue;
case R_AARCH64_ADD_ABS_LO12_NC:
*(u32 *)loc |= bits(S + A, 11, 0) << 10;
continue;
case R_AARCH64_MOVW_UABS_G0_NC:
*(u32 *)loc |= bits(S + A, 15, 0) << 5;
continue;
case R_AARCH64_MOVW_UABS_G1_NC:
*(u32 *)loc |= bits(S + A, 31, 16) << 5;
continue;
case R_AARCH64_MOVW_UABS_G2_NC:
*(u32 *)loc |= bits(S + A, 47, 32) << 5;
continue;
case R_AARCH64_MOVW_UABS_G3:
*(u32 *)loc |= bits(S + A, 63, 48) << 5;
continue;
case R_AARCH64_ADR_GOT_PAGE: {
i64 val = page(G + GOT + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
write_adr(loc, bits(val, 32, 12));
continue;
}
case R_AARCH64_ADR_PREL_PG_HI21: {
i64 val = page(S + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
write_adr(loc, bits(val, 32, 12));
continue;
}
case R_AARCH64_ADR_PREL_LO21: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
write_adr(loc, val);
continue;
}
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26: {
if (sym.esym().is_undef_weak()) {
// On ARM, calling an weak undefined symbol jumps to the
// next instruction.
*(u32 *)loc |= 1;
continue;
}
i64 lo = -(1 << 27);
i64 hi = 1 << 27;
i64 val = S + A - P;
if (val < lo || hi <= val) {
RangeExtensionRef ref = range_extn[i];
val = output_section->thunks[ref.thunk_idx]->get_addr(ref.sym_idx) + A - P;
assert(lo <= val && val < hi);
}
*(u32 *)loc |= (val >> 2) & 0x3ffffff;
continue;
}
case R_AARCH64_CONDBR19: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 20), (i64)1 << 20);
*(u32 *)loc |= bits(val, 20, 2) << 5;
continue;
}
case R_AARCH64_PREL16: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 15), (i64)1 << 15);
*(u16 *)loc = val;
continue;
}
case R_AARCH64_PREL32: {
i64 val = S + A - P;
overflow_check(val, -((i64)1 << 31), (i64)1 << 32);
*(u32 *)loc = val;
continue;
}
case R_AARCH64_PREL64:
*(u64 *)loc = S + A - P;
continue;
case R_AARCH64_LD64_GOT_LO12_NC:
*(u32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
continue;
case R_AARCH64_LD64_GOTPAGE_LO15: {
i64 val = G + GOT + A - page(GOT);
overflow_check(val, 0, 1 << 15);
*(u32 *)loc |= bits(val, 14, 3) << 10;
continue;
}
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
write_adr(loc, bits(val, 32, 12));
continue;
}
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
*(u32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
continue;
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
i64 val = S + A - ctx.tls_begin + 16;
overflow_check(val, 0, (i64)1 << 24);
*(u32 *)loc |= bits(val, 23, 12) << 10;
continue;
}
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
*(u32 *)loc |= bits(S + A - ctx.tls_begin + 16, 11, 0) << 10;
continue;
case R_AARCH64_TLSGD_ADR_PAGE21: {
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
write_adr(loc, bits(val, 32, 12));
continue;
}
case R_AARCH64_TLSGD_ADD_LO12_NC:
*(u32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
continue;
case R_AARCH64_TLSDESC_ADR_PAGE21: {
if (ctx.relax_tlsdesc && !sym.is_imported) {
// adrp x0, 0 -> movz x0, #tls_ofset_hi, lsl #16
i64 val = (S + A - ctx.tls_begin + 16);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
*(u32 *)loc = 0xd2a00000 | (bits(val, 32, 16) << 5);
} else {
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
overflow_check(val, -((i64)1 << 32), (i64)1 << 32);
write_adr(loc, bits(val, 32, 12));
}
continue;
}
case R_AARCH64_TLSDESC_LD64_LO12:
if (ctx.relax_tlsdesc && !sym.is_imported) {
// ldr x2, [x0] -> movk x0, #tls_ofset_lo
u32 offset_lo = (S + A - ctx.tls_begin + 16) & 0xffff;
*(u32 *)loc = 0xf2800000 | (offset_lo << 5);
} else {
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
}
continue;
case R_AARCH64_TLSDESC_ADD_LO12:
if (ctx.relax_tlsdesc && !sym.is_imported) {
// add x0, x0, #0 -> nop
*(u32 *)loc = 0xd503201f;
} else {
*(u32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
}
continue;
case R_AARCH64_TLSDESC_CALL:
if (ctx.relax_tlsdesc && !sym.is_imported) {
// blr x2 -> nop
*(u32 *)loc = 0xd503201f;
}
continue;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_AARCH64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? addend : rel.r_addend)
switch (rel.r_type) {
case R_AARCH64_ABS64:
*(u64 *)loc = S + A;
continue;
case R_AARCH64_ABS32:
*(u32 *)loc = S + A;
continue;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
#undef S
#undef A
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr.sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_AARCH64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
if (sym.get_type() == STT_GNU_IFUNC) {
sym.flags |= NEEDS_GOT;
sym.flags |= NEEDS_PLT;
}
switch (rel.r_type) {
case R_AARCH64_ABS64: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_AARCH64_ADR_GOT_PAGE:
case R_AARCH64_LD64_GOT_LO12_NC:
case R_AARCH64_LD64_GOTPAGE_LO15:
sym.flags |= NEEDS_GOT;
break;
case R_AARCH64_CALL26:
case R_AARCH64_JUMP26:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
sym.flags |= NEEDS_GOTTP;
break;
case R_AARCH64_ADR_PREL_PG_HI21: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, NONE, ERROR, ERROR }, // DSO
{ NONE, NONE, COPYREL, PLT }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_AARCH64_TLSGD_ADR_PAGE21:
sym.flags |= NEEDS_TLSGD;
break;
case R_AARCH64_TLSDESC_ADR_PAGE21:
case R_AARCH64_TLSDESC_LD64_LO12:
case R_AARCH64_TLSDESC_ADD_LO12:
if (!ctx.relax_tlsdesc || sym.is_imported)
sym.flags |= NEEDS_TLSDESC;
break;
case R_AARCH64_ADD_ABS_LO12_NC:
case R_AARCH64_ADR_PREL_LO21:
case R_AARCH64_CONDBR19:
case R_AARCH64_LDST16_ABS_LO12_NC:
case R_AARCH64_LDST32_ABS_LO12_NC:
case R_AARCH64_LDST64_ABS_LO12_NC:
case R_AARCH64_LDST128_ABS_LO12_NC:
case R_AARCH64_LDST8_ABS_LO12_NC:
case R_AARCH64_MOVW_UABS_G0_NC:
case R_AARCH64_MOVW_UABS_G1_NC:
case R_AARCH64_MOVW_UABS_G2_NC:
case R_AARCH64_MOVW_UABS_G3:
case R_AARCH64_PREL16:
case R_AARCH64_PREL32:
case R_AARCH64_PREL64:
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
case R_AARCH64_TLSGD_ADD_LO12_NC:
case R_AARCH64_TLSDESC_CALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
static void reset_thunk(RangeExtensionThunk<E> &thunk) {
for (Symbol<E> *sym : thunk.symbols) {
sym->thunk_idx = -1;
sym->thunk_sym_idx = -1;
sym->flags &= (u8)~NEEDS_THUNK;
}
}
static bool is_reachable(Context<E> &ctx, Symbol<E> &sym,
InputSection<E> &isec, const ElfRel<E> &rel) {
// We always create a thunk for an absolute symbol conservatively
// because `shrink_sections` may increase a distance between a
// branch instruction and an absolute symbol. Branching to an
// absolute location is extremely rare in real code, though.
if (sym.is_absolute())
return false;
// Compute a distance between the relocated place and the symbol
// and check if they are within reach.
i64 S = sym.get_addr(ctx);
i64 A = rel.r_addend;
i64 P = isec.get_addr() + rel.r_offset;
i64 val = S + A - P;
return -(1 << 27) <= val && val < (1 << 27);
}
// We create a thunk no further than 100 MiB from any section.
constexpr i64 MAX_DISTANCE = 100 * 1024 * 1024;
// We create a thunk for each 10 MiB input sections.
static constexpr i64 GROUP_SIZE = 10 * 1024 * 1024;
static void create_thunks(Context<E> &ctx, OutputSection<E> &osec) {
std::span<InputSection<E> *> members = osec.members;
members[0]->offset = 0;
// Initialize input sections with very large dummy offsets so that
// sections that have got real offsets are separated from the ones
// without in the virtual address space.
tbb::parallel_for((i64)1, (i64)members.size(), [&](i64 i) {
members[i]->offset = 1 << 31;
});
// We create thunks from the beginning of the section to the end.
// We manage progress using four offsets which increase monotonically.
// The locations they point to are always A <= B <= C <= D.
i64 a = 0;
i64 b = 0;
i64 c = 0;
i64 d = 0;
i64 offset = 0;
while (b < members.size()) {
// Move D foward as far as we can jump from B to D.
while (d < members.size() && offset - members[b]->offset < MAX_DISTANCE) {
offset = align_to(offset, members[d]->shdr.sh_addralign);
members[d]->offset = offset;
offset += members[d]->shdr.sh_size;
d++;
}
// Move C forward so that C is apart from B by GROUP_SIZE.
while (c < members.size() &&
members[c]->offset - members[b]->offset < GROUP_SIZE)
c++;
// Move A forward so that A is reachable from C.
if (c > 0) {
i64 c_end = members[c - 1]->offset + members[c - 1]->shdr.sh_size;
while (a < osec.thunks.size() &&
osec.thunks[a]->offset < c_end - MAX_DISTANCE)
reset_thunk(*osec.thunks[a++]);
}
// Create a thunk for input sections between B and C and place it at D.
osec.thunks.emplace_back(new RangeExtensionThunk<E>{osec});
RangeExtensionThunk<E> &thunk = *osec.thunks.back();
thunk.thunk_idx = osec.thunks.size() - 1;
thunk.offset = offset;
// Scan relocations between B and C to collect symbols that need thunks.
tbb::parallel_for_each(&members[b], &members[c], [&](InputSection<E> *isec) {
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
isec->range_extn.resize(rels.size());
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type != R_AARCH64_CALL26 && rel.r_type != R_AARCH64_JUMP26)
continue;
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
// Skip if the destination is within reach.
if (is_reachable(ctx, sym, *isec, rel))
continue;
// If the symbol is already in another thunk, reuse it.
if (sym.thunk_idx != -1) {
isec->range_extn[i] = {sym.thunk_idx, sym.thunk_sym_idx};
continue;
}
// Otherwise, add the symbol to this thunk if it's not added already.
isec->range_extn[i] = {thunk.thunk_idx, -1};
if (!(sym.flags.fetch_or(NEEDS_THUNK) & NEEDS_THUNK)) {
std::scoped_lock lock(thunk.mu);
thunk.symbols.push_back(&sym);
}
}
});
// Now that we know the number of symbols in the thunk, we can compute
// its size.
offset += thunk.size();
// Sort symbols added to the thunk to make the output deterministic.
sort(thunk.symbols, [](Symbol<E> *a, Symbol<E> *b) { return *a < *b; });
// Assign offsets within the thunk to the symbols.
for (i64 i = 0; Symbol<E> *sym : thunk.symbols) {
sym->thunk_idx = thunk.thunk_idx;
sym->thunk_sym_idx = i++;
}
// Scan relocations again to fix symbol offsets in the last thunk.
tbb::parallel_for_each(&members[b], &members[c], [&](InputSection<E> *isec) {
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
RangeExtensionRef &ref = isec->range_extn[i];
if (ref.thunk_idx == thunk.thunk_idx) {
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
ref.sym_idx = sym.thunk_sym_idx;
}
}
});
// Move B forward to point to the begining of the next group.
b = c;
}
while (a < osec.thunks.size())
reset_thunk(*osec.thunks[a++]);
osec.shdr.sh_size = offset;
}
static void mark_thunk_symbols(Context<E> &ctx, OutputSection<E> &osec) {
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec.thunks) {
i64 sz = thunk->symbols.size();
thunk->symbol_map.resize(sz);
thunk->used.reset(new std::atomic_bool[sz]{});
}
// Mark referenced thunk symbols
tbb::parallel_for_each(osec.members, [&](InputSection<E> *isec) {
std::span<ElfRel<E>> rels = isec->get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
RangeExtensionRef &ref = isec->range_extn[i];
if (ref.thunk_idx == -1)
continue;
Symbol<E> &sym = *isec->file.symbols[rels[i].r_sym];
if (!is_reachable(ctx, sym, *isec, rels[i]))
osec.thunks[ref.thunk_idx]->used[ref.sym_idx] = true;
}
});
// Remove unreferenced thunk symbols
tbb::parallel_for_each(osec.thunks,
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
i64 i = 0;
for (i64 j = 0; j < thunk->symbols.size(); j++) {
if (thunk->used[j]) {
thunk->symbol_map[j] = i;
thunk->symbols[i] = thunk->symbols[j];
i++;
}
}
thunk->symbols.resize(i);
});
}
static void shrink_section(Context<E> &ctx, OutputSection<E> &osec) {
std::span<std::unique_ptr<RangeExtensionThunk<E>>> thunks = osec.thunks;
std::span<InputSection<E> *> members = osec.members;
i64 offset = 0;
auto add_thunk = [&]() {
thunks[0]->offset = offset;
offset += thunks[0]->size();
thunks = thunks.subspan(1);
};
auto add_isec = [&]() {
offset = align_to(offset, members[0]->shdr.sh_addralign);
members[0]->offset = offset;
offset += members[0]->shdr.sh_size;
members = members.subspan(1);
};
while (!thunks.empty() && !members.empty()) {
if (thunks[0]->offset < members[0]->offset)
add_thunk();
else
add_isec();
}
while (!thunks.empty())
add_thunk();
while (!members.empty())
add_isec();
assert(offset <= osec.shdr.sh_size);
osec.shdr.sh_size = offset;
}
// ARM64's call/jump instructions take 27 bits displacement, so they
// can refer only up to ±128 MiB. If a branch target is further than
// that, we need to let it branch to a linker-synthesized code
// sequence that construct a full 32 bit address in a register and
// jump there. That linker-synthesized code is called "thunk".
i64 create_range_extension_thunks(Context<E> &ctx) {
Timer t(ctx, "create_range_extension_thunks");
// First, we create thunks with a pessimistic assumption that all
// out-of-section relocations would need thunks. To do so, we start
// with an initial layout in which output sections are separated far
// apart.
for (i64 i = 0; Chunk<E> *chunk : ctx.chunks)
chunk->shdr.sh_addr = i++ << 31;
std::vector<OutputSection<E> *> sections;
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
if (!osec->members.empty() && (osec->shdr.sh_flags & SHF_EXECINSTR))
sections.push_back(osec.get());
for (OutputSection<E> *osec : sections)
create_thunks(ctx, *osec);
// Recompute file layout.
set_osec_offsets(ctx);
// Based on the current file layout, remove thunk symbols that turned
// to be unnecessary.
tbb::parallel_for_each(sections, [&](OutputSection<E> *osec) {
mark_thunk_symbols(ctx, *osec);
});
// Recompute output section sizes that contain thunks. New section
// sizes must be equal to or smaller than previous values, so all
// relocations that were previously reachable will still be reachable
// after this step.
for (OutputSection<E> *osec : sections)
shrink_section(ctx, *osec);
// Compute the final layout.
return set_osec_offsets(ctx);
}
void RangeExtensionThunk<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + output_section.shdr.sh_offset + offset;
static const u8 insn[] = {
0x10, 0x00, 0x00, 0x90, // adrp x16, 0 # R_AARCH64_ADR_PREL_PG_HI21
0x10, 0x02, 0x00, 0x91, // add x16, x16 # R_AARCH64_ADD_ABS_LO12_NC
0x00, 0x02, 0x1f, 0xd6, // br x16
};
static_assert(ENTRY_SIZE == sizeof(insn));
for (i64 i = 0; i < symbols.size(); i++) {
u64 S = symbols[i]->get_addr(ctx);
u64 P = output_section.shdr.sh_addr + offset + i * ENTRY_SIZE;
u8 *loc = buf + i * ENTRY_SIZE;
memcpy(loc , insn, sizeof(insn));
write_adr(loc, bits(page(S) - page(P), 32, 12));
*(u32 *)(loc + 4) |= bits(S, 11, 0) << 10;
}
}
void write_thunks(Context<E> &ctx) {
Timer t(ctx, "write_thunks");
tbb::parallel_for_each(ctx.output_sections,
[&](std::unique_ptr<OutputSection<E>> &osec) {
tbb::parallel_for_each(osec->thunks,
[&](std::unique_ptr<RangeExtensionThunk<E>> &thunk) {
thunk->copy_buf(ctx);
});
});
}
} // namespace mold::elf
+473
View File
@@ -0,0 +1,473 @@
#include "mold.h"
namespace mold::elf {
using E = I386;
template <>
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
u32 *buf = (u32 *)(ctx.buf + this->shdr.sh_offset);
// The first slot of .got.plt points to _DYNAMIC.
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
buf[1] = 0;
buf[2] = 0;
for (Symbol<E> *sym : ctx.plt->symbols)
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 6;
}
static void write_plt_header(Context<E> &ctx, u8 *buf) {
if (ctx.arg.pic) {
static const u8 plt0[] = {
0xff, 0xb3, 0x04, 0, 0, 0, // pushl 4(%ebx)
0xff, 0xa3, 0x08, 0, 0, 0, // jmp *8(%ebx)
0x90, 0x90, 0x90, 0x90, // nop
};
memcpy(buf, plt0, sizeof(plt0));
} else {
static const u8 plt0[] = {
0xff, 0x35, 0, 0, 0, 0, // pushl (GOTPLT+4)
0xff, 0x25, 0, 0, 0, 0, // jmp *(GOTPLT+8)
0x90, 0x90, 0x90, 0x90, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr + 8;
}
}
static void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym,
i64 idx) {
u8 *ent = buf + ctx.plt_hdr_size + sym.get_plt_idx(ctx) * ctx.plt_size;
if (ctx.arg.pic) {
static const u8 data[] = {
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0x68, 0, 0, 0, 0, // pushl $reloc_offset
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
};
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx) - ctx.gotplt->shdr.sh_addr;
} else {
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x68, 0, 0, 0, 0, // pushl $reloc_offset
0xe9, 0, 0, 0, 0, // jmp .PLT0@PC
};
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym.get_gotplt_addr(ctx);
}
*(u32 *)(ent + 7) = idx * sizeof(ElfRel<E>);
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym.get_plt_addr(ctx) - 16;
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
write_plt_header(ctx, buf);
for (i64 i = 0; i < symbols.size(); i++)
write_plt_entry(ctx, buf, *symbols[i], i);
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
if (ctx.arg.pic) {
static const u8 data[] = {
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
0x66, 0x90, // nop
};
for (i64 i = 0; i < symbols.size(); i++) {
u8 *ent = buf + i * sizeof(data);
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr;
}
} else {
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x66, 0x90, // nop
};
for (i64 i = 0; i < symbols.size(); i++) {
u8 *ent = buf + i * sizeof(data);
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = symbols[i]->get_got_addr(ctx);
}
}
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
u64 loc, u64 val) {
u8 *base = ctx.buf + this->shdr.sh_offset;
switch (rel.r_type) {
case R_386_NONE:
return;
case R_386_32:
*(u32 *)(base + loc) = val;
return;
case R_386_PC32:
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
return;
}
unreachable();
}
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_386_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
overflow_check(val, -(1 << 7), 1 << 7);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(u16 *)loc = val;
};
auto write16s = [&](u64 val) {
overflow_check(val, -(1 << 15), 1 << 15);
*(u16 *)loc = val;
};
auto write32 = [&](u64 val) {
*(u32 *)loc = val;
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? frag_ref->addend : this->get_addend(rel))
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
#define GOTPLT ctx.gotplt->shdr.sh_addr
if (needs_dynrel[i]) {
*dynrel++ = {P, R_386_32, (u32)sym.get_dynsym_idx(ctx)};
write32(A);
continue;
}
if (needs_baserel[i]) {
if (!is_relr_reloc(ctx, rel))
*dynrel++ = {P, R_386_RELATIVE, 0};
write32(S + A);
continue;
}
switch (rel.r_type) {
case R_386_8:
write8(S + A);
continue;
case R_386_16:
write16(S + A);
continue;
case R_386_32:
write32(S + A);
continue;
case R_386_PC8:
write8s(S + A);
continue;
case R_386_PC16:
write16s(S + A);
continue;
case R_386_PC32:
case R_386_PLT32:
write32(S + A - P);
continue;
case R_386_GOT32:
case R_386_GOT32X:
write32(sym.get_got_addr(ctx) + A - GOTPLT);
continue;
case R_386_GOTOFF:
write32(S + A - GOTPLT);
continue;
case R_386_GOTPC:
write32(GOTPLT + A - P);
continue;
case R_386_TLS_GOTIE:
write32(sym.get_gottp_addr(ctx) + A - GOTPLT);
continue;
case R_386_TLS_LE:
write32(S + A - ctx.tls_end);
continue;
case R_386_TLS_IE:
write32(sym.get_gottp_addr(ctx) + A);
continue;
case R_386_TLS_GD:
write32(sym.get_tlsgd_addr(ctx) + A - GOTPLT);
continue;
case R_386_TLS_LDM:
write32(ctx.got->get_tlsld_addr(ctx) + A - GOTPLT);
continue;
case R_386_TLS_LDO_32:
write32(S + A - ctx.tls_begin);
continue;
case R_386_SIZE32:
write32(sym.esym().st_size + A);
continue;
case R_386_TLS_GOTDESC:
if (sym.get_tlsdesc_idx(ctx) == -1) {
static const u8 insn[] = {
0x8d, 0x05, 0, 0, 0, 0, // lea 0, %eax
};
memcpy(loc - 2, insn, sizeof(insn));
write32(S + A - ctx.tls_end);
} else {
write32(sym.get_tlsdesc_addr(ctx) + A - GOTPLT);
}
continue;
case R_386_TLS_DESC_CALL:
if (ctx.arg.relax && !ctx.arg.shared) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
continue;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOTPLT
}
}
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_386_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
overflow_check(val, -(1 << 7), 1 << 7);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(u16 *)loc = val;
};
auto write16s = [&](u64 val) {
overflow_check(val, -(1 << 15), 1 << 15);
*(u16 *)loc = val;
};
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? addend : this->get_addend(rel))
#define G (sym.get_got_addr(ctx) - ctx.got->shdr.sh_addr)
#define GOTPLT ctx.gotplt->shdr.sh_addr
switch (rel.r_type) {
case R_386_8:
write8(S + A);
continue;
case R_386_16:
write16(S + A);
continue;
case R_386_32:
*(u32 *)loc = S + A;
continue;
case R_386_PC8:
write8s(S + A);
continue;
case R_386_PC16:
write16s(S + A);
continue;
case R_386_PC32:
*(u32 *)loc = S + A;
continue;
case R_386_GOTPC:
*(u32 *)loc = GOTPLT + A;
continue;
case R_386_GOTOFF:
*(u32 *)loc = S + A - GOTPLT;
continue;
case R_386_TLS_LDO_32:
*(u32 *)loc = S + A - ctx.tls_begin;
continue;
case R_386_SIZE32:
*(u32 *)loc = sym.esym().st_size + A;
continue;
default:
unreachable();
}
#undef S
#undef A
#undef GOTPLT
}
}
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr.sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_386_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
if (sym.get_type() == STT_GNU_IFUNC) {
sym.flags |= NEEDS_GOT;
sym.flags |= NEEDS_PLT;
}
switch (rel.r_type) {
case R_386_8:
case R_386_16: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // DSO
{ NONE, ERROR, ERROR, ERROR }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_386_32: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_386_PC8:
case R_386_PC16: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, ERROR }, // DSO
{ ERROR, NONE, COPYREL, PLT }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_386_PC32: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ BASEREL, NONE, DYNREL, DYNREL }, // DSO
{ BASEREL, NONE, COPYREL, PLT }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_386_GOT32:
case R_386_GOT32X:
case R_386_GOTPC:
sym.flags |= NEEDS_GOT;
break;
case R_386_PLT32:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_386_TLS_GOTIE:
case R_386_TLS_LE:
case R_386_TLS_IE:
sym.flags |= NEEDS_GOTTP;
break;
case R_386_TLS_GD:
sym.flags |= NEEDS_TLSGD;
break;
case R_386_TLS_LDM:
sym.flags |= NEEDS_TLSLD;
break;
case R_386_TLS_GOTDESC:
if (!ctx.arg.relax || ctx.arg.shared)
sym.flags |= NEEDS_TLSDESC;
break;
case R_386_GOTOFF:
case R_386_TLS_LDO_32:
case R_386_SIZE32:
case R_386_TLS_DESC_CALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold::elf
+749
View File
@@ -0,0 +1,749 @@
#include "mold.h"
namespace mold::elf {
using E = X86_64;
template <>
void GotPltSection<E>::copy_buf(Context<E> &ctx) {
u64 *buf = (u64 *)(ctx.buf + this->shdr.sh_offset);
// The first slot of .got.plt points to _DYNAMIC, as requested by
// the x86-64 psABI. The second and the third slots are reserved by
// the psABI.
buf[0] = ctx.dynamic ? ctx.dynamic->shdr.sh_addr : 0;
buf[1] = 0;
buf[2] = 0;
for (Symbol<E> *sym : ctx.plt->symbols) {
if (ctx.arg.z_ibtplt)
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 10;
else
buf[sym->get_gotplt_idx(ctx)] = sym->get_plt_addr(ctx) + 6;
}
}
// The compact PLT format is used when `-z now` is given. If the flag
// is given, all PLT symbols are resolved eagerly on startup, so we
// can omit code for lazy symbol resolution from PLT in that case.
static void write_compact_plt(Context<E> &ctx) {
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x66, 0x90, // nop
};
for (Symbol<E> *sym : ctx.plt->symbols) {
u8 *ent = buf + sym->get_plt_idx(ctx) * ctx.plt_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
}
}
// The IBTPLT is a security-enhanced version of the regular PLT.
// It uses Indirect Branch Tracking (IBT) feature which is part of
// Intel Control-Flow Enforcement (CET). IBTPLT is slightly larger
// than the regular PLT (24 bytes vs 16 bytes for each entry).
//
// Note that our IBTPLT instruction sequence is different from the one
// used in GNU ld. GNU's IBTPLT implementation uses two separate
// sections (.plt and .plt.sec) in which one PLT entry takes 32 bytes
// in total.
static void write_ibtplt(Context<E> &ctx) {
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
// Write PLT header
static const u8 plt0[] = {
0xff, 0x35, 0, 0, 0, 0, // push GOTPLT+8(%rip)
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
0x0f, 0x1f, 0x40, 0x00, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
// Write PLT entries
i64 relplt_idx = 0;
static const u8 data[] = {
0xf3, 0x0f, 0x1e, 0xfa, // endbr64
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
0x68, 0, 0, 0, 0, // push $index_in_relplt
0xf2, 0xe9, 0, 0, 0, 0, // jmp PLT[0]
0x0f, 0x1f, 0x00, // nop
};
for (Symbol<E> *sym : ctx.plt->symbols) {
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 6) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 10;
*(u32 *)(ent + 11) = relplt_idx++;
*(u32 *)(ent + 17) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 21;
}
}
// The regular PLT.
static void write_plt(Context<E> &ctx) {
u8 *buf = ctx.buf + ctx.plt->shdr.sh_offset;
// Write PLT header
static const u8 plt0[] = {
0xff, 0x35, 0, 0, 0, 0, // pushq GOTPLT+8(%rip)
0xff, 0x25, 0, 0, 0, 0, // jmp *GOTPLT+16(%rip)
0x0f, 0x1f, 0x40, 0x00, // nop
};
memcpy(buf, plt0, sizeof(plt0));
*(u32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 2;
*(u32 *)(buf + 8) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr + 4;
// Write PLT entries
i64 relplt_idx = 0;
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOTPLT
0x68, 0, 0, 0, 0, // push $index_in_relplt
0xe9, 0, 0, 0, 0, // jmp PLT[0]
};
for (Symbol<E> *sym : ctx.plt->symbols) {
u8 *ent = buf + ctx.plt_hdr_size + sym->get_plt_idx(ctx) * ctx.plt_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym->get_gotplt_addr(ctx) - sym->get_plt_addr(ctx) - 6;
*(u32 *)(ent + 7) = relplt_idx++;
*(u32 *)(ent + 12) = ctx.plt->shdr.sh_addr - sym->get_plt_addr(ctx) - 16;
}
}
template <>
void PltSection<E>::copy_buf(Context<E> &ctx) {
if (ctx.arg.z_now)
write_compact_plt(ctx);
else if (ctx.arg.z_ibtplt)
write_ibtplt(ctx);
else
write_plt(ctx);
}
template <>
void PltGotSection<E>::copy_buf(Context<E> &ctx) {
u8 *buf = ctx.buf + this->shdr.sh_offset;
static const u8 data[] = {
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
0x66, 0x90, // nop
};
for (Symbol<E> *sym : symbols) {
u8 *ent = buf + sym->get_pltgot_idx(ctx) * X86_64::pltgot_size;
memcpy(ent, data, sizeof(data));
*(u32 *)(ent + 2) = sym->get_got_addr(ctx) - sym->get_plt_addr(ctx) - 6;
}
}
template <>
void EhFrameSection<E>::apply_reloc(Context<E> &ctx, ElfRel<E> &rel,
u64 loc, u64 val) {
u8 *base = ctx.buf + this->shdr.sh_offset;
switch (rel.r_type) {
case R_X86_64_NONE:
return;
case R_X86_64_32:
*(u32 *)(base + loc) = val;
return;
case R_X86_64_64:
*(u64 *)(base + loc) = val;
return;
case R_X86_64_PC32:
*(u32 *)(base + loc) = val - this->shdr.sh_addr - loc;
return;
case R_X86_64_PC64:
*(u64 *)(base + loc) = val - this->shdr.sh_addr - loc;
return;
}
unreachable();
}
static u32 relax_gotpcrelx(u8 *loc) {
switch ((loc[0] << 8) | loc[1]) {
case 0xff15: return 0x90e8; // call *0(%rip) -> call 0
case 0xff25: return 0x90e9; // jmp *0(%rip) -> jmp 0
}
return 0;
}
static u32 relax_rex_gotpcrelx(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x488d05; // mov 0(%rip), %rax -> lea 0(%rip), %rax
case 0x488b0d: return 0x488d0d; // mov 0(%rip), %rcx -> lea 0(%rip), %rcx
case 0x488b15: return 0x488d15; // mov 0(%rip), %rdx -> lea 0(%rip), %rdx
case 0x488b1d: return 0x488d1d; // mov 0(%rip), %rbx -> lea 0(%rip), %rbx
case 0x488b25: return 0x488d25; // mov 0(%rip), %rsp -> lea 0(%rip), %rsp
case 0x488b2d: return 0x488d2d; // mov 0(%rip), %rbp -> lea 0(%rip), %rbp
case 0x488b35: return 0x488d35; // mov 0(%rip), %rsi -> lea 0(%rip), %rsi
case 0x488b3d: return 0x488d3d; // mov 0(%rip), %rdi -> lea 0(%rip), %rdi
case 0x4c8b05: return 0x4c8d05; // mov 0(%rip), %r8 -> lea 0(%rip), %r8
case 0x4c8b0d: return 0x4c8d0d; // mov 0(%rip), %r9 -> lea 0(%rip), %r9
case 0x4c8b15: return 0x4c8d15; // mov 0(%rip), %r10 -> lea 0(%rip), %r10
case 0x4c8b1d: return 0x4c8d1d; // mov 0(%rip), %r11 -> lea 0(%rip), %r11
case 0x4c8b25: return 0x4c8d25; // mov 0(%rip), %r12 -> lea 0(%rip), %r12
case 0x4c8b2d: return 0x4c8d2d; // mov 0(%rip), %r13 -> lea 0(%rip), %r13
case 0x4c8b35: return 0x4c8d35; // mov 0(%rip), %r14 -> lea 0(%rip), %r14
case 0x4c8b3d: return 0x4c8d3d; // mov 0(%rip), %r15 -> lea 0(%rip), %r15
}
return 0;
}
static u32 relax_gottpoff(u8 *loc) {
switch ((loc[0] << 16) | (loc[1] << 8) | loc[2]) {
case 0x488b05: return 0x48c7c0; // mov 0(%rip), %rax -> mov $0, %rax
case 0x488b0d: return 0x48c7c1; // mov 0(%rip), %rcx -> mov $0, %rcx
case 0x488b15: return 0x48c7c2; // mov 0(%rip), %rdx -> mov $0, %rdx
case 0x488b1d: return 0x48c7c3; // mov 0(%rip), %rbx -> mov $0, %rbx
case 0x488b25: return 0x48c7c4; // mov 0(%rip), %rsp -> mov $0, %rsp
case 0x488b2d: return 0x48c7c5; // mov 0(%rip), %rbp -> mov $0, %rbp
case 0x488b35: return 0x48c7c6; // mov 0(%rip), %rsi -> mov $0, %rsi
case 0x488b3d: return 0x48c7c7; // mov 0(%rip), %rdi -> mov $0, %rdi
case 0x4c8b05: return 0x49c7c0; // mov 0(%rip), %r8 -> mov $0, %r8
case 0x4c8b0d: return 0x49c7c1; // mov 0(%rip), %r9 -> mov $0, %r9
case 0x4c8b15: return 0x49c7c2; // mov 0(%rip), %r10 -> mov $0, %r10
case 0x4c8b1d: return 0x49c7c3; // mov 0(%rip), %r11 -> mov $0, %r11
case 0x4c8b25: return 0x49c7c4; // mov 0(%rip), %r12 -> mov $0, %r12
case 0x4c8b2d: return 0x49c7c5; // mov 0(%rip), %r13 -> mov $0, %r13
case 0x4c8b35: return 0x49c7c6; // mov 0(%rip), %r14 -> mov $0, %r14
case 0x4c8b3d: return 0x49c7c7; // mov 0(%rip), %r15 -> mov $0, %r15
}
return 0;
}
// Apply relocations to SHF_ALLOC sections (i.e. sections that are
// mapped to memory at runtime) based on the result of
// scan_relocations().
template <>
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
ElfRel<E> *dynrel = nullptr;
std::span<ElfRel<E>> rels = get_rels(ctx);
i64 frag_idx = 0;
if (ctx.reldyn)
dynrel = (ElfRel<E> *)(ctx.buf + ctx.reldyn->shdr.sh_offset +
file.reldyn_offset + this->reldyn_offset);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_X86_64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
const SectionFragmentRef<E> *frag_ref = nullptr;
if (rel_fragments && rel_fragments[frag_idx].idx == i)
frag_ref = &rel_fragments[frag_idx++];
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write8s = [&](u64 val) {
overflow_check(val, -(1 << 7), 1 << 7);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(u16 *)loc = val;
};
auto write16s = [&](u64 val) {
overflow_check(val, -(1 << 15), 1 << 15);
*(u16 *)loc = val;
};
auto write32 = [&](u64 val) {
overflow_check(val, 0, (i64)1 << 32);
*(u32 *)loc = val;
};
auto write32s = [&](u64 val) {
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)loc = val;
};
auto write64 = [&](u64 val) {
*(u64 *)loc = val;
};
#define S (frag_ref ? frag_ref->frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag_ref ? frag_ref->addend : rel.r_addend)
#define P (output_section->shdr.sh_addr + offset + rel.r_offset)
#define G (sym.get_got_addr(ctx) - ctx.gotplt->shdr.sh_addr)
#define GOT ctx.gotplt->shdr.sh_addr
if (needs_dynrel[i]) {
*dynrel++ = {P, R_X86_64_64, (u32)sym.get_dynsym_idx(ctx), A};
write64(A);
continue;
}
if (needs_baserel[i]) {
if (!is_relr_reloc(ctx, rel))
*dynrel++ = {P, R_X86_64_RELATIVE, 0, (i64)(S + A)};
write64(S + A);
continue;
}
switch (rel.r_type) {
case R_X86_64_8:
write8(S + A);
continue;
case R_X86_64_16:
write16(S + A);
continue;
case R_X86_64_32:
write32(S + A);
continue;
case R_X86_64_32S:
write32s(S + A);
continue;
case R_X86_64_64:
write64(S + A);
continue;
case R_X86_64_PC8:
write8s(S + A - P);
continue;
case R_X86_64_PC16:
write16s(S + A - P);
continue;
case R_X86_64_PC32:
write32s(S + A - P);
continue;
case R_X86_64_PC64:
write64(S + A - P);
continue;
case R_X86_64_PLT32:
write32s(S + A - P);
continue;
case R_X86_64_PLTOFF64:
write64(S + A - GOT);
break;
case R_X86_64_GOT32:
write32s(G + A);
continue;
case R_X86_64_GOT64:
write64(G + A);
continue;
case R_X86_64_GOTOFF64:
write64(S + A - GOT);
continue;
case R_X86_64_GOTPC32:
write32s(GOT + A - P);
continue;
case R_X86_64_GOTPC64:
write64(GOT + A - P);
continue;
case R_X86_64_GOTPCREL:
write32s(G + GOT + A - P);
continue;
case R_X86_64_GOTPCREL64:
write64(G + GOT + A - P);
continue;
case R_X86_64_GOTPCRELX:
if (sym.get_got_idx(ctx) == -1) {
u32 insn = relax_gotpcrelx(loc - 2);
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
} else {
write32s(G + GOT + A - P);
}
continue;
case R_X86_64_REX_GOTPCRELX:
if (sym.get_got_idx(ctx) == -1) {
u32 insn = relax_rex_gotpcrelx(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - P);
} else {
write32s(G + GOT + A - P);
}
continue;
case R_X86_64_TLSGD:
if (sym.get_tlsgd_idx(ctx) == -1) {
// Relax GD to LE
static const u8 insn[] = {
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea 0(%rax), %rax
};
memcpy(loc - 4, insn, sizeof(insn));
i64 val = S - ctx.tls_end + A + 4;
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)(loc + 8) = val;
i++;
} else {
write32s(sym.get_tlsgd_addr(ctx) + A - P);
}
continue;
case R_X86_64_TLSLD:
if (ctx.got->tlsld_idx == -1) {
// Relax LD to LE
static const u8 insn[] = {
0x66, 0x66, 0x66, // (padding)
0x64, 0x48, 0x8b, 0x04, 0x25, 0, 0, 0, 0, // mov %fs:0, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
i++;
} else {
write32s(ctx.got->get_tlsld_addr(ctx) + A - P);
}
continue;
case R_X86_64_DTPOFF32:
if (ctx.arg.relax && !ctx.arg.shared)
write32s(S + A - ctx.tls_end);
else
write32s(S + A - ctx.tls_begin);
continue;
case R_X86_64_DTPOFF64:
if (ctx.arg.relax && !ctx.arg.shared)
write64(S + A - ctx.tls_end);
else
write64(S + A - ctx.tls_begin);
continue;
case R_X86_64_TPOFF32:
write32s(S + A - ctx.tls_end);
continue;
case R_X86_64_TPOFF64:
write64(S + A - ctx.tls_end);
continue;
case R_X86_64_GOTTPOFF:
if (sym.get_gottp_idx(ctx) == -1) {
u32 insn = relax_gottpoff(loc - 3);
loc[-3] = insn >> 16;
loc[-2] = insn >> 8;
loc[-1] = insn;
write32s(S + A - ctx.tls_end + 4);
} else {
write32s(sym.get_gottp_addr(ctx) + A - P);
}
continue;
case R_X86_64_GOTPC32_TLSDESC:
if (sym.get_tlsdesc_idx(ctx) == -1) {
static const u8 insn[] = {
0x48, 0xc7, 0xc0, 0, 0, 0, 0, // mov $0, %rax
};
memcpy(loc - 3, insn, sizeof(insn));
write32s(S + A - ctx.tls_end + 4);
} else {
write32s(sym.get_tlsdesc_addr(ctx) + A - P);
}
continue;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
continue;
case R_X86_64_SIZE64:
write64(sym.esym().st_size + A);
continue;
case R_X86_64_TLSDESC_CALL:
if (sym.get_tlsdesc_idx(ctx) == -1) {
// call *(%rax) -> nop
loc[0] = 0x66;
loc[1] = 0x90;
}
continue;
default:
unreachable();
}
#undef S
#undef A
#undef P
#undef G
#undef GOT
}
}
// This function is responsible for applying relocations against
// non-SHF_ALLOC sections (i.e. sections that are not mapped to memory
// at runtime).
//
// Relocations against non-SHF_ALLOC sections are much easier to
// handle than that against SHF_ALLOC sections. It is because, since
// they are not mapped to memory, they don't contain any variable or
// function and never need PLT or GOT. Non-SHF_ALLOC sections are
// mostly debug info sections.
//
// Relocations against non-SHF_ALLOC sections are not scanned by
// scan_relocations.
template <>
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
std::span<ElfRel<E>> rels = get_rels(ctx);
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_X86_64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = base + rel.r_offset;
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
SectionFragment<E> *frag;
i64 addend;
std::tie(frag, addend) = get_fragment(ctx, rel);
auto overflow_check = [&](i64 val, i64 lo, i64 hi) {
if (val < lo || hi <= val)
Error(ctx) << *this << ": relocation " << rel << " against "
<< sym << " out of range: " << val << " is not in ["
<< lo << ", " << hi << ")";
};
auto write8 = [&](u64 val) {
overflow_check(val, 0, 1 << 8);
*loc = val;
};
auto write16 = [&](u64 val) {
overflow_check(val, 0, 1 << 16);
*(u16 *)loc = val;
};
auto write32 = [&](u64 val) {
overflow_check(val, 0, (i64)1 << 32);
*(u32 *)loc = val;
};
auto write32s = [&](u64 val) {
overflow_check(val, -((i64)1 << 31), (i64)1 << 31);
*(u32 *)loc = val;
};
#define S (frag ? frag->get_addr(ctx) : sym.get_addr(ctx))
#define A (frag ? addend : rel.r_addend)
switch (rel.r_type) {
case R_X86_64_8:
write8(S + A);
break;
case R_X86_64_16:
write16(S + A);
break;
case R_X86_64_32:
write32(S + A);
break;
case R_X86_64_32S:
write32s(S + A);
break;
case R_X86_64_64:
*(u64 *)loc = S + A;
break;
case R_X86_64_DTPOFF32:
write32s(S + A - ctx.tls_begin);
break;
case R_X86_64_DTPOFF64:
*(u64 *)loc = S + A - ctx.tls_begin;
break;
case R_X86_64_SIZE32:
write32(sym.esym().st_size + A);
break;
case R_X86_64_SIZE64:
*(u64 *)loc = sym.esym().st_size + A;
break;
default:
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
<< rel;
break;
}
#undef S
#undef A
}
}
// Linker has to create data structures in an output file to apply
// some type of relocations. For example, if a relocation refers a GOT
// or a PLT entry of a symbol, linker has to create an entry in .got
// or in .plt for that symbol. In order to fix the file layout, we
// need to scan relocations.
template <>
void InputSection<E>::scan_relocations(Context<E> &ctx) {
assert(shdr.sh_flags & SHF_ALLOC);
this->reldyn_offset = file.num_dynrel * sizeof(ElfRel<E>);
std::span<ElfRel<E>> rels = get_rels(ctx);
// Scan relocations
for (i64 i = 0; i < rels.size(); i++) {
const ElfRel<E> &rel = rels[i];
if (rel.r_type == R_X86_64_NONE)
continue;
Symbol<E> &sym = *file.symbols[rel.r_sym];
u8 *loc = (u8 *)(contents.data() + rel.r_offset);
if (!sym.file) {
report_undef(ctx, sym);
continue;
}
if (sym.get_type() == STT_GNU_IFUNC) {
sym.flags |= NEEDS_GOT;
sym.flags |= NEEDS_PLT;
}
switch (rel.r_type) {
case R_X86_64_8:
case R_X86_64_16:
case R_X86_64_32:
case R_X86_64_32S: {
// Dynamic linker does not support 8, 16 or 32-bit dynamic
// relocations for these types of relocations. We report an
// error if we cannot relocate them even at load-time.
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, ERROR, ERROR, ERROR }, // DSO
{ NONE, ERROR, ERROR, ERROR }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_X86_64_64: {
// Unlike the above, we can use R_X86_64_RELATIVE and R_86_64_64
// relocations.
Action table[][4] = {
// Absolute Local Imported data Imported code
{ NONE, BASEREL, DYNREL, DYNREL }, // DSO
{ NONE, BASEREL, DYNREL, DYNREL }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_X86_64_PC8:
case R_X86_64_PC16:
case R_X86_64_PC32: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ ERROR, NONE, ERROR, ERROR }, // DSO
{ ERROR, NONE, COPYREL, PLT }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_X86_64_PC64: {
Action table[][4] = {
// Absolute Local Imported data Imported code
{ BASEREL, NONE, DYNREL, DYNREL }, // DSO
{ BASEREL, NONE, COPYREL, PLT }, // PIE
{ NONE, NONE, COPYREL, PLT }, // PDE
};
dispatch(ctx, table, i, rel, sym);
break;
}
case R_X86_64_GOT32:
case R_X86_64_GOT64:
case R_X86_64_GOTPC32:
case R_X86_64_GOTPC64:
case R_X86_64_GOTPCREL:
case R_X86_64_GOTPCREL64:
sym.flags |= NEEDS_GOT;
break;
case R_X86_64_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_gotpcrelx(loc - 2);
if (!do_relax)
sym.flags |= NEEDS_GOT;
break;
}
case R_X86_64_REX_GOTPCRELX: {
if (rel.r_addend != -4)
Fatal(ctx) << *this << ": bad r_addend for R_X86_64_REX_GOTPCRELX";
bool do_relax = ctx.arg.relax && !sym.is_imported &&
sym.is_relative() && relax_rex_gotpcrelx(loc - 3);
if (!do_relax)
sym.flags |= NEEDS_GOT;
break;
}
case R_X86_64_PLT32:
case R_X86_64_PLTOFF64:
if (sym.is_imported)
sym.flags |= NEEDS_PLT;
break;
case R_X86_64_TLSGD:
if (i + 1 == rels.size())
Fatal(ctx) << *this
<< ": TLSGD reloc must be followed by PLT32 or GOTPCREL";
if (ctx.arg.relax && !ctx.arg.shared && !sym.is_imported)
i++;
else
sym.flags |= NEEDS_TLSGD;
break;
case R_X86_64_TLSLD:
if (i + 1 == rels.size())
Fatal(ctx) << *this
<< ": TLSLD reloc must be followed by PLT32 or GOTPCREL";
if (ctx.arg.relax && !ctx.arg.shared)
i++;
else
sym.flags |= NEEDS_TLSLD;
break;
case R_X86_64_GOTTPOFF: {
ctx.has_gottp_rel = true;
bool do_relax = ctx.arg.relax && !ctx.arg.shared &&
!sym.is_imported && relax_gottpoff(loc - 3);
if (!do_relax)
sym.flags |= NEEDS_GOTTP;
break;
}
case R_X86_64_GOTPC32_TLSDESC: {
if (memcmp(loc - 3, "\x48\x8d\x05", 3))
Fatal(ctx) << *this << ": GOTPC32_TLSDESC relocation is used"
<< " against an invalid code sequence";
bool do_relax = ctx.relax_tlsdesc && !sym.is_imported;
if (!do_relax)
sym.flags |= NEEDS_TLSDESC;
break;
}
case R_X86_64_GOTOFF64:
case R_X86_64_DTPOFF32:
case R_X86_64_DTPOFF64:
case R_X86_64_TPOFF32:
case R_X86_64_TPOFF64:
case R_X86_64_SIZE32:
case R_X86_64_SIZE64:
case R_X86_64_TLSDESC_CALL:
break;
default:
Error(ctx) << *this << ": unknown relocation: " << rel;
}
}
}
} // namespace mold::elf
+973
View File
@@ -0,0 +1,973 @@
#include "mold.h"
#include <sstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <unordered_set>
namespace mold::elf {
static const char helpmsg[] = R"(
Options:
--help Report usage information
-v, --version Report version information
-V Report version and target information
-(, --start-group Ignored
-), --end-group Ignored
-C DIR, --directory DIR Change to DIR before doing anything
-E, --export-dynamic Put symbols in the dynamic symbol table
--no-export-dynamic
-F LIBNAME, --filter LIBNAME
Set DT_FILTER to the specified value
-I FILE, --dynamic-linker FILE
Set dynamic linker path
--no-dynamic-linker
-L DIR, --library-path DIR Add DIR to library search path
-M, --print-map Write map file to stdout
-N, --omagic Do not page align data, do not make text readonly
--no-omagic
-O NUMBER Ignored
-S, --strip-debug Strip .debug_* sections
-T FILE, --script FILE Read linker script
-X, --discard-locals Discard temporary local symbols
-e SYMBOL, --entry SYMBOL Set program entry point
-f SHLIB, --auxiliary SHLIB Set DT_AUXILIARY to the specified value
-h LIBNAME, --soname LIBNAME
Set shared library name
-l LIBNAME Search for a given library
-m TARGET Set target
-o FILE, --output FILE Set output filename
-r, --relocatable Generate relocatable output
-s, --strip-all Strip .symtab section
-u SYMBOL, --undefined SYMBOL
Force to resolve SYMBOL
--Bdynamic Link against shared libraries (default)
--Bstatic Do not link against shared libraries
--Bsymbolic Bind global symbols locally
--Bsymbolic-functions Bind global functions locally
--Bno-symbolic Cancel --Bsymbolic and --Bsymbolic-functions
--Map FILE Write map file to a given file
--allow-multiple-definition Allow multiple definitions
--as-needed Only set DT_NEEDED if used
--no-as-needed
--build-id [none,md5,sha1,sha256,uuid,HEXSTRING]
Generate build ID
--no-build-id
--chroot DIR Set a given path to root directory
--color-diagnostics=[auto,always,never]
Use colors in diagnostics
--color-diagnostics Alias for --color-diagnostics=always
--compress-debug-sections [none,zlib,zlib-gabi,zlib-gnu]
Compress .debug_* sections
--dc Ignored
--defsym=SYMBOL=VALUE Define a symbol alias
--demangle Demangle C++ symbols in log messages (default)
--no-demangle
--disable-new-dtags Ignored
--dp Ignored
--dynamic-list Read a list of dynamic symbols
--eh-frame-hdr Create .eh_frame_hdr section
--no-eh-frame-hdr
--enable-new-dtags Ignored
--exclude-libs LIB,LIB,.. Mark all symbols in given libraries hidden
--fatal-warnings Ignored
--no-fatal-warnings Ignored
--fini SYMBOL Call SYMBOL at unload-time
--fork Spawn a child process (default)
--no-fork
--gc-sections Remove unreferenced sections
--no-gc-sections
--gdb-index Ignored
--hash-style [sysv,gnu,both]
Set hash style
--icf Fold identical code
--no-icf
--image-base ADDR Set the base address to a given value
--init SYMBOL Call SYMBOl at load-time
--no-undefined Report undefined symbols (even with --shared)
--pack-dyn-relocs=[relr,none]
Pack dynamic relocations
--perf Print performance statistics
--pie, --pic-executable Create a position independent executable
--no-pie, --no-pic-executable
--plugin Ignored
--plugin-opt Ignored
--pop-state Pop state of flags governing input file handling
--preload
--no-preload
--print-gc-sections Print removed unreferenced sections
--no-print-gc-sections
--print-icf-sections Print folded identical sections
--no-print-icf-sections
--push-state Pop state of flags governing input file handling
--quick-exit Use quick_exit to exit (default)
--no-quick-exit
--relax Optimize instructions (default)
--no-relax
--repro Embed input files to .repro section
--require-defined SYMBOL Require SYMBOL be defined in the final output
--retain-symbols-file FILE Keep only symbols listed in FILE
--rpath DIR Add DIR to runtime search path
--rpath-link DIR Ignored
--run COMMAND ARG... Run COMMAND with mold as /usr/bin/ld
--shared, --Bshareable Create a share library
--sort-common Ignored
--sort-section Ignored
--spare-dynamic-tags NUMBER Reserve give number of tags in .dynamic section
--start-lib Give following object files in-archive-file semantics
--end-lib End the effect of --start-lib
--static Do not link against shared libraries
--stats Print input statistics
--sysroot DIR Set target system root directory
--thread-count COUNT, --threads=COUNT
Use COUNT number of threads
--threads Use multiple threads (default)
--no-threads
--trace Print name of each input file
--unique PATTERN Don't merge input sections that match a given pattern
--unresolved-symbols [report-all,ignore-all,ignore-in-object-files,ignore-in-shared-libs]
How to handle unresolved symbols
--version-script FILE Read version script
--warn-common Warn about common symbols
--no-warn-common
--warn-unresolved-symbols Report unresolved symbols as warnings
--error-unresolved-symbols
Report unresolved symbols as errors (default)
--whole-archive Include all objects from static archives
--no-whole-archive
--wrap SYMBOL Use wrapper function for a given symbol
-z defs Report undefined symbols (even with --shared)
-z nodefs
-z common-page-size=VALUE Ignored
-z execstack Require executable stack
-z noexecstack
-z initfirst Mark DSO to be initialized first at runtime
-z interpose Mark object to interpose all DSOs but executable
-z keep-text-section-prefix Keep .text.{hot,unknown,unlikely,startup,exit} as separate sections in the final binary
-z nokeep-text-section-prefix
-z lazy Enable lazy function resolution (default)
-z max-page-size=VALUE Use VALUE as the memory page size
-z nocopyreloc Do not create copy relocations
-z nodefaultlib Make the dynamic loader to ignore default search paths
-z nodelete Mark DSO non-deletable at runtime
-z nodlopen Mark DSO not available to dlopen
-z nodump Mark DSO not available to dldump
-z now Disable lazy function resolution
-z origin Mark object requiring immediate $ORIGIN processing at runtime
-z separate-loadable-segments
Separate all loadable segments to different pages
-z separate-code Separate code and data into different pages
-z noseparate-code Allow overlap in pages
-z relro Make some sections read-only after relocation (default)
-z norelro
-z text Report error if DT_TEXTREL is set
-z notext
-z textoff
mold: supported targets: elf32-i386 elf64-x86-64 elf64-littleaarch64
mold: supported emulations: elf_i386 elf_x86_64 aarch64linux aarch64elf)";
static std::vector<std::string> add_dashes(std::string name) {
// Multi-letter linker options can be preceded by either a single
// dash or double dashes except ones starting with "o", which must
// be preceded by double dashes. For example, "-omagic" is
// interpreted as "-o magic". If you really want to specify the
// "omagic" option, you have to pass "--omagic".
if (name[0] == 'o')
return {"--" + name};
return {"-" + name, "--" + name};
}
template <typename E>
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args,
std::string_view &arg, std::string name) {
if (name.size() == 1) {
if (args[0] == "-" + name) {
if (args.size() == 1)
Fatal(ctx) << "option -" << name << ": argument missing";
arg = args[1];
args = args.subspan(2);
return true;
}
if (args[0].starts_with("-" + name)) {
arg = args[0].substr(name.size() + 1);
args = args.subspan(1);
return true;
}
return false;
}
for (std::string opt : add_dashes(name)) {
if (args[0] == opt) {
if (args.size() == 1)
Fatal(ctx) << "option -" << name << ": argument missing";
arg = args[1];
args = args.subspan(2);
return true;
}
if (args[0].starts_with(opt + "=")) {
arg = args[0].substr(opt.size() + 1);
args = args.subspan(1);
return true;
}
}
return false;
}
bool read_flag(std::span<std::string_view> &args, std::string name) {
for (std::string opt : add_dashes(name)) {
if (args[0] == opt) {
args = args.subspan(1);
return true;
}
}
return false;
}
static bool read_z_flag(std::span<std::string_view> &args, std::string name) {
if (args.size() >= 2 && args[0] == "-z" && args[1] == name) {
args = args.subspan(2);
return true;
}
if (!args.empty() && args[0] == "-z" + name) {
args = args.subspan(1);
return true;
}
return false;
}
template <typename E>
bool read_z_arg(Context<E> &ctx, std::span<std::string_view> &args,
std::string_view &arg, std::string name) {
if (args.size() >= 2 && args[0] == "-z" && args[1].starts_with(name + "=")) {
arg = args[1].substr(name.size() + 1);
args = args.subspan(2);
return true;
}
if (!args.empty() && args[0].starts_with("-z" + name + "=")) {
arg = args[0].substr(name.size() + 3);
args = args.subspan(1);
return true;
}
return false;
}
template <typename E>
std::string create_response_file(Context<E> &ctx) {
std::string buf;
std::stringstream out;
std::string cwd = std::filesystem::current_path();
out << "-C " << cwd.substr(1) << "\n";
if (cwd != "/") {
out << "--chroot ..";
i64 depth = std::count(cwd.begin(), cwd.end(), '/');
for (i64 i = 1; i < depth; i++)
out << "/..";
out << "\n";
}
for (i64 i = 1; i < ctx.cmdline_args.size(); i++) {
std::string_view arg = ctx.cmdline_args[i];
if (arg == "-repro" || arg == "--repro") {
i++;
continue;
}
out << arg << "\n";
}
return out.str();
}
template <typename E>
static i64 parse_hex(Context<E> &ctx, std::string opt, std::string_view value) {
if (!value.starts_with("0x") && !value.starts_with("0X"))
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
value = value.substr(2);
if (value.find_first_not_of("0123456789abcdefABCDEF") != value.npos)
Fatal(ctx) << "option -" << opt << ": not a hexadecimal number";
return std::stol(std::string(value), nullptr, 16);
}
template <typename E>
static i64 parse_number(Context<E> &ctx, std::string opt,
std::string_view value) {
size_t nread;
i64 ret = std::stol(std::string(value), &nread, 0);
if (value.size() != nread)
Fatal(ctx) << "option -" << opt << ": not a number: " << value;
return ret;
}
template <typename E>
static std::vector<u8> parse_hex_build_id(Context<E> &ctx,
std::string_view arg) {
assert(arg.starts_with("0x") || arg.starts_with("0X"));
if (arg.size() % 2)
Fatal(ctx) << "invalid build-id: " << arg;
if (arg.substr(2).find_first_not_of("0123456789abcdefABCDEF") != arg.npos)
Fatal(ctx) << "invalid build-id: " << arg;
arg = arg.substr(2);
auto fn = [](char c) {
if ('0' <= c && c <= '9')
return c - '0';
if ('a' <= c && c <= 'f')
return c - 'a' + 10;
assert('A' <= c && c <= 'F');
return c - 'A' + 10;
};
std::vector<u8> vec(arg.size() / 2);
for (i64 i = 0; i < vec.size(); i++)
vec[i] = (fn(arg[i * 2]) << 4) | fn(arg[i * 2 + 1]);
return vec;
}
static std::vector<std::string_view>
split_by_comma_or_colon(std::string_view str) {
std::vector<std::string_view> vec;
for (;;) {
i64 pos = str.find_first_of(",:");
if (pos == str.npos) {
vec.push_back(str);
break;
}
vec.push_back(str.substr(0, pos));
str = str.substr(pos);
}
return vec;
}
static std::string_view trim(std::string_view str) {
size_t pos = str.find_first_not_of(" \t");
if (pos == str.npos)
return "";
str = str.substr(pos);
pos = str.find_last_not_of(" \t");
if (pos == str.npos)
return str;
return str.substr(0, pos + 1);
}
template <typename E>
static void read_retain_symbols_file(Context<E> &ctx, std::string_view path) {
MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::must_open(ctx, std::string(path));
std::string_view data((char *)mf->data, mf->size);
ctx.arg.retain_symbols_file.reset(new std::unordered_set<std::string_view>);
while (!data.empty()) {
size_t pos = data.find('\n');
std::string_view name;
if (pos == data.npos) {
name = data;
data = "";
} else {
name = data.substr(0, pos);
data = data.substr(pos + 1);
}
name = trim(name);
if (!name.empty())
ctx.arg.retain_symbols_file->insert(name);
}
}
static bool is_file(std::string_view path) {
struct stat st;
return stat(std::string(path).c_str(), &st) == 0 &&
(st.st_mode & S_IFMT) != S_IFDIR;
}
// Returns a PLT header size and a PLT entry size.
template <typename E>
static std::pair<i64, i64> get_plt_size(Context<E> &ctx) {
switch (E::e_machine) {
case EM_X86_64:
if (ctx.arg.z_now)
return {0, 8};
if (ctx.arg.z_ibtplt)
return {16, 24};
return {16, 16};
case EM_386:
return {16, 16};
case EM_AARCH64:
return {32, 16};
}
unreachable();
}
template <typename E>
void parse_nonpositional_args(Context<E> &ctx,
std::vector<std::string_view> &remaining) {
std::span<std::string_view> args = ctx.cmdline_args;
args = args.subspan(1);
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
ctx.page_size = E::page_size;
bool version_shown = false;
while (!args.empty()) {
std::string_view arg;
if (read_flag(args, "help")) {
SyncOut(ctx) << "Usage: " << ctx.cmdline_args[0]
<< " [options] file...\n" << helpmsg;
exit(0);
}
if (read_arg(ctx, args, arg, "o") || read_arg(ctx, args, arg, "output")) {
ctx.arg.output = arg;
} else if (read_arg(ctx, args, arg, "dynamic-linker") ||
read_arg(ctx, args, arg, "I")) {
ctx.arg.dynamic_linker = arg;
} else if (read_flag(args, "no-dynamic-linker")) {
ctx.arg.dynamic_linker = "";
} else if (read_flag(args, "v")) {
SyncOut(ctx) << mold_version;
version_shown = true;
} else if (read_flag(args, "version")) {
SyncOut(ctx) << mold_version;
exit(0);
} else if (read_flag(args, "V")) {
SyncOut(ctx) << mold_version
<< "\n Supported emulations:\n elf_x86_64\n elf_i386";
version_shown = true;
} else if (read_arg(ctx, args, arg, "m")) {
if (arg == "elf_x86_64") {
ctx.arg.emulation = EM_X86_64;
} else if (arg == "elf_i386") {
ctx.arg.emulation = EM_386;
} else if (arg == "aarch64linux") {
ctx.arg.emulation = EM_AARCH64;
} else {
Fatal(ctx) << "unknown -m argument: " << arg;
}
} else if (read_flag(args, "end-lib")) {
remaining.push_back("-end-lib");
} else if (read_flag(args, "export-dynamic") || read_flag(args, "E")) {
ctx.arg.export_dynamic = true;
} else if (read_flag(args, "no-export-dynamic")) {
ctx.arg.export_dynamic = false;
} else if (read_flag(args, "Bsymbolic")) {
ctx.arg.Bsymbolic = true;
} else if (read_flag(args, "Bsymbolic-functions")) {
ctx.arg.Bsymbolic_functions = true;
} else if (read_flag(args, "Bno-symbolic")) {
ctx.arg.Bsymbolic = false;
ctx.arg.Bsymbolic_functions = false;
} else if (read_arg(ctx, args, arg, "exclude-libs")) {
append(ctx.arg.exclude_libs, split_by_comma_or_colon(arg));
} else if (read_arg(ctx, args, arg, "e") ||
read_arg(ctx, args, arg, "entry")) {
ctx.arg.entry = arg;
} else if (read_arg(ctx, args, arg, "Map")) {
ctx.arg.Map = arg;
ctx.arg.print_map = true;
} else if (read_flag(args, "print-map") || read_flag(args, "M")) {
ctx.arg.print_map = true;
} else if (read_flag(args, "static") || read_flag(args, "Bstatic")) {
ctx.arg.is_static = true;
remaining.push_back("-Bstatic");
} else if (read_flag(args, "Bdynamic")) {
ctx.arg.is_static = false;
remaining.push_back("-Bdynamic");
} else if (read_flag(args, "shared") || read_flag(args, "Bshareable")) {
ctx.arg.shared = true;
} else if (read_arg(ctx, args, arg, "spare-dynamic-tags")) {
ctx.arg.spare_dynamic_tags = parse_number(ctx, "spare-dynamic-tags", arg);
} else if (read_flag(args, "start-lib")) {
remaining.push_back("-start-lib");
} else if (read_arg(ctx, args, arg, "defsym")) {
size_t pos = arg.find('=');
if (pos == arg.npos || pos == arg.size() - 1)
Fatal(ctx) << "-defsym: syntax error: " << arg;
ctx.arg.defsyms.push_back({arg.substr(0, pos), arg.substr(pos + 1)});
} else if (read_flag(args, "demangle")) {
ctx.arg.demangle = true;
} else if (read_flag(args, "no-demangle")) {
ctx.arg.demangle = false;
} else if (read_flag(args, "default-symver")) {
ctx.arg.default_symver = true;
} else if (read_arg(ctx, args, arg, "y") ||
read_arg(ctx, args, arg, "trace-symbol")) {
ctx.arg.trace_symbol.push_back(arg);
} else if (read_arg(ctx, args, arg, "filler")) {
ctx.arg.filler = parse_hex(ctx, "filler", arg);
} else if (read_arg(ctx, args, arg, "L") ||
read_arg(ctx, args, arg, "library-path")) {
ctx.arg.library_paths.push_back(std::string(arg));
} else if (read_arg(ctx, args, arg, "sysroot")) {
ctx.arg.sysroot = arg;
} else if (read_arg(ctx, args, arg, "unique")) {
std::optional<GlobPattern> pat = GlobPattern::compile(arg);
if (!pat)
Fatal(ctx) << "-unique: invalid glob pattern: " << arg;
ctx.arg.unique = std::move(*pat);
} else if (read_arg(ctx, args, arg, "unresolved-symbols")) {
if (arg == "report-all" || arg == "ignore-in-shared-libs")
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
else if (arg == "ignore-all" || arg == "ignore-in-object-files")
ctx.arg.unresolved_symbols = UNRESOLVED_IGNORE;
else
Fatal(ctx) << "unknown --unresolved-symbols argument: " << arg;
} else if (read_arg(ctx, args, arg, "u") ||
read_arg(ctx, args, arg, "undefined")) {
ctx.arg.undefined.push_back(arg);
} else if (read_arg(ctx, args, arg, "require-defined")) {
ctx.arg.require_defined.push_back(arg);
} else if (read_arg(ctx, args, arg, "init")) {
ctx.arg.init = arg;
} else if (read_arg(ctx, args, arg, "fini")) {
ctx.arg.fini = arg;
} else if (read_arg(ctx, args, arg, "hash-style")) {
if (arg == "sysv") {
ctx.arg.hash_style_sysv = true;
ctx.arg.hash_style_gnu = false;
} else if (arg == "gnu") {
ctx.arg.hash_style_sysv = false;
ctx.arg.hash_style_gnu = true;
} else if (arg == "both") {
ctx.arg.hash_style_sysv = true;
ctx.arg.hash_style_gnu = true;
} else {
Fatal(ctx) << "invalid --hash-style argument: " << arg;
}
} else if (read_arg(ctx, args, arg, "soname") ||
read_arg(ctx, args, arg, "h")) {
ctx.arg.soname = arg;
} else if (read_flag(args, "allow-multiple-definition")) {
ctx.arg.allow_multiple_definition = true;
} else if (read_flag(args, "trace")) {
ctx.arg.trace = true;
} else if (read_flag(args, "eh-frame-hdr")) {
ctx.arg.eh_frame_hdr = true;
} else if (read_flag(args, "no-eh-frame-hdr")) {
ctx.arg.eh_frame_hdr = false;
} else if (read_flag(args, "pie") || read_flag(args, "pic-executable")) {
ctx.arg.pic = true;
ctx.arg.pie = true;
} else if (read_flag(args, "no-pie") ||
read_flag(args, "no-pic-executable")) {
ctx.arg.pic = false;
ctx.arg.pie = false;
} else if (read_flag(args, "relax")) {
ctx.arg.relax = true;
} else if (read_flag(args, "no-relax")) {
ctx.arg.relax = false;
} else if (read_flag(args, "r") || read_flag(args, "relocatable")) {
ctx.arg.relocatable = true;
} else if (read_flag(args, "perf")) {
ctx.arg.perf = true;
} else if (read_flag(args, "pack-dyn-relocs=relr")) {
ctx.arg.pack_dyn_relocs_relr = true;
} else if (read_flag(args, "pack-dyn-relocs=none")) {
ctx.arg.pack_dyn_relocs_relr = false;
} else if (read_flag(args, "stats")) {
ctx.arg.stats = true;
Counter::enabled = true;
} else if (read_arg(ctx, args, arg, "C") ||
read_arg(ctx, args, arg, "directory")) {
ctx.arg.directory = arg;
} else if (read_arg(ctx, args, arg, "chroot")) {
ctx.arg.chroot = arg;
} else if (args[0] == "-color-diagnostics=auto" ||
args[0] == "--color-diagnostics=auto") {
ctx.arg.color_diagnostics = isatty(STDERR_FILENO);
args = args.subspan(1);
} else if (args[0] == "-color-diagnostics=always" ||
args[0] == "--color-diagnostics=always") {
ctx.arg.color_diagnostics = true;
args = args.subspan(1);
} else if (args[0] == "-color-diagnostics=never" ||
args[0] == "--color-diagnostics=never") {
ctx.arg.color_diagnostics = false;
args = args.subspan(1);
} else if (read_flag(args, "color-diagnostics")) {
ctx.arg.color_diagnostics = true;
} else if (read_flag(args, "warn-common")) {
ctx.arg.warn_common = true;
} else if (read_flag(args, "no-warn-common")) {
ctx.arg.warn_common = false;
} else if (read_arg(ctx, args, arg, "compress-debug-sections")) {
if (arg == "zlib" || arg == "zlib-gabi")
ctx.arg.compress_debug_sections = COMPRESS_GABI;
else if (arg == "zlib-gnu")
ctx.arg.compress_debug_sections = COMPRESS_GNU;
else if (arg == "none")
ctx.arg.compress_debug_sections = COMPRESS_NONE;
else
Fatal(ctx) << "invalid --compress-debug-sections argument: " << arg;
} else if (read_arg(ctx, args, arg, "wrap")) {
ctx.arg.wrap.insert(arg);
} else if (read_flag(args, "omagic") || read_flag(args, "N")) {
ctx.arg.omagic = true;
ctx.arg.is_static = true;
} else if (read_flag(args, "no-omagic")) {
ctx.arg.omagic = false;
} else if (read_arg(ctx, args, arg, "retain-symbols-file")) {
read_retain_symbols_file(ctx, arg);
} else if (read_flag(args, "repro")) {
ctx.arg.repro = true;
} else if (read_z_flag(args, "now")) {
ctx.arg.z_now = true;
} else if (read_z_flag(args, "lazy")) {
ctx.arg.z_now = false;
} else if (read_z_flag(args, "cet-report=none")) {
ctx.arg.z_cet_report = CET_REPORT_NONE;
} else if (read_z_flag(args, "cet-report=warning")) {
ctx.arg.z_cet_report = CET_REPORT_WARNING;
} else if (read_z_flag(args, "cet-report=error")) {
ctx.arg.z_cet_report = CET_REPORT_ERROR;
} else if (read_z_flag(args, "execstack")) {
ctx.arg.z_execstack = true;
} else if (read_z_arg(ctx, args, arg, "max-page-size")) {
ctx.page_size = parse_number(ctx, "-z max-page-size", arg);
if (__builtin_popcountll(ctx.page_size) != 1)
Fatal(ctx) << "-z max-page-size " << arg << ": value must be a power of 2";
} else if (read_z_flag(args, "noexecstack")) {
ctx.arg.z_execstack = false;
} else if (read_z_flag(args, "relro")) {
ctx.arg.z_relro = true;
} else if (read_z_flag(args, "norelro")) {
ctx.arg.z_relro = false;
} else if (read_z_flag(args, "defs")) {
ctx.arg.z_defs = true;
} else if (read_z_flag(args, "nodefs")) {
ctx.arg.z_defs = false;
} else if (read_z_flag(args, "nodlopen")) {
ctx.arg.z_dlopen = false;
} else if (read_z_flag(args, "nodelete")) {
ctx.arg.z_delete = false;
} else if (read_z_flag(args, "nocopyreloc")) {
ctx.arg.z_copyreloc = false;
} else if (read_z_flag(args, "nodump")) {
ctx.arg.z_dump = false;
} else if (read_z_flag(args, "initfirst")) {
ctx.arg.z_initfirst = true;
} else if (read_z_flag(args, "interpose")) {
ctx.arg.z_interpose = true;
} else if (read_z_flag(args, "ibt")) {
ctx.arg.z_ibt = true;
ctx.arg.z_ibtplt = true;
} else if (read_z_flag(args, "ibtplt")) {
ctx.arg.z_ibtplt = true;
} else if (read_z_flag(args, "muldefs")) {
ctx.arg.allow_multiple_definition = true;
} else if (read_z_flag(args, "keep-text-section-prefix")) {
ctx.arg.z_keep_text_section_prefix = true;
} else if (read_z_flag(args, "nokeep-text-section-prefix")) {
ctx.arg.z_keep_text_section_prefix = false;
} else if (read_z_flag(args, "shstk")) {
ctx.arg.z_shstk = true;
} else if (read_z_flag(args, "text")) {
ctx.arg.z_text = true;
} else if (read_z_flag(args, "notext") || read_z_flag(args, "textoff")) {
ctx.arg.z_text = false;
} else if (read_z_flag(args, "origin")) {
ctx.arg.z_origin = true;
} else if (read_z_flag(args, "nodefaultlib")) {
ctx.arg.z_nodefaultlib = true;
} else if (read_z_flag(args, "separate-loadable-segments")) {
ctx.arg.z_separate_code = SEPARATE_LOADABLE_SEGMENTS;
} else if (read_z_flag(args, "separate-code")) {
ctx.arg.z_separate_code = SEPARATE_CODE;
} else if (read_z_flag(args, "noseparate-code")) {
ctx.arg.z_separate_code = NOSEPARATE_CODE;
} else if (read_flag(args, "no-undefined")) {
ctx.arg.z_defs = true;
} else if (read_flag(args, "fatal-warnings")) {
ctx.arg.fatal_warnings = true;
} else if (read_flag(args, "no-fatal-warnings")) {
ctx.arg.fatal_warnings = false;
} else if (read_flag(args, "fork")) {
ctx.arg.fork = true;
} else if (read_flag(args, "no-fork")) {
ctx.arg.fork = false;
} else if (read_flag(args, "gc-sections")) {
ctx.arg.gc_sections = true;
} else if (read_flag(args, "no-gc-sections")) {
ctx.arg.gc_sections = false;
} else if (read_flag(args, "print-gc-sections")) {
ctx.arg.print_gc_sections = true;
} else if (read_flag(args, "no-print-gc-sections")) {
ctx.arg.print_gc_sections = false;
} else if (read_arg(ctx, args, arg, "icf")) {
if (arg == "all")
ctx.arg.icf = true;
else if (arg == "none")
ctx.arg.icf = false;
else
Fatal(ctx) << "unknown --icf argument: " << arg;
} else if (read_flag(args, "no-icf")) {
ctx.arg.icf = false;
} else if (read_arg(ctx, args, arg, "image-base")) {
ctx.arg.image_base = parse_number(ctx, "image-base", arg);
} else if (read_flag(args, "print-icf-sections")) {
ctx.arg.print_icf_sections = true;
} else if (read_flag(args, "no-print-icf-sections")) {
ctx.arg.print_icf_sections = false;
} else if (read_flag(args, "quick-exit")) {
ctx.arg.quick_exit = true;
} else if (read_flag(args, "no-quick-exit")) {
ctx.arg.quick_exit = false;
} else if (read_arg(ctx, args, arg, "thread-count")) {
ctx.arg.thread_count = parse_number(ctx, "thread-count", arg);
} else if (read_flag(args, "threads")) {
ctx.arg.thread_count = 0;
} else if (read_flag(args, "no-threads")) {
ctx.arg.thread_count = 1;
} else if (args[0].starts_with("-threads=")) {
ctx.arg.thread_count = parse_number(ctx, "threads=", args[0].substr(9));
args = args.subspan(1);
} else if (args[0].starts_with("--threads=")) {
ctx.arg.thread_count = parse_number(ctx, "threads=", args[0].substr(10));
args = args.subspan(1);
} else if (read_flag(args, "discard-all") || read_flag(args, "x")) {
ctx.arg.discard_all = true;
} else if (read_flag(args, "discard-locals") || read_flag(args, "X")) {
ctx.arg.discard_locals = true;
} else if (read_flag(args, "strip-all") || read_flag(args, "s")) {
ctx.arg.strip_all = true;
} else if (read_flag(args, "strip-debug") || read_flag(args, "S")) {
ctx.arg.strip_all = true;
} else if (read_flag(args, "warn-unresolved-symbols")) {
ctx.arg.unresolved_symbols = UNRESOLVED_WARN;
} else if (read_flag(args, "error-unresolved-symbols")) {
ctx.arg.unresolved_symbols = UNRESOLVED_ERROR;
} else if (read_arg(ctx, args, arg, "rpath")) {
if (!ctx.arg.rpaths.empty())
ctx.arg.rpaths += ":";
ctx.arg.rpaths += arg;
} else if (read_arg(ctx, args, arg, "R")) {
if (is_file(arg))
Fatal(ctx) << "-R" << arg
<< ": -R as an alias for --just-symbols is not supported";
if (!ctx.arg.rpaths.empty())
ctx.arg.rpaths += ":";
ctx.arg.rpaths += arg;
} else if (read_flag(args, "build-id")) {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 20;
} else if (read_arg(ctx, args, arg, "build-id")) {
if (arg == "none") {
ctx.arg.build_id.kind = BuildId::NONE;
} else if (arg == "uuid") {
ctx.arg.build_id.kind = BuildId::UUID;
} else if (arg == "md5") {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 16;
} else if (arg == "sha1") {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 20;
} else if (arg == "sha256") {
ctx.arg.build_id.kind = BuildId::HASH;
ctx.arg.build_id.hash_size = 32;
} else if (arg.starts_with("0x") || arg.starts_with("0X")) {
ctx.arg.build_id.kind = BuildId::HEX;
ctx.arg.build_id.value = parse_hex_build_id(ctx, arg);
} else {
Fatal(ctx) << "invalid --build-id argument: " << arg;
}
} else if (read_flag(args, "no-build-id")) {
ctx.arg.build_id.kind = BuildId::NONE;
} else if (read_arg(ctx, args, arg, "format") ||
read_arg(ctx, args, arg, "b")) {
if (arg == "binary")
Fatal(ctx)
<< "mold does not suppor `-b binary`. If you want to convert a binary"
" file into an object file, use `objcopy -I binary -O elf64-x86-64"
<< " <input-file> <output-file.o>` instead.";
Fatal(ctx) << "unknown command line option: -b " << arg;
} else if (read_arg(ctx, args, arg, "auxiliary") ||
read_arg(ctx, args, arg, "f")) {
ctx.arg.auxiliary.push_back(arg);
} else if (read_arg(ctx, args, arg, "filter") ||
read_arg(ctx, args, arg, "F")) {
ctx.arg.filter.push_back(arg);
} else if (read_flag(args, "preload")) {
ctx.arg.preload = true;
} else if (read_flag(args, "no-preload")) {
ctx.arg.preload = false;
} else if (read_flag(args, "apply-dynamic-relocs")) {
} else if (read_arg(ctx, args, arg, "O")) {
} else if (read_flag(args, "O0")) {
} else if (read_flag(args, "O1")) {
} else if (read_flag(args, "O2")) {
} else if (read_flag(args, "verbose")) {
} else if (read_arg(ctx, args, arg, "plugin")) {
} else if (read_arg(ctx, args, arg, "plugin-opt")) {
} else if (read_flag(args, "color-diagnostics")) {
} else if (read_flag(args, "gdb-index")) {
} else if (read_flag(args, "eh-frame-hdr")) {
} else if (read_flag(args, "start-group")) {
} else if (read_flag(args, "end-group")) {
} else if (read_flag(args, "(")) {
} else if (read_flag(args, ")")) {
} else if (read_flag(args, "fatal-warnings")) {
} else if (read_flag(args, "enable-new-dtags")) {
} else if (read_flag(args, "disable-new-dtags")) {
} else if (read_flag(args, "nostdlib")) {
} else if (read_flag(args, "allow-shlib-undefined")) {
} else if (read_flag(args, "no-allow-shlib-undefined")) {
} else if (read_flag(args, "no-add-needed")) {
} else if (read_flag(args, "no-call-graph-profile-sort")) {
} else if (read_flag(args, "no-copy-dt-needed-entries")) {
} else if (read_flag(args, "no-undefined-version")) {
} else if (read_arg(ctx, args, arg, "sort-section")) {
} else if (read_flag(args, "sort-common")) {
} else if (read_flag(args, "dc")) {
} else if (read_flag(args, "dp")) {
} else if (read_flag(args, "fix-cortex-a53-835769")) {
} else if (read_flag(args, "fix-cortex-a53-843419")) {
} else if (read_flag(args, "EL")) {
} else if (read_flag(args, "warn-once")) {
} else if (read_flag(args, "nodefaultlibs")) {
} else if (read_flag(args, "warn-constructors")) {
} else if (read_flag(args, "warn-execstack")) {
} else if (read_flag(args, "no-warn-execstack")) {
} else if (read_arg(ctx, args, arg, "rpath-link")) {
} else if (read_z_flag(args, "combreloc")) {
} else if (read_z_flag(args, "nocombreloc")) {
} else if (read_z_arg(ctx, args, arg, "common-page-size")) {
} else if (read_arg(ctx, args, arg, "version-script")) {
remaining.push_back("--version-script");
remaining.push_back(arg);
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
remaining.push_back("--dynamic-list");
remaining.push_back(arg);
} else if (read_flag(args, "as-needed")) {
remaining.push_back("-as-needed");
} else if (read_flag(args, "no-as-needed")) {
remaining.push_back("-no-as-needed");
} else if (read_flag(args, "whole-archive")) {
remaining.push_back("-whole-archive");
} else if (read_flag(args, "no-whole-archive")) {
remaining.push_back("-no-whole-archive");
} else if (read_arg(ctx, args, arg, "l")) {
remaining.push_back("-l");
remaining.push_back(arg);
} else if (read_arg(ctx, args, arg, "script") ||
read_arg(ctx, args, arg, "T")) {
remaining.push_back(arg);
} else if (read_flag(args, "push-state")) {
remaining.push_back("-push-state");
} else if (read_flag(args, "pop-state")) {
remaining.push_back("-pop-state");
} else if (args[0].starts_with("-z") && args[0].size() > 2) {
Warn(ctx) << "unknown command line option: " << args[0];
args = args.subspan(1);
} else if (args[0] == "-z" && args.size() >= 2) {
Warn(ctx) << "unknown command line option: -z " << args[1];
args = args.subspan(2);
} else {
if (args[0][0] == '-')
Fatal(ctx) << "unknown command line option: " << args[0];
remaining.push_back(args[0]);
args = args.subspan(1);
}
}
if (!ctx.arg.sysroot.empty()) {
for (std::string &path : ctx.arg.library_paths) {
if (std::string_view(path).starts_with('='))
path = ctx.arg.sysroot + path.substr(1);
else if (std::string_view(path).starts_with("$SYSROOT"))
path = ctx.arg.sysroot + path.substr(8);
}
}
if (ctx.arg.shared) {
ctx.arg.pic = true;
ctx.arg.dynamic_linker = "";
}
if (ctx.arg.pic)
ctx.arg.image_base = 0;
if (ctx.arg.retain_symbols_file) {
ctx.arg.strip_all = false;
ctx.arg.discard_all = false;
}
if (ctx.arg.relocatable)
ctx.arg.is_static = true;
if (!ctx.arg.shared) {
if (!ctx.arg.filter.empty())
Fatal(ctx) << "-filter may not be used without -shared";
if (!ctx.arg.auxiliary.empty())
Fatal(ctx) << "-auxiliary may not be used without -shared";
}
if (ctx.arg.image_base % ctx.page_size)
Fatal(ctx) << "-image-base msut be a multiple of -max-page-size";
if (char *env = getenv("MOLD_REPRO"); env && env[0])
ctx.arg.repro = true;
if (ctx.arg.output.empty())
ctx.arg.output = "a.out";
if (ctx.arg.shared || ctx.arg.export_dynamic)
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.default_version = VER_NDX_LOCAL;
if (ctx.arg.default_symver) {
std::string ver = ctx.arg.soname.empty() ?
filepath(ctx.arg.output).filename().string() : std::string(ctx.arg.soname);
ctx.arg.version_definitions.push_back(ver);
ctx.default_version = VER_NDX_LAST_RESERVED + 1;
}
std::tie(ctx.plt_hdr_size, ctx.plt_size) = get_plt_size(ctx);
ctx.arg.undefined.push_back(ctx.arg.entry);
// TLSDESC relocs must be always relaxed for statically-linked
// executables even if -no-relax is given. It is because a
// statically-linked executable doesn't contain a tranpoline
// function needed for TLSDESC.
ctx.relax_tlsdesc = ctx.arg.is_static || (ctx.arg.relax && !ctx.arg.shared);
if (version_shown && remaining.empty())
exit(0);
}
#define INSTANTIATE(E) \
template \
bool read_arg(Context<E> &ctx, std::span<std::string_view> &args, \
std::string_view &arg, \
std::string name); \
\
template std::string create_response_file(Context<E> &ctx); \
\
template \
void parse_nonpositional_args(Context<E> &ctx, \
std::vector<std::string_view> &remaining)
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+953
View File
@@ -0,0 +1,953 @@
#pragma once
#include <cstdint>
#include <ostream>
#include <string>
#ifdef __BIG_ENDIAN__
#error "mold does not support big-endian hosts"
#endif
namespace mold::elf {
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64;
struct X86_64;
struct I386;
struct ARM64;
template <typename E> struct ElfSym;
template <typename E> struct ElfShdr;
template <typename E> struct ElfEhdr;
template <typename E> struct ElfPhdr;
template <typename E> struct ElfRel;
template <typename E> struct ElfDyn;
template <typename E> struct ElfChdr;
template <typename E>
std::string rel_to_string(u32 r_type);
template <typename E>
std::ostream &operator<<(std::ostream &out, const ElfRel<E> &rel) {
out << rel_to_string<E>(rel.r_type);
return out;
}
static constexpr u32 SHN_UNDEF = 0;
static constexpr u32 SHN_ABS = 0xfff1;
static constexpr u32 SHN_COMMON = 0xfff2;
static constexpr u32 SHN_XINDEX = 0xffff;
static constexpr u32 SHT_NULL = 0;
static constexpr u32 SHT_PROGBITS = 1;
static constexpr u32 SHT_SYMTAB = 2;
static constexpr u32 SHT_STRTAB = 3;
static constexpr u32 SHT_RELA = 4;
static constexpr u32 SHT_HASH = 5;
static constexpr u32 SHT_DYNAMIC = 6;
static constexpr u32 SHT_NOTE = 7;
static constexpr u32 SHT_NOBITS = 8;
static constexpr u32 SHT_REL = 9;
static constexpr u32 SHT_SHLIB = 10;
static constexpr u32 SHT_DYNSYM = 11;
static constexpr u32 SHT_INIT_ARRAY = 14;
static constexpr u32 SHT_FINI_ARRAY = 15;
static constexpr u32 SHT_PREINIT_ARRAY = 16;
static constexpr u32 SHT_GROUP = 17;
static constexpr u32 SHT_SYMTAB_SHNDX = 18;
static constexpr u32 SHT_RELR = 19;
static constexpr u32 SHT_GNU_HASH = 0x6ffffff6;
static constexpr u32 SHT_GNU_VERDEF = 0x6ffffffd;
static constexpr u32 SHT_GNU_VERNEED = 0x6ffffffe;
static constexpr u32 SHT_GNU_VERSYM = 0x6fffffff;
static constexpr u32 SHT_X86_64_UNWIND = 0x70000001;
static constexpr u32 SHF_WRITE = 0x1;
static constexpr u32 SHF_ALLOC = 0x2;
static constexpr u32 SHF_EXECINSTR = 0x4;
static constexpr u32 SHF_MERGE = 0x10;
static constexpr u32 SHF_STRINGS = 0x20;
static constexpr u32 SHF_INFO_LINK = 0x40;
static constexpr u32 SHF_LINK_ORDER = 0x80;
static constexpr u32 SHF_GROUP = 0x200;
static constexpr u32 SHF_TLS = 0x400;
static constexpr u32 SHF_COMPRESSED = 0x800;
static constexpr u32 SHF_EXCLUDE = 0x80000000;
static constexpr u32 GRP_COMDAT = 1;
static constexpr u32 STT_NOTYPE = 0;
static constexpr u32 STT_OBJECT = 1;
static constexpr u32 STT_FUNC = 2;
static constexpr u32 STT_SECTION = 3;
static constexpr u32 STT_FILE = 4;
static constexpr u32 STT_COMMON = 5;
static constexpr u32 STT_TLS = 6;
static constexpr u32 STT_GNU_IFUNC = 10;
static constexpr u32 STB_LOCAL = 0;
static constexpr u32 STB_GLOBAL = 1;
static constexpr u32 STB_WEAK = 2;
static constexpr u32 STB_GNU_UNIQUE = 10;
static constexpr u32 STV_DEFAULT = 0;
static constexpr u32 STV_INTERNAL = 1;
static constexpr u32 STV_HIDDEN = 2;
static constexpr u32 STV_PROTECTED = 3;
static constexpr u32 VER_NDX_LOCAL = 0;
static constexpr u32 VER_NDX_GLOBAL = 1;
static constexpr u32 VER_NDX_LAST_RESERVED = 1;
static constexpr u32 VER_FLG_BASE = 1;
static constexpr u32 VER_FLG_WEAK = 2;
static constexpr u32 VER_FLG_INFO = 4;
static constexpr u32 VERSYM_HIDDEN = 0x8000;
static constexpr u32 PT_NULL = 0;
static constexpr u32 PT_LOAD = 1;
static constexpr u32 PT_DYNAMIC = 2;
static constexpr u32 PT_INTERP = 3;
static constexpr u32 PT_NOTE = 4;
static constexpr u32 PT_SHLIB = 5;
static constexpr u32 PT_PHDR = 6;
static constexpr u32 PT_TLS = 7;
static constexpr u32 PT_GNU_EH_FRAME = 0x6474e550;
static constexpr u32 PT_GNU_STACK = 0x6474e551;
static constexpr u32 PT_GNU_RELRO = 0x6474e552;
static constexpr u32 PF_X = 1;
static constexpr u32 PF_W = 2;
static constexpr u32 PF_R = 4;
static constexpr u32 ET_NONE = 0;
static constexpr u32 ET_REL = 1;
static constexpr u32 ET_EXEC = 2;
static constexpr u32 ET_DYN = 3;
static constexpr u32 ELFDATA2LSB = 1;
static constexpr u32 ELFDATA2MSB = 2;
static constexpr u32 ELFCLASS32 = 1;
static constexpr u32 ELFCLASS64 = 2;
static constexpr u32 EV_CURRENT = 1;
static constexpr u32 EM_386 = 3;
static constexpr u32 EM_X86_64 = 62;
static constexpr u32 EM_AARCH64 = 183;
static constexpr u32 EI_CLASS = 4;
static constexpr u32 EI_DATA = 5;
static constexpr u32 EI_VERSION = 6;
static constexpr u32 EI_OSABI = 7;
static constexpr u32 EI_ABIVERSION = 8;
static constexpr u32 ELFOSABI_NONE = 0;
static constexpr u32 ELFOSABI_GNU = 3;
static constexpr u32 DT_NULL = 0;
static constexpr u32 DT_NEEDED = 1;
static constexpr u32 DT_PLTRELSZ = 2;
static constexpr u32 DT_PLTGOT = 3;
static constexpr u32 DT_HASH = 4;
static constexpr u32 DT_STRTAB = 5;
static constexpr u32 DT_SYMTAB = 6;
static constexpr u32 DT_RELA = 7;
static constexpr u32 DT_RELASZ = 8;
static constexpr u32 DT_RELAENT = 9;
static constexpr u32 DT_STRSZ = 10;
static constexpr u32 DT_SYMENT = 11;
static constexpr u32 DT_INIT = 12;
static constexpr u32 DT_FINI = 13;
static constexpr u32 DT_SONAME = 14;
static constexpr u32 DT_RPATH = 15;
static constexpr u32 DT_SYMBOLIC = 16;
static constexpr u32 DT_REL = 17;
static constexpr u32 DT_RELSZ = 18;
static constexpr u32 DT_RELENT = 19;
static constexpr u32 DT_PLTREL = 20;
static constexpr u32 DT_DEBUG = 21;
static constexpr u32 DT_TEXTREL = 22;
static constexpr u32 DT_JMPREL = 23;
static constexpr u32 DT_BIND_NOW = 24;
static constexpr u32 DT_INIT_ARRAY = 25;
static constexpr u32 DT_FINI_ARRAY = 26;
static constexpr u32 DT_INIT_ARRAYSZ = 27;
static constexpr u32 DT_FINI_ARRAYSZ = 28;
static constexpr u32 DT_RUNPATH = 29;
static constexpr u32 DT_FLAGS = 30;
static constexpr u32 DT_RELRSZ = 35;
static constexpr u32 DT_RELR = 36;
static constexpr u32 DT_RELRENT = 37;
static constexpr u32 DT_GNU_HASH = 0x6ffffef5;
static constexpr u32 DT_VERSYM = 0x6ffffff0;
static constexpr u32 DT_RELACOUNT = 0x6ffffff9;
static constexpr u32 DT_RELCOUNT = 0x6ffffffa;
static constexpr u32 DT_FLAGS_1 = 0x6ffffffb;
static constexpr u32 DT_VERDEF = 0x6ffffffc;
static constexpr u32 DT_VERDEFNUM = 0x6ffffffd;
static constexpr u32 DT_VERNEED = 0x6ffffffe;
static constexpr u32 DT_VERNEEDNUM = 0x6fffffff;
static constexpr u32 DT_AUXILIARY = 0x7ffffffd;
static constexpr u32 DT_FILTER = 0x7fffffff;
static constexpr u32 DF_ORIGIN = 0x01;
static constexpr u32 DF_SYMBOLIC = 0x02;
static constexpr u32 DF_TEXTREL = 0x04;
static constexpr u32 DF_BIND_NOW = 0x08;
static constexpr u32 DF_STATIC_TLS = 0x10;
static constexpr u32 DF_1_NOW = 0x00000001;
static constexpr u32 DF_1_NODELETE = 0x00000008;
static constexpr u32 DF_1_INITFIRST = 0x00000020;
static constexpr u32 DF_1_NOOPEN = 0x00000040;
static constexpr u32 DF_1_ORIGIN = 0x00000080;
static constexpr u32 DF_1_INTERPOSE = 0x00000400;
static constexpr u32 DF_1_NODEFLIB = 0x00000800;
static constexpr u32 DF_1_NODUMP = 0x00001000;
static constexpr u32 DF_1_PIE = 0x08000000;
static constexpr u32 NT_GNU_ABI_TAG = 1;
static constexpr u32 NT_GNU_HWCAP = 2;
static constexpr u32 NT_GNU_BUILD_ID = 3;
static constexpr u32 NT_GNU_GOLD_VERSION = 4;
static constexpr u32 NT_GNU_PROPERTY_TYPE_0 = 5;
static constexpr u32 GNU_PROPERTY_AARCH64_FEATURE_1_AND = 0xc0000000;
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_AND = 0xc0000002;
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_IBT = 1;
static constexpr u32 GNU_PROPERTY_X86_FEATURE_1_SHSTK = 2;
static constexpr u32 ELFCOMPRESS_ZLIB = 1;
static constexpr u32 R_X86_64_NONE = 0;
static constexpr u32 R_X86_64_64 = 1;
static constexpr u32 R_X86_64_PC32 = 2;
static constexpr u32 R_X86_64_GOT32 = 3;
static constexpr u32 R_X86_64_PLT32 = 4;
static constexpr u32 R_X86_64_COPY = 5;
static constexpr u32 R_X86_64_GLOB_DAT = 6;
static constexpr u32 R_X86_64_JUMP_SLOT = 7;
static constexpr u32 R_X86_64_RELATIVE = 8;
static constexpr u32 R_X86_64_GOTPCREL = 9;
static constexpr u32 R_X86_64_32 = 10;
static constexpr u32 R_X86_64_32S = 11;
static constexpr u32 R_X86_64_16 = 12;
static constexpr u32 R_X86_64_PC16 = 13;
static constexpr u32 R_X86_64_8 = 14;
static constexpr u32 R_X86_64_PC8 = 15;
static constexpr u32 R_X86_64_DTPMOD64 = 16;
static constexpr u32 R_X86_64_DTPOFF64 = 17;
static constexpr u32 R_X86_64_TPOFF64 = 18;
static constexpr u32 R_X86_64_TLSGD = 19;
static constexpr u32 R_X86_64_TLSLD = 20;
static constexpr u32 R_X86_64_DTPOFF32 = 21;
static constexpr u32 R_X86_64_GOTTPOFF = 22;
static constexpr u32 R_X86_64_TPOFF32 = 23;
static constexpr u32 R_X86_64_PC64 = 24;
static constexpr u32 R_X86_64_GOTOFF64 = 25;
static constexpr u32 R_X86_64_GOTPC32 = 26;
static constexpr u32 R_X86_64_GOT64 = 27;
static constexpr u32 R_X86_64_GOTPCREL64 = 28;
static constexpr u32 R_X86_64_GOTPC64 = 29;
static constexpr u32 R_X86_64_GOTPLT64 = 30;
static constexpr u32 R_X86_64_PLTOFF64 = 31;
static constexpr u32 R_X86_64_SIZE32 = 32;
static constexpr u32 R_X86_64_SIZE64 = 33;
static constexpr u32 R_X86_64_GOTPC32_TLSDESC = 34;
static constexpr u32 R_X86_64_TLSDESC_CALL = 35;
static constexpr u32 R_X86_64_TLSDESC = 36;
static constexpr u32 R_X86_64_IRELATIVE = 37;
static constexpr u32 R_X86_64_GOTPCRELX = 41;
static constexpr u32 R_X86_64_REX_GOTPCRELX = 42;
template <>
inline std::string rel_to_string<X86_64>(u32 r_type) {
switch (r_type) {
case R_X86_64_NONE: return "R_X86_64_NONE";
case R_X86_64_64: return "R_X86_64_64";
case R_X86_64_PC32: return "R_X86_64_PC32";
case R_X86_64_GOT32: return "R_X86_64_GOT32";
case R_X86_64_PLT32: return "R_X86_64_PLT32";
case R_X86_64_COPY: return "R_X86_64_COPY";
case R_X86_64_GLOB_DAT: return "R_X86_64_GLOB_DAT";
case R_X86_64_JUMP_SLOT: return "R_X86_64_JUMP_SLOT";
case R_X86_64_RELATIVE: return "R_X86_64_RELATIVE";
case R_X86_64_GOTPCREL: return "R_X86_64_GOTPCREL";
case R_X86_64_32: return "R_X86_64_32";
case R_X86_64_32S: return "R_X86_64_32S";
case R_X86_64_16: return "R_X86_64_16";
case R_X86_64_PC16: return "R_X86_64_PC16";
case R_X86_64_8: return "R_X86_64_8";
case R_X86_64_PC8: return "R_X86_64_PC8";
case R_X86_64_DTPMOD64: return "R_X86_64_DTPMOD64";
case R_X86_64_DTPOFF64: return "R_X86_64_DTPOFF64";
case R_X86_64_TPOFF64: return "R_X86_64_TPOFF64";
case R_X86_64_TLSGD: return "R_X86_64_TLSGD";
case R_X86_64_TLSLD: return "R_X86_64_TLSLD";
case R_X86_64_DTPOFF32: return "R_X86_64_DTPOFF32";
case R_X86_64_GOTTPOFF: return "R_X86_64_GOTTPOFF";
case R_X86_64_TPOFF32: return "R_X86_64_TPOFF32";
case R_X86_64_PC64: return "R_X86_64_PC64";
case R_X86_64_GOTOFF64: return "R_X86_64_GOTOFF64";
case R_X86_64_GOTPC32: return "R_X86_64_GOTPC32";
case R_X86_64_GOT64: return "R_X86_64_GOT64";
case R_X86_64_GOTPCREL64: return "R_X86_64_GOTPCREL64";
case R_X86_64_GOTPC64: return "R_X86_64_GOTPC64";
case R_X86_64_GOTPLT64: return "R_X86_64_GOTPLT64";
case R_X86_64_PLTOFF64: return "R_X86_64_PLTOFF64";
case R_X86_64_SIZE32: return "R_X86_64_SIZE32";
case R_X86_64_SIZE64: return "R_X86_64_SIZE64";
case R_X86_64_GOTPC32_TLSDESC: return "R_X86_64_GOTPC32_TLSDESC";
case R_X86_64_TLSDESC_CALL: return "R_X86_64_TLSDESC_CALL";
case R_X86_64_TLSDESC: return "R_X86_64_TLSDESC";
case R_X86_64_IRELATIVE: return "R_X86_64_IRELATIVE";
case R_X86_64_GOTPCRELX: return "R_X86_64_GOTPCRELX";
case R_X86_64_REX_GOTPCRELX: return "R_X86_64_REX_GOTPCRELX";
}
return "unknown (" + std::to_string(r_type) + ")";
}
static constexpr u32 R_386_NONE = 0;
static constexpr u32 R_386_32 = 1;
static constexpr u32 R_386_PC32 = 2;
static constexpr u32 R_386_GOT32 = 3;
static constexpr u32 R_386_PLT32 = 4;
static constexpr u32 R_386_COPY = 5;
static constexpr u32 R_386_GLOB_DAT = 6;
static constexpr u32 R_386_JUMP_SLOT = 7;
static constexpr u32 R_386_RELATIVE = 8;
static constexpr u32 R_386_GOTOFF = 9;
static constexpr u32 R_386_GOTPC = 10;
static constexpr u32 R_386_32PLT = 11;
static constexpr u32 R_386_TLS_TPOFF = 14;
static constexpr u32 R_386_TLS_IE = 15;
static constexpr u32 R_386_TLS_GOTIE = 16;
static constexpr u32 R_386_TLS_LE = 17;
static constexpr u32 R_386_TLS_GD = 18;
static constexpr u32 R_386_TLS_LDM = 19;
static constexpr u32 R_386_16 = 20;
static constexpr u32 R_386_PC16 = 21;
static constexpr u32 R_386_8 = 22;
static constexpr u32 R_386_PC8 = 23;
static constexpr u32 R_386_TLS_GD_32 = 24;
static constexpr u32 R_386_TLS_GD_PUSH = 25;
static constexpr u32 R_386_TLS_GD_CALL = 26;
static constexpr u32 R_386_TLS_GD_POP = 27;
static constexpr u32 R_386_TLS_LDM_32 = 28;
static constexpr u32 R_386_TLS_LDM_PUSH = 29;
static constexpr u32 R_386_TLS_LDM_CALL = 30;
static constexpr u32 R_386_TLS_LDM_POP = 31;
static constexpr u32 R_386_TLS_LDO_32 = 32;
static constexpr u32 R_386_TLS_IE_32 = 33;
static constexpr u32 R_386_TLS_LE_32 = 34;
static constexpr u32 R_386_TLS_DTPMOD32 = 35;
static constexpr u32 R_386_TLS_DTPOFF32 = 36;
static constexpr u32 R_386_TLS_TPOFF32 = 37;
static constexpr u32 R_386_SIZE32 = 38;
static constexpr u32 R_386_TLS_GOTDESC = 39;
static constexpr u32 R_386_TLS_DESC_CALL = 40;
static constexpr u32 R_386_TLS_DESC = 41;
static constexpr u32 R_386_IRELATIVE = 42;
static constexpr u32 R_386_GOT32X = 43;
template <>
inline std::string rel_to_string<I386>(u32 r_type) {
switch (r_type) {
case R_386_NONE: return "R_386_NONE";
case R_386_32: return "R_386_32";
case R_386_PC32: return "R_386_PC32";
case R_386_GOT32: return "R_386_GOT32";
case R_386_PLT32: return "R_386_PLT32";
case R_386_COPY: return "R_386_COPY";
case R_386_GLOB_DAT: return "R_386_GLOB_DAT";
case R_386_JUMP_SLOT: return "R_386_JUMP_SLOT";
case R_386_RELATIVE: return "R_386_RELATIVE";
case R_386_GOTOFF: return "R_386_GOTOFF";
case R_386_GOTPC: return "R_386_GOTPC";
case R_386_32PLT: return "R_386_32PLT";
case R_386_TLS_TPOFF: return "R_386_TLS_TPOFF";
case R_386_TLS_IE: return "R_386_TLS_IE";
case R_386_TLS_GOTIE: return "R_386_TLS_GOTIE";
case R_386_TLS_LE: return "R_386_TLS_LE";
case R_386_TLS_GD: return "R_386_TLS_GD";
case R_386_TLS_LDM: return "R_386_TLS_LDM";
case R_386_16: return "R_386_16";
case R_386_PC16: return "R_386_PC16";
case R_386_8: return "R_386_8";
case R_386_PC8: return "R_386_PC8";
case R_386_TLS_GD_32: return "R_386_TLS_GD_32";
case R_386_TLS_GD_PUSH: return "R_386_TLS_GD_PUSH";
case R_386_TLS_GD_CALL: return "R_386_TLS_GD_CALL";
case R_386_TLS_GD_POP: return "R_386_TLS_GD_POP";
case R_386_TLS_LDM_32: return "R_386_TLS_LDM_32";
case R_386_TLS_LDM_PUSH: return "R_386_TLS_LDM_PUSH";
case R_386_TLS_LDM_CALL: return "R_386_TLS_LDM_CALL";
case R_386_TLS_LDM_POP: return "R_386_TLS_LDM_POP";
case R_386_TLS_LDO_32: return "R_386_TLS_LDO_32";
case R_386_TLS_IE_32: return "R_386_TLS_IE_32";
case R_386_TLS_LE_32: return "R_386_TLS_LE_32";
case R_386_TLS_DTPMOD32: return "R_386_TLS_DTPMOD32";
case R_386_TLS_DTPOFF32: return "R_386_TLS_DTPOFF32";
case R_386_TLS_TPOFF32: return "R_386_TLS_TPOFF32";
case R_386_SIZE32: return "R_386_SIZE32";
case R_386_TLS_GOTDESC: return "R_386_TLS_GOTDESC";
case R_386_TLS_DESC_CALL: return "R_386_TLS_DESC_CALL";
case R_386_TLS_DESC: return "R_386_TLS_DESC";
case R_386_IRELATIVE: return "R_386_IRELATIVE";
case R_386_GOT32X: return "R_386_GOT32X";
}
return "unknown (" + std::to_string(r_type) + ")";
}
static constexpr u32 R_AARCH64_NONE = 0;
static constexpr u32 R_AARCH64_ABS64 = 0x101;
static constexpr u32 R_AARCH64_ABS32 = 0x102;
static constexpr u32 R_AARCH64_ABS16 = 0x103;
static constexpr u32 R_AARCH64_PREL64 = 0x104;
static constexpr u32 R_AARCH64_PREL32 = 0x105;
static constexpr u32 R_AARCH64_PREL16 = 0x106;
static constexpr u32 R_AARCH64_MOVW_UABS_G0 = 0x107;
static constexpr u32 R_AARCH64_MOVW_UABS_G0_NC = 0x108;
static constexpr u32 R_AARCH64_MOVW_UABS_G1 = 0x109;
static constexpr u32 R_AARCH64_MOVW_UABS_G1_NC = 0x10a;
static constexpr u32 R_AARCH64_MOVW_UABS_G2 = 0x10b;
static constexpr u32 R_AARCH64_MOVW_UABS_G2_NC = 0x10c;
static constexpr u32 R_AARCH64_MOVW_UABS_G3 = 0x10d;
static constexpr u32 R_AARCH64_MOVW_SABS_G0 = 0x10e;
static constexpr u32 R_AARCH64_MOVW_SABS_G1 = 0x10f;
static constexpr u32 R_AARCH64_MOVW_SABS_G2 = 0x110;
static constexpr u32 R_AARCH64_LD_PREL_LO19 = 0x111;
static constexpr u32 R_AARCH64_ADR_PREL_LO21 = 0x112;
static constexpr u32 R_AARCH64_ADR_PREL_PG_HI21 = 0x113;
static constexpr u32 R_AARCH64_ADR_PREL_PG_HI21_NC = 0x114;
static constexpr u32 R_AARCH64_ADD_ABS_LO12_NC = 0x115;
static constexpr u32 R_AARCH64_LDST8_ABS_LO12_NC = 0x116;
static constexpr u32 R_AARCH64_TSTBR14 = 0x117;
static constexpr u32 R_AARCH64_CONDBR19 = 0x118;
static constexpr u32 R_AARCH64_JUMP26 = 0x11a;
static constexpr u32 R_AARCH64_CALL26 = 0x11b;
static constexpr u32 R_AARCH64_LDST16_ABS_LO12_NC = 0x11c;
static constexpr u32 R_AARCH64_LDST32_ABS_LO12_NC = 0x11d;
static constexpr u32 R_AARCH64_LDST64_ABS_LO12_NC = 0x11e;
static constexpr u32 R_AARCH64_MOVW_PREL_G0 = 0x11f;
static constexpr u32 R_AARCH64_MOVW_PREL_G0_NC = 0x120;
static constexpr u32 R_AARCH64_MOVW_PREL_G1 = 0x121;
static constexpr u32 R_AARCH64_MOVW_PREL_G1_NC = 0x122;
static constexpr u32 R_AARCH64_MOVW_PREL_G2 = 0x123;
static constexpr u32 R_AARCH64_MOVW_PREL_G2_NC = 0x124;
static constexpr u32 R_AARCH64_MOVW_PREL_G3 = 0x125;
static constexpr u32 R_AARCH64_LDST128_ABS_LO12_NC = 0x12b;
static constexpr u32 R_AARCH64_ADR_GOT_PAGE = 0x137;
static constexpr u32 R_AARCH64_LD64_GOT_LO12_NC = 0x138;
static constexpr u32 R_AARCH64_LD64_GOTPAGE_LO15 = 0x139;
static constexpr u32 R_AARCH64_PLT32 = 0x13a;
static constexpr u32 R_AARCH64_TLSGD_ADR_PREL21 = 0x200;
static constexpr u32 R_AARCH64_TLSGD_ADR_PAGE21 = 0x201;
static constexpr u32 R_AARCH64_TLSGD_ADD_LO12_NC = 0x202;
static constexpr u32 R_AARCH64_TLSGD_MOVW_G1 = 0x203;
static constexpr u32 R_AARCH64_TLSGD_MOVW_G0_NC = 0x204;
static constexpr u32 R_AARCH64_TLSLD_ADR_PREL21 = 0x205;
static constexpr u32 R_AARCH64_TLSLD_ADR_PAGE21 = 0x206;
static constexpr u32 R_AARCH64_TLSLD_ADD_LO12_NC = 0x207;
static constexpr u32 R_AARCH64_TLSLD_MOVW_G1 = 0x208;
static constexpr u32 R_AARCH64_TLSLD_MOVW_G0_NC = 0x209;
static constexpr u32 R_AARCH64_TLSLD_LD_PREL19 = 0x20a;
static constexpr u32 R_AARCH64_TLSLD_MOVW_DTPREL_G2 = 0x20b;
static constexpr u32 R_AARCH64_TLSLD_MOVW_DTPREL_G1 = 0x20c;
static constexpr u32 R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC = 0x20d;
static constexpr u32 R_AARCH64_TLSLD_MOVW_DTPREL_G0 = 0x20e;
static constexpr u32 R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC = 0x20f;
static constexpr u32 R_AARCH64_TLSLD_ADD_DTPREL_HI12 = 0x210;
static constexpr u32 R_AARCH64_TLSLD_ADD_DTPREL_LO12 = 0x211;
static constexpr u32 R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC = 0x212;
static constexpr u32 R_AARCH64_TLSLD_LDST8_DTPREL_LO12 = 0x213;
static constexpr u32 R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC = 0x214;
static constexpr u32 R_AARCH64_TLSLD_LDST16_DTPREL_LO12 = 0x215;
static constexpr u32 R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC = 0x216;
static constexpr u32 R_AARCH64_TLSLD_LDST32_DTPREL_LO12 = 0x217;
static constexpr u32 R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC = 0x218;
static constexpr u32 R_AARCH64_TLSLD_LDST64_DTPREL_LO12 = 0x219;
static constexpr u32 R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC = 0x21a;
static constexpr u32 R_AARCH64_TLSIE_MOVW_GOTTPREL_G1 = 0x21b;
static constexpr u32 R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC = 0x21c;
static constexpr u32 R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21 = 0x21d;
static constexpr u32 R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC = 0x21e;
static constexpr u32 R_AARCH64_TLSIE_LD_GOTTPREL_PREL19 = 0x21f;
static constexpr u32 R_AARCH64_TLSLE_MOVW_TPREL_G2 = 0x220;
static constexpr u32 R_AARCH64_TLSLE_MOVW_TPREL_G1 = 0x221;
static constexpr u32 R_AARCH64_TLSLE_MOVW_TPREL_G1_NC = 0x222;
static constexpr u32 R_AARCH64_TLSLE_MOVW_TPREL_G0 = 0x223;
static constexpr u32 R_AARCH64_TLSLE_MOVW_TPREL_G0_NC = 0x224;
static constexpr u32 R_AARCH64_TLSLE_ADD_TPREL_HI12 = 0x225;
static constexpr u32 R_AARCH64_TLSLE_ADD_TPREL_LO12 = 0x226;
static constexpr u32 R_AARCH64_TLSLE_ADD_TPREL_LO12_NC = 0x227;
static constexpr u32 R_AARCH64_TLSLE_LDST8_TPREL_LO12 = 0x228;
static constexpr u32 R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC = 0x229;
static constexpr u32 R_AARCH64_TLSLE_LDST16_TPREL_LO12 = 0x22a;
static constexpr u32 R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC = 0x22b;
static constexpr u32 R_AARCH64_TLSLE_LDST32_TPREL_LO12 = 0x22c;
static constexpr u32 R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC = 0x22d;
static constexpr u32 R_AARCH64_TLSLE_LDST64_TPREL_LO12 = 0x22e;
static constexpr u32 R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC = 0x22f;
static constexpr u32 R_AARCH64_TLSDESC_ADR_PAGE21 = 0x232;
static constexpr u32 R_AARCH64_TLSDESC_LD64_LO12 = 0x233;
static constexpr u32 R_AARCH64_TLSDESC_ADD_LO12 = 0x234;
static constexpr u32 R_AARCH64_TLSDESC_CALL = 0x239;
static constexpr u32 R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC = 0x23b;
static constexpr u32 R_AARCH64_COPY = 0x400;
static constexpr u32 R_AARCH64_GLOB_DAT = 0x401;
static constexpr u32 R_AARCH64_JUMP_SLOT = 0x402;
static constexpr u32 R_AARCH64_RELATIVE = 0x403;
static constexpr u32 R_AARCH64_TLS_DTPMOD64 = 0x404;
static constexpr u32 R_AARCH64_TLS_DTPREL64 = 0x405;
static constexpr u32 R_AARCH64_TLS_TPREL64 = 0x406;
static constexpr u32 R_AARCH64_TLSDESC = 0x407;
static constexpr u32 R_AARCH64_IRELATIVE = 0x408;
template <>
inline std::string rel_to_string<ARM64>(u32 r_type) {
switch (r_type) {
case R_AARCH64_NONE: return "R_AARCH64_NONE";
case R_AARCH64_ABS64: return "R_AARCH64_ABS64";
case R_AARCH64_ABS32: return "R_AARCH64_ABS32";
case R_AARCH64_ABS16: return "R_AARCH64_ABS16";
case R_AARCH64_PREL64: return "R_AARCH64_PREL64";
case R_AARCH64_PREL32: return "R_AARCH64_PREL32";
case R_AARCH64_PREL16: return "R_AARCH64_PREL16";
case R_AARCH64_MOVW_UABS_G0: return "R_AARCH64_MOVW_UABS_G0";
case R_AARCH64_MOVW_UABS_G0_NC: return "R_AARCH64_MOVW_UABS_G0_NC";
case R_AARCH64_MOVW_UABS_G1: return "R_AARCH64_MOVW_UABS_G1";
case R_AARCH64_MOVW_UABS_G1_NC: return "R_AARCH64_MOVW_UABS_G1_NC";
case R_AARCH64_MOVW_UABS_G2: return "R_AARCH64_MOVW_UABS_G2";
case R_AARCH64_MOVW_UABS_G2_NC: return "R_AARCH64_MOVW_UABS_G2_NC";
case R_AARCH64_MOVW_UABS_G3: return "R_AARCH64_MOVW_UABS_G3";
case R_AARCH64_MOVW_SABS_G0: return "R_AARCH64_MOVW_SABS_G0";
case R_AARCH64_MOVW_SABS_G1: return "R_AARCH64_MOVW_SABS_G1";
case R_AARCH64_MOVW_SABS_G2: return "R_AARCH64_MOVW_SABS_G2";
case R_AARCH64_LD_PREL_LO19: return "R_AARCH64_LD_PREL_LO19";
case R_AARCH64_ADR_PREL_LO21: return "R_AARCH64_ADR_PREL_LO21";
case R_AARCH64_ADR_PREL_PG_HI21: return "R_AARCH64_ADR_PREL_PG_HI21";
case R_AARCH64_ADR_PREL_PG_HI21_NC: return "R_AARCH64_ADR_PREL_PG_HI21_NC";
case R_AARCH64_ADD_ABS_LO12_NC: return "R_AARCH64_ADD_ABS_LO12_NC";
case R_AARCH64_LDST8_ABS_LO12_NC: return "R_AARCH64_LDST8_ABS_LO12_NC";
case R_AARCH64_TSTBR14: return "R_AARCH64_TSTBR14";
case R_AARCH64_CONDBR19: return "R_AARCH64_CONDBR19";
case R_AARCH64_JUMP26: return "R_AARCH64_JUMP26";
case R_AARCH64_CALL26: return "R_AARCH64_CALL26";
case R_AARCH64_LDST16_ABS_LO12_NC: return "R_AARCH64_LDST16_ABS_LO12_NC";
case R_AARCH64_LDST32_ABS_LO12_NC: return "R_AARCH64_LDST32_ABS_LO12_NC";
case R_AARCH64_LDST64_ABS_LO12_NC: return "R_AARCH64_LDST64_ABS_LO12_NC";
case R_AARCH64_MOVW_PREL_G0: return "R_AARCH64_MOVW_PREL_G0";
case R_AARCH64_MOVW_PREL_G0_NC: return "R_AARCH64_MOVW_PREL_G0_NC";
case R_AARCH64_MOVW_PREL_G1: return "R_AARCH64_MOVW_PREL_G1";
case R_AARCH64_MOVW_PREL_G1_NC: return "R_AARCH64_MOVW_PREL_G1_NC";
case R_AARCH64_MOVW_PREL_G2: return "R_AARCH64_MOVW_PREL_G2";
case R_AARCH64_MOVW_PREL_G2_NC: return "R_AARCH64_MOVW_PREL_G2_NC";
case R_AARCH64_MOVW_PREL_G3: return "R_AARCH64_MOVW_PREL_G3";
case R_AARCH64_LDST128_ABS_LO12_NC: return "R_AARCH64_LDST128_ABS_LO12_NC";
case R_AARCH64_ADR_GOT_PAGE: return "R_AARCH64_ADR_GOT_PAGE";
case R_AARCH64_LD64_GOT_LO12_NC: return "R_AARCH64_LD64_GOT_LO12_NC";
case R_AARCH64_LD64_GOTPAGE_LO15: return "R_AARCH64_LD64_GOTPAGE_LO15";
case R_AARCH64_PLT32: return "R_AARCH64_PLT32";
case R_AARCH64_TLSGD_ADR_PREL21: return "R_AARCH64_TLSGD_ADR_PREL21";
case R_AARCH64_TLSGD_ADR_PAGE21: return "R_AARCH64_TLSGD_ADR_PAGE21";
case R_AARCH64_TLSGD_ADD_LO12_NC: return "R_AARCH64_TLSGD_ADD_LO12_NC";
case R_AARCH64_TLSGD_MOVW_G1: return "R_AARCH64_TLSGD_MOVW_G1";
case R_AARCH64_TLSGD_MOVW_G0_NC: return "R_AARCH64_TLSGD_MOVW_G0_NC";
case R_AARCH64_TLSLD_ADR_PREL21: return "R_AARCH64_TLSLD_ADR_PREL21";
case R_AARCH64_TLSLD_ADR_PAGE21: return "R_AARCH64_TLSLD_ADR_PAGE21";
case R_AARCH64_TLSLD_ADD_LO12_NC: return "R_AARCH64_TLSLD_ADD_LO12_NC";
case R_AARCH64_TLSLD_MOVW_G1: return "R_AARCH64_TLSLD_MOVW_G1";
case R_AARCH64_TLSLD_MOVW_G0_NC: return "R_AARCH64_TLSLD_MOVW_G0_NC";
case R_AARCH64_TLSLD_LD_PREL19: return "R_AARCH64_TLSLD_LD_PREL19";
case R_AARCH64_TLSLD_MOVW_DTPREL_G2: return "R_AARCH64_TLSLD_MOVW_DTPREL_G2";
case R_AARCH64_TLSLD_MOVW_DTPREL_G1: return "R_AARCH64_TLSLD_MOVW_DTPREL_G1";
case R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: return "R_AARCH64_TLSLD_MOVW_DTPREL_G1_NC";
case R_AARCH64_TLSLD_MOVW_DTPREL_G0: return "R_AARCH64_TLSLD_MOVW_DTPREL_G0";
case R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: return "R_AARCH64_TLSLD_MOVW_DTPREL_G0_NC";
case R_AARCH64_TLSLD_ADD_DTPREL_HI12: return "R_AARCH64_TLSLD_ADD_DTPREL_HI12";
case R_AARCH64_TLSLD_ADD_DTPREL_LO12: return "R_AARCH64_TLSLD_ADD_DTPREL_LO12";
case R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_ADD_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST8_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST8_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST16_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST16_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST32_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST32_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC";
case R_AARCH64_TLSLD_LDST64_DTPREL_LO12: return "R_AARCH64_TLSLD_LDST64_DTPREL_LO12";
case R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC: return "R_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC";
case R_AARCH64_TLSIE_MOVW_GOTTPREL_G1: return "R_AARCH64_TLSIE_MOVW_GOTTPREL_G1";
case R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: return "R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC";
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: return "R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21";
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: return "R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC";
case R_AARCH64_TLSIE_LD_GOTTPREL_PREL19: return "R_AARCH64_TLSIE_LD_GOTTPREL_PREL19";
case R_AARCH64_TLSLE_MOVW_TPREL_G2: return "R_AARCH64_TLSLE_MOVW_TPREL_G2";
case R_AARCH64_TLSLE_MOVW_TPREL_G1: return "R_AARCH64_TLSLE_MOVW_TPREL_G1";
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC: return "R_AARCH64_TLSLE_MOVW_TPREL_G1_NC";
case R_AARCH64_TLSLE_MOVW_TPREL_G0: return "R_AARCH64_TLSLE_MOVW_TPREL_G0";
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC: return "R_AARCH64_TLSLE_MOVW_TPREL_G0_NC";
case R_AARCH64_TLSLE_ADD_TPREL_HI12: return "R_AARCH64_TLSLE_ADD_TPREL_HI12";
case R_AARCH64_TLSLE_ADD_TPREL_LO12: return "R_AARCH64_TLSLE_ADD_TPREL_LO12";
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC: return "R_AARCH64_TLSLE_ADD_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST8_TPREL_LO12: return "R_AARCH64_TLSLE_LDST8_TPREL_LO12";
case R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST8_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST16_TPREL_LO12: return "R_AARCH64_TLSLE_LDST16_TPREL_LO12";
case R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST16_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST32_TPREL_LO12: return "R_AARCH64_TLSLE_LDST32_TPREL_LO12";
case R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST32_TPREL_LO12_NC";
case R_AARCH64_TLSLE_LDST64_TPREL_LO12: return "R_AARCH64_TLSLE_LDST64_TPREL_LO12";
case R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST64_TPREL_LO12_NC";
case R_AARCH64_TLSDESC_ADR_PAGE21: return "R_AARCH64_TLSDESC_ADR_PAGE21";
case R_AARCH64_TLSDESC_LD64_LO12: return "R_AARCH64_TLSDESC_LD64_LO12";
case R_AARCH64_TLSDESC_ADD_LO12: return "R_AARCH64_TLSDESC_ADD_LO12";
case R_AARCH64_TLSDESC_CALL: return "R_AARCH64_TLSDESC_CALL";
case R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC: return "R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC";
case R_AARCH64_COPY: return "R_AARCH64_COPY";
case R_AARCH64_GLOB_DAT: return "R_AARCH64_GLOB_DAT";
case R_AARCH64_JUMP_SLOT: return "R_AARCH64_JUMP_SLOT";
case R_AARCH64_RELATIVE: return "R_AARCH64_RELATIVE";
case R_AARCH64_TLS_DTPMOD64: return "R_AARCH64_TLS_DTPMOD64";
case R_AARCH64_TLS_DTPREL64: return "R_AARCH64_TLS_DTPREL64";
case R_AARCH64_TLS_TPREL64: return "R_AARCH64_TLS_TPREL64";
case R_AARCH64_TLSDESC: return "R_AARCH64_TLSDESC";
case R_AARCH64_IRELATIVE: return "R_AARCH64_IRELATIVE";
}
return "unknown (" + std::to_string(r_type) + ")";
}
static constexpr u32 DW_EH_PE_absptr = 0;
static constexpr u32 DW_EH_PE_omit = 0xff;
static constexpr u32 DW_EH_PE_uleb128 = 0x01;
static constexpr u32 DW_EH_PE_udata2 = 0x02;
static constexpr u32 DW_EH_PE_udata4 = 0x03;
static constexpr u32 DW_EH_PE_udata8 = 0x04;
static constexpr u32 DW_EH_PE_signed = 0x08;
static constexpr u32 DW_EH_PE_sleb128 = 0x09;
static constexpr u32 DW_EH_PE_sdata2 = 0x0a;
static constexpr u32 DW_EH_PE_sdata4 = 0x0b;
static constexpr u32 DW_EH_PE_sdata8 = 0x0c;
static constexpr u32 DW_EH_PE_pcrel = 0x10;
static constexpr u32 DW_EH_PE_textrel = 0x20;
static constexpr u32 DW_EH_PE_datarel = 0x30;
static constexpr u32 DW_EH_PE_funcrel = 0x40;
static constexpr u32 DW_EH_PE_aligned = 0x50;
struct Elf64Sym {
bool is_defined() const { return !is_undef(); }
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_strong() const {
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
}
bool is_undef_weak() const {
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
}
u32 st_name;
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u16 st_shndx;
u64 st_value;
u64 st_size;
};
struct Elf32Sym {
bool is_defined() const { return !is_undef(); }
bool is_undef() const { return st_shndx == SHN_UNDEF; }
bool is_abs() const { return st_shndx == SHN_ABS; }
bool is_common() const { return st_shndx == SHN_COMMON; }
bool is_weak() const { return st_bind == STB_WEAK; }
bool is_undef_strong() const {
return st_shndx == SHN_UNDEF && st_bind != STB_WEAK;
}
bool is_undef_weak() const {
return st_shndx == SHN_UNDEF && st_bind == STB_WEAK;
}
u32 st_name;
u32 st_value;
u32 st_size;
u8 st_type : 4;
u8 st_bind : 4;
u8 st_visibility : 2;
u16 st_shndx;
};
struct Elf64Shdr {
u32 sh_name;
u32 sh_type;
u64 sh_flags;
u64 sh_addr;
u64 sh_offset;
u64 sh_size;
u32 sh_link;
u32 sh_info;
u64 sh_addralign;
u64 sh_entsize;
};
struct Elf32Shdr {
u32 sh_name;
u32 sh_type;
u32 sh_flags;
u32 sh_addr;
u32 sh_offset;
u32 sh_size;
u32 sh_link;
u32 sh_info;
u32 sh_addralign;
u32 sh_entsize;
};
struct Elf64Ehdr {
u8 e_ident[16];
u16 e_type;
u16 e_machine;
u32 e_version;
u64 e_entry;
u64 e_phoff;
u64 e_shoff;
u32 e_flags;
u16 e_ehsize;
u16 e_phentsize;
u16 e_phnum;
u16 e_shentsize;
u16 e_shnum;
u16 e_shstrndx;
};
struct Elf32Ehdr {
u8 e_ident[16];
u16 e_type;
u16 e_machine;
u32 e_version;
u32 e_entry;
u32 e_phoff;
u32 e_shoff;
u32 e_flags;
u16 e_ehsize;
u16 e_phentsize;
u16 e_phnum;
u16 e_shentsize;
u16 e_shnum;
u16 e_shstrndx;
};
struct Elf64Phdr {
u32 p_type;
u32 p_flags;
u64 p_offset;
u64 p_vaddr;
u64 p_paddr;
u64 p_filesz;
u64 p_memsz;
u64 p_align;
};
struct Elf32Phdr {
u32 p_type;
u32 p_offset;
u32 p_vaddr;
u32 p_paddr;
u32 p_filesz;
u32 p_memsz;
u32 p_flags;
u32 p_align;
};
struct Elf64Rel {
u64 r_offset;
u32 r_type;
u32 r_sym;
};
struct Elf32Rel {
u32 r_offset;
u32 r_type : 8;
u32 r_sym : 24;
};
struct Elf64Rela {
u64 r_offset;
u32 r_type;
u32 r_sym;
i64 r_addend;
};
struct Elf32Rela {
u32 r_offset;
u32 r_type : 8;
u32 r_sym : 24;
i32 r_addend;
};
struct Elf64Dyn {
u64 d_tag;
u64 d_val;
};
struct Elf32Dyn {
u32 d_tag;
u32 d_val;
};
struct ElfVerneed {
u16 vn_version;
u16 vn_cnt;
u32 vn_file;
u32 vn_aux;
u32 vn_next;
};
struct ElfVernaux {
u32 vna_hash;
u16 vna_flags;
u16 vna_other;
u32 vna_name;
u32 vna_next;
};
struct ElfVerdef {
u16 vd_version;
u16 vd_flags;
u16 vd_ndx;
u16 vd_cnt;
u32 vd_hash;
u32 vd_aux;
u32 vd_next;
};
struct ElfVerdaux {
u32 vda_name;
u32 vda_next;
};
struct Elf64Chdr {
u32 ch_type;
u32 ch_reserved;
u64 ch_size;
u64 ch_addralign;
};
struct Elf32Chdr {
u32 ch_type;
u32 ch_size;
u32 ch_addralign;
};
struct ElfNhdr {
u32 n_namesz;
u32 n_descsz;
u32 n_type;
};
struct X86_64 {
using WordTy = u64;
static constexpr u32 R_NONE = R_X86_64_NONE;
static constexpr u32 R_COPY = R_X86_64_COPY;
static constexpr u32 R_GLOB_DAT = R_X86_64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_X86_64_JUMP_SLOT;
static constexpr u32 R_RELATIVE = R_X86_64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_X86_64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_X86_64_DTPOFF64;
static constexpr u32 R_TPOFF = R_X86_64_TPOFF64;
static constexpr u32 R_DTPMOD = R_X86_64_DTPMOD64;
static constexpr u32 R_TLSDESC = R_X86_64_TLSDESC;
static constexpr u32 word_size = 8;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_X86_64;
static constexpr u32 pltgot_size = 8;
static constexpr bool is_rel = false;
static constexpr bool is_le = true;
};
template <> struct ElfSym<X86_64> : public Elf64Sym {};
template <> struct ElfShdr<X86_64> : public Elf64Shdr {};
template <> struct ElfEhdr<X86_64> : public Elf64Ehdr {};
template <> struct ElfPhdr<X86_64> : public Elf64Phdr {};
template <> struct ElfRel<X86_64> : public Elf64Rela {};
template <> struct ElfDyn<X86_64> : public Elf64Dyn {};
template <> struct ElfChdr<X86_64> : public Elf64Chdr {};
struct I386 {
using WordTy = u32;
static constexpr u32 R_NONE = R_386_NONE;
static constexpr u32 R_COPY = R_386_COPY;
static constexpr u32 R_GLOB_DAT = R_386_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_386_JUMP_SLOT;
static constexpr u32 R_RELATIVE = R_386_RELATIVE;
static constexpr u32 R_IRELATIVE = R_386_IRELATIVE;
static constexpr u32 R_DTPOFF = R_386_TLS_DTPOFF32;
static constexpr u32 R_TPOFF = R_386_TLS_TPOFF;
static constexpr u32 R_DTPMOD = R_386_TLS_DTPMOD32;
static constexpr u32 R_TLSDESC = R_386_TLS_DESC;
static constexpr u32 word_size = 4;
static constexpr u32 page_size = 4096;
static constexpr u32 e_machine = EM_386;
static constexpr u32 pltgot_size = 8;
static constexpr bool is_rel = true;
static constexpr bool is_le = true;
};
template <> struct ElfSym<I386> : public Elf32Sym {};
template <> struct ElfShdr<I386> : public Elf32Shdr {};
template <> struct ElfEhdr<I386> : public Elf32Ehdr {};
template <> struct ElfPhdr<I386> : public Elf32Phdr {};
template <> struct ElfRel<I386> : public Elf32Rel {};
template <> struct ElfDyn<I386> : public Elf32Dyn {};
template <> struct ElfChdr<I386> : public Elf32Chdr {};
struct ARM64 {
using WordTy = u64;
static constexpr u32 R_NONE = R_AARCH64_NONE;
static constexpr u32 R_COPY = R_AARCH64_COPY;
static constexpr u32 R_GLOB_DAT = R_AARCH64_GLOB_DAT;
static constexpr u32 R_JUMP_SLOT = R_AARCH64_JUMP_SLOT;
static constexpr u32 R_RELATIVE = R_AARCH64_RELATIVE;
static constexpr u32 R_IRELATIVE = R_AARCH64_IRELATIVE;
static constexpr u32 R_DTPOFF = R_AARCH64_TLS_DTPREL64;
static constexpr u32 R_TPOFF = R_AARCH64_TLS_TPREL64;
static constexpr u32 R_DTPMOD = R_AARCH64_TLS_DTPMOD64;
static constexpr u32 R_TLSDESC = R_AARCH64_TLSDESC;
static constexpr u32 word_size = 8;
static constexpr u32 page_size = 65536;
static constexpr u32 e_machine = EM_AARCH64;
static constexpr u32 pltgot_size = 16;
static constexpr bool is_rel = false;
static constexpr bool is_le = true;
};
template <> struct ElfSym<ARM64> : public Elf64Sym {};
template <> struct ElfShdr<ARM64> : public Elf64Shdr {};
template <> struct ElfEhdr<ARM64> : public Elf64Ehdr {};
template <> struct ElfPhdr<ARM64> : public Elf64Phdr {};
template <> struct ElfRel<ARM64> : public Elf64Rela {};
template <> struct ElfDyn<ARM64> : public Elf64Dyn {};
template <> struct ElfChdr<ARM64> : public Elf64Chdr {};
} // namespace mold::elf
+201
View File
@@ -0,0 +1,201 @@
// This file implements a mark-sweep garbage collector for -gc-sections.
// In this algorithm, vertices are sections and edges are relocations.
// Any section that is reachable from a root section is considered alive.
#include "mold.h"
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for_each.h>
namespace mold::elf {
template <typename E>
static bool is_init_fini(const InputSection<E> &isec) {
return isec.shdr.sh_type == SHT_INIT_ARRAY ||
isec.shdr.sh_type == SHT_FINI_ARRAY ||
isec.shdr.sh_type == SHT_PREINIT_ARRAY ||
isec.name().starts_with(".ctors") ||
isec.name().starts_with(".dtors") ||
isec.name().starts_with(".init") ||
isec.name().starts_with(".fini");
}
template <typename E>
static bool mark_section(InputSection<E> *isec) {
return isec && isec->is_alive && !isec->is_visited.exchange(true);
}
template <typename E>
static void visit(Context<E> &ctx, InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder, i64 depth) {
assert(isec->is_visited);
// A relocation can refer either a section fragment (i.e. a piece of
// string in a mergeable string section) or a symbol. Mark all
// section fragments as alive.
if (SectionFragmentRef<E> *refs = isec->rel_fragments.get())
for (i64 i = 0; refs[i].idx >= 0; i++)
refs[i].frag->is_alive.store(true, std::memory_order_relaxed);
// If this is a text section, .eh_frame may contain records
// describing how to handle exceptions for that function.
// We want to keep associated .eh_frame records.
for (FdeRecord<E> &fde : isec->get_fdes())
for (ElfRel<E> &rel : fde.get_rels().subspan(1))
if (Symbol<E> *sym = isec->file.symbols[rel.r_sym])
if (mark_section(sym->input_section))
feeder.add(sym->input_section);
for (ElfRel<E> &rel : isec->get_rels(ctx)) {
Symbol<E> &sym = *isec->file.symbols[rel.r_sym];
// Symbol can refer either a section fragment or an input section.
// Mark a fragment as alive.
if (SectionFragment<E> *frag = sym.get_frag()) {
frag->is_alive.store(true, std::memory_order_relaxed);
continue;
}
if (!mark_section(sym.input_section))
continue;
// Mark a section alive. For better performacne, we don't call
// `feeder.add` too often.
if (depth < 3)
visit(ctx, sym.input_section, feeder, depth + 1);
else
feeder.add(sym.input_section);
}
}
template <typename E>
static tbb::concurrent_vector<InputSection<E> *>
collect_root_set(Context<E> &ctx) {
Timer t(ctx, "collect_root_set");
tbb::concurrent_vector<InputSection<E> *> rootset;
auto enqueue_section = [&](InputSection<E> *isec) {
if (mark_section(isec))
rootset.push_back(isec);
};
auto enqueue_symbol = [&](Symbol<E> *sym) {
if (sym) {
if (SectionFragment<E> *frag = sym->get_frag())
frag->is_alive.store(true, std::memory_order_relaxed);
else
enqueue_section(sym->input_section);
}
};
// Add sections that are not subject to garbage collection.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (!isec || !isec->is_alive)
continue;
// -gc-sections discards only SHF_ALLOC sections. If you want to
// reduce the amount of non-memory-mapped segments, you should
// use `strip` command, compile without debug info or use
// -strip-all linker option.
if (!(isec->shdr.sh_flags & SHF_ALLOC))
isec->is_visited = true;
if (is_init_fini(*isec) || is_c_identifier(isec->name()) ||
isec->shdr.sh_type == SHT_NOTE)
enqueue_section(isec.get());
}
});
// Add sections containing exported symbols
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols)
if (sym->file == file && sym->is_exported)
enqueue_symbol(sym);
});
// Add sections referenced by root symbols.
enqueue_symbol(get_symbol(ctx, ctx.arg.entry));
for (std::string_view name : ctx.arg.undefined)
enqueue_symbol(get_symbol(ctx, name));
for (std::string_view name : ctx.arg.require_defined)
enqueue_symbol(get_symbol(ctx, name));
// .eh_frame consists of variable-length records called CIE and FDE
// records, and they are a unit of inclusion or exclusion.
// We just keep all CIEs and everything that are referenced by them.
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (CieRecord<E> &cie : file->cies)
for (ElfRel<E> &rel : cie.get_rels())
enqueue_symbol(file->symbols[rel.r_sym]);
});
return rootset;
}
// Mark all reachable sections
template <typename E>
static void mark(Context<E> &ctx,
tbb::concurrent_vector<InputSection<E> *> &rootset) {
Timer t(ctx, "mark");
tbb::parallel_for_each(rootset, [&](InputSection<E> *isec,
tbb::feeder<InputSection<E> *> &feeder) {
visit(ctx, isec, feeder, 0);
});
}
// Remove unreachable sections
template <typename E>
static void sweep(Context<E> &ctx) {
Timer t(ctx, "sweep");
static Counter counter("garbage_sections");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && !isec->is_visited) {
if (ctx.arg.print_gc_sections)
SyncOut(ctx) << "removing unused section " << *isec;
isec->kill();
counter++;
}
}
});
}
// Non-alloc section fragments are not subject of garbage collection.
// This function marks such fragments.
template <typename E>
static void mark_nonalloc_fragments(Context<E> &ctx) {
Timer t(ctx, "mark_nonalloc_fragments");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (std::unique_ptr<MergeableSection<E>> &m : file->mergeable_sections)
if (m)
for (SectionFragment<E> *frag : m->fragments)
if (!(frag->output_section.shdr.sh_flags & SHF_ALLOC))
frag->is_alive.store(true, std::memory_order_relaxed);
});
}
template <typename E>
void gc_sections(Context<E> &ctx) {
Timer t(ctx, "gc");
mark_nonalloc_fragments(ctx);
tbb::concurrent_vector<InputSection<E> *> rootset = collect_root_set(ctx);
mark(ctx, rootset);
sweep(ctx);
}
#define INSTANTIATE(E) \
template void gc_sections(Context<E> &ctx);
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+151
View File
@@ -0,0 +1,151 @@
#include "mold.h"
#include <cstring>
namespace mold::elf {
std::optional<GlobPattern> GlobPattern::compile(std::string_view pat) {
std::vector<Element> vec;
while (!pat.empty()) {
u8 c = pat[0];
pat = pat.substr(1);
switch (c) {
case '[': {
// Here are a few bracket pattern examples:
//
// [abc]: a, b or c
// [$\]!]: $, ] or !
// [a-czg-i]: a, b, c, z, g, h, or i
// [^a-z]: Any character except lowercase letters
vec.push_back({BRACKET});
std::vector<bool> &bitmap = vec.back().bitmap;
bitmap.resize(256);
bool negate = false;
if (!pat.empty() && pat[0] == '^') {
negate = true;
pat = pat.substr(1);
}
bool closed = false;
while (!pat.empty()) {
if (pat[0] == ']') {
pat = pat.substr(1);
closed = true;
break;
}
if (pat[0] == '\\') {
pat = pat.substr(1);
if (pat.empty())
return {};
}
if (pat.size() >= 3 && pat[1] == '-') {
u8 start = pat[0];
u8 end = pat[2];
pat = pat.substr(3);
if (end == '\\') {
if (pat.empty())
return {};
end = pat[0];
pat = pat.substr(1);
}
if (end < start)
return {};
for (i64 i = start; i <= end; i++)
bitmap[i] = true;
} else {
bitmap[(u8)pat[0]] = true;
pat = pat.substr(1);
}
}
if (!closed)
return {};
if (negate)
for (i64 i = 0; i < 256; i++)
bitmap[i] = !bitmap[i];
break;
}
case '?':
vec.push_back({QUESTION});
break;
case '*':
vec.push_back({STAR});
break;
default:
if (vec.empty() || vec.back().kind != STRING)
vec.push_back({STRING});
vec.back().str += c;
break;
}
}
return {GlobPattern{std::move(vec)}};
}
bool GlobPattern::match(std::string_view str) {
return do_match(str, elements);
}
bool GlobPattern::do_match(std::string_view str, std::span<Element> elements) {
while (!elements.empty()) {
Element &e = elements[0];
elements = elements.subspan(1);
switch (e.kind) {
case STRING:
if (str.empty() || !str.starts_with(e.str))
return false;
str = str.substr(e.str.size());
break;
case STAR:
if (elements.empty())
return true;
// Patterns like "*foo*bar*" should be much more common than more
// complex ones like "*foo*[abc]*" or "*foo**?bar*", so we optimize
// the former case here.
if (elements[0].kind == STRING) {
for (;;) {
size_t pos = str.find(elements[0].str);
if (pos == str.npos)
break;
if (do_match(str.substr(pos + elements[0].str.size()),
elements.subspan(1)))
return true;
str = str.substr(pos + 1);
}
return false;
}
// Other cases are handled here.
for (i64 j = 0; j < str.size(); j++)
if (do_match(str.substr(j), elements))
return true;
return false;
case QUESTION:
if (str.empty())
return false;
str = str.substr(1);
break;
case BRACKET:
if (str.empty() || !e.bitmap[str[0]])
return false;
str = str.substr(1);
break;
}
}
return str.empty();
}
} // namespace mold::elf
+592
View File
@@ -0,0 +1,592 @@
// This file implements the Identical Code Folding feature which can
// reduce the output file size of a typical program by a few percent.
// ICF identifies read-only input sections that happen to be identical
// and thus can be used interchangeably. ICF leaves one of them and discards
// the others.
//
// ICF is usually used in combination with -ffunction-sections and
// -fdata-sections compiler options, so that object files have one section
// for each function or variable instead of having one large .text or .data.
// The unit of ICF merging is section.
//
// Two sections are considered identical by ICF if they have the exact
// same contents, metadata such as section flags, exception handling
// records, and relocations. The last one is interesting because two
// relocations are considered identical if they point to the _same_
// section in terms of ICF.
//
// To see what that means, consider two sections, A and B, which are
// identical except for one pair of relocations. Say, A has a relocation to
// section C, and B has a relocation to D. In this case, A and B are
// considered identical if C and D are considered identical. C and D can be
// either really the same section or two different sections that are
// considered identical by ICF. Below is an example of such inputs, A, B, C
// and D:
//
// void A() { C(); }
// void B() { D(); }
// void C() { A(); }
// void D() { B(); }
//
// If we assume A and B are mergeable, we can merge C and D, which makes A
// and B mergeable. There's no contradiction in our assumption, so we can
// conclude that A and B as well as C and D are mergeable.
//
// This problem boils down to one in graph theory. Input to ICF can be
// considered as a directed graph in which vertices are sections and edges
// are relocations. Vertices have labels (section contents, etc.), and so
// are edges (relocation offsets, etc.). Two vertices are considered
// identical if and only if the (possibly infinite) their unfoldings into
// regular trees are equal. Given this formulation, we want to find as
// many identical vertices as possible.
//
// Solving such problem is computationally intensive, but mold is quite fast.
// For Chromium, mold's ICF finishes in less than 1 second with 20 threads.
// This is contrary to lld and gold, which take about 5 and 50 seconds to
// run ICF under the same condition, respectively.
//
// mold's ICF is faster because we are using a better algorithm.
// It's actually me who developed and implemented the lld's ICF algorithm,
// and I can say that mold's algorithm is better than that in all aspects.
// It scales better for number of available cores, require less overall
// computation, and has a smaller working set. So, it's better with a single
// thread and even better with multiple threads.
#include "mold.h"
#include <array>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_vector.h>
#include <tbb/enumerable_thread_specific.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_for_each.h>
#include <tbb/parallel_sort.h>
#ifdef __APPLE__
# define COMMON_DIGEST_FOR_OPENSSL
# include <CommonCrypto/CommonDigest.h>
#else
# include <openssl/sha.h>
#endif
static constexpr int64_t HASH_SIZE = 16;
typedef std::array<uint8_t, HASH_SIZE> Digest;
namespace std {
template<> struct hash<Digest> {
size_t operator()(const Digest &k) const {
return *(int64_t *)&k[0];
}
};
}
namespace mold::elf {
template <typename E>
static void uniquify_cies(Context<E> &ctx) {
Timer t(ctx, "uniquify_cies");
std::vector<CieRecord<E> *> cies;
for (ObjectFile<E> *file : ctx.objs) {
for (CieRecord<E> &cie : file->cies) {
for (i64 i = 0; i < cies.size(); i++) {
if (cie.equals(*cies[i])) {
cie.icf_idx = i;
goto found;
}
}
cie.icf_idx = cies.size();
cies.push_back(&cie);
found:;
}
}
}
template <typename E>
static bool is_eligible(InputSection<E> &isec) {
const ElfShdr<E> &shdr = isec.shdr;
std::string_view name = isec.name();
bool is_alloc = (shdr.sh_flags & SHF_ALLOC);
bool is_executable = (shdr.sh_flags & SHF_EXECINSTR);
bool is_relro = (name == ".data.rel.ro" ||
name.starts_with(".data.rel.ro."));
bool is_readonly = !(shdr.sh_flags & SHF_WRITE) || is_relro;
bool is_bss = (shdr.sh_type == SHT_NOBITS);
bool is_empty = (shdr.sh_size == 0);
bool is_init = (shdr.sh_type == SHT_INIT_ARRAY || name == ".init");
bool is_fini = (shdr.sh_type == SHT_FINI_ARRAY || name == ".fini");
bool is_enumerable = is_c_identifier(name);
return is_alloc && is_executable && is_readonly && !is_bss &&
!is_empty && !is_init && !is_fini && !is_enumerable;
}
static Digest digest_final(SHA256_CTX &sha) {
u8 buf[SHA256_SIZE];
int res = SHA256_Final(buf, &sha);
assert(res == 1);
Digest digest;
memcpy(digest.data(), buf, HASH_SIZE);
return digest;
}
template <typename E>
static bool is_leaf(Context<E> &ctx, InputSection<E> &isec) {
if (!isec.get_rels(ctx).empty())
return false;
for (FdeRecord<E> &fde : isec.get_fdes())
if (fde.get_rels().size() > 1)
return false;
return true;
}
static u64 combine_hash(u64 a, u64 b) {
return a ^ (b + 0x9e3779b9 + (a << 6) + (a >> 2));
}
template <typename E>
struct LeafHasher {
size_t operator()(const InputSection<E> *isec) const {
u64 h = hash_string(isec->contents);
for (FdeRecord<E> &fde : isec->get_fdes()) {
u64 h2 = hash_string(fde.get_contents().substr(8));
h = combine_hash(h, h2);
}
return h;
}
};
template <typename E>
struct LeafEq {
bool operator()(const InputSection<E> *a, const InputSection<E> *b) const {
if (a->contents != b->contents)
return false;
std::span<FdeRecord<E>> x = a->get_fdes();
std::span<FdeRecord<E>> y = b->get_fdes();
if (x.size() != y.size())
return false;
for (i64 i = 0; i < x.size(); i++)
if (x[i].get_contents().substr(8) != y[i].get_contents().substr(8))
return false;
return true;
}
};
template <typename E>
static void merge_leaf_nodes(Context<E> &ctx) {
Timer t(ctx, "merge_leaf_nodes");
static Counter eligible("icf_eligibles");
static Counter non_eligible("icf_non_eligibles");
static Counter leaf("icf_leaf_nodes");
tbb::concurrent_unordered_map<InputSection<E> *, InputSection<E> *,
LeafHasher<E>, LeafEq<E>> map;
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
if (!isec || !isec->is_alive)
continue;
if (!is_eligible(*isec)) {
non_eligible++;
continue;
}
if (is_leaf(ctx, *isec)) {
leaf++;
isec->icf_leaf = true;
auto [it, inserted] = map.insert({isec.get(), isec.get()});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec.get();
} else {
eligible++;
isec->icf_eligible = true;
}
}
});
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections) {
if (isec && isec->is_alive && isec->icf_leaf) {
auto it = map.find(isec.get());
assert(it != map.end());
isec->leader = it->second;
}
}
});
}
template <typename E>
static Digest compute_digest(Context<E> &ctx, InputSection<E> &isec) {
SHA256_CTX sha;
SHA256_Init(&sha);
auto hash = [&](auto val) {
SHA256_Update(&sha, &val, sizeof(val));
};
auto hash_string = [&](std::string_view str) {
hash(str.size());
SHA256_Update(&sha, str.data(), str.size());
};
auto hash_symbol = [&](Symbol<E> &sym) {
InputSection<E> *isec = sym.input_section;
if (!sym.file) {
hash('1');
hash((u64)&sym);
} else if (SectionFragment<E> *frag = sym.get_frag()) {
hash('2');
hash((u64)frag);
} else if (!isec) {
hash('3');
} else if (isec->leader) {
hash('4');
hash((u64)isec->leader);
} else if (isec->icf_eligible) {
hash('5');
} else {
hash('6');
hash((u64)isec);
}
hash(sym.value);
};
hash_string(isec.contents);
hash(isec.shdr.sh_flags);
hash(isec.get_fdes().size());
hash(isec.get_rels(ctx).size());
for (FdeRecord<E> &fde : isec.get_fdes()) {
hash(fde.cie->icf_idx);
// Bytes 0 to 4 contain the length of this record, and
// bytes 4 to 8 contain an offset to CIE.
hash_string(fde.get_contents().substr(8));
hash(fde.get_rels().size());
for (ElfRel<E> &rel : fde.get_rels().subspan(1)) {
hash_symbol(*isec.file.symbols[rel.r_sym]);
hash(rel.r_type);
hash(rel.r_offset - fde.input_offset);
hash(fde.cie->input_section.get_addend(rel));
}
}
i64 frag_idx = 0;
for (i64 i = 0; i < isec.get_rels(ctx).size(); i++) {
ElfRel<E> &rel = isec.get_rels(ctx)[i];
hash(rel.r_offset);
hash(rel.r_type);
hash(isec.get_addend(rel));
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == i) {
SectionFragmentRef<E> &ref = isec.rel_fragments[frag_idx++];
hash('a');
isec.get_addend(rel);
hash((u64)ref.frag);
} else {
hash_symbol(*isec.file.symbols[rel.r_sym]);
}
}
return digest_final(sha);
}
template <typename E>
static std::vector<InputSection<E> *> gather_sections(Context<E> &ctx) {
Timer t(ctx, "gather_sections");
// Count the number of input sections for each input file.
std::vector<i64> num_sections(ctx.objs.size());
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
if (isec && isec->is_alive && isec->icf_eligible)
num_sections[i]++;
});
std::vector<i64> section_indices(ctx.objs.size());
for (i64 i = 0; i < ctx.objs.size() - 1; i++)
section_indices[i + 1] = section_indices[i] + num_sections[i];
std::vector<InputSection<E> *> sections(
section_indices.back() + num_sections.back());
// Fill `sections` contents.
tbb::parallel_for((i64)0, (i64)ctx.objs.size(), [&](i64 i) {
i64 idx = section_indices[i];
for (std::unique_ptr<InputSection<E>> &isec : ctx.objs[i]->sections)
if (isec && isec->is_alive && isec->icf_eligible)
sections[idx++] = isec.get();
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
sections[i]->icf_idx = i;
});
return sections;
}
template <typename E>
static std::vector<Digest>
compute_digests(Context<E> &ctx, std::span<InputSection<E> *> sections) {
Timer t(ctx, "compute_digests");
std::vector<Digest> digests(sections.size());
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
digests[i] = compute_digest(ctx, *sections[i]);
});
return digests;
}
template <typename E>
static void gather_edges(Context<E> &ctx,
std::span<InputSection<E> *> sections,
std::vector<u32> &edges,
std::vector<u32> &edge_indices) {
Timer t(ctx, "gather_edges");
std::vector<i64> num_edges(sections.size());
edge_indices.resize(sections.size());
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
assert(isec.icf_eligible);
i64 frag_idx = 0;
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
frag_idx++;
} else {
ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag() && sym.input_section &&
sym.input_section->icf_eligible)
num_edges[i]++;
}
}
});
for (i64 i = 0; i < num_edges.size() - 1; i++)
edge_indices[i + 1] = edge_indices[i] + num_edges[i];
edges.resize(edge_indices.back() + num_edges.back());
tbb::parallel_for((i64)0, (i64)num_edges.size(), [&](i64 i) {
InputSection<E> &isec = *sections[i];
i64 frag_idx = 0;
i64 idx = edge_indices[i];
for (i64 j = 0; j < isec.get_rels(ctx).size(); j++) {
if (isec.rel_fragments && isec.rel_fragments[frag_idx].idx == j) {
frag_idx++;
ElfRel<E> &rel = isec.get_rels(ctx)[j];
Symbol<E> &sym = *isec.file.symbols[rel.r_sym];
if (!sym.get_frag() && sym.input_section &&
sym.input_section->icf_eligible)
edges[idx++] = sym.input_section->icf_idx;
}
}
});
}
template <typename E>
static i64 propagate(std::span<std::vector<Digest>> digests,
std::span<u32> edges, std::span<u32> edge_indices,
bool &slot, tbb::affinity_partitioner &ap) {
static Counter round("icf_round");
round++;
i64 num_digests = digests[0].size();
tbb::enumerable_thread_specific<i64> changed;
tbb::parallel_for((i64)0, num_digests, [&](i64 i) {
if (digests[slot][i] == digests[!slot][i])
return;
SHA256_CTX sha;
SHA256_Init(&sha);
SHA256_Update(&sha, digests[2][i].data(), HASH_SIZE);
i64 begin = edge_indices[i];
i64 end = (i + 1 == num_digests) ? edges.size() : edge_indices[i + 1];
for (i64 j : edges.subspan(begin, end - begin))
SHA256_Update(&sha, digests[slot][j].data(), HASH_SIZE);
digests[!slot][i] = digest_final(sha);
if (digests[slot][i] != digests[!slot][i])
changed.local()++;
}, ap);
slot = !slot;
return changed.combine(std::plus());
}
template <typename E>
static i64 count_num_classes(std::span<Digest> digests,
tbb::affinity_partitioner &ap) {
std::vector<Digest> vec(digests.begin(), digests.end());
tbb::parallel_sort(vec);
tbb::enumerable_thread_specific<i64> num_classes;
tbb::parallel_for((i64)0, (i64)vec.size() - 1, [&](i64 i) {
if (vec[i] != vec[i + 1])
num_classes.local()++;
}, ap);
return num_classes.combine(std::plus());
}
template <typename E>
static void print_icf_sections(Context<E> &ctx) {
tbb::concurrent_vector<InputSection<E> *> leaders;
tbb::concurrent_unordered_multimap<InputSection<E> *, InputSection<E> *> map;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (std::unique_ptr<InputSection<E>> &isec : file->sections) {
if (isec && isec->is_alive && isec->leader) {
if (isec.get() == isec->leader)
leaders.push_back(isec.get());
else
map.insert({isec->leader, isec.get()});
}
}
});
tbb::parallel_sort(leaders.begin(), leaders.end(),
[&](InputSection<E> *a, InputSection<E> *b) {
return a->get_priority() < b->get_priority();
});
i64 saved_bytes = 0;
for (InputSection<E> *leader : leaders) {
auto [begin, end] = map.equal_range(leader);
if (begin == end)
continue;
SyncOut(ctx) << "selected section " << *leader;
i64 n = 0;
for (auto it = begin; it != end; it++) {
SyncOut(ctx) << " removing identical section " << *it->second;
n++;
}
saved_bytes += leader->contents.size() * n;
}
SyncOut(ctx) << "ICF saved " << saved_bytes << " bytes";
}
template <typename E>
void icf_sections(Context<E> &ctx) {
Timer t(ctx, "icf");
uniquify_cies(ctx);
merge_leaf_nodes(ctx);
// Prepare for the propagation rounds.
std::vector<InputSection<E> *> sections = gather_sections(ctx);
std::vector<std::vector<Digest>> digests(3);
digests[0] = compute_digests<E>(ctx, sections);
digests[1].resize(digests[0].size());
digests[2] = digests[0];
std::vector<u32> edges;
std::vector<u32> edge_indices;
gather_edges<E>(ctx, sections, edges, edge_indices);
bool slot = 0;
// Execute the propagation rounds until convergence is obtained.
{
Timer t(ctx, "propagate");
tbb::affinity_partitioner ap;
i64 num_changed = -1;
for (;;) {
i64 n = propagate<E>(digests, edges, edge_indices, slot, ap);
if (n == num_changed)
break;
num_changed = n;
}
i64 num_classes = -1;
for (;;) {
for (i64 i = 0; i < 10; i++)
propagate<E>(digests, edges, edge_indices, slot, ap);
i64 n = count_num_classes<E>(digests[slot], ap);
if (n == num_classes)
break;
num_classes = n;
}
}
// Group sections by SHA digest.
{
Timer t(ctx, "group");
auto *map = new tbb::concurrent_unordered_map<Digest, InputSection<E> *>;
std::span<Digest> digest = digests[slot];
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
InputSection<E> *isec = sections[i];
auto [it, inserted] = map->insert({digest[i], isec});
if (!inserted && isec->get_priority() < it->second->get_priority())
it->second = isec;
});
tbb::parallel_for((i64)0, (i64)sections.size(), [&](i64 i) {
auto it = map->find(digest[i]);
assert(it != map->end());
sections[i]->leader = it->second;
});
// Since free'ing the map is slow, postpone it.
ctx.on_exit.push_back([=]() { delete map; });
}
if (ctx.arg.print_icf_sections)
print_icf_sections(ctx);
// Re-assign input sections to symbols.
{
Timer t(ctx, "reassign");
tbb::parallel_for_each(ctx.objs, [](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file != file)
continue;
InputSection<E> *isec = sym->input_section;
if (isec && isec->leader && isec->leader != isec) {
sym->input_section = isec->leader;
isec->kill();
}
}
});
}
}
#define INSTANTIATE(E) \
template void icf_sections(Context<E> &ctx);
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+1394
View File
File diff suppressed because it is too large. Load diff
+162
View File
@@ -0,0 +1,162 @@
#include "mold.h"
#include <limits>
namespace mold::elf {
template <typename E>
bool CieRecord<E>::equals(const CieRecord<E> &other) const {
if (get_contents() != other.get_contents())
return false;
std::span<ElfRel<E>> x = get_rels();
std::span<ElfRel<E>> y = other.get_rels();
if (x.size() != y.size())
return false;
for (i64 i = 0; i < x.size(); i++) {
if (x[i].r_offset - input_offset != y[i].r_offset - other.input_offset ||
x[i].r_type != y[i].r_type ||
file.symbols[x[i].r_sym] != other.file.symbols[y[i].r_sym] ||
input_section.get_addend(x[i]) != other.input_section.get_addend(y[i]))
return false;
}
return true;
}
template <typename E>
InputSection<E>::InputSection(Context<E> &ctx, ObjectFile<E> &file,
const ElfShdr<E> &shdr, std::string_view name,
std::string_view contents, i64 section_idx)
: file(file), shdr(shdr), contents(contents), nameptr(name.data()),
namelen(name.size()), section_idx(section_idx) {
output_section =
OutputSection<E>::get_instance(ctx, name, shdr.sh_type, shdr.sh_flags);
}
template <typename E>
void InputSection<E>::write_to(Context<E> &ctx, u8 *buf) {
if (shdr.sh_type == SHT_NOBITS || shdr.sh_size == 0)
return;
// Copy data
memcpy(buf, contents.data(), contents.size());
// Apply relocations
if (shdr.sh_flags & SHF_ALLOC)
apply_reloc_alloc(ctx, buf);
else
apply_reloc_nonalloc(ctx, buf);
}
template <typename E>
static i64 get_output_type(Context<E> &ctx) {
if (ctx.arg.shared)
return 0;
if (ctx.arg.pie)
return 1;
return 2;
}
template <typename E>
static i64 get_sym_type(Symbol<E> &sym) {
if (sym.is_absolute())
return 0;
if (!sym.is_imported)
return 1;
if (sym.get_type() != STT_FUNC)
return 2;
return 3;
}
template <typename E>
void InputSection<E>::dispatch(Context<E> &ctx, Action table[3][4], i64 i,
const ElfRel<E> &rel, Symbol<E> &sym) {
Action action = table[get_output_type(ctx)][get_sym_type(sym)];
bool is_code = (shdr.sh_flags & SHF_EXECINSTR);
bool is_writable = (shdr.sh_flags & SHF_WRITE);
auto error = [&]() {
Error(ctx) << *this << ": " << rel << " relocation against symbol `"
<< sym << "' can not be used; recompile with -fPIC";
};
switch (action) {
case NONE:
return;
case ERROR:
error();
return;
case COPYREL:
if (!ctx.arg.z_copyreloc) {
error();
return;
}
if (sym.esym().st_visibility == STV_PROTECTED) {
Error(ctx) << *this
<< ": cannot make copy relocation for protected symbol '"
<< sym << "', defined in " << *sym.file
<< "; recompile with -fPIC";
return;
}
sym.flags |= NEEDS_COPYREL;
return;
case PLT:
sym.flags |= NEEDS_PLT;
return;
case DYNREL:
if (!is_writable) {
if (!is_code || ctx.arg.z_text) {
error();
return;
}
ctx.has_textrel = true;
}
assert(sym.is_imported);
needs_dynrel[i] = true;
file.num_dynrel++;
return;
case BASEREL:
if (!is_writable) {
if (!is_code || ctx.arg.z_text) {
error();
return;
}
ctx.has_textrel = true;
}
needs_baserel[i] = true;
if (!is_relr_reloc(ctx, rel))
file.num_dynrel++;
return;
default:
unreachable();
}
}
template <typename E>
void InputSection<E>::report_undef(Context<E> &ctx, Symbol<E> &sym) {
switch (ctx.arg.unresolved_symbols) {
case UNRESOLVED_ERROR:
Error(ctx) << "undefined symbol: " << file << ": " << sym;
break;
case UNRESOLVED_WARN:
Warn(ctx) << "undefined symbol: " << file << ": " << sym;
break;
case UNRESOLVED_IGNORE:
break;
}
}
#define INSTANTIATE(E) \
template struct CieRecord<E>; \
template class InputSection<E>;
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+412
View File
@@ -0,0 +1,412 @@
// On Linux, /usr/lib/x86_64-linux-gnu/libc.so is not actually
// a shared object file but an ASCII text file containing a linker
// script to include a "real" libc.so file. Therefore, we need to
// support a (very limited) subset of the linker script language.
#include "mold.h"
#include <cctype>
#include <iomanip>
namespace mold::elf {
template <typename E>
static thread_local MappedFile<Context<E>> *current_file;
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok);
static std::string_view get_line(std::string_view input, const char *pos) {
assert(input.data() <= pos);
assert(pos < input.data() + input.size());
i64 start = input.rfind('\n', pos - input.data());
if (start == input.npos)
start = 0;
else
start++;
i64 end = input.find('\n', pos - input.data());
if (end == input.npos)
end = input.size();
return input.substr(start, end - start);
}
template <typename E>
class SyntaxError {
public:
SyntaxError(Context<E> &ctx, std::string_view errpos) : out(ctx) {
std::string_view contents = current_file<E>->get_contents();
std::string_view line = get_line(contents, errpos.data());
i64 lineno = 1;
for (i64 i = 0; contents.data() + i < line.data(); i++)
if (contents[i] == '\n')
lineno++;
i64 column = errpos.data() - line.data();
std::stringstream ss;
ss << current_file<E>->name << ":" << lineno << ": ";
i64 indent = (i64)ss.tellp() + strlen("mold: ");
ss << line << "\n" << std::setw(indent + column) << " " << "^ ";
out << ss.str();
}
template <class T> SyntaxError &operator<<(T &&val) {
out << std::forward<T>(val);
return *this;
}
[[noreturn]] ~SyntaxError() = default;
Fatal<Context<E>> out;
};
template <typename E>
static std::vector<std::string_view>
tokenize(Context<E> &ctx, std::string_view input) {
std::vector<std::string_view> vec;
while (!input.empty()) {
if (isspace(input[0])) {
input = input.substr(1);
continue;
}
if (input.starts_with("/*")) {
i64 pos = input.find("*/", 2);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed comment";
input = input.substr(pos + 2);
continue;
}
if (input[0] == '#') {
i64 pos = input.find("\n", 1);
if (pos == std::string_view::npos)
break;
input = input.substr(pos + 1);
continue;
}
if (input[0] == '"') {
i64 pos = input.find('"', 1);
if (pos == std::string_view::npos)
SyntaxError(ctx, input) << "unclosed string literal";
vec.push_back(input.substr(0, pos + 1));
input = input.substr(pos + 1);
continue;
}
i64 pos = input.find_first_not_of(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
"0123456789_.$/\\~=+[]*?-!^:");
if (pos == 0)
pos = 1;
else if (pos == input.npos)
pos = input.size();
vec.push_back(input.substr(0, pos));
input = input.substr(pos);
}
return vec;
}
template <typename E>
static std::span<std::string_view>
skip(Context<E> &ctx, std::span<std::string_view> tok, std::string_view str) {
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected '" << str
<< "', but got EOF";
if (tok[0] != str)
SyntaxError(ctx, tok[0]) << "expected '" << str << "'";
return tok.subspan(1);
}
static std::string_view unquote(std::string_view s) {
if (s.size() > 0 && s[0] == '"') {
assert(s[s.size() - 1] == '"');
return s.substr(1, s.size() - 2);
}
return s;
}
template <typename E>
static std::span<std::string_view>
read_output_format(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")")
tok = tok.subspan(1);
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
static bool is_in_sysroot(Context<E> &ctx, std::string path) {
std::string rel =
to_abs_path(path).lexically_relative(to_abs_path(ctx.arg.sysroot));
return rel != "." && !rel.starts_with("../");
}
template <typename E>
static MappedFile<Context<E>> *resolve_path(Context<E> &ctx, std::string_view tok) {
std::string str(unquote(tok));
// GNU ld prepends the sysroot if a pathname starts with '/' and the
// script being processed is in the sysroot. We do the same.
if (str.starts_with('/') && is_in_sysroot(ctx, current_file<E>->name))
return MappedFile<Context<E>>::must_open(ctx, ctx.arg.sysroot + str);
if (str.starts_with('=')) {
std::string path;
if (ctx.arg.sysroot.empty())
path = str.substr(1);
else
path = ctx.arg.sysroot + str.substr(1);
return MappedFile<Context<E>>::must_open(ctx, path);
}
if (str.starts_with("-l"))
return find_library(ctx, str.substr(2));
if (MappedFile<Context<E>> *mf = open_library(ctx, str))
return mf;
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + str;
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
return mf;
}
SyntaxError(ctx, tok) << "library not found: " << str;
}
template <typename E>
static std::span<std::string_view>
read_group(Context<E> &ctx, std::span<std::string_view> tok) {
tok = skip(ctx, tok, "(");
while (!tok.empty() && tok[0] != ")") {
if (tok[0] == "AS_NEEDED") {
bool orig = ctx.as_needed;
ctx.as_needed = true;
tok = read_group(ctx, tok.subspan(1));
ctx.as_needed = orig;
continue;
}
MappedFile<Context<E>> *mf = resolve_path(ctx, tok[0]);
read_file(ctx, mf);
tok = tok.subspan(1);
}
if (tok.empty())
Fatal(ctx) << current_file<E>->name << ": expected ')', but got EOF";
return tok.subspan(1);
}
template <typename E>
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
while (!tok.empty()) {
if (tok[0] == "OUTPUT_FORMAT") {
tok = read_output_format(ctx, tok.subspan(1));
} else if (tok[0] == "INPUT" || tok[0] == "GROUP") {
tok = read_group(ctx, tok.subspan(1));
} else if (tok[0] == "VERSION") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script(ctx, tok);
tok = skip(ctx, tok, "}");
} else if (tok[0] == ";") {
tok = tok.subspan(1);
} else {
SyntaxError(ctx, tok[0]) << "unknown linker script token";
}
}
}
template <typename E>
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
current_file<E> = mf;
std::vector<std::string_view> vec = tokenize(ctx, mf->get_contents());
std::span<std::string_view> tok = vec;
if (tok.size() >= 3 && tok[0] == "OUTPUT_FORMAT" && tok[1] == "(") {
if (tok[2] == "elf64-x86-64")
return EM_X86_64;
if (tok[2] == "elf32-i386")
return EM_386;
}
return -1;
}
static bool read_label(std::span<std::string_view> &tok,
std::string label) {
if (tok.size() >= 1 && tok[0] == label + ":") {
tok = tok.subspan(1);
return true;
}
if (tok.size() >= 2 && tok[0] == label && tok[1] == ":") {
tok = tok.subspan(2);
return true;
}
return false;
}
template <typename E>
static void
read_version_script_commands(Context<E> &ctx, std::span<std::string_view> &tok,
u16 ver_idx, bool is_cpp) {
bool is_global = true;
while (!tok.empty() && tok[0] != "}") {
if (read_label(tok, "global")) {
is_global = true;
continue;
}
if (read_label(tok, "local")) {
is_global = false;
continue;
}
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_version_script_commands( ctx, tok, ver_idx, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_idx, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
if (tok[0] == "*") {
ctx.default_version = (is_global ? ver_idx : VER_NDX_LOCAL);
} else if (is_global) {
ctx.version_patterns.push_back({unquote(tok[0]), ver_idx, is_cpp});
} else {
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_LOCAL, is_cpp});
}
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "}")
return;
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void read_version_script(Context<E> &ctx, std::span<std::string_view> &tok) {
u16 next_ver = VER_NDX_LAST_RESERVED + ctx.arg.version_definitions.size() + 1;
while (!tok.empty() && tok[0] != "}") {
u16 ver_idx;
if (tok[0] == "{") {
ver_idx = VER_NDX_GLOBAL;
} else {
ver_idx = next_ver++;
ctx.arg.version_definitions.push_back(std::string(tok[0]));
tok = tok.subspan(1);
}
tok = skip(ctx, tok, "{");
read_version_script_commands(ctx, tok, ver_idx, false);
tok = skip(ctx, tok, "}");
if (!tok.empty() && tok[0] != ";")
tok = tok.subspan(1);
tok = skip(ctx, tok, ";");
}
}
template <typename E>
void parse_version_script(Context<E> &ctx, std::string path) {
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
std::vector<std::string_view> vec =
tokenize(ctx, current_file<E>->get_contents());
std::span<std::string_view> tok = vec;
read_version_script(ctx, tok);
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
template <typename E>
void read_dynamic_list_commands(Context<E> &ctx, std::span<std::string_view> &tok,
bool is_cpp) {
while (!tok.empty() && tok[0] != "}") {
if (tok[0] == "extern") {
tok = tok.subspan(1);
if (!tok.empty() && tok[0] == "\"C\"") {
tok = tok.subspan(1);
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
} else {
tok = skip(ctx, tok, "\"C++\"");
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, true);
}
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
continue;
}
if (tok[0] == "*")
ctx.default_version = VER_NDX_GLOBAL;
else
ctx.version_patterns.push_back({unquote(tok[0]), VER_NDX_GLOBAL, is_cpp});
tok = skip(ctx, tok.subspan(1), ";");
}
}
template <typename E>
void parse_dynamic_list(Context<E> &ctx, std::string path) {
current_file<E> = MappedFile<Context<E>>::must_open(ctx, path);
std::vector<std::string_view> vec =
tokenize(ctx, current_file<E>->get_contents());
std::span<std::string_view> tok = vec;
tok = skip(ctx, tok, "{");
read_dynamic_list_commands(ctx, tok, false);
tok = skip(ctx, tok, "}");
tok = skip(ctx, tok, ";");
if (!tok.empty())
SyntaxError(ctx, tok[0]) << "trailing garbage token";
}
#define INSTANTIATE(E) \
template \
void parse_linker_script(Context<E> &ctx, MappedFile<Context<E>> *mf); \
template \
i64 get_script_output_type(Context<E> &ctx, MappedFile<Context<E>> *mf); \
template \
void parse_version_script(Context<E> &ctx, std::string path); \
template \
void parse_dynamic_list(Context<E> &ctx, std::string path)
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+731
View File
@@ -0,0 +1,731 @@
#include "mold.h"
#include "../archive-file.h"
#include "../cmdline.h"
#include <cstring>
#include <functional>
#include <iomanip>
#include <map>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <tbb/global_control.h>
#include <tbb/parallel_for_each.h>
#include <unistd.h>
#include <unordered_set>
namespace mold::elf {
template <typename E>
static ObjectFile<E> *new_object_file(Context<E> &ctx, MappedFile<Context<E>> *mf,
std::string archive_name) {
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
static Counter count("parsed_objs");
count++;
bool in_lib = ctx.in_lib || (!archive_name.empty() && !ctx.whole_archive);
ObjectFile<E> *file = ObjectFile<E>::create(ctx, mf, archive_name, in_lib);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
if (ctx.arg.trace)
SyncOut(ctx) << "trace: " << *file;
return file;
}
template <typename E>
static SharedFile<E> *
new_shared_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (i64 type = ((ElfEhdr<E> *)mf->data)->e_machine; type != E::e_machine)
Fatal(ctx) << mf->name << ": incompatible file type: " << type;
SharedFile<E> *file = SharedFile<E>::create(ctx, mf);
file->priority = ctx.file_priority++;
ctx.tg.run([file, &ctx]() { file->parse(ctx); });
if (ctx.arg.trace)
SyncOut(ctx) << "trace: " << *file;
return file;
}
template <typename E>
void read_file(Context<E> &ctx, MappedFile<Context<E>> *mf) {
if (ctx.visited.contains(mf->name))
return;
FileType type = get_file_type(mf);
switch (type) {
case FileType::ELF_OBJ:
ctx.objs.push_back(new_object_file(ctx, mf, ""));
return;
case FileType::ELF_DSO:
ctx.dsos.push_back(new_shared_file(ctx, mf));
ctx.visited.insert(mf->name);
return;
case FileType::AR:
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
ctx.objs.push_back(new_object_file(ctx, child, mf->name));
ctx.visited.insert(mf->name);
return;
case FileType::TEXT:
parse_linker_script(ctx, mf);
return;
case FileType::LLVM_BITCODE:
Warn(ctx) << mf->name << ": looks like this is an LLVM bitcode, "
<< "but mold does not support LTO";
ctx.llvm_lto = true;
return;
default:
Fatal(ctx) << mf->name << ": unknown file type: " << type;
}
}
// Read the beginning of a given file and returns its machine type
// (e.g. EM_X86_64 or EM_386). Return -1 if unknown.
template <typename E>
static i64 get_machine_type(Context<E> &ctx, MappedFile<Context<E>> *mf) {
switch (get_file_type(mf)) {
case FileType::ELF_OBJ:
case FileType::ELF_DSO:
return ((ElfEhdr<E> *)mf->data)->e_machine;
case FileType::AR:
for (MappedFile<Context<E>> *child : read_fat_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
return ((ElfEhdr<E> *)child->data)->e_machine;
return -1;
case FileType::THIN_AR:
for (MappedFile<Context<E>> *child : read_thin_archive_members(ctx, mf))
if (get_file_type(child) == FileType::ELF_OBJ)
return ((ElfEhdr<E> *)child->data)->e_machine;
return -1;
case FileType::TEXT:
return get_script_output_type(ctx, mf);
default:
return -1;
}
}
template <typename E>
static i64
deduce_machine_type(Context<E> &ctx, std::span<std::string_view> args) {
for (std::string_view arg : args)
if (!arg.starts_with('-'))
if (auto *mf = MappedFile<Context<E>>::open(ctx, std::string(arg)))
if (i64 type = get_machine_type(ctx, mf); type != -1)
return type;
Fatal(ctx) << "-m option is missing";
}
template <typename E>
MappedFile<Context<E>> *open_library(Context<E> &ctx, std::string path) {
MappedFile<Context<E>> *mf = MappedFile<Context<E>>::open(ctx, path);
if (!mf)
return nullptr;
i64 type = get_machine_type(ctx, mf);
if (type == -1 || type == E::e_machine)
return mf;
Warn(ctx) << path << ": skipping incompatible file " << (int)type
<< " " << (int)E::e_machine;
return nullptr;
}
template <typename E>
MappedFile<Context<E>> *find_library(Context<E> &ctx, std::string name) {
if (name.starts_with(':')) {
for (std::string_view dir : ctx.arg.library_paths) {
std::string path = std::string(dir) + "/" + name.substr(1);
if (MappedFile<Context<E>> *mf = open_library(ctx, path))
return mf;
}
Fatal(ctx) << "library not found: " << name;
}
for (std::string_view dir : ctx.arg.library_paths) {
std::string stem = std::string(dir) + "/lib" + name;
if (!ctx.is_static)
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".so"))
return mf;
if (MappedFile<Context<E>> *mf = open_library(ctx, stem + ".a"))
return mf;
}
Fatal(ctx) << "library not found: " << name;
}
template <typename E>
static void read_input_files(Context<E> &ctx, std::span<std::string_view> args) {
Timer t(ctx, "read_input_files");
std::vector<std::tuple<bool, bool, bool, bool>> state;
ctx.is_static = ctx.arg.is_static;
while (!args.empty()) {
std::string_view arg;
if (read_flag(args, "as-needed")) {
ctx.as_needed = true;
} else if (read_flag(args, "no-as-needed")) {
ctx.as_needed = false;
} else if (read_flag(args, "whole-archive")) {
ctx.whole_archive = true;
} else if (read_flag(args, "no-whole-archive")) {
ctx.whole_archive = false;
} else if (read_flag(args, "Bstatic")) {
ctx.is_static = true;
} else if (read_flag(args, "Bdynamic")) {
ctx.is_static = false;
} else if (read_flag(args, "start-lib")) {
ctx.in_lib = true;
} else if (read_flag(args, "end-lib")) {
ctx.in_lib = false;
} else if (read_arg(ctx, args, arg, "version-script")) {
parse_version_script(ctx, std::string(arg));
} else if (read_arg(ctx, args, arg, "dynamic-list")) {
parse_dynamic_list(ctx, std::string(arg));
} else if (read_flag(args, "push-state")) {
state.push_back({ctx.as_needed, ctx.whole_archive, ctx.is_static,
ctx.in_lib});
} else if (read_flag(args, "pop-state")) {
if (state.empty())
Fatal(ctx) << "no state pushed before popping";
std::tie(ctx.as_needed, ctx.whole_archive, ctx.is_static, ctx.in_lib) =
state.back();
state.pop_back();
} else if (read_arg(ctx, args, arg, "l")) {
MappedFile<Context<E>> *mf = find_library(ctx, std::string(arg));
mf->given_fullpath = false;
read_file(ctx, mf);
} else {
read_file(ctx, MappedFile<Context<E>>::must_open(ctx, std::string(args[0])));
args = args.subspan(1);
}
}
if (ctx.objs.empty())
Fatal(ctx) << "no input files";
ctx.tg.wait();
}
template <typename E>
static i64 get_mtime(Context<E> &ctx, std::string path) {
struct stat st;
if (stat(path.c_str(), &st) < 0)
Fatal(ctx) << path << ": stat failed: " << errno_string();
return st.st_mtime;
}
template <typename E>
static bool reload_input_files(Context<E> &ctx) {
Timer t(ctx, "reload_input_files");
std::vector<ObjectFile<E> *> objs;
std::vector<SharedFile<E> *> dsos;
// Reload updated .o files
for (ObjectFile<E> *file : ctx.objs) {
if (file->mf->parent) {
if (get_mtime(ctx, file->mf->parent->name) != file->mf->parent->mtime)
return false;
objs.push_back(file);
continue;
}
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
objs.push_back(file);
continue;
}
MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
objs.push_back(new_object_file(ctx, mf, file->mf->name));
}
// Reload updated .so files
for (SharedFile<E> *file : ctx.dsos) {
if (get_mtime(ctx, file->mf->name) == file->mf->mtime) {
dsos.push_back(file);
} else {
MappedFile<Context<E>> *mf =
MappedFile<Context<E>>::must_open(ctx, file->mf->name);
dsos.push_back(new_shared_file(ctx, mf));
}
}
ctx.objs = objs;
ctx.dsos = dsos;
return true;
}
template <typename E>
static void show_stats(Context<E> &ctx) {
for (ObjectFile<E> *obj : ctx.objs) {
static Counter defined("defined_syms");
defined += obj->first_global - 1;
static Counter undefined("undefined_syms");
undefined += obj->symbols.size() - obj->first_global;
for (std::unique_ptr<InputSection<E>> &sec : obj->sections) {
if (!sec || !sec->is_alive)
continue;
static Counter alloc("reloc_alloc");
static Counter nonalloc("reloc_nonalloc");
if (sec->shdr.sh_flags & SHF_ALLOC)
alloc += sec->get_rels(ctx).size();
else
nonalloc += sec->get_rels(ctx).size();
}
static Counter comdats("comdats");
comdats += obj->comdat_groups.size();
static Counter removed_comdats("removed_comdat_mem");
for (auto &pair : obj->comdat_groups)
if (ComdatGroup *group = pair.first; group->owner != obj->priority)
removed_comdats += pair.second.size();
static Counter num_cies("num_cies");
num_cies += obj->cies.size();
static Counter num_unique_cies("num_unique_cies");
for (CieRecord<E> &cie : obj->cies)
if (cie.is_leader)
num_unique_cies++;
static Counter num_fdes("num_fdes");
num_fdes += obj->fdes.size();
}
static Counter num_bytes("total_input_bytes");
for (std::unique_ptr<MappedFile<Context<E>>> &mf : ctx.mf_pool)
num_bytes += mf->size;
static Counter num_input_sections("input_sections");
for (ObjectFile<E> *file : ctx.objs)
num_input_sections += file->sections.size();
static Counter num_output_chunks("output_chunks", ctx.chunks.size());
static Counter num_objs("num_objs", ctx.objs.size());
static Counter num_dsos("num_dsos", ctx.dsos.size());
if constexpr (E::e_machine == EM_AARCH64) {
static Counter num_thunks("num_thunks");
for (std::unique_ptr<OutputSection<E>> &osec : ctx.output_sections)
for (std::unique_ptr<RangeExtensionThunk<E>> &thunk : osec->thunks)
num_thunks += thunk->symbols.size();
}
Counter::print();
}
static i64 get_default_thread_count() {
// mold doesn't scale above 32 threads.
int n = tbb::global_control::active_value(
tbb::global_control::max_allowed_parallelism);
return std::min(n, 32);
}
template <typename E>
static int elf_main(int argc, char **argv) {
Context<E> ctx;
// Process -run option first. process_run_subcommand() does not return.
if (argc >= 2)
if (argv[1] == "-run"sv || argv[1] == "--run"sv)
process_run_subcommand(ctx, argc, argv);
// Parse non-positional command line options
ctx.cmdline_args = expand_response_files(ctx, argv);
std::vector<std::string_view> file_args;
parse_nonpositional_args(ctx, file_args);
// If no -m option is given, deduce it from input files.
if (ctx.arg.emulation == -1)
ctx.arg.emulation = deduce_machine_type(ctx, file_args);
// Redo if -m is not x86-64.
if (ctx.arg.emulation != E::e_machine) {
switch (ctx.arg.emulation) {
case EM_386:
return elf_main<I386>(argc, argv);
case EM_AARCH64:
return elf_main<ARM64>(argc, argv);
}
unreachable();
}
Timer t_all(ctx, "all");
if (ctx.arg.relocatable) {
combine_objects(ctx, file_args);
return 0;
}
if (!ctx.arg.preload)
try_resume_daemon(ctx);
i64 thread_count = ctx.arg.thread_count;
if (thread_count == 0)
thread_count = get_default_thread_count();
tbb::global_control tbb_cont(tbb::global_control::max_allowed_parallelism,
thread_count);
install_signal_handler();
if (!ctx.arg.directory.empty())
if (chdir(ctx.arg.directory.c_str()) == -1)
Fatal(ctx) << "chdir failed: " << ctx.arg.directory
<< ": " << errno_string();
// Handle --wrap options if any.
for (std::string_view name : ctx.arg.wrap)
get_symbol(ctx, name)->wrap = true;
// Handle --retain-symbols-file options if any.
if (ctx.arg.retain_symbols_file)
for (std::string_view name : *ctx.arg.retain_symbols_file)
get_symbol(ctx, name)->write_to_symtab = true;
// Preload input files
std::function<void()> on_complete;
std::function<void()> wait_for_client;
if (ctx.arg.preload)
daemonize(ctx, &wait_for_client, &on_complete);
else if (ctx.arg.fork)
on_complete = fork_child();
for (std::string_view arg : ctx.arg.trace_symbol)
get_symbol(ctx, arg)->traced = true;
// Parse input files
read_input_files(ctx, file_args);
if (ctx.arg.preload) {
wait_for_client();
if (!reload_input_files(ctx)) {
std::vector<char *> args(argv, argv + argc);
args.push_back((char *)"--no-preload");
args.push_back(nullptr);
return elf_main<E>(argc + 1, args.data());
}
}
{
Timer t(ctx, "register_section_pieces");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->register_section_pieces(ctx);
});
}
// Uniquify shared object files by soname
{
std::unordered_set<std::string_view> seen;
std::erase_if(ctx.dsos, [&](SharedFile<E> *file) {
return !seen.insert(file->soname).second;
});
}
Timer t_total(ctx, "total");
Timer t_before_copy(ctx, "before_copy");
// Apply -exclude-libs
apply_exclude_libs(ctx);
// Create instances of linker-synthesized sections such as
// .got or .plt.
create_synthetic_sections(ctx);
// Resolve symbols and fix the set of object files that are
// included to the final output.
resolve_symbols(ctx);
// We currently do not natively support LTO.
// If LLVM LTO is in use, fallback to the lld linker by invoking
// it with the exact same command line arguments as we got.
if (ctx.llvm_lto) {
Warn(ctx) << "LLVM LTO is detected, so falling back to ld.lld";
argv[0] = (char *)"ld.lld";
execvp("ld.lld", argv);
Fatal(ctx) << "execvp failed: ld.lld: " << errno_string();
}
// Do the same for GCC LTO.
if (Symbol<E> *sym = get_symbol(ctx, "__gnu_lto_slim"); sym->file) {
Warn(ctx) << *sym->file
<< "GCC LTO is detected, so falling back to ld.bfd";
execvp("ld.bfd", argv);
Fatal(ctx) << "execvp failed: ld.bfd: " << errno_string();
}
// Remove redundant comdat sections (e.g. duplicate inline functions).
eliminate_comdats(ctx);
// Create .bss sections for common symbols.
convert_common_symbols(ctx);
// Apply version scripts.
apply_version_script(ctx);
// Parse symbol version suffixes (e.g. "foo@ver1").
parse_symbol_version(ctx);
// Set is_import and is_export bits for each symbol.
compute_import_export(ctx);
// Garbage-collect unreachable sections.
if (ctx.arg.gc_sections)
gc_sections(ctx);
// Merge identical read-only sections.
if (ctx.arg.icf)
icf_sections(ctx);
// Compute sizes of sections containing mergeable strings.
compute_merged_section_sizes(ctx);
// Bin input sections into output sections.
bin_sections(ctx);
// Get a list of output sections.
append(ctx.chunks, collect_output_sections(ctx));
// Create a dummy file containing linker-synthesized symbols
// (e.g. `__bss_start`).
ctx.internal_obj = create_internal_file(ctx);
ctx.internal_obj->resolve_symbols(ctx);
ctx.objs.push_back(ctx.internal_obj);
// Beyond this point, no new files will be added to ctx.objs
// or ctx.dsos.
// Handle `-z cet-report`.
if (ctx.arg.z_cet_report != CET_REPORT_NONE)
check_cet_errors(ctx);
// If we are linking a .so file, remaining undefined symbols does
// not cause a linker error. Instead, they are treated as if they
// were imported symbols.
//
// If we are linking an executable, weak undefs are converted to
// weakly imported symbol so that they'll have another chance to be
// resolved.
claim_unresolved_symbols(ctx);
// Beyond this point, no new symbols will be added to the result.
// Make sure that all symbols have been resolved.
if (!ctx.arg.allow_multiple_definition)
check_duplicate_symbols(ctx);
// Handle --require-defined
for (std::string_view name : ctx.arg.require_defined)
if (!get_symbol(ctx, name)->file)
Error(ctx) << "--require-defined: undefined symbol: " << name;
// .init_array and .fini_array contents have to be sorted by
// a special rule. Sort them.
sort_init_fini(ctx);
// Compute sizes of output sections while assigning offsets
// within an output section to input sections.
compute_section_sizes(ctx);
// Sort sections by section attributes so that we'll have to
// create as few segments as possible.
sort(ctx.chunks, [&](Chunk<E> *a, Chunk<E> *b) {
return get_section_rank(ctx, a) < get_section_rank(ctx, b);
});
// Copy string referred by .dynamic to .dynstr.
for (SharedFile<E> *file : ctx.dsos)
ctx.dynstr->add_string(file->soname);
for (std::string_view str : ctx.arg.auxiliary)
ctx.dynstr->add_string(str);
for (std::string_view str : ctx.arg.filter)
ctx.dynstr->add_string(str);
if (!ctx.arg.rpaths.empty())
ctx.dynstr->add_string(ctx.arg.rpaths);
if (!ctx.arg.soname.empty())
ctx.dynstr->add_string(ctx.arg.soname);
// Scan relocations to find symbols that need entries in .got, .plt,
// .got.plt, .dynsym, .dynstr, etc.
scan_rels(ctx);
// If --packed_dyn_relocs=relr was given, base relocations are stored
// to a .relr.dyn section in a compressed form. Construct a compressed
// relocations now so that we can fix section sizes and file layout.
if (ctx.arg.pack_dyn_relocs_relr)
construct_relr(ctx);
// Reserve a space for dynamic symbol strings in .dynstr and sort
// .dynsym contents if necessary. Beyond this point, no symbol will
// be added to .dynsym.
ctx.dynsym->finalize(ctx);
// Fill .gnu.version_d section contents.
ctx.verdef->construct(ctx);
// Fill .gnu.version_r section contents.
ctx.verneed->construct(ctx);
// Compute .symtab and .strtab sizes for each file.
{
Timer t(ctx, "compute_symtab");
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
file->compute_symtab(ctx);
});
}
// .eh_frame is a special section from the linker's point of view,
// as its contents are parsed and reconstructed by the linker,
// unlike other sections that are regarded as opaque bytes.
// Here, we transplant .eh_frame sections from a regular output
// section to the special EHFrameSection.
{
Timer t(ctx, "eh_frame");
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
return chunk->kind == Chunk<E>::REGULAR && chunk->name == ".eh_frame";
});
ctx.eh_frame->construct(ctx);
}
// Update shdr.sh_size for each chunk and remove empty ones.
for (Chunk<E> *chunk : ctx.chunks)
chunk->update_shdr(ctx);
std::erase_if(ctx.chunks, [](Chunk<E> *chunk) {
return chunk->kind == Chunk<E>::SYNTHETIC && chunk->shdr.sh_size == 0;
});
// Set section indices.
for (i64 i = 0, shndx = 1; i < ctx.chunks.size(); i++)
if (ctx.chunks[i]->kind != Chunk<E>::HEADER)
ctx.chunks[i]->shndx = shndx++;
// Some types of section header refer other section by index.
// Recompute the section header to fill such fields with correct values.
for (Chunk<E> *chunk : ctx.chunks)
chunk->update_shdr(ctx);
// Assign offsets to output sections
i64 filesize = set_osec_offsets(ctx);
// On ARM64, we may need to create so-called "range extension thunks"
// to extend branch instructions reach, as they can jump only to
// ±128 MiB.
if constexpr (E::e_machine == EM_AARCH64)
filesize = create_range_extension_thunks(ctx);
// Fix linker-synthesized symbol addresses.
fix_synthetic_symbols(ctx);
// If --compress-debug-sections is given, compress .debug_* sections
// using zlib.
if (ctx.arg.compress_debug_sections != COMPRESS_NONE) {
compress_debug_sections(ctx);
filesize = set_osec_offsets(ctx);
}
// At this point, file layout is fixed.
// Beyond this, you can assume that symbol addresses including their
// GOT or PLT addresses have a correct final value.
// Some types of TLS relocations are defined relative to the beginning
// or the end of the TLS segment address. Find these addresses now.
for (ElfPhdr<E> phdr : create_phdr(ctx)) {
if (phdr.p_type == PT_TLS) {
ctx.tls_begin = phdr.p_vaddr;
ctx.tls_end = align_to(phdr.p_vaddr + phdr.p_memsz, phdr.p_align);
break;
}
}
t_before_copy.stop();
// Create an output file
ctx.output_file = OutputFile<E>::open(ctx, ctx.arg.output, filesize, 0777);
ctx.buf = ctx.output_file->buf;
Timer t_copy(ctx, "copy");
// Copy input sections to the output file
{
Timer t(ctx, "copy_buf");
tbb::parallel_for_each(ctx.chunks, [&](Chunk<E> *chunk) {
std::string name(chunk->name);
if (name.empty())
name = "(header)";
Timer t2(ctx, name, &t);
chunk->copy_buf(ctx);
});
ctx.checkpoint();
}
if constexpr (E::e_machine == EM_AARCH64)
write_thunks(ctx);
// Dynamic linker works better with sorted .rela.dyn section,
// so we sort them.
ctx.reldyn->sort(ctx);
// Zero-clear paddings between sections
clear_padding(ctx);
if (ctx.buildid) {
Timer t(ctx, "build_id");
ctx.buildid->write_buildid(ctx);
}
t_copy.stop();
// Close the output file. This is the end of the linker's main job.
ctx.output_file->close(ctx);
t_total.stop();
t_all.stop();
if (ctx.arg.print_map)
print_map(ctx);
// Show stats numbers
if (ctx.arg.stats)
show_stats(ctx);
if (ctx.arg.perf)
print_timer_records(ctx.timer_records);
std::cout << std::flush;
std::cerr << std::flush;
if (on_complete)
on_complete();
if (ctx.arg.quick_exit)
_exit(0);
for (std::function<void()> &fn : ctx.on_exit)
fn();
return 0;
}
int main(int argc, char **argv) {
return elf_main<X86_64>(argc, argv);
}
#define INSTANTIATE(E) \
template void read_file(Context<E> &, MappedFile<Context<E>> *);
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+111
View File
@@ -0,0 +1,111 @@
#include "mold.h"
#include <fstream>
#include <iomanip>
#include <ios>
#include <sstream>
#include <tbb/parallel_for_each.h>
#include <unordered_map>
namespace mold::elf {
template <typename E>
using Map =
tbb::concurrent_hash_map<InputSection<E> *, std::vector<Symbol<E> *>>;
template <typename E>
static std::unique_ptr<std::ofstream> open_output_file(Context<E> &ctx) {
std::unique_ptr<std::ofstream> file(new std::ofstream);
file->open(ctx.arg.Map.c_str());
if (!file->is_open())
Fatal(ctx) << "cannot open " << ctx.arg.Map << ": " << errno_string();
return file;
}
template <typename E>
static Map<E> get_map(Context<E> &ctx) {
Map<E> map;
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
for (Symbol<E> *sym : file->symbols) {
if (sym->file == file && sym->input_section &&
sym->get_type() != STT_SECTION) {
assert(file == &sym->input_section->file);
typename Map<E>::accessor acc;
map.insert(acc, {sym->input_section, {}});
acc->second.push_back(sym);
}
}
});
tbb::parallel_for(map.range(), [](const typename Map<E>::range_type &range) {
for (auto it = range.begin(); it != range.end(); it++) {
std::vector<Symbol<E> *> &vec = it->second;
sort(vec, [](Symbol<E> *a, Symbol<E> *b) { return a->value < b->value; });
}
});
return map;
}
template <typename E>
void print_map(Context<E> &ctx) {
std::ostream *out = &std::cout;
std::unique_ptr<std::ofstream> file;
if (!ctx.arg.Map.empty()) {
file = open_output_file(ctx);
out = file.get();
}
// Construct a section-to-symbol map.
Map<E> map = get_map(ctx);
// Print a mapfile.
*out << " VMA Size Align Out In Symbol\n";
for (Chunk<E> *osec : ctx.chunks) {
*out << std::setw(16) << (u64)osec->shdr.sh_addr
<< std::setw(11) << (u64)osec->shdr.sh_size
<< std::setw(6) << (u64)osec->shdr.sh_addralign
<< " " << osec->name << "\n";
if (osec->kind != Chunk<E>::REGULAR)
continue;
std::span<InputSection<E> *> members = ((OutputSection<E> *)osec)->members;
std::vector<std::string> bufs(members.size());
tbb::parallel_for((i64)0, (i64)members.size(), [&](i64 i) {
InputSection<E> *mem = members[i];
std::ostringstream ss;
opt_demangle = ctx.arg.demangle;
ss << std::setw(16) << (osec->shdr.sh_addr + mem->offset)
<< std::setw(11) << (u64)mem->shdr.sh_size
<< std::setw(6) << (u64)mem->shdr.sh_addralign
<< " " << *mem << "\n";
typename Map<E>::const_accessor acc;
if (map.find(acc, mem))
for (Symbol<E> *sym : acc->second)
ss << std::setw(16) << sym->get_addr(ctx)
<< " 0 0 "
<< *sym << "\n";
bufs[i] = ss.str();
});
for (std::string &str : bufs)
*out << str;
}
}
#define INSTANTIATE(E) \
template void print_map(Context<E> &ctx);
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
+128
View File
@@ -0,0 +1,128 @@
#define _GNU_SOURCE 1
#include <dlfcn.h>
#include <spawn.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
extern char **environ;
static char *get_mold_path() {
char *path = getenv("MOLD_PATH");
if (path)
return path;
fprintf(stderr, "MOLD_PATH is not set\n");
exit(1);
}
static void debug_print(char *fmt, ...) {
if (!getenv("MOLD_WRAPPER_DEBUG"))
return;
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "mold-wrapper.so: ");
vfprintf(stderr, fmt, ap);
fflush(stderr);
va_end(ap);
}
static void get_args(va_list *ap, int argc, char **argv) {
for (int i = 1; i < argc - 1; i++) {
char *arg = va_arg(*ap, char *);
if (!arg)
break;
argv[i] = arg;
}
}
static bool is_ld(const char *path) {
const char *ptr = path + strlen(path);
while (path < ptr && ptr[-1] != '/')
ptr--;
return !strcmp(ptr, "ld") || !strcmp(ptr, "ld.lld") ||
!strcmp(ptr, "ld.gold");
}
int execvpe(const char *file, char *const *argv, char *const *envp) {
debug_print("execvpe %s\n", file);
if (!strcmp(file, "ld") || is_ld(file)) {
file = get_mold_path();
((const char **)argv)[0] = file;
}
for (int i = 0; envp[i]; i++)
putenv(envp[i]);
typeof(execvpe) *real = dlsym(RTLD_NEXT, "execvp");
return real(file, argv, environ);
}
int execve(const char *path, char *const *argv, char *const *envp) {
debug_print("execve %s\n", path);
if (is_ld(path)) {
path = get_mold_path();
((const char **)argv)[0] = path;
}
typeof(execve) *real = dlsym(RTLD_NEXT, "execve");
return real(path, argv, envp);
}
int execl(const char *path, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char *argv[4096] = {(char *)arg0};
get_args(&ap, 4096, argv);
va_end(ap);
return execve(path, argv, environ);
}
int execlp(const char *file, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char *argv[4096] = {(char *)arg0};
get_args(&ap, 4096, argv);
va_end(ap);
return execvpe(file, argv, environ);
}
int execle(const char *path, const char *arg0, ...) {
va_list ap;
va_start(ap, arg0);
char *argv[4096] = {(char *)arg0};
get_args(&ap, 4096, argv);
char **env = va_arg(ap, char **);
va_end(ap);
return execve(path, argv, env);
}
int execv(const char *path, char *const *argv) {
return execve(path, argv, environ);
}
int execvp(const char *file, char *const *argv) {
return execvpe(file, argv, environ);
}
int posix_spawn(pid_t *pid, const char *path,
const posix_spawn_file_actions_t *file_actions,
const posix_spawnattr_t *attrp,
char *const *argv, char *const *envp) {
debug_print("posix_spawn %s\n", path);
if (is_ld(path)) {
path = get_mold_path();
((const char **)argv)[0] = path;
}
typeof(posix_spawn) *real = dlsym(RTLD_NEXT, "posix_spawn");
return real(pid, path, file_actions, attrp, argv, envp);
}
+2195
View File
File diff suppressed because it is too large. Load diff
+2042
View File
File diff suppressed because it is too large. Load diff
+142
View File
@@ -0,0 +1,142 @@
#include "mold.h"
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
namespace mold::elf {
inline u32 get_umask() {
u32 orig_umask = umask(0);
umask(orig_umask);
return orig_umask;
}
template <typename C>
static std::pair<i64, char *>
open_or_create_file(C &ctx, std::string path, i64 filesize, i64 perm) {
std::string tmpl = filepath(path).parent_path() / ".mold-XXXXXX";
char *path2 = (char *)save_string(ctx, tmpl).data();
i64 fd = mkstemp(path2);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
// Reuse an existing file if exists and writable.
if (rename(path.c_str(), path2) == 0) {
::close(fd);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd != -1 && !ftruncate(fd, filesize) && !fchmod(fd, perm & ~get_umask()))
return {fd, path2};
unlink(path2);
fd = ::open(path2, O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << path2 << ": " << errno_string();
}
if (ftruncate(fd, filesize))
Fatal(ctx) << "ftruncate failed: " << errno_string();
if (fchmod(fd, (perm & ~get_umask())) == -1)
Fatal(ctx) << "fchmod failed: " << errno_string();
return {fd, path2};
}
template <typename E>
class MemoryMappedOutputFile : public OutputFile<E> {
public:
MemoryMappedOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<E>(path, filesize, true) {
i64 fd;
std::tie(fd, output_tmpfile) = open_or_create_file(ctx, path, filesize, perm);
this->buf = (u8 *)mmap(nullptr, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << path << ": mmap failed: " << errno_string();
::close(fd);
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
if (!this->is_unmapped)
munmap(this->buf, this->filesize);
if (rename(output_tmpfile, this->path.c_str()) == -1)
Fatal(ctx) << this->path << ": rename failed: " << errno_string();
output_tmpfile = nullptr;
}
};
template <typename E>
class MallocOutputFile : public OutputFile<E> {
public:
MallocOutputFile(Context<E> &ctx, std::string path, i64 filesize, i64 perm)
: OutputFile<E>(path, filesize, false), perm(perm) {
this->buf = (u8 *)mmap(NULL, filesize, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (this->buf == MAP_FAILED)
Fatal(ctx) << "mmap failed: " << errno_string();
}
void close(Context<E> &ctx) override {
Timer t(ctx, "close_file");
if (this->path == "-") {
fwrite(this->buf, this->filesize, 1, stdout);
fclose(stdout);
return;
}
i64 fd = ::open(this->path.c_str(), O_RDWR | O_CREAT, perm);
if (fd == -1)
Fatal(ctx) << "cannot open " << this->path << ": " << errno_string();
FILE *fp = fdopen(fd, "w");
fwrite(this->buf, this->filesize, 1, fp);
fclose(fp);
}
private:
i64 perm;
};
template <typename E>
std::unique_ptr<OutputFile<E>>
OutputFile<E>::open(Context<E> &ctx, std::string path, i64 filesize, i64 perm) {
Timer t(ctx, "open_file");
if (path.starts_with('/') && !ctx.arg.chroot.empty())
path = ctx.arg.chroot + "/" + path_clean(path);
bool is_special = false;
if (path == "-") {
is_special = true;
} else {
struct stat st;
if (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) != S_IFREG)
is_special = true;
}
std::unique_ptr<OutputFile<E>> file;
if (is_special)
file = std::make_unique<MallocOutputFile<E>>(ctx, path, filesize, perm);
else
file = std::make_unique<MemoryMappedOutputFile<E>>(ctx, path, filesize, perm);
if (ctx.arg.filler != -1)
memset(file->buf, ctx.arg.filler, filesize);
return file;
}
#define INSTANTIATE(E) \
template class OutputFile<E>;
INSTANTIATE(X86_64);
INSTANTIATE(I386);
INSTANTIATE(ARM64);
} // namespace mold::elf
Loaded 100 of 2393 files, more files were not shown because too many files have changed in this diff. Show more